diff --git a/src/components/NotificationRow.test.tsx b/src/components/NotificationRow.test.tsx index 8432db2ae..c4d3693da 100644 --- a/src/components/NotificationRow.test.tsx +++ b/src/components/NotificationRow.test.tsx @@ -82,7 +82,7 @@ describe('components/NotificationRow.tsx', () => { describe('notification interactions', () => { it('should open a notification in the browser - click', () => { - const markNotificationRead = jest.fn(); + const markNotificationsAsRead = jest.fn(); const props = { notification: mockSingleNotification, @@ -93,7 +93,7 @@ describe('components/NotificationRow.tsx', () => { @@ -103,11 +103,11 @@ describe('components/NotificationRow.tsx', () => { fireEvent.click(screen.getByRole('main')); expect(links.openNotification).toHaveBeenCalledTimes(1); - expect(markNotificationRead).toHaveBeenCalledTimes(1); + expect(markNotificationsAsRead).toHaveBeenCalledTimes(1); }); it('should open a notification in the browser - delay notification setting enabled', () => { - const markNotificationRead = jest.fn(); + const markNotificationsAsRead = jest.fn(); const props = { notification: mockSingleNotification, @@ -122,7 +122,7 @@ describe('components/NotificationRow.tsx', () => { markAsDoneOnOpen: false, delayNotificationState: true, }, - markNotificationRead, + markNotificationsAsRead, auth: mockAuth, }} > @@ -132,11 +132,11 @@ describe('components/NotificationRow.tsx', () => { fireEvent.click(screen.getByRole('main')); expect(links.openNotification).toHaveBeenCalledTimes(1); - expect(markNotificationRead).toHaveBeenCalledTimes(1); + expect(markNotificationsAsRead).toHaveBeenCalledTimes(1); }); it('should open a notification in the browser - key down', () => { - const markNotificationRead = jest.fn(); + const markNotificationsAsRead = jest.fn(); const props = { notification: mockSingleNotification, @@ -147,7 +147,7 @@ describe('components/NotificationRow.tsx', () => { @@ -157,11 +157,11 @@ describe('components/NotificationRow.tsx', () => { fireEvent.click(screen.getByRole('main')); expect(links.openNotification).toHaveBeenCalledTimes(1); - expect(markNotificationRead).toHaveBeenCalledTimes(1); + expect(markNotificationsAsRead).toHaveBeenCalledTimes(1); }); it('should open a notification in browser & mark it as done', () => { - const markNotificationDone = jest.fn(); + const markNotificationsAsDone = jest.fn(); const props = { notification: mockSingleNotification, @@ -172,7 +172,7 @@ describe('components/NotificationRow.tsx', () => { @@ -182,11 +182,11 @@ describe('components/NotificationRow.tsx', () => { fireEvent.click(screen.getByRole('main')); expect(links.openNotification).toHaveBeenCalledTimes(1); - expect(markNotificationDone).toHaveBeenCalledTimes(1); + expect(markNotificationsAsDone).toHaveBeenCalledTimes(1); }); - it('should mark a notification as read', () => { - const markNotificationRead = jest.fn(); + it('should mark notifications as read', () => { + const markNotificationsAsRead = jest.fn(); const props = { notification: mockSingleNotification, @@ -197,7 +197,7 @@ describe('components/NotificationRow.tsx', () => { @@ -205,11 +205,11 @@ describe('components/NotificationRow.tsx', () => { ); fireEvent.click(screen.getByTitle('Mark as read')); - expect(markNotificationRead).toHaveBeenCalledTimes(1); + expect(markNotificationsAsRead).toHaveBeenCalledTimes(1); }); - it('should mark a notification as done', () => { - const markNotificationDone = jest.fn(); + it('should mark notifications as done', () => { + const markNotificationsAsDone = jest.fn(); const props = { notification: mockSingleNotification, @@ -218,14 +218,14 @@ describe('components/NotificationRow.tsx', () => { render( , ); fireEvent.click(screen.getByTitle('Mark as done')); - expect(markNotificationDone).toHaveBeenCalledTimes(1); + expect(markNotificationsAsDone).toHaveBeenCalledTimes(1); }); it('should unsubscribe from a notification thread', () => { diff --git a/src/components/NotificationRow.tsx b/src/components/NotificationRow.tsx index c5299372c..3bd305433 100644 --- a/src/components/NotificationRow.tsx +++ b/src/components/NotificationRow.tsx @@ -37,8 +37,8 @@ export const NotificationRow: FC = ({ }: INotificationRow) => { const { settings, - markNotificationRead, - markNotificationDone, + markNotificationsAsRead, + markNotificationsAsDone, unsubscribeNotification, } = useContext(AppContext); const [animateExit, setAnimateExit] = useState(false); @@ -51,11 +51,16 @@ export const NotificationRow: FC = ({ openNotification(notification); if (settings.markAsDoneOnOpen) { - markNotificationDone(notification); + markNotificationsAsDone([notification]); } else { - markNotificationRead(notification); + markNotificationsAsRead([notification]); } - }, [notification, markNotificationDone, markNotificationRead, settings]); + }, [ + notification, + markNotificationsAsRead, + markNotificationsAsDone, + settings, + ]); const unsubscribeFromThread = (event: MouseEvent) => { // Don't trigger onClick of parent element. @@ -137,7 +142,7 @@ export const NotificationRow: FC = ({ onClick={() => { setAnimateExit(!settings.delayNotificationState); setShowAsRead(settings.delayNotificationState); - markNotificationDone(notification); + markNotificationsAsDone([notification]); }} /> )} @@ -148,7 +153,7 @@ export const NotificationRow: FC = ({ onClick={() => { setAnimateExit(!settings.delayNotificationState); setShowAsRead(settings.delayNotificationState); - markNotificationRead(notification); + markNotificationsAsRead([notification]); }} /> ({ })); describe('components/RepositoryNotifications.tsx', () => { - const markRepoNotificationsRead = jest.fn(); - const markRepoNotificationsDone = jest.fn(); + const markNotificationsAsRead = jest.fn(); + const markNotificationsAsDone = jest.fn(); const props = { account: mockGitHubCloudAccount, @@ -58,7 +55,7 @@ describe('components/RepositoryNotifications.tsx', () => { it('should mark a repo as read', () => { render( , @@ -66,15 +63,15 @@ describe('components/RepositoryNotifications.tsx', () => { fireEvent.click(screen.getByTitle('Mark repository as read')); - expect(markRepoNotificationsRead).toHaveBeenCalledWith( - mockSingleNotification, + expect(markNotificationsAsRead).toHaveBeenCalledWith( + mockGitHubNotifications, ); }); it('should mark a repo as done', () => { render( , @@ -82,8 +79,8 @@ describe('components/RepositoryNotifications.tsx', () => { fireEvent.click(screen.getByTitle('Mark repository as done')); - expect(markRepoNotificationsDone).toHaveBeenCalledWith( - mockSingleNotification, + expect(markNotificationsAsDone).toHaveBeenCalledWith( + mockGitHubNotifications, ); }); diff --git a/src/components/RepositoryNotifications.tsx b/src/components/RepositoryNotifications.tsx index fa5604432..877958600 100644 --- a/src/components/RepositoryNotifications.tsx +++ b/src/components/RepositoryNotifications.tsx @@ -26,7 +26,7 @@ export const RepositoryNotifications: FC = ({ repoName, repoNotifications, }) => { - const { settings, markRepoNotificationsRead, markRepoNotificationsDone } = + const { settings, markNotificationsAsRead, markNotificationsAsDone } = useContext(AppContext); const [animateExit, setAnimateExit] = useState(false); const [showAsRead, setShowAsRead] = useState(false); @@ -91,7 +91,7 @@ export const RepositoryNotifications: FC = ({ event.stopPropagation(); setAnimateExit(!settings.delayNotificationState); setShowAsRead(settings.delayNotificationState); - markRepoNotificationsDone(repoNotifications[0]); + markNotificationsAsDone(repoNotifications); }} /> )} @@ -104,7 +104,7 @@ export const RepositoryNotifications: FC = ({ event.stopPropagation(); setAnimateExit(!settings.delayNotificationState); setShowAsRead(settings.delayNotificationState); - markRepoNotificationsRead(repoNotifications[0]); + markNotificationsAsRead(repoNotifications); }} /> { getNotificationCountMock.mockReturnValue(1); const fetchNotificationsMock = jest.fn(); - const markNotificationReadMock = jest.fn(); - const markNotificationDoneMock = jest.fn(); + const markNotificationsAsReadMock = jest.fn(); + const markNotificationsAsDoneMock = jest.fn(); const unsubscribeNotificationMock = jest.fn(); - const markRepoNotificationsReadMock = jest.fn(); - const markRepoNotificationsDoneMock = jest.fn(); const mockDefaultState = { auth: { accounts: [], enterpriseAccounts: [], token: null, user: null }, @@ -57,11 +55,9 @@ describe('context/App.tsx', () => { beforeEach(() => { (useNotifications as jest.Mock).mockReturnValue({ fetchNotifications: fetchNotificationsMock, - markNotificationRead: markNotificationReadMock, - markNotificationDone: markNotificationDoneMock, + markNotificationsAsRead: markNotificationsAsReadMock, + markNotificationsAsDone: markNotificationsAsDoneMock, unsubscribeNotification: unsubscribeNotificationMock, - markRepoNotificationsRead: markRepoNotificationsReadMock, - markRepoNotificationsDone: markRepoNotificationsDoneMock, }); }); @@ -117,14 +113,14 @@ describe('context/App.tsx', () => { expect(fetchNotificationsMock).toHaveBeenCalledTimes(1); }); - it('should call markNotificationRead', async () => { + it('should call markNotificationsAsRead', async () => { const TestComponent = () => { - const { markNotificationRead } = useContext(AppContext); + const { markNotificationsAsRead } = useContext(AppContext); return ( @@ -135,21 +131,21 @@ describe('context/App.tsx', () => { fireEvent.click(getByText('Test Case')); - expect(markNotificationReadMock).toHaveBeenCalledTimes(1); - expect(markNotificationReadMock).toHaveBeenCalledWith( + expect(markNotificationsAsReadMock).toHaveBeenCalledTimes(1); + expect(markNotificationsAsReadMock).toHaveBeenCalledWith( mockDefaultState, - mockSingleNotification, + [mockSingleNotification], ); }); - it('should call markNotificationDone', async () => { + it('should call markNotificationsAsDone', async () => { const TestComponent = () => { - const { markNotificationDone } = useContext(AppContext); + const { markNotificationsAsDone } = useContext(AppContext); return ( @@ -160,10 +156,10 @@ describe('context/App.tsx', () => { fireEvent.click(getByText('Test Case')); - expect(markNotificationDoneMock).toHaveBeenCalledTimes(1); - expect(markNotificationDoneMock).toHaveBeenCalledWith( + expect(markNotificationsAsDoneMock).toHaveBeenCalledTimes(1); + expect(markNotificationsAsDoneMock).toHaveBeenCalledWith( mockDefaultState, - mockSingleNotification, + [mockSingleNotification], ); }); @@ -191,64 +187,6 @@ describe('context/App.tsx', () => { mockSingleNotification, ); }); - - it('should call markRepoNotificationsRead', async () => { - const TestComponent = () => { - const { markRepoNotificationsRead } = useContext(AppContext); - - return ( - - ); - }; - - const { getByText } = customRender(); - - fireEvent.click(getByText('Test Case')); - - expect(markRepoNotificationsReadMock).toHaveBeenCalledTimes(1); - expect(markRepoNotificationsReadMock).toHaveBeenCalledWith( - mockDefaultState, - mockSingleNotification, - ); - }); - - it('should call markRepoNotificationsDone', async () => { - const TestComponent = () => { - const { markRepoNotificationsDone } = useContext(AppContext); - - return ( - - ); - }; - - const { getByText } = customRender(); - - fireEvent.click(getByText('Test Case')); - - expect(markRepoNotificationsDoneMock).toHaveBeenCalledTimes(1); - expect(markRepoNotificationsDoneMock).toHaveBeenCalledWith( - { - auth: { - accounts: [], - enterpriseAccounts: [], - token: null, - user: null, - }, - settings: mockSettings, - }, - mockSingleNotification, - ); - }); }); describe('authentication methods', () => { diff --git a/src/context/App.tsx b/src/context/App.tsx index 51a11b802..3322430fb 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -108,11 +108,9 @@ interface AppContextState { globalError: GitifyError; removeAccountNotifications: (account: Account) => Promise; fetchNotifications: () => Promise; - markNotificationRead: (notification: Notification) => Promise; - markNotificationDone: (notification: Notification) => Promise; + markNotificationsAsRead: (notifications: Notification[]) => Promise; + markNotificationsAsDone: (notifications: Notification[]) => Promise; unsubscribeNotification: (notification: Notification) => Promise; - markRepoNotificationsRead: (notification: Notification) => Promise; - markRepoNotificationsDone: (notification: Notification) => Promise; settings: SettingsState; clearFilters: () => void; @@ -131,11 +129,9 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { notifications, globalError, status, - markNotificationRead, - markNotificationDone, + markNotificationsAsRead, + markNotificationsAsDone, unsubscribeNotification, - markRepoNotificationsRead, - markRepoNotificationsDone, } = useNotifications(); useEffect(() => { @@ -291,16 +287,16 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { [auth, settings, fetchNotifications], ); - const markNotificationReadWithAccounts = useCallback( - async (notification: Notification) => - await markNotificationRead({ auth, settings }, notification), - [auth, settings, markNotificationRead], + const markNotificationsAsReadWithAccounts = useCallback( + async (notifications: Notification[]) => + await markNotificationsAsRead({ auth, settings }, notifications), + [auth, settings, markNotificationsAsRead], ); - const markNotificationDoneWithAccounts = useCallback( - async (notification: Notification) => - await markNotificationDone({ auth, settings }, notification), - [auth, settings, markNotificationDone], + const markNotificationsAsDoneWithAccounts = useCallback( + async (notifications: Notification[]) => + await markNotificationsAsDone({ auth, settings }, notifications), + [auth, settings, markNotificationsAsDone], ); const unsubscribeNotificationWithAccounts = useCallback( @@ -309,18 +305,6 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { [auth, settings, unsubscribeNotification], ); - const markRepoNotificationsReadWithAccounts = useCallback( - async (notification: Notification) => - await markRepoNotificationsRead({ auth, settings }, notification), - [auth, settings, markRepoNotificationsRead], - ); - - const markRepoNotificationsDoneWithAccounts = useCallback( - async (notification: Notification) => - await markRepoNotificationsDone({ auth, settings }, notification), - [auth, settings, markRepoNotificationsDone], - ); - return ( { status, globalError, fetchNotifications: fetchNotificationsWithAccounts, - markNotificationRead: markNotificationReadWithAccounts, - markNotificationDone: markNotificationDoneWithAccounts, + markNotificationsAsRead: markNotificationsAsReadWithAccounts, + markNotificationsAsDone: markNotificationsAsDoneWithAccounts, unsubscribeNotification: unsubscribeNotificationWithAccounts, - markRepoNotificationsRead: markRepoNotificationsReadWithAccounts, - markRepoNotificationsDone: markRepoNotificationsDoneWithAccounts, settings, clearFilters, diff --git a/src/electron/main.js b/src/electron/main.js index 108fede99..d4c1f88c0 100644 --- a/src/electron/main.js +++ b/src/electron/main.js @@ -26,9 +26,6 @@ const idleAlternateUpdateIcon = getIconPath('tray-idle-white-update.png'); const activeIcon = getIconPath('tray-active.png'); const activeUpdateIcon = getIconPath('tray-active-update.png'); -/** - * @type {Electron.BrowserWindowConstructorOptions} - */ const browserWindowOpts = { width: 500, height: 400, diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index ad41dc3d5..35f750140 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -345,8 +345,8 @@ describe('hooks/useNotifications.ts', () => { }); }); - describe('markNotificationRead', () => { - it('should mark a notification as read with success', async () => { + describe('markNotificationsAsRead', () => { + it('should mark notifications as read with success', async () => { nock('https://api.github.com/') .patch(`/notifications/threads/${id}`) .reply(200); @@ -354,7 +354,9 @@ describe('hooks/useNotifications.ts', () => { const { result } = renderHook(() => useNotifications()); act(() => { - result.current.markNotificationRead(mockState, mockSingleNotification); + result.current.markNotificationsAsRead(mockState, [ + mockSingleNotification, + ]); }); await waitFor(() => { @@ -364,7 +366,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it('should mark a notification as read with failure', async () => { + it('should mark notifications as read with failure', async () => { nock('https://api.github.com/') .patch(`/notifications/threads/${id}`) .reply(400); @@ -372,7 +374,9 @@ describe('hooks/useNotifications.ts', () => { const { result } = renderHook(() => useNotifications()); act(() => { - result.current.markNotificationRead(mockState, mockSingleNotification); + result.current.markNotificationsAsRead(mockState, [ + mockSingleNotification, + ]); }); await waitFor(() => { @@ -384,8 +388,8 @@ describe('hooks/useNotifications.ts', () => { }); }); - describe('markNotificationDone', () => { - it('should mark a notification as done with success', async () => { + describe('markNotificationsAsDone', () => { + it('should mark notifications as done with success', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(200); @@ -393,7 +397,9 @@ describe('hooks/useNotifications.ts', () => { const { result } = renderHook(() => useNotifications()); act(() => { - result.current.markNotificationDone(mockState, mockSingleNotification); + result.current.markNotificationsAsDone(mockState, [ + mockSingleNotification, + ]); }); await waitFor(() => { @@ -403,7 +409,7 @@ describe('hooks/useNotifications.ts', () => { expect(result.current.notifications.length).toBe(0); }); - it('should mark a notification as done with failure', async () => { + it('should mark notifications as done with failure', async () => { nock('https://api.github.com/') .delete(`/notifications/threads/${id}`) .reply(400); @@ -411,7 +417,9 @@ describe('hooks/useNotifications.ts', () => { const { result } = renderHook(() => useNotifications()); act(() => { - result.current.markNotificationDone(mockState, mockSingleNotification); + result.current.markNotificationsAsDone(mockState, [ + mockSingleNotification, + ]); }); await waitFor(() => { @@ -514,95 +522,4 @@ describe('hooks/useNotifications.ts', () => { expect(logErrorSpy).toHaveBeenCalledTimes(1); }); }); - - describe('markRepoNotificationsRead', () => { - const repoSlug = 'gitify-app/notifications-test'; - - it('should mark repository notifications as read with success', async () => { - nock('https://api.github.com/') - .put(`/repos/${repoSlug}/notifications`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsRead( - mockState, - mockSingleNotification, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); - }); - - it('should mark repository notifications as read with failure', async () => { - nock('https://api.github.com/') - .put(`/repos/${repoSlug}/notifications`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsRead( - mockState, - mockSingleNotification, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); - expect(logErrorSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('markRepoNotificationsDone', () => { - it('should mark repository notifications as done with success', async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsDone( - mockState, - mockSingleNotification, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); - }); - - it('should mark repository notifications as done with failure', async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsDone( - mockState, - mockSingleNotification, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); - }); - }); }); diff --git a/src/hooks/useNotifications.ts b/src/hooks/useNotifications.ts index e732aa0ba..5b377f311 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -12,42 +12,31 @@ import { ignoreNotificationThreadSubscription, markNotificationThreadAsDone, markNotificationThreadAsRead, - markRepositoryNotificationsAsRead, } from '../utils/api/client'; -import { getAccountUUID } from '../utils/auth/utils'; import { isMarkAsDoneFeatureSupported } from '../utils/helpers'; import { getAllNotifications, setTrayIconColor, triggerNativeNotifications, } from '../utils/notifications'; -import { removeNotification } from '../utils/remove-notification'; -import { removeNotifications } from '../utils/remove-notifications'; +import { removeNotifications } from '../utils/notifications/remove'; interface NotificationsState { notifications: AccountNotifications[]; removeAccountNotifications: (account: Account) => Promise; fetchNotifications: (state: GitifyState) => Promise; - markNotificationRead: ( + markNotificationsAsRead: ( state: GitifyState, - notification: Notification, + notifications: Notification[], ) => Promise; - markNotificationDone: ( + markNotificationsAsDone: ( state: GitifyState, - notification: Notification, + notifications: Notification[], ) => Promise; unsubscribeNotification: ( state: GitifyState, notification: Notification, ) => Promise; - markRepoNotificationsRead: ( - state: GitifyState, - notification: Notification, - ) => Promise; - markRepoNotificationsDone: ( - state: GitifyState, - notification: Notification, - ) => Promise; status: Status; globalError: GitifyError; } @@ -108,20 +97,24 @@ export const useNotifications = (): NotificationsState => { [notifications], ); - const markNotificationRead = useCallback( - async (state: GitifyState, notification: Notification) => { + const markNotificationsAsRead = useCallback( + async (state: GitifyState, readNotifications: Notification[]) => { setStatus('loading'); try { - await markNotificationThreadAsRead( - notification.id, - notification.account.hostname, - notification.account.token, + await Promise.all( + readNotifications.map((notification) => + markNotificationThreadAsRead( + notification.id, + notification.account.hostname, + notification.account.token, + ), + ), ); - const updatedNotifications = removeNotification( + const updatedNotifications = removeNotifications( state.settings, - notification, + readNotifications, notifications, ); @@ -136,29 +129,33 @@ export const useNotifications = (): NotificationsState => { [notifications], ); - const markNotificationDone = useCallback( - async (state: GitifyState, notification: Notification) => { + const markNotificationsAsDone = useCallback( + async (state: GitifyState, doneNotifications: Notification[]) => { setStatus('loading'); try { - if (isMarkAsDoneFeatureSupported(notification.account)) { - await markNotificationThreadAsDone( - notification.id, - notification.account.hostname, - notification.account.token, + if (isMarkAsDoneFeatureSupported(doneNotifications[0].account)) { + await Promise.all( + doneNotifications.map((notification) => + markNotificationThreadAsDone( + notification.id, + notification.account.hostname, + notification.account.token, + ), + ), ); } - const updatedNotifications = removeNotification( + const updatedNotifications = removeNotifications( state.settings, - notification, + doneNotifications, notifications, ); setNotifications(updatedNotifications); setTrayIconColor(updatedNotifications); } catch (err) { - log.error('Error occurred while marking notification as done', err); + log.error('Error occurred while marking notifications as done', err); } setStatus('success'); @@ -178,9 +175,9 @@ export const useNotifications = (): NotificationsState => { ); if (state.settings.markAsDoneOnUnsubscribe) { - await markNotificationDone(state, notification); + await markNotificationsAsDone(state, [notification]); } else { - await markNotificationRead(state, notification); + await markNotificationsAsRead(state, [notification]); } } catch (err) { log.error( @@ -191,88 +188,7 @@ export const useNotifications = (): NotificationsState => { setStatus('success'); }, - [markNotificationRead], - ); - - const markRepoNotificationsRead = useCallback( - async (state: GitifyState, notification: Notification) => { - setStatus('loading'); - - const repoSlug = notification.repository.full_name; - const hostname = notification.account.hostname; - - try { - await markRepositoryNotificationsAsRead( - repoSlug, - hostname, - notification.account.token, - ); - - const updatedNotifications = removeNotifications( - state.settings, - notification, - notifications, - ); - - setNotifications(updatedNotifications); - setTrayIconColor(updatedNotifications); - } catch (err) { - log.error( - 'Error occurred while marking repository notifications as read', - err, - ); - } - - setStatus('success'); - }, - [notifications], - ); - - const markRepoNotificationsDone = useCallback( - async (state: GitifyState, notification: Notification) => { - setStatus('loading'); - - const repoSlug = notification.repository.full_name; - - try { - const accountIndex = notifications.findIndex( - (accountNotifications) => - getAccountUUID(accountNotifications.account) === - getAccountUUID(notification.account), - ); - - if (accountIndex !== -1) { - const notificationsToRemove = notifications[ - accountIndex - ].notifications.filter( - (notification) => notification.repository.full_name === repoSlug, - ); - - await Promise.all( - notificationsToRemove.map((notification) => - markNotificationDone(state, notification), - ), - ); - } - - const updatedNotifications = removeNotifications( - state.settings, - notification, - notifications, - ); - - setNotifications(updatedNotifications); - setTrayIconColor(updatedNotifications); - } catch (err) { - log.error( - 'Error occurred while marking repository notifications as done', - err, - ); - } - - setStatus('success'); - }, - [notifications, markNotificationDone], + [markNotificationsAsRead, markNotificationsAsDone], ); return { @@ -282,10 +198,8 @@ export const useNotifications = (): NotificationsState => { removeAccountNotifications, fetchNotifications, - markNotificationRead, - markNotificationDone, + markNotificationsAsRead, + markNotificationsAsDone, unsubscribeNotification, - markRepoNotificationsRead, - markRepoNotificationsDone, }; }; diff --git a/src/utils/api/__snapshots__/client.test.ts.snap b/src/utils/api/__snapshots__/client.test.ts.snap index 5e586536a..a149d8da2 100644 --- a/src/utils/api/__snapshots__/client.test.ts.snap +++ b/src/utils/api/__snapshots__/client.test.ts.snap @@ -116,21 +116,3 @@ exports[`utils/api/client.ts markNotificationThreadAsRead should mark notificati "Content-Type": "application/json", } `; - -exports[`utils/api/client.ts markRepositoryNotificationsAsRead should mark repository notifications as read - enterprise 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token token-123-456", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; - -exports[`utils/api/client.ts markRepositoryNotificationsAsRead should mark repository notifications as read - github 1`] = ` -{ - "Accept": "application/json", - "Authorization": "token token-123-456", - "Cache-Control": "", - "Content-Type": "application/json", -} -`; diff --git a/src/utils/api/client.test.ts b/src/utils/api/client.test.ts index 0581106cf..6faf66f39 100644 --- a/src/utils/api/client.test.ts +++ b/src/utils/api/client.test.ts @@ -14,7 +14,6 @@ import { listNotificationsForAuthenticatedUser, markNotificationThreadAsDone, markNotificationThreadAsRead, - markRepositoryNotificationsAsRead, } from './client'; import * as apiRequests from './request'; @@ -23,7 +22,6 @@ jest.mock('axios'); const mockGitHubHostname = 'github.com' as Hostname; const mockEnterpriseHostname = 'example.com' as Hostname; const mockThreadId = '1234'; -const mockRepoSlug = 'gitify-app/notifications-test'; describe('utils/api/client.ts', () => { afterEach(() => { @@ -245,40 +243,6 @@ describe('utils/api/client.ts', () => { }); }); - describe('markRepositoryNotificationsAsRead', () => { - it('should mark repository notifications as read - github', async () => { - await markRepositoryNotificationsAsRead( - mockRepoSlug, - mockGitHubHostname, - mockToken, - ); - - expect(axios).toHaveBeenCalledWith({ - url: `https://api.github.com/repos/${mockRepoSlug}/notifications`, - method: 'PUT', - data: {}, - }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); - }); - - it('should mark repository notifications as read - enterprise', async () => { - await markRepositoryNotificationsAsRead( - mockRepoSlug, - mockEnterpriseHostname, - mockToken, - ); - - expect(axios).toHaveBeenCalledWith({ - url: `https://example.com/api/v3/repos/${mockRepoSlug}/notifications`, - method: 'PUT', - data: {}, - }); - - expect(axios.defaults.headers.common).toMatchSnapshot(); - }); - }); - describe('getHtmlUrl', () => { it('should return the HTML URL', async () => { const apiRequestAuthMock = jest.spyOn(apiRequests, 'apiRequestAuth'); diff --git a/src/utils/api/client.ts b/src/utils/api/client.ts index 54115c276..e5c4e5dd9 100644 --- a/src/utils/api/client.ts +++ b/src/utils/api/client.ts @@ -131,25 +131,6 @@ export function ignoreNotificationThreadSubscription( }); } -/** - * Marks all notifications in a repository as "read" for the current user. - * If the number of notifications is too large to complete in one request, - * you will receive a 202 Accepted status and GitHub will run an asynchronous - * process to mark notifications as "read." - * - * Endpoint documentation: https://docs.github.com/en/rest/activity/notifications#mark-repository-notifications-as-read - */ -export function markRepositoryNotificationsAsRead( - repoSlug: string, - hostname: Hostname, - token: Token, -): AxiosPromise { - const url = getGitHubAPIBaseUrl(hostname); - url.pathname += `repos/${repoSlug}/notifications`; - - return apiRequestAuth(url.toString() as Link, 'PUT', token, {}); -} - /** * Returns the contents of a single commit reference. * diff --git a/src/utils/remove-notification.test.ts b/src/utils/notifications/remove.test.ts similarity index 57% rename from src/utils/remove-notification.test.ts rename to src/utils/notifications/remove.test.ts index fc80b22cd..dc433cb52 100644 --- a/src/utils/remove-notification.test.ts +++ b/src/utils/notifications/remove.test.ts @@ -1,15 +1,15 @@ -import { mockSingleAccountNotifications } from '../__mocks__/notifications-mocks'; -import { mockSettings } from '../__mocks__/state-mocks'; -import { mockSingleNotification } from './api/__mocks__/response-mocks'; -import { removeNotification } from './remove-notification'; +import { mockSingleAccountNotifications } from '../../__mocks__/notifications-mocks'; +import { mockSettings } from '../../__mocks__/state-mocks'; +import { mockSingleNotification } from '../api/__mocks__/response-mocks'; +import { removeNotifications } from './remove'; -describe('utils/remove-notification.ts', () => { +describe('utils/remove.ts', () => { it('should remove a notification if it exists', () => { expect(mockSingleAccountNotifications[0].notifications.length).toBe(1); - const result = removeNotification( + const result = removeNotifications( { ...mockSettings, delayNotificationState: false }, - mockSingleNotification, + [mockSingleNotification], mockSingleAccountNotifications, ); @@ -19,9 +19,9 @@ describe('utils/remove-notification.ts', () => { it('should skip notification removal if delay state enabled', () => { expect(mockSingleAccountNotifications[0].notifications.length).toBe(1); - const result = removeNotification( + const result = removeNotifications( { ...mockSettings, delayNotificationState: true }, - mockSingleNotification, + [mockSingleNotification], mockSingleAccountNotifications, ); diff --git a/src/utils/notifications/remove.ts b/src/utils/notifications/remove.ts new file mode 100644 index 000000000..6b1d0cbd3 --- /dev/null +++ b/src/utils/notifications/remove.ts @@ -0,0 +1,41 @@ +import type { AccountNotifications, SettingsState } from '../../types'; +import type { Notification } from '../../typesGitHub'; +import { getAccountUUID } from '../auth/utils'; + +export function removeNotifications( + settings: SettingsState, + notificationsToRemove: Notification[], + allNotifications: AccountNotifications[], +): AccountNotifications[] { + if (settings.delayNotificationState) { + return allNotifications; + } + + if (notificationsToRemove.length === 0) { + return allNotifications; + } + + const removeNotificationAccount = notificationsToRemove[0].account; + const removeNotificationIDs = notificationsToRemove.map( + (notification) => notification.id, + ); + + const accountIndex = allNotifications.findIndex( + (accountNotifications) => + getAccountUUID(accountNotifications.account) === + getAccountUUID(removeNotificationAccount), + ); + + if (accountIndex !== -1) { + const updatedNotifications = [...allNotifications]; + updatedNotifications[accountIndex] = { + ...updatedNotifications[accountIndex], + notifications: updatedNotifications[accountIndex].notifications.filter( + (notification) => !removeNotificationIDs.includes(notification.id), + ), + }; + return updatedNotifications; + } + + return allNotifications; +} diff --git a/src/utils/remove-notification.ts b/src/utils/remove-notification.ts deleted file mode 100644 index 84734f425..000000000 --- a/src/utils/remove-notification.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { AccountNotifications, SettingsState } from '../types'; -import type { Notification } from '../typesGitHub'; -import { getAccountUUID } from './auth/utils'; - -export function removeNotification( - settings: SettingsState, - notification: Notification, - notifications: AccountNotifications[], -): AccountNotifications[] { - if (settings.delayNotificationState) { - return notifications; - } - - const notificationId = notification.id; - - const accountIndex = notifications.findIndex( - (accountNotifications) => - getAccountUUID(accountNotifications.account) === - getAccountUUID(notification.account), - ); - - if (accountIndex !== -1) { - const updatedNotifications = [...notifications]; - updatedNotifications[accountIndex] = { - ...updatedNotifications[accountIndex], - notifications: updatedNotifications[accountIndex].notifications.filter( - (notification) => notification.id !== notificationId, - ), - }; - return updatedNotifications; - } - - return notifications; -} diff --git a/src/utils/remove-notifications.test.ts b/src/utils/remove-notifications.test.ts deleted file mode 100644 index 98d476de8..000000000 --- a/src/utils/remove-notifications.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - mockAccountNotifications, - mockSingleAccountNotifications, -} from '../__mocks__/notifications-mocks'; -import { mockSettings } from '../__mocks__/state-mocks'; -import { mockSingleNotification } from './api/__mocks__/response-mocks'; -import { removeNotifications } from './remove-notifications'; - -describe('utils/remove-notifications.ts', () => { - it("should remove a repo's notifications - single", () => { - expect(mockSingleAccountNotifications[0].notifications.length).toBe(1); - - const result = removeNotifications( - mockSettings, - mockSingleNotification, - mockSingleAccountNotifications, - ); - - expect(result[0].notifications.length).toBe(0); - }); - - it("should remove a repo's notifications - multiple", () => { - expect(mockAccountNotifications[0].notifications.length).toBe(2); - expect(mockAccountNotifications[1].notifications.length).toBe(2); - - const result = removeNotifications( - mockSettings, - mockSingleNotification, - mockAccountNotifications, - ); - - expect(result[0].notifications.length).toBe(0); - expect(result[1].notifications.length).toBe(2); - }); - - it('should skip notification removal if delay state enabled', () => { - expect(mockSingleAccountNotifications[0].notifications.length).toBe(1); - - const result = removeNotifications( - { ...mockSettings, delayNotificationState: true }, - mockSingleNotification, - mockSingleAccountNotifications, - ); - - expect(result[0].notifications.length).toBe(1); - }); -}); diff --git a/src/utils/remove-notifications.ts b/src/utils/remove-notifications.ts deleted file mode 100644 index 507d46fa0..000000000 --- a/src/utils/remove-notifications.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { AccountNotifications, SettingsState } from '../types'; -import type { Notification } from '../typesGitHub'; -import { getAccountUUID } from './auth/utils'; - -export function removeNotifications( - settings: SettingsState, - notification: Notification, - notifications: AccountNotifications[], -): AccountNotifications[] { - if (settings.delayNotificationState) { - return notifications; - } - - const repoSlug = notification.repository.full_name; - - const accountIndex = notifications.findIndex( - (accountNotifications) => - getAccountUUID(accountNotifications.account) === - getAccountUUID(notification.account), - ); - - if (accountIndex !== -1) { - const updatedNotifications = [...notifications]; - updatedNotifications[accountIndex] = { - ...updatedNotifications[accountIndex], - notifications: updatedNotifications[accountIndex].notifications.filter( - (notification) => notification.repository.full_name !== repoSlug, - ), - }; - return updatedNotifications; - } - - return notifications; -}