diff --git a/src/__mocks__/state-mocks.ts b/src/__mocks__/state-mocks.ts index 118e11bbe..d00eaebbc 100644 --- a/src/__mocks__/state-mocks.ts +++ b/src/__mocks__/state-mocks.ts @@ -1,6 +1,7 @@ import { type Account, type AuthState, + type GitifyState, type GitifyUser, type SettingsState, Theme, @@ -72,3 +73,8 @@ export const mockSettings: SettingsState = { showAccountHostname: false, delayNotificationState: false, }; + +export const mockState: GitifyState = { + auth: mockAuth, + settings: mockSettings, +}; diff --git a/src/context/App.test.tsx b/src/context/App.test.tsx index 71dc830d9..9ae3cdcfc 100644 --- a/src/context/App.test.tsx +++ b/src/context/App.test.tsx @@ -48,6 +48,11 @@ describe('context/App.tsx', () => { const markRepoNotificationsMock = jest.fn(); const markRepoNotificationsDoneMock = jest.fn(); + const mockDefaultState = { + auth: { accounts: [], enterpriseAccounts: [], token: null, user: null }, + settings: mockSettings, + }; + beforeEach(() => { (useNotifications as jest.Mock).mockReturnValue({ fetchNotifications: fetchNotificationsMock, @@ -131,13 +136,7 @@ describe('context/App.tsx', () => { expect(markNotificationReadMock).toHaveBeenCalledTimes(1); expect(markNotificationReadMock).toHaveBeenCalledWith( - { - accounts: [], - enterpriseAccounts: [], - token: null, - user: null, - }, - mockSettings, + mockDefaultState, '123-456', 'github.com', ); @@ -165,8 +164,7 @@ describe('context/App.tsx', () => { expect(markNotificationDoneMock).toHaveBeenCalledTimes(1); expect(markNotificationDoneMock).toHaveBeenCalledWith( - { accounts: [], enterpriseAccounts: [], token: null, user: null }, - mockSettings, + mockDefaultState, '123-456', 'github.com', ); @@ -194,8 +192,7 @@ describe('context/App.tsx', () => { expect(unsubscribeNotificationMock).toHaveBeenCalledTimes(1); expect(unsubscribeNotificationMock).toHaveBeenCalledWith( - { accounts: [], enterpriseAccounts: [], token: null, user: null }, - mockSettings, + mockDefaultState, '123-456', 'github.com', ); @@ -228,8 +225,7 @@ describe('context/App.tsx', () => { expect(markRepoNotificationsMock).toHaveBeenCalledTimes(1); expect(markRepoNotificationsMock).toHaveBeenCalledWith( - { accounts: [], enterpriseAccounts: [], token: null, user: null }, - mockSettings, + mockDefaultState, 'gitify-app/notifications-test', 'github.com', ); @@ -262,8 +258,15 @@ describe('context/App.tsx', () => { expect(markRepoNotificationsDoneMock).toHaveBeenCalledTimes(1); expect(markRepoNotificationsDoneMock).toHaveBeenCalledWith( - { accounts: [], enterpriseAccounts: [], token: null, user: null }, - mockSettings, + { + auth: { + accounts: [], + enterpriseAccounts: [], + token: null, + user: null, + }, + settings: mockSettings, + }, 'gitify-app/notifications-test', 'github.com', ); diff --git a/src/context/App.tsx b/src/context/App.tsx index b84c8b3ec..0ef0bf767 100644 --- a/src/context/App.tsx +++ b/src/context/App.tsx @@ -113,7 +113,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { // biome-ignore lint/correctness/useExhaustiveDependencies: We only want fetchNotifications to be called for certain account or setting changes. useEffect(() => { - fetchNotifications(auth, settings); + fetchNotifications({ auth, settings }); }, [ settings.participating, settings.showBots, @@ -122,7 +122,7 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { ]); useInterval(() => { - fetchNotifications(auth, settings); + fetchNotifications({ auth, settings }); }, Constants.FETCH_INTERVAL); // biome-ignore lint/correctness/useExhaustiveDependencies: We need to update tray title when settings or notifications changes. @@ -213,37 +213,37 @@ export const AppProvider = ({ children }: { children: ReactNode }) => { }, []); const fetchNotificationsWithAccounts = useCallback( - async () => await fetchNotifications(auth, settings), + async () => await fetchNotifications({ auth, settings }), [auth, settings, notifications], ); const markNotificationReadWithAccounts = useCallback( async (id: string, hostname: string) => - await markNotificationRead(auth, settings, id, hostname), + await markNotificationRead({ auth, settings }, id, hostname), [auth, notifications], ); const markNotificationDoneWithAccounts = useCallback( async (id: string, hostname: string) => - await markNotificationDone(auth, settings, id, hostname), + await markNotificationDone({ auth, settings }, id, hostname), [auth, notifications], ); const unsubscribeNotificationWithAccounts = useCallback( async (id: string, hostname: string) => - await unsubscribeNotification(auth, settings, id, hostname), + await unsubscribeNotification({ auth, settings }, id, hostname), [auth, notifications], ); const markRepoNotificationsWithAccounts = useCallback( async (repoSlug: string, hostname: string) => - await markRepoNotifications(auth, settings, repoSlug, hostname), + await markRepoNotifications({ auth, settings }, repoSlug, hostname), [auth, notifications], ); const markRepoNotificationsDoneWithAccounts = useCallback( async (repoSlug: string, hostname: string) => - await markRepoNotificationsDone(auth, settings, repoSlug, hostname), + await markRepoNotificationsDone({ auth, settings }, repoSlug, hostname), [auth, notifications], ); diff --git a/src/hooks/useNotifications.test.ts b/src/hooks/useNotifications.test.ts index cf33a3b79..049732824 100644 --- a/src/hooks/useNotifications.test.ts +++ b/src/hooks/useNotifications.test.ts @@ -6,6 +6,7 @@ import { mockAuth, mockGitHubCloudAccount, mockSettings, + mockState, } from '../__mocks__/state-mocks'; import { mockNotificationUser } from '../utils/api/__mocks__/response-mocks'; import { Errors } from '../utils/constants'; @@ -19,274 +20,275 @@ describe('hooks/useNotifications.ts', () => { }); describe('fetchNotifications', () => { - describe('github.com & enterprise', () => { - it('should fetch notifications with success - github.com & enterprise', async () => { - const notifications = [ - { id: 1, title: 'This is a notification.' }, - { id: 2, title: 'This is another one.' }, - ]; - - nock('https://api.github.com') - .get('/notifications?participating=false') - .reply(200, notifications); - - nock('https://github.gitify.io/api/v3') - .get('/notifications?participating=false') - .reply(200, notifications); - - const { result } = renderHook(() => useNotifications()); + it('should fetch non-detailed notifications with success', async () => { + const mockState = { + auth: mockAuth, + settings: { + ...mockSettings, + detailedNotifications: false, + }, + }; - act(() => { - result.current.fetchNotifications(mockAuth, { - ...mockSettings, - detailedNotifications: false, - }); - }); + const notifications = [ + { id: 1, title: 'This is a notification.' }, + { id: 2, title: 'This is another one.' }, + ]; - expect(result.current.status).toBe('loading'); + nock('https://api.github.com') + .get('/notifications?participating=false') + .reply(200, notifications); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + nock('https://github.gitify.io/api/v3') + .get('/notifications?participating=false') + .reply(200, notifications); - expect(result.current.notifications[0].account.hostname).toBe( - 'github.com', - ); - expect(result.current.notifications[0].notifications.length).toBe(2); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications[1].account.hostname).toBe( - 'github.gitify.io', - ); - expect(result.current.notifications[1].notifications.length).toBe(2); + act(() => { + result.current.fetchNotifications(mockState); }); - it('should fetch notifications with failures - github.com & enterprise', async () => { - const code = AxiosError.ERR_BAD_REQUEST; - const status = 400; - const message = 'Oops! Something went wrong.'; - - nock('https://api.github.com/') - .get('/notifications?participating=false') - .replyWithError({ - code, - response: { - status, - data: { - message, - }, - }, - }); - - nock('https://github.gitify.io/api/v3/') - .get('/notifications?participating=false') - .replyWithError({ - code, - response: { - status, - data: { - message, - }, - }, - }); - - const { result } = renderHook(() => useNotifications()); + expect(result.current.status).toBe('loading'); - act(() => { - result.current.fetchNotifications(mockAuth, mockSettings); - }); - - expect(result.current.status).toBe('loading'); + await waitFor(() => { + expect(result.current.status).toBe('success'); + }); - await waitFor(() => { - expect(result.current.status).toBe('error'); - }); + expect(result.current.notifications[0].account.hostname).toBe( + 'github.com', + ); + expect(result.current.notifications[0].notifications.length).toBe(2); - expect(result.current.errorDetails).toBe(Errors.UNKNOWN); - }); + expect(result.current.notifications[1].account.hostname).toBe( + 'github.gitify.io', + ); + expect(result.current.notifications[1].notifications.length).toBe(2); }); - describe('with detailed notifications', () => { - it('should fetch notifications with success', async () => { - const notifications = [ - { - id: 1, - subject: { - title: 'This is a check suite workflow.', - type: 'CheckSuite', - url: null, - latest_comment_url: null, - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, + it('should fetch detailed notifications with success', async () => { + const mockNotifications = [ + { + id: 1, + subject: { + title: 'This is a check suite workflow.', + type: 'CheckSuite', + url: null, + latest_comment_url: null, }, - { - id: 2, - subject: { - title: 'This is a Discussion.', - type: 'Discussion', - url: null, - latest_comment_url: null, - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, - updated_at: '2024-02-26T00:00:00Z', + repository: { + full_name: 'gitify-app/notifications-test', }, - { - id: 3, - subject: { - title: 'This is an Issue.', - type: 'Issue', - url: 'https://api.github.com/repos/gitify-app/notifications-test/issues/3', - latest_comment_url: - 'https://api.github.com/repos/gitify-app/notifications-test/issues/3/comments', - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, + }, + { + id: 2, + subject: { + title: 'This is a Discussion.', + type: 'Discussion', + url: null, + latest_comment_url: null, }, - { - id: 4, - subject: { - title: 'This is a Pull Request.', - type: 'PullRequest', - url: 'https://api.github.com/repos/gitify-app/notifications-test/pulls/4', - latest_comment_url: - 'https://api.github.com/repos/gitify-app/notifications-test/issues/4/comments', - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, + repository: { + full_name: 'gitify-app/notifications-test', }, - { - id: 5, - subject: { - title: 'This is an invitation.', - type: 'RepositoryInvitation', - url: null, - latest_comment_url: null, - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, + updated_at: '2024-02-26T00:00:00Z', + }, + { + id: 3, + subject: { + title: 'This is an Issue.', + type: 'Issue', + url: 'https://api.github.com/repos/gitify-app/notifications-test/issues/3', + latest_comment_url: + 'https://api.github.com/repos/gitify-app/notifications-test/issues/3/comments', }, - { - id: 6, - subject: { - title: 'This is a workflow run.', - type: 'WorkflowRun', - url: null, - latest_comment_url: null, - }, - repository: { - full_name: 'gitify-app/notifications-test', - }, + repository: { + full_name: 'gitify-app/notifications-test', + }, + }, + { + id: 4, + subject: { + title: 'This is a Pull Request.', + type: 'PullRequest', + url: 'https://api.github.com/repos/gitify-app/notifications-test/pulls/4', + latest_comment_url: + 'https://api.github.com/repos/gitify-app/notifications-test/issues/4/comments', + }, + repository: { + full_name: 'gitify-app/notifications-test', + }, + }, + { + id: 5, + subject: { + title: 'This is an invitation.', + type: 'RepositoryInvitation', + url: null, + latest_comment_url: null, + }, + repository: { + full_name: 'gitify-app/notifications-test', + }, + }, + { + id: 6, + subject: { + title: 'This is a workflow run.', + type: 'WorkflowRun', + url: null, + latest_comment_url: null, }, - ]; + repository: { + full_name: 'gitify-app/notifications-test', + }, + }, + ]; - nock('https://api.github.com') - .get('/notifications?participating=false') - .reply(200, notifications); + nock('https://api.github.com') + .get('/notifications?participating=false') + .reply(200, mockNotifications); - nock('https://api.github.com') - .post('/graphql') - .reply(200, { - data: { - search: { - nodes: [ - { - title: 'This is a Discussion.', - stateReason: null, - isAnswered: true, - url: 'https://github.com/gitify-app/notifications-test/discussions/612', - author: { - login: 'discussion-creator', - url: 'https://github.com/discussion-creator', - avatar_url: - 'https://avatars.githubusercontent.com/u/133795385?s=200&v=4', - type: 'User', - }, - comments: { - nodes: [ - { - databaseId: 2297637, - createdAt: '2022-03-04T20:39:44Z', - author: { - login: 'comment-user', - url: 'https://github.com/comment-user', - avatar_url: - 'https://avatars.githubusercontent.com/u/1?v=4', - type: 'User', - }, - replies: { - nodes: [], - }, + nock('https://api.github.com') + .post('/graphql') + .reply(200, { + data: { + search: { + nodes: [ + { + title: 'This is a Discussion.', + stateReason: null, + isAnswered: true, + url: 'https://github.com/gitify-app/notifications-test/discussions/612', + author: { + login: 'discussion-creator', + url: 'https://github.com/discussion-creator', + avatar_url: + 'https://avatars.githubusercontent.com/u/133795385?s=200&v=4', + type: 'User', + }, + comments: { + nodes: [ + { + databaseId: 2297637, + createdAt: '2022-03-04T20:39:44Z', + author: { + login: 'comment-user', + url: 'https://github.com/comment-user', + avatar_url: + 'https://avatars.githubusercontent.com/u/1?v=4', + type: 'User', + }, + replies: { + nodes: [], }, - ], - }, - labels: null, + }, + ], }, - ], - }, + labels: null, + }, + ], }, - }); - - nock('https://api.github.com') - .get('/repos/gitify-app/notifications-test/issues/3') - .reply(200, { - state: 'closed', - merged: true, - user: mockNotificationUser, - labels: [], - }); - nock('https://api.github.com') - .get('/repos/gitify-app/notifications-test/issues/3/comments') - .reply(200, { - user: mockNotificationUser, - }); - nock('https://api.github.com') - .get('/repos/gitify-app/notifications-test/pulls/4') - .reply(200, { - state: 'closed', - merged: false, - user: mockNotificationUser, - labels: [], - }); - nock('https://api.github.com') - .get('/repos/gitify-app/notifications-test/pulls/4/reviews') - .reply(200, {}); - nock('https://api.github.com') - .get('/repos/gitify-app/notifications-test/issues/4/comments') - .reply(200, { - user: mockNotificationUser, - }); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.fetchNotifications( - { - accounts: [mockGitHubCloudAccount], + }, + }); + + nock('https://api.github.com') + .get('/repos/gitify-app/notifications-test/issues/3') + .reply(200, { + state: 'closed', + merged: true, + user: mockNotificationUser, + labels: [], + }); + nock('https://api.github.com') + .get('/repos/gitify-app/notifications-test/issues/3/comments') + .reply(200, { + user: mockNotificationUser, + }); + nock('https://api.github.com') + .get('/repos/gitify-app/notifications-test/pulls/4') + .reply(200, { + state: 'closed', + merged: false, + user: mockNotificationUser, + labels: [], + }); + nock('https://api.github.com') + .get('/repos/gitify-app/notifications-test/pulls/4/reviews') + .reply(200, {}); + nock('https://api.github.com') + .get('/repos/gitify-app/notifications-test/issues/4/comments') + .reply(200, { + user: mockNotificationUser, + }); + + const { result } = renderHook(() => useNotifications()); + + act(() => { + result.current.fetchNotifications({ + auth: { + accounts: [mockGitHubCloudAccount], + }, + settings: { + ...mockSettings, + detailedNotifications: true, + }, + }); + }); + + expect(result.current.status).toBe('loading'); + + await waitFor(() => { + expect(result.current.status).toBe('success'); + }); + + expect(result.current.notifications[0].account.hostname).toBe( + 'github.com', + ); + expect(result.current.notifications[0].notifications.length).toBe(6); + }); + + it('should fetch notifications with failures', async () => { + const code = AxiosError.ERR_BAD_REQUEST; + const status = 400; + const message = 'Oops! Something went wrong.'; + + nock('https://api.github.com/') + .get('/notifications?participating=false') + .replyWithError({ + code, + response: { + status, + data: { + message, }, - { - ...mockSettings, - detailedNotifications: true, + }, + }); + + nock('https://github.gitify.io/api/v3/') + .get('/notifications?participating=false') + .replyWithError({ + code, + response: { + status, + data: { + message, }, - ); + }, }); - expect(result.current.status).toBe('loading'); + const { result } = renderHook(() => useNotifications()); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + act(() => { + result.current.fetchNotifications(mockState); + }); - expect(result.current.notifications[0].account.hostname).toBe( - 'github.com', - ); - expect(result.current.notifications[0].notifications.length).toBe(6); + expect(result.current.status).toBe('loading'); + + await waitFor(() => { + expect(result.current.status).toBe('error'); }); + + expect(result.current.errorDetails).toBe(Errors.UNKNOWN); }); }); @@ -308,9 +310,9 @@ describe('hooks/useNotifications.ts', () => { const { result } = renderHook(() => useNotifications()); act(() => { - result.current.fetchNotifications(mockAuth, { - ...mockSettings, - detailedNotifications: false, + result.current.fetchNotifications({ + ...mockState, + settings: { ...mockSettings, detailedNotifications: false }, }); }); @@ -331,557 +333,220 @@ describe('hooks/useNotifications.ts', () => { }); describe('markNotificationRead', () => { + const hostname = 'github.com'; const id = 'notification-123'; - describe('github.com', () => { - const accounts = { ...mockAuth, enterpriseAccounts: [] }; - const hostname = 'github.com'; + it('should mark a notification as read with success', async () => { + nock('https://api.github.com/') + .patch(`/notifications/threads/${id}`) + .reply(200); - it('should mark a notification as read with success - github.com', async () => { - nock('https://api.github.com/') - .patch(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationRead( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markNotificationRead(mockState, id, hostname); }); - it('should mark a notification as read with failure - github.com', async () => { - nock('https://api.github.com/') - .patch(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationRead( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); - }); - describe('enterprise', () => { - const accounts = { ...mockAuth, token: null }; - const hostname = 'github.gitify.io'; - - it('should mark a notification as read with success - enterprise', async () => { - nock('https://github.gitify.io/api/v3') - .patch(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); + expect(result.current.notifications.length).toBe(0); + }); - act(() => { - result.current.markNotificationRead( - accounts, - mockSettings, - id, - hostname, - ); - }); + it('should mark a notification as read with failure', async () => { + nock('https://api.github.com/') + .patch(`/notifications/threads/${id}`) + .reply(400); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markNotificationRead(mockState, id, hostname); }); - it('should mark a notification as read with failure - enterprise', async () => { - nock('https://github.gitify.io/api/v3') - .patch(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationRead( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); + + expect(result.current.notifications.length).toBe(0); }); }); describe('markNotificationDone', () => { + const hostname = 'github.com'; const id = 'notification-123'; - describe('github.com', () => { - const accounts = { ...mockAuth, enterpriseAccounts: [] }; - const hostname = 'github.com'; + it('should mark a notification as done with success', async () => { + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}`) + .reply(200); - it('should mark a notification as done with success - github.com', async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationDone( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markNotificationDone(mockState, id, hostname); }); - it('should mark a notification as done with failure - github.com', async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationDone( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); - }); - describe('enterprise', () => { - const accounts = { ...mockAuth, token: null }; - const hostname = 'github.gitify.io'; - - it('should mark a notification as done with success - enterprise', async () => { - nock('https://github.gitify.io/api/v3') - .delete(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); + expect(result.current.notifications.length).toBe(0); + }); - act(() => { - result.current.markNotificationDone( - accounts, - mockSettings, - id, - hostname, - ); - }); + it('should mark a notification as done with failure', async () => { + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}`) + .reply(400); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markNotificationDone(mockState, id, hostname); }); - it('should mark a notification as done with failure - enterprise', async () => { - nock('https://github.gitify.io/api/v3') - .delete(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markNotificationDone( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); + + expect(result.current.notifications.length).toBe(0); }); }); describe('unsubscribeNotification', () => { + const hostname = 'github.com'; const id = 'notification-123'; - describe('github.com', () => { - const accounts = { ...mockAuth, enterpriseAccounts: [] }; - const hostname = 'github.com'; - - it('should unsubscribe from a notification with success - github.com', async () => { - // The unsubscribe endpoint call. - nock('https://api.github.com/') - .put(`/notifications/threads/${id}/subscription`) - .reply(200); - - // The mark read endpoint call. - nock('https://api.github.com/') - .patch(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.unsubscribeNotification( - accounts, - mockSettings, - id, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + it('should unsubscribe from a notification with success', async () => { + // The unsubscribe endpoint call. + nock('https://api.github.com/') + .put(`/notifications/threads/${id}/subscription`) + .reply(200); - expect(result.current.notifications.length).toBe(0); - }); + // The mark read endpoint call. + nock('https://api.github.com/') + .patch(`/notifications/threads/${id}`) + .reply(200); - it('should unsubscribe from a notification with failure - github.com', async () => { - // The unsubscribe endpoint call. - nock('https://api.github.com/') - .put(`/notifications/threads/${id}/subscription`) - .reply(400); - - // The mark read endpoint call. - nock('https://api.github.com/') - .patch(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.unsubscribeNotification( - accounts, - mockSettings, - id, - hostname, - ); - }); + const { result } = renderHook(() => useNotifications()); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + act(() => { + result.current.unsubscribeNotification(mockState, id, hostname); + }); - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); - }); - describe('enterprise', () => { - const accounts = { ...mockAuth, token: null }; - const hostname = 'github.gitify.io'; - - it('should unsubscribe from a notification with success - enterprise', async () => { - // The unsubscribe endpoint call. - nock('https://github.gitify.io/api/v3') - .put(`/notifications/threads/${id}/subscription`) - .reply(200); - - // The mark read endpoint call. - nock('https://github.gitify.io/api/v3') - .patch(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.unsubscribeNotification( - accounts, - mockSettings, - id, - hostname, - ); - }); + expect(result.current.notifications.length).toBe(0); + }); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + it('should unsubscribe from a notification with failure', async () => { + // The unsubscribe endpoint call. + nock('https://api.github.com/') + .put(`/notifications/threads/${id}/subscription`) + .reply(400); - expect(result.current.notifications.length).toBe(0); - }); + // The mark read endpoint call. + nock('https://api.github.com/') + .patch(`/notifications/threads/${id}`) + .reply(400); - it('should unsubscribe from a notification with failure - enterprise', async () => { - // The unsubscribe endpoint call. - nock('https://github.gitify.io/api/v3') - .put(`/notifications/threads/${id}/subscription`) - .reply(400); - - // The mark read endpoint call. - nock('https://github.gitify.io/api/v3') - .patch(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.unsubscribeNotification( - accounts, - mockSettings, - id, - hostname, - ); - }); + const { result } = renderHook(() => useNotifications()); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + act(() => { + result.current.unsubscribeNotification(mockState, id, hostname); + }); - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); + + expect(result.current.notifications.length).toBe(0); }); }); describe('markRepoNotifications', () => { + const hostname = 'github.com'; const repoSlug = 'gitify-app/notifications-test'; - describe('github.com', () => { - const accounts = { ...mockAuth, enterpriseAccounts: [] }; - const hostname = 'github.com'; + it("should mark a repository's notifications as read with success", async () => { + nock('https://api.github.com/') + .put(`/repos/${repoSlug}/notifications`) + .reply(200); - it("should mark a repository's notifications as read with success - github.com", async () => { - nock('https://api.github.com/') - .put(`/repos/${repoSlug}/notifications`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotifications( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markRepoNotifications(mockState, repoSlug, hostname); }); - it("should mark a repository's notifications as read with failure - github.com", async () => { - nock('https://api.github.com/') - .put(`/repos/${repoSlug}/notifications`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotifications( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); - }); - describe('enterprise', () => { - const accounts = { ...mockAuth, token: null }; - const hostname = 'github.gitify.io'; - - it("should mark a repository's notifications as read with success - enterprise", async () => { - nock('https://github.gitify.io/api/v3') - .put(`/repos/${repoSlug}/notifications`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); + expect(result.current.notifications.length).toBe(0); + }); - act(() => { - result.current.markRepoNotifications( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); + it("should mark a repository's notifications as read with failure", async () => { + nock('https://api.github.com/') + .put(`/repos/${repoSlug}/notifications`) + .reply(400); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markRepoNotifications(mockState, repoSlug, hostname); }); - it("should mark a repository's notifications as read with failure - enterprise", async () => { - nock('https://github.gitify.io/api/v3') - .put(`/repos/${repoSlug}/notifications`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotifications( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); + + expect(result.current.notifications.length).toBe(0); }); }); describe('markRepoNotificationsDone', () => { + const hostname = 'github.com'; const repoSlug = 'gitify-app/notifications-test'; const id = 'notification-123'; - describe('github.com', () => { - const accounts = { ...mockAuth, enterpriseAccounts: [] }; - const hostname = 'github.com'; + it("should mark a repository's notifications as done with success", async () => { + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}`) + .reply(200); - it("should mark a repository's notifications as done with success - github.com", async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsDone( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markRepoNotificationsDone(mockState, repoSlug, hostname); }); - it("should mark a repository's notifications as done with failure - github.com", async () => { - nock('https://api.github.com/') - .delete(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsDone( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + await waitFor(() => { + expect(result.current.status).toBe('success'); }); - }); - describe('enterprise', () => { - const accounts = { ...mockAuth, token: null }; - const hostname = 'github.gitify.io'; - - it("should mark a repository's notifications as done with success - enterprise", async () => { - nock('https://github.gitify.io/api/v3') - .delete(`/notifications/threads/${id}`) - .reply(200); - - const { result } = renderHook(() => useNotifications()); + expect(result.current.notifications.length).toBe(0); + }); - act(() => { - result.current.markRepoNotificationsDone( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); + it("should mark a repository's notifications as done with failure", async () => { + nock('https://api.github.com/') + .delete(`/notifications/threads/${id}`) + .reply(400); - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); + const { result } = renderHook(() => useNotifications()); - expect(result.current.notifications.length).toBe(0); + act(() => { + result.current.markRepoNotificationsDone(mockState, repoSlug, hostname); }); - it("should mark a repository's notifications as done with failure - enterprise", async () => { - nock('https://github.gitify.io/api/v3') - .delete(`/notifications/threads/${id}`) - .reply(400); - - const { result } = renderHook(() => useNotifications()); - - act(() => { - result.current.markRepoNotificationsDone( - accounts, - mockSettings, - repoSlug, - hostname, - ); - }); - - await waitFor(() => { - expect(result.current.status).toBe('success'); - }); - - expect(result.current.notifications.length).toBe(0); + 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 337ffc2ba..7dd93dff4 100644 --- a/src/hooks/useNotifications.ts +++ b/src/hooks/useNotifications.ts @@ -1,8 +1,8 @@ import { useCallback, useState } from 'react'; import type { AccountNotifications, - AuthState, GitifyError, + GitifyState, SettingsState, Status, } from '../types'; @@ -29,37 +29,29 @@ interface NotificationsState { id: string, hostname: string, ) => void; - fetchNotifications: ( - auth: AuthState, - settings: SettingsState, - ) => Promise; + fetchNotifications: (state: GitifyState) => Promise; markNotificationRead: ( - auth: AuthState, - settings: SettingsState, + state: GitifyState, id: string, hostname: string, ) => Promise; markNotificationDone: ( - auth: AuthState, - settings: SettingsState, + state: GitifyState, id: string, hostname: string, ) => Promise; unsubscribeNotification: ( - auth: AuthState, - settings: SettingsState, + state: GitifyState, id: string, hostname: string, ) => Promise; markRepoNotifications: ( - auth: AuthState, - settings: SettingsState, + state: GitifyState, repoSlug: string, hostname: string, ) => Promise; markRepoNotificationsDone: ( - auth: AuthState, - settings: SettingsState, + state: GitifyState, repoSlug: string, hostname: string, ) => Promise; @@ -76,19 +68,14 @@ export const useNotifications = (): NotificationsState => { ); const fetchNotifications = useCallback( - async (auth: AuthState, settings: SettingsState) => { + async (state: GitifyState) => { setStatus('loading'); try { - const fetchedNotifications = await getAllNotifications(auth, settings); + const fetchedNotifications = await getAllNotifications(state); setNotifications(fetchedNotifications); - triggerNativeNotifications( - notifications, - fetchedNotifications, - settings, - auth, - ); + triggerNativeNotifications(notifications, fetchedNotifications, state); setStatus('success'); } catch (err) { setStatus('error'); @@ -99,21 +86,16 @@ export const useNotifications = (): NotificationsState => { ); const markNotificationRead = useCallback( - async ( - auth: AuthState, - settings: SettingsState, - id: string, - hostname: string, - ) => { + async (state: GitifyState, id: string, hostname: string) => { setStatus('loading'); - const account = getAccountForHost(hostname, auth); + const account = getAccountForHost(hostname, state.auth); try { await markNotificationThreadAsRead(id, hostname, account.token); const updatedNotifications = removeNotification( - settings, + state.settings, id, notifications, hostname, @@ -130,21 +112,16 @@ export const useNotifications = (): NotificationsState => { ); const markNotificationDone = useCallback( - async ( - auth: AuthState, - settings: SettingsState, - id: string, - hostname: string, - ) => { + async (state: GitifyState, id: string, hostname: string) => { setStatus('loading'); - const account = getAccountForHost(hostname, auth); + const account = getAccountForHost(hostname, state.auth); try { await markNotificationThreadAsDone(id, hostname, account.token); const updatedNotifications = removeNotification( - settings, + state.settings, id, notifications, hostname, @@ -161,19 +138,14 @@ export const useNotifications = (): NotificationsState => { ); const unsubscribeNotification = useCallback( - async ( - auth: AuthState, - settings: SettingsState, - id: string, - hostname: string, - ) => { + async (state: GitifyState, id: string, hostname: string) => { setStatus('loading'); - const account = getAccountForHost(hostname, auth); + const account = getAccountForHost(hostname, state.auth); try { await ignoreNotificationThreadSubscription(id, hostname, account.token); - await markNotificationRead(auth, settings, id, hostname); + await markNotificationRead(state, id, hostname); setStatus('success'); } catch (err) { setStatus('success'); @@ -183,15 +155,10 @@ export const useNotifications = (): NotificationsState => { ); const markRepoNotifications = useCallback( - async ( - auth: AuthState, - settings: SettingsState, - repoSlug: string, - hostname: string, - ) => { + async (state: GitifyState, repoSlug: string, hostname: string) => { setStatus('loading'); - const account = getAccountForHost(hostname, auth); + const account = getAccountForHost(hostname, state.auth); try { await markRepositoryNotificationsAsRead( @@ -216,12 +183,7 @@ export const useNotifications = (): NotificationsState => { ); const markRepoNotificationsDone = useCallback( - async ( - auth: AuthState, - settings: SettingsState, - repoSlug: string, - hostname: string, - ) => { + async (state: GitifyState, repoSlug: string, hostname: string) => { setStatus('loading'); try { @@ -240,8 +202,7 @@ export const useNotifications = (): NotificationsState => { await Promise.all( notificationsToRemove.map((notification) => markNotificationDone( - auth, - settings, + state, notification.id, notifications[accountIndex].account.hostname, ), diff --git a/src/utils/notifications.test.ts b/src/utils/notifications.test.ts index ac22a3a62..128c22c65 100644 --- a/src/utils/notifications.test.ts +++ b/src/utils/notifications.test.ts @@ -31,8 +31,10 @@ describe('utils/notifications.ts', () => { notificationsHelpers.triggerNativeNotifications( [], mockAccountNotifications, - settings, - mockAuth, + { + auth: mockAuth, + settings, + }, ); expect(notificationsHelpers.raiseNativeNotification).toHaveBeenCalledTimes( @@ -56,8 +58,10 @@ describe('utils/notifications.ts', () => { notificationsHelpers.triggerNativeNotifications( [], mockAccountNotifications, - settings, - mockAuth, + { + auth: mockAuth, + settings, + }, ); expect(notificationsHelpers.raiseNativeNotification).not.toHaveBeenCalled(); @@ -77,8 +81,7 @@ describe('utils/notifications.ts', () => { notificationsHelpers.triggerNativeNotifications( mockSingleAccountNotifications, mockSingleAccountNotifications, - settings, - mockAuth, + { auth: mockAuth, settings }, ); expect(notificationsHelpers.raiseNativeNotification).not.toHaveBeenCalled(); @@ -95,8 +98,14 @@ describe('utils/notifications.ts', () => { jest.spyOn(notificationsHelpers, 'raiseNativeNotification'); jest.spyOn(notificationsHelpers, 'raiseSoundNotification'); - notificationsHelpers.triggerNativeNotifications([], [], settings, mockAuth); - notificationsHelpers.triggerNativeNotifications([], [], settings, mockAuth); + notificationsHelpers.triggerNativeNotifications([], [], { + auth: mockAuth, + settings, + }); + notificationsHelpers.triggerNativeNotifications([], [], { + auth: mockAuth, + settings, + }); expect(notificationsHelpers.raiseNativeNotification).not.toHaveBeenCalled(); expect(notificationsHelpers.raiseSoundNotification).not.toHaveBeenCalled(); diff --git a/src/utils/notifications.ts b/src/utils/notifications.ts index 33695a4af..f2e3bbc88 100644 --- a/src/utils/notifications.ts +++ b/src/utils/notifications.ts @@ -1,5 +1,10 @@ import { ipcRenderer } from 'electron'; -import type { AccountNotifications, AuthState, SettingsState } from '../types'; +import type { + AccountNotifications, + AuthState, + GitifyState, + SettingsState, +} from '../types'; import { Notification } from '../typesGitHub'; import { openInBrowser } from '../utils/helpers'; import { listNotificationsForAuthenticatedUser } from './api/client'; @@ -23,8 +28,7 @@ export function getNotificationCount(notifications: AccountNotifications[]) { export const triggerNativeNotifications = ( previousNotifications: AccountNotifications[], newNotifications: AccountNotifications[], - settings: SettingsState, - auth: AuthState, + state: GitifyState, ) => { const diffNotifications = newNotifications .map((accountNotifications) => { @@ -57,12 +61,12 @@ export const triggerNativeNotifications = ( return; } - if (settings.playSound) { + if (state.settings.playSound) { raiseSoundNotification(); } - if (settings.showNotifications) { - raiseNativeNotification(diffNotifications, auth); + if (state.settings.showNotifications) { + raiseNativeNotification(diffNotifications, state.auth); } }; @@ -107,20 +111,22 @@ export const raiseSoundNotification = () => { audio.play(); }; -function getNotifications(auth: AuthState, settings: SettingsState) { - return auth.accounts.map((account) => { +function getNotifications(state: GitifyState) { + return state.auth.accounts.map((account) => { return { account, - notifications: listNotificationsForAuthenticatedUser(account, settings), + notifications: listNotificationsForAuthenticatedUser( + account, + state.settings, + ), }; }); } export async function getAllNotifications( - auth: AuthState, - settings: SettingsState, + state: GitifyState, ): Promise { - const responses = await Promise.all([...getNotifications(auth, settings)]); + const responses = await Promise.all([...getNotifications(state)]); const notifications: AccountNotifications[] = await Promise.all( responses @@ -133,13 +139,9 @@ export async function getAllNotifications( }), ); - notifications = await enrichNotifications( - notifications, - auth, - settings, - ); + notifications = await enrichNotifications(notifications, state); - notifications = filterNotifications(notifications, settings); + notifications = filterNotifications(notifications, state.settings); return { account: accountNotifications.account, @@ -153,10 +155,9 @@ export async function getAllNotifications( export async function enrichNotifications( notifications: Notification[], - auth: AuthState, - settings: SettingsState, + state: GitifyState, ): Promise { - if (!settings.detailedNotifications) { + if (!state.settings.detailedNotifications) { return notifications; }