Skip to content

Commit 75d892a

Browse files
authored
fix: duplicate system notifications (#1038)
1 parent 8d8341d commit 75d892a

File tree

4 files changed

+39
-88
lines changed

4 files changed

+39
-88
lines changed

src/components/Sidebar.test.tsx

Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { act, fireEvent, render, screen } from '@testing-library/react';
1+
import { fireEvent, render, screen } from '@testing-library/react';
22
import { MemoryRouter } from 'react-router-dom';
33
import * as TestRenderer from 'react-test-renderer';
44
const { shell, ipcRenderer } = require('electron');
55
import { mockSettings } from '../__mocks__/mock-state';
66
import { mockedAccountNotifications } from '../__mocks__/mockedData';
77
import { AppContext } from '../context/App';
8-
import Constants from '../utils/constants';
98
import { Sidebar } from './Sidebar';
109

1110
const mockNavigate = jest.fn();
@@ -20,15 +19,12 @@ describe('components/Sidebar.tsx', () => {
2019
beforeEach(() => {
2120
fetchNotifications.mockReset();
2221

23-
jest.useFakeTimers();
24-
2522
jest.spyOn(ipcRenderer, 'send');
2623
jest.spyOn(shell, 'openExternal');
2724
jest.spyOn(window, 'clearInterval');
2825
});
2926

3027
afterEach(() => {
31-
jest.clearAllTimers();
3228
jest.clearAllMocks();
3329
});
3430

@@ -61,37 +57,6 @@ describe('components/Sidebar.tsx', () => {
6157
expect(tree).toMatchSnapshot();
6258
});
6359

64-
it('should fetch notifications every minute', async () => {
65-
render(
66-
<AppContext.Provider
67-
value={{ isLoggedIn: true, notifications: [], fetchNotifications }}
68-
>
69-
<MemoryRouter>
70-
<Sidebar />
71-
</MemoryRouter>
72-
</AppContext.Provider>,
73-
);
74-
fetchNotifications.mockReset();
75-
76-
act(() => {
77-
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
78-
return;
79-
});
80-
expect(fetchNotifications).toHaveBeenCalledTimes(1);
81-
82-
act(() => {
83-
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
84-
return;
85-
});
86-
expect(fetchNotifications).toHaveBeenCalledTimes(2);
87-
88-
act(() => {
89-
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
90-
return;
91-
});
92-
expect(fetchNotifications).toHaveBeenCalledTimes(3);
93-
});
94-
9560
it('should refresh the notifications', () => {
9661
render(
9762
<AppContext.Provider
@@ -103,19 +68,9 @@ describe('components/Sidebar.tsx', () => {
10368
</AppContext.Provider>,
10469
);
10570
fetchNotifications.mockReset();
106-
107-
const enabledRefreshButton = 'Refresh Notifications';
108-
109-
fireEvent.click(screen.getByTitle(enabledRefreshButton));
71+
fireEvent.click(screen.getByTitle('Refresh Notifications'));
11072

11173
expect(fetchNotifications).toHaveBeenCalledTimes(1);
112-
113-
act(() => {
114-
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
115-
return;
116-
});
117-
118-
expect(fetchNotifications).toHaveBeenCalledTimes(2);
11974
});
12075

12176
it('go to the settings route', () => {

src/components/Sidebar.tsx

Lines changed: 1 addition & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,7 @@ import {
66
} from '@primer/octicons-react';
77
import { ipcRenderer } from 'electron';
88

9-
import {
10-
type FC,
11-
useCallback,
12-
useContext,
13-
useEffect,
14-
useMemo,
15-
useRef,
16-
} from 'react';
9+
import { type FC, useCallback, useContext, useMemo } from 'react';
1710
import { useLocation, useNavigate } from 'react-router-dom';
1811

1912
import { Logo } from '../components/Logo';
@@ -29,38 +22,6 @@ export const Sidebar: FC = () => {
2922
const { notifications, fetchNotifications, isLoggedIn, isFetching } =
3023
useContext(AppContext);
3124

32-
const useFetchInterval = (callback, delay: number) => {
33-
const savedCallback = useRef(callback);
34-
const intervalRef = useRef(null);
35-
36-
useEffect(() => {
37-
savedCallback.current = callback;
38-
}, [callback]);
39-
40-
useEffect(() => {
41-
if (delay !== null) {
42-
const id = setInterval(savedCallback.current, delay);
43-
intervalRef.current = id;
44-
return () => clearInterval(id);
45-
}
46-
}, [delay]);
47-
48-
const resetFetchInterval = useCallback(() => {
49-
if (intervalRef.current !== null) {
50-
clearInterval(intervalRef.current);
51-
intervalRef.current = setInterval(savedCallback.current, delay);
52-
}
53-
}, [delay]);
54-
55-
return { resetFetchInterval };
56-
};
57-
58-
const { resetFetchInterval } = useFetchInterval(() => {
59-
if (isLoggedIn) {
60-
fetchNotifications();
61-
}
62-
}, Constants.FETCH_INTERVAL);
63-
6425
const onOpenBrowser = useCallback(() => {
6526
openExternalLink(`https://github.com/${Constants.REPO_SLUG}`);
6627
}, []);
@@ -119,7 +80,6 @@ export const Sidebar: FC = () => {
11980
onClick={() => {
12081
navigate('/', { replace: true });
12182
fetchNotifications();
122-
resetFetchInterval();
12383
}}
12484
disabled={isFetching}
12585
>

src/context/App.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { useNotifications } from '../hooks/useNotifications';
66
import type { AuthState, SettingsState } from '../types';
77
import * as apiRequests from '../utils/api-requests';
88
import * as comms from '../utils/comms';
9+
import Constants from '../utils/constants';
910
import * as notifications from '../utils/notifications';
1011
import * as storage from '../utils/storage';
1112
import { AppContext, AppProvider } from './App';
@@ -58,6 +59,36 @@ describe('context/App.tsx', () => {
5859
});
5960
});
6061

62+
it('fetch notifications every minute', async () => {
63+
customRender(null);
64+
65+
// Wait for the useEffects, for settings.participating and accounts, to run.
66+
// Those aren't what we're testing
67+
await waitFor(() =>
68+
expect(fetchNotificationsMock).toHaveBeenCalledTimes(1),
69+
);
70+
71+
fetchNotificationsMock.mockReset();
72+
73+
act(() => {
74+
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
75+
return;
76+
});
77+
expect(fetchNotificationsMock).toHaveBeenCalledTimes(1);
78+
79+
act(() => {
80+
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
81+
return;
82+
});
83+
expect(fetchNotificationsMock).toHaveBeenCalledTimes(2);
84+
85+
act(() => {
86+
jest.advanceTimersByTime(Constants.FETCH_INTERVAL);
87+
return;
88+
});
89+
expect(fetchNotificationsMock).toHaveBeenCalledTimes(3);
90+
});
91+
6192
it('should call fetchNotifications', async () => {
6293
const TestComponent = () => {
6394
const { fetchNotifications } = useContext(AppContext);

src/context/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
useState,
88
} from 'react';
99

10+
import { useInterval } from '../hooks/useInterval';
1011
import { useNotifications } from '../hooks/useNotifications';
1112
import {
1213
type AccountNotifications,
@@ -110,6 +111,10 @@ export const AppProvider = ({ children }: { children: ReactNode }) => {
110111
accounts.enterpriseAccounts.length,
111112
]);
112113

114+
useInterval(() => {
115+
fetchNotifications(accounts, settings);
116+
}, Constants.FETCH_INTERVAL);
117+
113118
// biome-ignore lint/correctness/useExhaustiveDependencies: We need to update tray title when settings or notifications changes.
114119
useEffect(() => {
115120
const count = getNotificationCount(notifications);

0 commit comments

Comments
 (0)