Skip to content

Commit 1542023

Browse files
committed
Fix errors getting spammed when server is unreachable
Fixes #217
1 parent b55ffd3 commit 1542023

File tree

2 files changed

+28
-9
lines changed

2 files changed

+28
-9
lines changed

frontend/src/components/Alert.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,16 @@ const clearAlertTimeout = (id: string) => {
1919
}
2020
};
2121

22-
export function showAlert(text: string, variant: "danger" | "success" | "warning", id?: string, heading?: string, timeout_ms?: number) {
22+
export async function showAlert(text: string, variant: "danger" | "success" | "warning", id?: string, heading?: string, timeout_ms?: number) {
2323
if (text.indexOf("Failed to fetch") !== -1) {
2424
console.warn("Alert suppressed due to 'Failed to fetch' message");
2525
return;
2626
}
2727

28-
id = id ? id : Math.random().toString(36).substr(2);
28+
const hash = await crypto.subtle.digest("SHA-1", new TextEncoder().encode(text + variant + (heading || ""))).then(buf => {
29+
return new TextDecoder().decode(buf);
30+
});
31+
id = id ? id : hash;
2932
const alert: AlertItem = {
3033
id,
3134
text,

frontend/src/components/__tests__/alert.test.tsx

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ describe('Alert component & showAlert', () => {
1414

1515
it('renders an alert with heading and text', async () => {
1616
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
17-
real.showAlert('Some text', 'danger', 'abc', 'Heading Text');
17+
await real.showAlert('Some text', 'danger', 'abc', 'Heading Text');
1818
render(<real.ErrorAlert />);
1919
expect(screen.getByText('Some text')).toBeTruthy();
2020
expect(screen.getByTestId('alert-heading')).toHaveTextContent('Heading Text');
2121
});
2222

2323
it('suppresses alert containing Failed to fetch', async () => {
2424
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
25-
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
26-
real.showAlert('Failed to fetch resource', 'danger');
25+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
26+
await real.showAlert('Failed to fetch resource', 'danger');
2727
render(<real.ErrorAlert />);
2828
expect(screen.queryByText('Failed to fetch resource')).toBeNull();
2929
expect(warnSpy).toHaveBeenCalled();
@@ -32,7 +32,7 @@ describe('Alert component & showAlert', () => {
3232

3333
it('auto dismisses alert after timeout', async () => {
3434
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
35-
real.showAlert('Autoclose', 'success', 'auto', 'Auto Heading', 2000);
35+
await real.showAlert('Autoclose', 'success', 'auto', 'Auto Heading', 2000);
3636
const { rerender } = render(<real.ErrorAlert />);
3737
expect(screen.getByText('Autoclose')).toBeTruthy();
3838
await advanceTimers(2000);
@@ -43,20 +43,36 @@ describe('Alert component & showAlert', () => {
4343
it('replaces alert with same id and clears previous timeout', async () => {
4444
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
4545
const clearSpy = vi.spyOn(window, 'clearTimeout');
46-
real.showAlert('First', 'warning', 'same', 'First Heading', 5000);
46+
await real.showAlert('First', 'warning', 'same', 'First Heading', 5000);
4747
const utils = render(<real.ErrorAlert />);
4848
expect(screen.getByText('First')).toBeTruthy();
49-
real.showAlert('Second', 'warning', 'same', 'Second Heading');
49+
await real.showAlert('Second', 'warning', 'same', 'Second Heading');
5050
utils.rerender(<real.ErrorAlert />);
5151
expect(screen.getByText('Second')).toBeTruthy();
5252
expect(screen.queryByText('First')).toBeNull();
5353
expect(clearSpy).toHaveBeenCalled();
5454
clearSpy.mockRestore();
5555
});
5656

57+
it('replaces alert with same text/heading/variant (no id) and clears previous timeout', async () => {
58+
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
59+
const clearSpy = vi.spyOn(window, 'clearTimeout');
60+
await real.showAlert('Same Text', 'warning', undefined, 'Same Heading', 2000);
61+
const utils = render(<real.ErrorAlert />);
62+
expect(screen.getByText('Same Text')).toBeTruthy();
63+
await real.showAlert('Same Text', 'warning', undefined, 'Same Heading');
64+
utils.rerender(<real.ErrorAlert />);
65+
expect(screen.getByText('Same Text')).toBeTruthy();
66+
await advanceTimers(2000);
67+
utils.rerender(<real.ErrorAlert />);
68+
expect(screen.getByText('Same Text')).toBeTruthy();
69+
expect(clearSpy).toHaveBeenCalled();
70+
clearSpy.mockRestore();
71+
});
72+
5773
it('manual dismiss via close button triggers onClose and removes alert', async () => {
5874
const real = await vi.importActual<typeof import('../Alert')>('../Alert');
59-
real.showAlert('Dismiss me', 'danger', 'dismiss', 'Dismiss Heading');
75+
await real.showAlert('Dismiss me', 'danger', 'dismiss', 'Dismiss Heading');
6076
const { rerender } = render(<real.ErrorAlert />);
6177
expect(screen.getByText('Dismiss me')).toBeTruthy();
6278
const closeButtons = screen.getAllByTestId('close-alert');

0 commit comments

Comments
 (0)