From ffd8536a33db01fbbfe8c848b78d55a289a8fe10 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 10:47:06 -0500 Subject: [PATCH 1/7] refactor: test suites Signed-off-by: Adam Setch --- src/preload/index.test.ts | 27 ++++ src/renderer/components/Sidebar.test.tsx | 33 +++-- .../AccountNotifications.test.tsx | 18 +-- .../notifications/NotificationFooter.test.tsx | 30 +---- .../notifications/NotificationHeader.test.tsx | 6 +- .../notifications/NotificationRow.test.tsx | 21 +-- .../RepositoryNotifications.test.tsx | 6 +- .../components/primitives/Header.test.tsx | 6 +- .../settings/NotificationSettings.test.tsx | 6 +- .../settings/SettingsFooter.test.tsx | 10 +- .../settings/SettingsReset.test.tsx | 6 + src/renderer/routes/Accounts.test.tsx | 54 ++++---- src/renderer/routes/Filters.test.tsx | 3 +- src/renderer/routes/Login.test.tsx | 16 +-- .../routes/LoginWithOAuthApp.test.tsx | 105 ++++++++++----- src/renderer/routes/LoginWithOAuthApp.tsx | 8 +- .../LoginWithPersonalAccessToken.test.tsx | 69 +++++----- .../routes/LoginWithPersonalAccessToken.tsx | 6 +- src/renderer/routes/Settings.test.tsx | 3 +- src/renderer/utils/api/client.test.ts | 22 ++- src/renderer/utils/auth/utils.test.ts | 126 +++++++++--------- src/renderer/utils/helpers.ts | 7 +- src/renderer/utils/links.test.ts | 40 ++++-- .../notifications/handlers/index.test.ts | 15 ++- .../utils/notifications/notifications.test.ts | 8 ++ src/renderer/utils/storage.test.ts | 16 ++- 26 files changed, 369 insertions(+), 298 deletions(-) diff --git a/src/preload/index.test.ts b/src/preload/index.test.ts index 8f1a12ec3..46dac3057 100644 --- a/src/preload/index.test.ts +++ b/src/preload/index.test.ts @@ -75,7 +75,9 @@ describe('preload/index', () => { it('exposes api on window when context isolation disabled', async () => { await importPreload(); + const w = window as unknown as { gitify: Record }; + expect(w.gitify).toBeDefined(); expect(exposeInMainWorld).not.toHaveBeenCalled(); }); @@ -84,7 +86,9 @@ describe('preload/index', () => { (process as unknown as { contextIsolated?: boolean }).contextIsolated = true; await importPreload(); + expect(exposeInMainWorld).toHaveBeenCalledTimes(1); + const [key, api] = exposeInMainWorld.mock.calls[0]; expect(key).toBe('gitify'); expect(api).toHaveProperty('openExternalLink'); @@ -92,8 +96,10 @@ describe('preload/index', () => { it('tray.updateColor sends correct events', async () => { await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; // casting only in test boundary api.tray.updateColor(-1); + expect(sendMainEvent).toHaveBeenNthCalledWith( 1, EVENTS.UPDATE_ICON_COLOR, @@ -103,8 +109,10 @@ describe('preload/index', () => { it('openExternalLink sends event with payload', async () => { await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; api.openExternalLink('https://example.com', true); + expect(sendMainEvent).toHaveBeenCalledWith(EVENTS.OPEN_EXTERNAL, { url: 'https://example.com', activate: true, @@ -114,8 +122,11 @@ describe('preload/index', () => { it('app.version returns dev in development', async () => { const originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'development'; + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + await expect(api.app.version()).resolves.toBe('dev'); process.env.NODE_ENV = originalEnv; }); @@ -123,51 +134,67 @@ describe('preload/index', () => { it('app.version prefixes production version', async () => { const originalEnv = process.env.NODE_ENV; process.env.NODE_ENV = 'production'; + invokeMainEvent.mockResolvedValueOnce('1.2.3'); + await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; + await expect(api.app.version()).resolves.toBe('v1.2.3'); process.env.NODE_ENV = originalEnv; }); it('onSystemThemeUpdate registers listener', async () => { await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; const callback = jest.fn(); api.onSystemThemeUpdate(callback); + expect(onRendererEvent).toHaveBeenCalledWith( EVENTS.UPDATE_THEME, expect.any(Function), ); + // Simulate event const listener = onRendererEvent.mock.calls[0][1]; listener({}, 'dark'); + expect(callback).toHaveBeenCalledWith('dark'); }); it('raiseNativeNotification without url calls app.show', async () => { await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; api.app.show = jest.fn(); + const notification = api.raiseNativeNotification( 'Title', 'Body', ) as MockNotification; + notification.triggerClick(); + expect(api.app.show).toHaveBeenCalled(); }); it('raiseNativeNotification with url hides app then opens link', async () => { await importPreload(); + const api = (window as unknown as { gitify: TestApi }).gitify; api.app.hide = jest.fn(); api.openExternalLink = jest.fn(); + const notification = api.raiseNativeNotification( 'Title', 'Body', 'https://x', ) as MockNotification; + notification.triggerClick(); + expect(api.app.hide).toHaveBeenCalled(); expect(api.openExternalLink).toHaveBeenCalledWith('https://x', true); }); diff --git a/src/renderer/components/Sidebar.test.tsx b/src/renderer/components/Sidebar.test.tsx index 93626c1e5..064c2f8c8 100644 --- a/src/renderer/components/Sidebar.test.tsx +++ b/src/renderer/components/Sidebar.test.tsx @@ -15,7 +15,7 @@ jest.mock('react-router-dom', () => ({ describe('renderer/components/Sidebar.tsx', () => { const mockFetchNotifications = jest.fn(); - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -61,7 +61,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-home')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/', { replace: true }); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true }); }); describe('notifications icon', () => { @@ -77,8 +78,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-notifications')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/notifications', ); }); @@ -125,7 +126,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-filter-notifications')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/filters'); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/filters'); }); it('go to the home if filters path already shown', async () => { @@ -140,7 +142,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-filter-notifications')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/', { replace: true }); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true }); }); }); @@ -158,8 +161,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-my-issues')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/issues', ); }); @@ -177,8 +180,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-my-pull-requests')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/pulls', ); }); @@ -235,7 +238,8 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-settings')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/settings'); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/settings'); }); it('go to the home if settings path already shown', async () => { @@ -252,12 +256,13 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-settings')); expect(mockFetchNotifications).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/', { replace: true }); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true }); }); }); it('should quit the app', async () => { - const mockQuitApp = jest.spyOn(comms, 'quitApp'); + const quitAppSpy = jest.spyOn(comms, 'quitApp').mockImplementation(); renderWithAppContext( @@ -270,6 +275,6 @@ describe('renderer/components/Sidebar.tsx', () => { await userEvent.click(screen.getByTestId('sidebar-quit')); - expect(mockQuitApp).toHaveBeenCalledTimes(1); + expect(quitAppSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/renderer/components/notifications/AccountNotifications.test.tsx b/src/renderer/components/notifications/AccountNotifications.test.tsx index 5c023bbaf..9375eed23 100644 --- a/src/renderer/components/notifications/AccountNotifications.test.tsx +++ b/src/renderer/components/notifications/AccountNotifications.test.tsx @@ -113,7 +113,7 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { }); it('should open profile when clicked', async () => { - const mockOpenAccountProfile = jest + const openAccountProfileSpy = jest .spyOn(links, 'openAccountProfile') .mockImplementation(); @@ -128,12 +128,12 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { await userEvent.click(screen.getByTestId('account-profile')); - expect(mockOpenAccountProfile).toHaveBeenCalledTimes(1); - expect(mockOpenAccountProfile).toHaveBeenCalledWith(mockGitHubCloudAccount); + expect(openAccountProfileSpy).toHaveBeenCalledTimes(1); + expect(openAccountProfileSpy).toHaveBeenCalledWith(mockGitHubCloudAccount); }); it('should open my issues when clicked', async () => { - const mockOpenGitHubIssues = jest + const openGitHubIssuesSpy = jest .spyOn(links, 'openGitHubIssues') .mockImplementation(); @@ -148,14 +148,14 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { await userEvent.click(screen.getByTestId('account-issues')); - expect(mockOpenGitHubIssues).toHaveBeenCalledTimes(1); - expect(mockOpenGitHubIssues).toHaveBeenCalledWith( + expect(openGitHubIssuesSpy).toHaveBeenCalledTimes(1); + expect(openGitHubIssuesSpy).toHaveBeenCalledWith( mockGitHubCloudAccount.hostname, ); }); it('should open my pull requests when clicked', async () => { - const mockOpenGitHubPulls = jest + const openGitHubPullsSpy = jest .spyOn(links, 'openGitHubPulls') .mockImplementation(); @@ -170,8 +170,8 @@ describe('renderer/components/notifications/AccountNotifications.tsx', () => { await userEvent.click(screen.getByTestId('account-pull-requests')); - expect(mockOpenGitHubPulls).toHaveBeenCalledTimes(1); - expect(mockOpenGitHubPulls).toHaveBeenCalledWith( + expect(openGitHubPullsSpy).toHaveBeenCalledTimes(1); + expect(openGitHubPullsSpy).toHaveBeenCalledWith( mockGitHubCloudAccount.hostname, ); }); diff --git a/src/renderer/components/notifications/NotificationFooter.test.tsx b/src/renderer/components/notifications/NotificationFooter.test.tsx index 465612ddb..f05b5f1f4 100644 --- a/src/renderer/components/notifications/NotificationFooter.test.tsx +++ b/src/renderer/components/notifications/NotificationFooter.test.tsx @@ -10,15 +10,15 @@ import * as comms from '../../utils/comms'; import { NotificationFooter } from './NotificationFooter'; describe('renderer/components/notifications/NotificationFooter.tsx', () => { + jest + .spyOn(globalThis.Date, 'now') + .mockImplementation(() => new Date('2024').valueOf()); + afterEach(() => { jest.clearAllMocks(); }); it('should render itself & its children', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const props = { notification: mockSingleNotification, }; @@ -29,10 +29,6 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { }); it('should render itself & its children when last_read_at is null', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const mockNotification = mockSingleNotification; mockNotification.last_read_at = null; @@ -47,10 +43,6 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { describe('security alerts should use github icon for avatar', () => { it('Repository Dependabot Alerts Thread', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const mockNotification = mockSingleNotification; mockNotification.subject.type = 'RepositoryDependabotAlertsThread'; @@ -64,10 +56,6 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { }); it('Repository Vulnerability Alert', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const mockNotification = mockSingleNotification; mockNotification.subject.type = 'RepositoryVulnerabilityAlert'; @@ -82,10 +70,6 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { }); it('should default to known avatar if no user found', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const mockNotification = mockSingleNotification; mockNotification.subject.user = null; @@ -99,7 +83,7 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { }); it('should open notification user profile', async () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -125,8 +109,8 @@ describe('renderer/components/notifications/NotificationFooter.tsx', () => { await userEvent.click(screen.getByTestId('view-profile')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( props.notification.subject.user.html_url, ); }); diff --git a/src/renderer/components/notifications/NotificationHeader.test.tsx b/src/renderer/components/notifications/NotificationHeader.test.tsx index b54e7e8d4..a05b39cc2 100644 --- a/src/renderer/components/notifications/NotificationHeader.test.tsx +++ b/src/renderer/components/notifications/NotificationHeader.test.tsx @@ -71,7 +71,7 @@ describe('renderer/components/notifications/NotificationHeader.tsx', () => { }); it('should open notification user profile - group by date', async () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -85,8 +85,8 @@ describe('renderer/components/notifications/NotificationHeader.tsx', () => { await userEvent.click(screen.getByTestId('view-repository')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( props.notification.repository.html_url, ); }); diff --git a/src/renderer/components/notifications/NotificationRow.test.tsx b/src/renderer/components/notifications/NotificationRow.test.tsx index 29c7ee9d3..65704bc2f 100644 --- a/src/renderer/components/notifications/NotificationRow.test.tsx +++ b/src/renderer/components/notifications/NotificationRow.test.tsx @@ -11,18 +11,17 @@ import * as links from '../../utils/links'; import { NotificationRow } from './NotificationRow'; describe('renderer/components/notifications/NotificationRow.tsx', () => { - jest.spyOn(links, 'openNotification'); + jest.spyOn(links, 'openNotification').mockImplementation(); jest.spyOn(comms, 'openExternalLink').mockImplementation(); + jest + .spyOn(globalThis.Date, 'now') + .mockImplementation(() => new Date('2024').valueOf()); afterEach(() => { jest.clearAllMocks(); }); it('should render itself & its children - group by date', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const props = { notification: mockSingleNotification, account: mockGitHubCloudAccount, @@ -36,10 +35,6 @@ describe('renderer/components/notifications/NotificationRow.tsx', () => { }); it('should render itself & its children - group by repositories', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const props = { notification: mockSingleNotification, account: mockGitHubCloudAccount, @@ -53,10 +48,6 @@ describe('renderer/components/notifications/NotificationRow.tsx', () => { }); it('should render itself & its children - hide numbers', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const props = { notification: mockSingleNotification, account: mockGitHubCloudAccount, @@ -70,10 +61,6 @@ describe('renderer/components/notifications/NotificationRow.tsx', () => { }); it('should render itself & its children - notification is read', async () => { - jest - .spyOn(globalThis.Date, 'now') - .mockImplementation(() => new Date('2024').valueOf()); - const props = { notification: { ...mockSingleNotification, diff --git a/src/renderer/components/notifications/RepositoryNotifications.test.tsx b/src/renderer/components/notifications/RepositoryNotifications.test.tsx index 52ada8edd..3e2961414 100644 --- a/src/renderer/components/notifications/RepositoryNotifications.test.tsx +++ b/src/renderer/components/notifications/RepositoryNotifications.test.tsx @@ -50,7 +50,7 @@ describe('renderer/components/notifications/RepositoryNotifications.tsx', () => }); it('should open the browser when clicking on the repo name', async () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -58,8 +58,8 @@ describe('renderer/components/notifications/RepositoryNotifications.tsx', () => await userEvent.click(screen.getByTestId('open-repository')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/gitify-app/notifications-test', ); }); diff --git a/src/renderer/components/primitives/Header.test.tsx b/src/renderer/components/primitives/Header.test.tsx index 7a1f665b2..1af920270 100644 --- a/src/renderer/components/primitives/Header.test.tsx +++ b/src/renderer/components/primitives/Header.test.tsx @@ -32,7 +32,8 @@ describe('renderer/components/primitives/Header.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); it('should navigate back and fetch notifications', async () => { @@ -45,7 +46,8 @@ describe('renderer/components/primitives/Header.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); expect(mockFetchNotifications).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); }); diff --git a/src/renderer/components/settings/NotificationSettings.test.tsx b/src/renderer/components/settings/NotificationSettings.test.tsx index bf0463b7a..de3a1e406 100644 --- a/src/renderer/components/settings/NotificationSettings.test.tsx +++ b/src/renderer/components/settings/NotificationSettings.test.tsx @@ -245,7 +245,7 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { }); it('should open official docs for showOnlyParticipating tooltip', async () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -266,8 +266,8 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { ), ); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://docs.github.com/en/account-and-profile/managing-subscriptions-and-notifications-on-github/setting-up-notifications/configuring-notifications#about-participating-and-watching-notifications', ); }); diff --git a/src/renderer/components/settings/SettingsFooter.test.tsx b/src/renderer/components/settings/SettingsFooter.test.tsx index f7438c8eb..4352b2f4e 100644 --- a/src/renderer/components/settings/SettingsFooter.test.tsx +++ b/src/renderer/components/settings/SettingsFooter.test.tsx @@ -33,7 +33,7 @@ describe('renderer/components/settings/SettingsFooter.tsx', () => { }); it('should open release notes', async () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -43,8 +43,8 @@ describe('renderer/components/settings/SettingsFooter.tsx', () => { await userEvent.click(screen.getByTestId('settings-release-notes')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/gitify-app/gitify/releases/tag/v0.0.1', ); }); @@ -60,7 +60,7 @@ describe('renderer/components/settings/SettingsFooter.tsx', () => { }); it('should quit the app', async () => { - const mockQuitApp = jest.spyOn(comms, 'quitApp'); + const quitAppSpy = jest.spyOn(comms, 'quitApp').mockImplementation(); await act(async () => { renderWithAppContext(); @@ -68,6 +68,6 @@ describe('renderer/components/settings/SettingsFooter.tsx', () => { await userEvent.click(screen.getByTestId('settings-quit')); - expect(mockQuitApp).toHaveBeenCalledTimes(1); + expect(quitAppSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/renderer/components/settings/SettingsReset.test.tsx b/src/renderer/components/settings/SettingsReset.test.tsx index daa7770f2..78ac8793a 100644 --- a/src/renderer/components/settings/SettingsReset.test.tsx +++ b/src/renderer/components/settings/SettingsReset.test.tsx @@ -2,6 +2,7 @@ import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithAppContext } from '../../__helpers__/test-utils'; +import * as logger from '../../utils/logger'; import { SettingsReset } from './SettingsReset'; describe('renderer/components/settings/SettingsReset.tsx', () => { @@ -12,6 +13,10 @@ describe('renderer/components/settings/SettingsReset.tsx', () => { }); it('should reset default settings when `OK`', async () => { + const rendererLogInfoSpy = jest + .spyOn(logger, 'rendererLogInfo') + .mockImplementation(); + globalThis.confirm = jest.fn(() => true); // always click 'OK' await act(async () => { @@ -24,6 +29,7 @@ describe('renderer/components/settings/SettingsReset.tsx', () => { await userEvent.click(screen.getByText('Reset')); expect(mockResetSettings).toHaveBeenCalled(); + expect(rendererLogInfoSpy).toHaveBeenCalled(); }); it('should skip reset default settings when `cancelled`', async () => { diff --git a/src/renderer/routes/Accounts.test.tsx b/src/renderer/routes/Accounts.test.tsx index 68cbb91f8..1eae8d661 100644 --- a/src/renderer/routes/Accounts.test.tsx +++ b/src/renderer/routes/Accounts.test.tsx @@ -1,4 +1,4 @@ -import { act, screen, waitFor } from '@testing-library/react'; +import { act, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithAppContext } from '../__helpers__/test-utils'; @@ -49,17 +49,18 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); }); describe('Account interactions', () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); it('open profile in external browser', async () => { - const mockOpenAccountProfile = jest + const openAccountProfileSpy = jest .spyOn(links, 'openAccountProfile') .mockImplementation(); @@ -71,8 +72,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-profile')); - expect(mockOpenAccountProfile).toHaveBeenCalledTimes(1); - expect(mockOpenAccountProfile).toHaveBeenCalledWith( + expect(openAccountProfileSpy).toHaveBeenCalledTimes(1); + expect(openAccountProfileSpy).toHaveBeenCalledWith( mockPersonalAccessTokenAccount, ); }); @@ -86,8 +87,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-host')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith('https://github.com'); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith('https://github.com'); }); it('open developer settings in external browser', async () => { @@ -99,8 +100,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-developer-settings')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/settings/tokens', ); }); @@ -124,14 +125,14 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getAllByTestId('account-missing-scopes')[0]); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/settings/tokens', ); }); it('should set account as primary account', async () => { - const mockSaveState = jest + const saveStateSpy = jest .spyOn(storage, 'saveState') .mockImplementation(); @@ -153,11 +154,11 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getAllByTestId('account-set-primary')[0]); - expect(mockSaveState).toHaveBeenCalled(); + expect(saveStateSpy).toHaveBeenCalled(); }); it('should refresh account', async () => { - const mockRefreshAccount = jest + const refreshAccountSpy = jest .spyOn(authUtils, 'refreshAccount') .mockImplementation(); @@ -169,13 +170,11 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-refresh')); - expect(mockRefreshAccount).toHaveBeenCalledTimes(1); - - await waitFor(() => - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/accounts', { - replace: true, - }), - ); + expect(refreshAccountSpy).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/accounts', { + replace: true, + }); }); it('should logout', async () => { @@ -191,8 +190,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-logout')); expect(mockLogoutFromAccount).toHaveBeenCalledTimes(1); - - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); }); @@ -223,8 +222,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-add-new')); await userEvent.click(screen.getByTestId('account-add-pat')); - expect(mockNavigate).toHaveBeenNthCalledWith( - 1, + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith( '/login-personal-access-token', { replace: true, @@ -242,7 +241,8 @@ describe('renderer/routes/Accounts.tsx', () => { await userEvent.click(screen.getByTestId('account-add-new')); await userEvent.click(screen.getByTestId('account-add-oauth-app')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/login-oauth-app', { + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/login-oauth-app', { replace: true, }); }); diff --git a/src/renderer/routes/Filters.test.tsx b/src/renderer/routes/Filters.test.tsx index 1a5e0efe6..5abd987d7 100644 --- a/src/renderer/routes/Filters.test.tsx +++ b/src/renderer/routes/Filters.test.tsx @@ -37,7 +37,8 @@ describe('renderer/routes/Filters.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); expect(mockFetchNotifications).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); }); diff --git a/src/renderer/routes/Login.test.tsx b/src/renderer/routes/Login.test.tsx index 3fb3740c9..dd2a3405f 100644 --- a/src/renderer/routes/Login.test.tsx +++ b/src/renderer/routes/Login.test.tsx @@ -25,14 +25,15 @@ describe('renderer/routes/Login.tsx', () => { }); it('should redirect to notifications once logged in', () => { - const mockShowWindow = jest.spyOn(comms, 'showWindow'); + const showWindowSpy = jest.spyOn(comms, 'showWindow'); renderWithAppContext(, { isLoggedIn: true, }); - expect(mockShowWindow).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/', { replace: true }); + expect(showWindowSpy).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/', { replace: true }); }); it('should login with github', async () => { @@ -52,10 +53,8 @@ describe('renderer/routes/Login.tsx', () => { await userEvent.click(screen.getByTestId('login-pat')); - expect(mockNavigate).toHaveBeenNthCalledWith( - 1, - '/login-personal-access-token', - ); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/login-personal-access-token'); }); it('should navigate to login with oauth app', async () => { @@ -63,6 +62,7 @@ describe('renderer/routes/Login.tsx', () => { await userEvent.click(screen.getByTestId('login-oauth-app')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, '/login-oauth-app'); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith('/login-oauth-app'); }); }); diff --git a/src/renderer/routes/LoginWithOAuthApp.test.tsx b/src/renderer/routes/LoginWithOAuthApp.test.tsx index 601e62efb..848d9301c 100644 --- a/src/renderer/routes/LoginWithOAuthApp.test.tsx +++ b/src/renderer/routes/LoginWithOAuthApp.test.tsx @@ -4,7 +4,12 @@ import userEvent from '@testing-library/user-event'; import { renderWithAppContext } from '../__helpers__/test-utils'; import type { ClientID, ClientSecret, Hostname } from '../types'; import * as comms from '../utils/comms'; -import { LoginWithOAuthAppRoute, validateForm } from './LoginWithOAuthApp'; +import * as logger from '../utils/logger'; +import { + type IFormData, + LoginWithOAuthAppRoute, + validateForm, +} from './LoginWithOAuthApp'; const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ @@ -15,7 +20,7 @@ jest.mock('react-router-dom', () => ({ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { const mockLoginWithOAuthApp = jest.fn(); - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -34,34 +39,38 @@ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); - it('should validate the form values', () => { - const emptyValues = { - hostname: null, - clientId: null, - clientSecret: null, - }; - - let values = { - ...emptyValues, - }; - expect(validateForm(values).hostname).toBe('Hostname is required'); - expect(validateForm(values).clientId).toBe('Client ID is required'); - expect(validateForm(values).clientSecret).toBe('Client Secret is required'); - - values = { - ...emptyValues, - hostname: 'hello' as Hostname, - clientId: '!@£INVALID-.1' as ClientID, - clientSecret: '!@£INVALID-.1' as ClientSecret, - }; - expect(validateForm(values).hostname).toBe('Hostname format is invalid'); - expect(validateForm(values).clientId).toBe('Client ID format is invalid'); - expect(validateForm(values).clientSecret).toBe( - 'Client Secret format is invalid', - ); + describe('form validation', () => { + it('should validate the form values are not empty', () => { + const values: IFormData = { + hostname: null, + clientId: null, + clientSecret: null, + }; + + expect(validateForm(values).hostname).toBe('Hostname is required'); + expect(validateForm(values).clientId).toBe('Client ID is required'); + expect(validateForm(values).clientSecret).toBe( + 'Client Secret is required', + ); + }); + + it('should validate the form values are correct format', () => { + const values: IFormData = { + hostname: 'hello' as Hostname, + clientId: '!@£INVALID-.1' as ClientID, + clientSecret: '!@£INVALID-.1' as ClientSecret, + }; + + expect(validateForm(values).hostname).toBe('Hostname format is invalid'); + expect(validateForm(values).clientId).toBe('Client ID format is invalid'); + expect(validateForm(values).clientSecret).toBe( + 'Client Secret format is invalid', + ); + }); }); describe("'Create new OAuth App' button", () => { @@ -72,7 +81,7 @@ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { await userEvent.click(screen.getByTestId('login-create-oauth-app')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(0); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(0); }); it('should open in browser if hostname configured', async () => { @@ -84,7 +93,7 @@ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { await userEvent.click(screen.getByTestId('login-create-oauth-app')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); }); }); @@ -112,7 +121,39 @@ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { await userEvent.click(screen.getByTestId('login-submit')); expect(mockLoginWithOAuthApp).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); + }); + + it('should login using a token - failure', async () => { + const rendererLogErrorSpy = jest + .spyOn(logger, 'rendererLogError') + .mockImplementation(); + mockLoginWithOAuthApp.mockRejectedValueOnce(null); + + renderWithAppContext(, { + loginWithOAuthApp: mockLoginWithOAuthApp, + }); + + const hostname = screen.getByTestId('login-hostname'); + await userEvent.clear(hostname); + await userEvent.type(hostname, 'github.com'); + + await userEvent.type( + screen.getByTestId('login-clientId'), + '1234567890_ASDFGHJKL', + ); + + await userEvent.type( + screen.getByTestId('login-clientSecret'), + '1234567890_asdfghjklPOIUYTREWQ0987654321', + ); + + await userEvent.click(screen.getByTestId('login-submit')); + + expect(mockLoginWithOAuthApp).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledTimes(0); + expect(rendererLogErrorSpy).toHaveBeenCalledTimes(1); }); it('should render the form with errors', async () => { @@ -140,6 +181,6 @@ describe('renderer/routes/LoginWithOAuthApp.tsx', () => { await userEvent.click(screen.getByTestId('login-docs')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/renderer/routes/LoginWithOAuthApp.tsx b/src/renderer/routes/LoginWithOAuthApp.tsx index 16a607feb..1a2bd9b4e 100644 --- a/src/renderer/routes/LoginWithOAuthApp.tsx +++ b/src/renderer/routes/LoginWithOAuthApp.tsx @@ -35,10 +35,10 @@ import { import { openExternalLink } from '../utils/comms'; import { rendererLogError } from '../utils/logger'; -interface IFormData { - hostname?: Hostname; - clientId?: ClientID; - clientSecret?: ClientSecret; +export interface IFormData { + hostname: Hostname; + clientId: ClientID; + clientSecret: ClientSecret; } interface IFormErrors { diff --git a/src/renderer/routes/LoginWithPersonalAccessToken.test.tsx b/src/renderer/routes/LoginWithPersonalAccessToken.test.tsx index 84f6afe72..c1f559c94 100644 --- a/src/renderer/routes/LoginWithPersonalAccessToken.test.tsx +++ b/src/renderer/routes/LoginWithPersonalAccessToken.test.tsx @@ -1,9 +1,12 @@ -import { screen, waitFor } from '@testing-library/react'; +import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { renderWithAppContext } from '../__helpers__/test-utils'; +import type { Hostname, Token } from '../types'; import * as comms from '../utils/comms'; +import * as logger from '../utils/logger'; import { + type IFormData, LoginWithPersonalAccessTokenRoute, validateForm, } from './LoginWithPersonalAccessToken'; @@ -16,7 +19,7 @@ jest.mock('react-router-dom', () => ({ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { const mockLoginWithPersonalAccessToken = jest.fn(); - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -35,28 +38,29 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); - it('should validate the form values', () => { - const emptyValues = { - hostname: null, - token: null, - }; - - let values = { - ...emptyValues, - }; - expect(validateForm(values).hostname).toBe('Hostname is required'); - expect(validateForm(values).token).toBe('Token is required'); - - values = { - ...emptyValues, - hostname: 'hello', - token: '!@£INVALID-.1', - }; - expect(validateForm(values).hostname).toBe('Hostname format is invalid'); - expect(validateForm(values).token).toBe('Token format is invalid'); + describe('form validation', () => { + it('should validate the form values are not empty', () => { + const values: IFormData = { + hostname: null, + token: null, + }; + expect(validateForm(values).hostname).toBe('Hostname is required'); + expect(validateForm(values).token).toBe('Token is required'); + }); + + it('should validate the form values are correct format', () => { + const values: IFormData = { + hostname: 'hello' as Hostname, + token: '!@£INVALID-.1' as Token, + }; + + expect(validateForm(values).hostname).toBe('Hostname format is invalid'); + expect(validateForm(values).token).toBe('Token format is invalid'); + }); }); describe("'Generate a PAT' button", () => { @@ -69,7 +73,7 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('login-create-token')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(0); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(0); }); it('should open in browser if hostname configured', async () => { @@ -79,7 +83,7 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('login-create-token')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); }); }); @@ -101,15 +105,15 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('login-submit')); - await waitFor(() => - expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1), - ); - expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); it('should login using a token - failure', async () => { + const rendererLogErrorSpy = jest + .spyOn(logger, 'rendererLogError') + .mockImplementation(); mockLoginWithPersonalAccessToken.mockRejectedValueOnce(null); renderWithAppContext(, { @@ -127,12 +131,9 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('login-submit')); - await waitFor(() => - expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1), - ); - expect(mockLoginWithPersonalAccessToken).toHaveBeenCalledTimes(1); expect(mockNavigate).toHaveBeenCalledTimes(0); + expect(rendererLogErrorSpy).toHaveBeenCalledTimes(1); }); it('should render the form with errors', async () => { @@ -157,6 +158,6 @@ describe('renderer/routes/LoginWithPersonalAccessToken.tsx', () => { await userEvent.click(screen.getByTestId('login-docs')); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); }); }); diff --git a/src/renderer/routes/LoginWithPersonalAccessToken.tsx b/src/renderer/routes/LoginWithPersonalAccessToken.tsx index 78cd5f75d..9ec15609b 100644 --- a/src/renderer/routes/LoginWithPersonalAccessToken.tsx +++ b/src/renderer/routes/LoginWithPersonalAccessToken.tsx @@ -35,9 +35,9 @@ import { import { openExternalLink } from '../utils/comms'; import { rendererLogError } from '../utils/logger'; -interface IFormData { - token?: Token; - hostname?: Hostname; +export interface IFormData { + token: Token; + hostname: Hostname; } interface IFormErrors { diff --git a/src/renderer/routes/Settings.test.tsx b/src/renderer/routes/Settings.test.tsx index a254d8a1d..aa4a75e73 100644 --- a/src/renderer/routes/Settings.test.tsx +++ b/src/renderer/routes/Settings.test.tsx @@ -35,6 +35,7 @@ describe('renderer/routes/Settings.tsx', () => { await userEvent.click(screen.getByTestId('header-nav-back')); expect(mockFetchNotifications).toHaveBeenCalledTimes(1); - expect(mockNavigate).toHaveBeenNthCalledWith(1, -1); + expect(mockNavigate).toHaveBeenCalledTimes(1); + expect(mockNavigate).toHaveBeenCalledWith(-1); }); }); diff --git a/src/renderer/utils/api/client.test.ts b/src/renderer/utils/api/client.test.ts index 92ca0319e..07f000a78 100644 --- a/src/renderer/utils/api/client.test.ts +++ b/src/renderer/utils/api/client.test.ts @@ -136,18 +136,16 @@ describe('renderer/utils/api/client.ts', () => { describe('getHtmlUrl', () => { it('should return the HTML URL', async () => { - const mockApiRequestAuth = jest.spyOn(apiRequests, 'apiRequestAuth'); + const apiRequestAuthSpy = jest.spyOn(apiRequests, 'apiRequestAuth'); - const requestPromise = new Promise((resolve) => - resolve({ - data: { - html_url: - 'https://github.com/gitify-app/notifications-test/issues/785', - }, - } as AxiosResponse), - ) as AxiosPromise; + const requestPromise = Promise.resolve({ + data: { + html_url: + 'https://github.com/gitify-app/notifications-test/issues/785', + }, + } as AxiosResponse) as AxiosPromise; - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue(requestPromise); const result = await getHtmlUrl( 'https://api.github.com/repos/gitify-app/notifications-test/issues/785' as Link, @@ -163,11 +161,11 @@ describe('renderer/utils/api/client.ts', () => { .spyOn(logger, 'rendererLogError') .mockImplementation(); - const mockApiRequestAuth = jest.spyOn(apiRequests, 'apiRequestAuth'); + const apiRequestAuthSpy = jest.spyOn(apiRequests, 'apiRequestAuth'); const mockError = new Error('Test error'); - mockApiRequestAuth.mockRejectedValue(mockError); + apiRequestAuthSpy.mockRejectedValue(mockError); await getHtmlUrl( 'https://api.github.com/repos/gitify-app/gitify/issues/785' as Link, diff --git a/src/renderer/utils/auth/utils.test.ts b/src/renderer/utils/auth/utils.test.ts index cd370a01d..3aeaeb2b8 100644 --- a/src/renderer/utils/auth/utils.test.ts +++ b/src/renderer/utils/auth/utils.test.ts @@ -16,13 +16,15 @@ import type { } from '../../types'; import * as comms from '../../utils/comms'; import * as apiRequests from '../api/request'; +import * as logger from '../logger'; import type { AuthMethod } from './types'; -import * as auth from './utils'; +import * as authUtils from './utils'; import { getNewOAuthAppURL, getNewTokenURL } from './utils'; describe('renderer/utils/auth/utils.ts', () => { describe('authGitHub', () => { - const mockOpenExternalLink = jest + jest.spyOn(logger, 'rendererLogInfo').mockImplementation(); + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -37,10 +39,10 @@ describe('renderer/utils/auth/utils.ts', () => { callback('gitify://auth?code=123-456'); }); - const res = await auth.authGitHub(); + const res = await authUtils.authGitHub(); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/login/oauth/authorize?client_id=FAKE_CLIENT_ID_123&scope=read%3Auser%2Cnotifications%2Crepo', ); @@ -60,14 +62,14 @@ describe('renderer/utils/auth/utils.ts', () => { callback('gitify://oauth?code=123-456'); }); - const res = await auth.authGitHub({ + const res = await authUtils.authGitHub({ clientId: 'BYO_CLIENT_ID' as ClientID, clientSecret: 'BYO_CLIENT_SECRET' as ClientSecret, hostname: 'my.git.com' as Hostname, }); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://my.git.com/login/oauth/authorize?client_id=BYO_CLIENT_ID&scope=read%3Auser%2Cnotifications%2Crepo', ); @@ -89,14 +91,14 @@ describe('renderer/utils/auth/utils.ts', () => { ); }); - await expect(async () => await auth.authGitHub()).rejects.toEqual( + await expect(async () => await authUtils.authGitHub()).rejects.toEqual( new Error( "Oops! Something went wrong and we couldn't log you in using GitHub. Please try again. Reason: The redirect_uri is missing or invalid. Docs: https://docs.github.com/en/developers/apps/troubleshooting-oauth-errors", ), ); - expect(mockOpenExternalLink).toHaveBeenCalledTimes(1); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + expect(openExternalLinkSpy).toHaveBeenCalledTimes(1); + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/login/oauth/authorize?client_id=FAKE_CLIENT_ID_123&scope=read%3Auser%2Cnotifications%2Crepo', ); @@ -109,16 +111,16 @@ describe('renderer/utils/auth/utils.ts', () => { describe('getToken', () => { const authCode = '123-456' as AuthCode; - const mockApiRequest = jest.spyOn(apiRequests, 'apiRequest'); + const apiRequestSpy = jest.spyOn(apiRequests, 'apiRequest'); - it('should get a token - success', async () => { + it('should get a token', async () => { const requestPromise = new Promise((resolve) => resolve({ data: { access_token: 'this-is-a-token' } } as AxiosResponse), ) as AxiosPromise; - mockApiRequest.mockResolvedValueOnce(requestPromise); + apiRequestSpy.mockResolvedValueOnce(requestPromise); - const res = await auth.getToken(authCode); + const res = await authUtils.getToken(authCode); expect(apiRequests.apiRequest).toHaveBeenCalledWith( 'https://github.com/login/oauth/access_token', @@ -132,20 +134,6 @@ describe('renderer/utils/auth/utils.ts', () => { expect(res.token).toBe('this-is-a-token'); expect(res.hostname).toBe('github.com' as Hostname); }); - - it('should get a token - failure', async () => { - const message = 'Something went wrong.'; - - const requestPromise = new Promise((_, reject) => - reject({ data: { message } } as AxiosResponse), - ) as AxiosPromise; - - mockApiRequest.mockResolvedValueOnce(requestPromise); - - const call = async () => await auth.getToken(authCode); - - await expect(call).rejects.toEqual({ data: { message } }); - }); }); describe('addAccount', () => { @@ -169,7 +157,7 @@ describe('renderer/utils/auth/utils.ts', () => { }); it('should add personal access token account', async () => { - const result = await auth.addAccount( + const result = await authUtils.addAccount( mockAuthState, 'Personal Access Token', '123-456' as Token, @@ -189,7 +177,7 @@ describe('renderer/utils/auth/utils.ts', () => { }); it('should add oauth app account', async () => { - const result = await auth.addAccount( + const result = await authUtils.addAccount( mockAuthState, 'OAuth App', '123-456' as Token, @@ -221,7 +209,7 @@ describe('renderer/utils/auth/utils.ts', () => { }); it('should add personal access token account', async () => { - const result = await auth.addAccount( + const result = await authUtils.addAccount( mockAuthState, 'Personal Access Token', '123-456' as Token, @@ -241,7 +229,7 @@ describe('renderer/utils/auth/utils.ts', () => { }); it('should add oauth app account', async () => { - const result = await auth.addAccount( + const result = await authUtils.addAccount( mockAuthState, 'OAuth App', '123-456' as Token, @@ -266,7 +254,7 @@ describe('renderer/utils/auth/utils.ts', () => { it('should remove account with matching token', async () => { expect(mockAuth.accounts.length).toBe(2); - const result = auth.removeAccount(mockAuth, mockGitHubCloudAccount); + const result = authUtils.removeAccount(mockAuth, mockGitHubCloudAccount); expect(result.accounts.length).toBe(1); }); @@ -278,39 +266,43 @@ describe('renderer/utils/auth/utils.ts', () => { expect(mockAuth.accounts.length).toBe(2); - const result = auth.removeAccount(mockAuth, mockAccount); + const result = authUtils.removeAccount(mockAuth, mockAccount); expect(result.accounts.length).toBe(2); }); }); it('extractHostVersion', () => { - expect(auth.extractHostVersion(null)).toBe('latest'); + expect(authUtils.extractHostVersion(null)).toBe('latest'); - expect(auth.extractHostVersion('foo')).toBe(null); + expect(authUtils.extractHostVersion('foo')).toBe(null); - expect(auth.extractHostVersion('3')).toBe('3.0.0'); + expect(authUtils.extractHostVersion('3')).toBe('3.0.0'); - expect(auth.extractHostVersion('3.15')).toBe('3.15.0'); + expect(authUtils.extractHostVersion('3.15')).toBe('3.15.0'); - expect(auth.extractHostVersion('3.15.0')).toBe('3.15.0'); + expect(authUtils.extractHostVersion('3.15.0')).toBe('3.15.0'); - expect(auth.extractHostVersion('3.15.0-beta1')).toBe('3.15.0'); + expect(authUtils.extractHostVersion('3.15.0-beta1')).toBe('3.15.0'); - expect(auth.extractHostVersion('enterprise-server@3')).toBe('3.0.0'); + expect(authUtils.extractHostVersion('enterprise-server@3')).toBe('3.0.0'); - expect(auth.extractHostVersion('enterprise-server@3.15')).toBe('3.15.0'); + expect(authUtils.extractHostVersion('enterprise-server@3.15')).toBe( + '3.15.0', + ); - expect(auth.extractHostVersion('enterprise-server@3.15.0')).toBe('3.15.0'); + expect(authUtils.extractHostVersion('enterprise-server@3.15.0')).toBe( + '3.15.0', + ); - expect(auth.extractHostVersion('enterprise-server@3.15.0-beta1')).toBe( + expect(authUtils.extractHostVersion('enterprise-server@3.15.0-beta1')).toBe( '3.15.0', ); }); it('getDeveloperSettingsURL', () => { expect( - auth.getDeveloperSettingsURL({ + authUtils.getDeveloperSettingsURL({ hostname: 'github.com' as Hostname, method: 'GitHub App', } as Account), @@ -319,21 +311,21 @@ describe('renderer/utils/auth/utils.ts', () => { ); expect( - auth.getDeveloperSettingsURL({ + authUtils.getDeveloperSettingsURL({ hostname: 'github.com' as Hostname, method: 'OAuth App', } as Account), ).toBe('https://github.com/settings/developers'); expect( - auth.getDeveloperSettingsURL({ + authUtils.getDeveloperSettingsURL({ hostname: 'github.com' as Hostname, method: 'Personal Access Token', } as Account), ).toBe('https://github.com/settings/tokens'); expect( - auth.getDeveloperSettingsURL({ + authUtils.getDeveloperSettingsURL({ hostname: 'github.com', method: 'unknown' as AuthMethod, } as Account), @@ -378,62 +370,68 @@ describe('renderer/utils/auth/utils.ts', () => { describe('isValidHostname', () => { it('should validate hostname - github cloud', () => { - expect(auth.isValidHostname('github.com' as Hostname)).toBeTruthy(); + expect(authUtils.isValidHostname('github.com' as Hostname)).toBeTruthy(); }); it('should validate hostname - github enterprise server', () => { - expect(auth.isValidHostname('github.gitify.io' as Hostname)).toBeTruthy(); + expect( + authUtils.isValidHostname('github.gitify.io' as Hostname), + ).toBeTruthy(); }); it('should invalidate hostname - empty', () => { - expect(auth.isValidHostname('' as Hostname)).toBeFalsy(); + expect(authUtils.isValidHostname('' as Hostname)).toBeFalsy(); }); it('should invalidate hostname - invalid', () => { - expect(auth.isValidHostname('github' as Hostname)).toBeFalsy(); + expect(authUtils.isValidHostname('github' as Hostname)).toBeFalsy(); }); }); describe('isValidClientId', () => { it('should validate client id - valid', () => { expect( - auth.isValidClientId('1234567890_ASDFGHJKL' as ClientID), + authUtils.isValidClientId('1234567890_ASDFGHJKL' as ClientID), ).toBeTruthy(); }); it('should validate client id - empty', () => { - expect(auth.isValidClientId('' as ClientID)).toBeFalsy(); + expect(authUtils.isValidClientId('' as ClientID)).toBeFalsy(); }); it('should validate client id - invalid', () => { - expect(auth.isValidClientId('1234567890asdfg' as ClientID)).toBeFalsy(); + expect( + authUtils.isValidClientId('1234567890asdfg' as ClientID), + ).toBeFalsy(); }); }); describe('isValidToken', () => { it('should validate token - valid', () => { expect( - auth.isValidToken('1234567890_asdfghjklPOIUYTREWQ0987654321' as Token), + authUtils.isValidToken( + '1234567890_asdfghjklPOIUYTREWQ0987654321' as Token, + ), ).toBeTruthy(); }); it('should validate token - empty', () => { - expect(auth.isValidToken('' as Token)).toBeFalsy(); + expect(authUtils.isValidToken('' as Token)).toBeFalsy(); }); it('should validate token - invalid', () => { - expect(auth.isValidToken('1234567890asdfg' as Token)).toBeFalsy(); + expect(authUtils.isValidToken('1234567890asdfg' as Token)).toBeFalsy(); }); }); describe('hasAccounts', () => { it('should return true', () => { - expect(auth.hasAccounts(mockAuth)).toBeTruthy(); + expect(authUtils.hasAccounts(mockAuth)).toBeTruthy(); }); it('should validate false', () => { expect( - auth.hasAccounts({ + authUtils.hasAccounts({ accounts: [], }), ).toBeFalsy(); @@ -442,17 +440,17 @@ describe('renderer/utils/auth/utils.ts', () => { describe('hasMultipleAccounts', () => { it('should return true', () => { - expect(auth.hasMultipleAccounts(mockAuth)).toBeTruthy(); + expect(authUtils.hasMultipleAccounts(mockAuth)).toBeTruthy(); }); it('should validate false', () => { expect( - auth.hasMultipleAccounts({ + authUtils.hasMultipleAccounts({ accounts: [], }), ).toBeFalsy(); expect( - auth.hasMultipleAccounts({ + authUtils.hasMultipleAccounts({ accounts: [mockGitHubCloudAccount], }), ).toBeFalsy(); diff --git a/src/renderer/utils/helpers.ts b/src/renderer/utils/helpers.ts index 7266ed837..25c87e753 100644 --- a/src/renderer/utils/helpers.ts +++ b/src/renderer/utils/helpers.ts @@ -9,7 +9,7 @@ import type { Chevron, Hostname, Link } from '../types'; import type { Notification } from '../typesGitHub'; import { getHtmlUrl, getLatestDiscussion } from './api/client'; import type { PlatformType } from './auth/types'; -import { rendererLogError, rendererLogWarn } from './logger'; +import { rendererLogError } from './logger'; import { getCheckSuiteAttributes } from './notifications/handlers/checkSuite'; import { getClosestDiscussionCommentOrReply } from './notifications/handlers/discussion'; import { getWorkflowRunAttributes } from './notifications/handlers/workflowRun'; @@ -156,11 +156,6 @@ export async function generateGitHubWebUrl( err, notification, ); - - rendererLogWarn( - 'generateGitHubWebUrl', - `Falling back to repository root url: ${notification.repository.full_name}`, - ); } url.searchParams.set( diff --git a/src/renderer/utils/links.test.ts b/src/renderer/utils/links.test.ts index 80ed81f25..d311d648b 100644 --- a/src/renderer/utils/links.test.ts +++ b/src/renderer/utils/links.test.ts @@ -22,7 +22,7 @@ import { } from './links'; describe('renderer/utils/links.ts', () => { - const mockOpenExternalLink = jest + const openExternalLinkSpy = jest .spyOn(comms, 'openExternalLink') .mockImplementation(); @@ -32,71 +32,80 @@ describe('renderer/utils/links.ts', () => { it('openGitifyReleaseNotes', () => { openGitifyReleaseNotes('v1.0.0'); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/gitify-app/gitify/releases/tag/v1.0.0', ); }); it('openGitHubNotifications', () => { openGitHubNotifications(mockGitHubCloudAccount.hostname); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/notifications', ); }); it('openGitHubIssues', () => { openGitHubIssues(mockGitHubCloudAccount.hostname); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/issues', ); }); it('openGitHubPulls', () => { openGitHubPulls(mockGitHubCloudAccount.hostname); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/pulls', ); }); it('openAccountProfile', () => { openAccountProfile(mockGitHubCloudAccount); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/octocat', ); }); it('openUserProfile', () => { const mockUser = createPartialMockUser('mock-user'); + openUserProfile(mockUser); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( 'https://github.com/mock-user', ); }); it('openHost', () => { openHost('github.com' as Hostname); - expect(mockOpenExternalLink).toHaveBeenCalledWith('https://github.com'); + + expect(openExternalLinkSpy).toHaveBeenCalledWith('https://github.com'); }); it('openDeveloperSettings', () => { const mockSettingsURL = 'https://github.com/settings/tokens' as Link; - jest .spyOn(authUtils, 'getDeveloperSettingsURL') .mockReturnValue(mockSettingsURL); + openDeveloperSettings(mockGitHubCloudAccount); - expect(mockOpenExternalLink).toHaveBeenCalledWith(mockSettingsURL); + + expect(openExternalLinkSpy).toHaveBeenCalledWith(mockSettingsURL); }); it('openRepository', () => { const mockHtmlUrl = 'https://github.com/gitify-app/gitify'; - const repo = { html_url: mockHtmlUrl, } as Repository; openRepository(repo); - expect(mockOpenExternalLink).toHaveBeenCalledWith(mockHtmlUrl); + + expect(openExternalLinkSpy).toHaveBeenCalledWith(mockHtmlUrl); }); it('openNotification', async () => { @@ -104,13 +113,16 @@ describe('renderer/utils/links.ts', () => { jest .spyOn(helpers, 'generateGitHubWebUrl') .mockResolvedValue(mockNotificationUrl); + await openNotification(mockSingleNotification); - expect(mockOpenExternalLink).toHaveBeenCalledWith(mockNotificationUrl); + + expect(openExternalLinkSpy).toHaveBeenCalledWith(mockNotificationUrl); }); it('openParticipatingDocs', () => { openGitHubParticipatingDocs(); - expect(mockOpenExternalLink).toHaveBeenCalledWith( + + expect(openExternalLinkSpy).toHaveBeenCalledWith( Constants.GITHUB_DOCS.PARTICIPATING_URL, ); }); diff --git a/src/renderer/utils/notifications/handlers/index.test.ts b/src/renderer/utils/notifications/handlers/index.test.ts index f06a83e99..68568b309 100644 --- a/src/renderer/utils/notifications/handlers/index.test.ts +++ b/src/renderer/utils/notifications/handlers/index.test.ts @@ -31,13 +31,14 @@ describe('renderer/utils/notifications/handlers/index.ts', () => { ['WorkflowRun', workflowRunHandler], ]; - it.each( - cases, - )('returns expected handler instance for %s', (type, expected) => { - const notification = createPartialMockNotification({ type }); - const handler = createNotificationHandler(notification); - expect(handler).toBe(expected); - }); + it.each(cases)( + 'returns expected handler instance for %s', + (type, expected) => { + const notification = createPartialMockNotification({ type }); + const handler = createNotificationHandler(notification); + expect(handler).toBe(expected); + }, + ); it('falls back to default handler for unknown type', () => { const notification = createPartialMockNotification({ diff --git a/src/renderer/utils/notifications/notifications.test.ts b/src/renderer/utils/notifications/notifications.test.ts index e39605513..e3205c954 100644 --- a/src/renderer/utils/notifications/notifications.test.ts +++ b/src/renderer/utils/notifications/notifications.test.ts @@ -53,6 +53,9 @@ describe('renderer/utils/notifications/notifications.ts', () => { const rendererLogErrorSpy = jest .spyOn(logger, 'rendererLogError') .mockImplementation(); + const rendererLogWarnSpy = jest + .spyOn(logger, 'rendererLogWarn') + .mockImplementation(); const mockError = new Error('Test error'); const mockNotification = createPartialMockNotification({ @@ -77,6 +80,11 @@ describe('renderer/utils/notifications/notifications.ts', () => { mockError, mockNotification, ); + + expect(rendererLogWarnSpy).toHaveBeenCalledWith( + 'enrichNotification', + 'Continuing with base notification details', + ); }); describe('stabilizeNotificationsOrder', () => { diff --git a/src/renderer/utils/storage.test.ts b/src/renderer/utils/storage.test.ts index 958f66178..bc7a32810 100644 --- a/src/renderer/utils/storage.test.ts +++ b/src/renderer/utils/storage.test.ts @@ -5,7 +5,7 @@ import { clearState, loadState, saveState } from './storage'; describe('renderer/utils/storage.ts', () => { it('should load the state from localstorage - existing', () => { - jest.spyOn(localStorage.__proto__, 'getItem').mockReturnValueOnce( + jest.spyOn(Storage.prototype, 'getItem').mockReturnValueOnce( JSON.stringify({ auth: { accounts: [ @@ -36,17 +36,18 @@ describe('renderer/utils/storage.ts', () => { }); it('should load the state from localstorage - empty', () => { - jest - .spyOn(localStorage.__proto__, 'getItem') - .mockReturnValueOnce(JSON.stringify({})); + jest.spyOn(localStorage, 'getItem').mockReturnValueOnce(JSON.stringify({})); + const result = loadState(); + expect(result.auth).toBeUndefined(); expect(result.auth).toBeUndefined(); expect(result.settings).toBeUndefined(); }); it('should save the state to localstorage', () => { - jest.spyOn(localStorage.__proto__, 'setItem'); + jest.spyOn(Storage.prototype, 'setItem').mockImplementation(); + saveState({ auth: { accounts: [ @@ -61,12 +62,15 @@ describe('renderer/utils/storage.ts', () => { }, settings: mockSettings, }); + expect(localStorage.setItem).toHaveBeenCalledTimes(1); }); it('should clear the state from localstorage', () => { - jest.spyOn(localStorage.__proto__, 'clear'); + jest.spyOn(Storage.prototype, 'clear').mockImplementation(); + clearState(); + expect(localStorage.clear).toHaveBeenCalledTimes(1); }); }); From 8ff3676f074e8a89716422992f4d349eeb1bb60f Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:17:38 -0500 Subject: [PATCH 2/7] refactor: test suites Signed-off-by: Adam Setch --- src/renderer/utils/auth/utils.test.ts | 16 ++++++++++++++-- src/renderer/utils/helpers.test.ts | 5 +++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/renderer/utils/auth/utils.test.ts b/src/renderer/utils/auth/utils.test.ts index 3aeaeb2b8..4ba63c606 100644 --- a/src/renderer/utils/auth/utils.test.ts +++ b/src/renderer/utils/auth/utils.test.ts @@ -5,6 +5,7 @@ import nock from 'nock'; import { mockGitHubCloudAccount } from '../../__mocks__/account-mocks'; import { mockAuth } from '../../__mocks__/state-mocks'; import { mockGitifyUser } from '../../__mocks__/user-mocks'; +import { Constants } from '../../constants'; import type { Account, AuthCode, @@ -153,7 +154,11 @@ describe('renderer/utils/auth/utils.ts', () => { beforeEach(() => { nock('https://api.github.com') .get('/user') - .reply(200, { ...mockGitifyUser, avatar_url: mockGitifyUser.avatar }); + .reply( + 200, + { ...mockGitifyUser, avatar_url: mockGitifyUser.avatar }, + { 'x-oauth-scopes': Constants.OAUTH_SCOPES.RECOMMENDED }, + ); }); it('should add personal access token account', async () => { @@ -166,6 +171,7 @@ describe('renderer/utils/auth/utils.ts', () => { expect(result.accounts).toEqual([ { + hasRequiredScopes: true, hostname: 'github.com' as Hostname, method: 'Personal Access Token', platform: 'GitHub Cloud', @@ -186,6 +192,7 @@ describe('renderer/utils/auth/utils.ts', () => { expect(result.accounts).toEqual([ { + hasRequiredScopes: true, hostname: 'github.com' as Hostname, method: 'OAuth App', platform: 'GitHub Cloud', @@ -204,7 +211,10 @@ describe('renderer/utils/auth/utils.ts', () => { .reply( 200, { ...mockGitifyUser, avatar_url: mockGitifyUser.avatar }, - { 'x-github-enterprise-version': '3.0.0' }, + { + 'x-github-enterprise-version': '3.0.0', + 'x-oauth-scopes': Constants.OAUTH_SCOPES.RECOMMENDED, + }, ); }); @@ -218,6 +228,7 @@ describe('renderer/utils/auth/utils.ts', () => { expect(result.accounts).toEqual([ { + hasRequiredScopes: true, hostname: 'github.gitify.io' as Hostname, method: 'Personal Access Token', platform: 'GitHub Enterprise Server', @@ -238,6 +249,7 @@ describe('renderer/utils/auth/utils.ts', () => { expect(result.accounts).toEqual([ { + hasRequiredScopes: true, hostname: 'github.gitify.io' as Hostname, method: 'OAuth App', platform: 'GitHub Enterprise Server', diff --git a/src/renderer/utils/helpers.test.ts b/src/renderer/utils/helpers.test.ts index c7b1c963c..ddfcaaef3 100644 --- a/src/renderer/utils/helpers.test.ts +++ b/src/renderer/utils/helpers.test.ts @@ -338,6 +338,10 @@ describe('renderer/utils/helpers.ts', () => { }); it('default to base discussions url when graphql query fails', async () => { + const rendererLogErrorSpy = jest + .spyOn(logger, 'rendererLogError') + .mockImplementation(); + const subject = { title: '1.16.0', url: null, @@ -360,6 +364,7 @@ describe('renderer/utils/helpers.ts', () => { expect(result).toBe( `https://github.com/gitify-app/notifications-test/discussions?${mockNotificationReferrer}`, ); + expect(rendererLogErrorSpy).toHaveBeenCalledTimes(1); }); }); From b9ca474afe56af6efb69edd655a435f4c4840a40 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:30:24 -0500 Subject: [PATCH 3/7] refactor: test suites Signed-off-by: Adam Setch --- src/renderer/context/App.test.tsx | 12 +-- src/renderer/utils/auth/utils.test.ts | 12 +-- src/renderer/utils/helpers.test.ts | 108 +++++++++++--------------- 3 files changed, 56 insertions(+), 76 deletions(-) diff --git a/src/renderer/context/App.test.tsx b/src/renderer/context/App.test.tsx index d8270aa9a..37a3a8e70 100644 --- a/src/renderer/context/App.test.tsx +++ b/src/renderer/context/App.test.tsx @@ -208,7 +208,7 @@ describe('renderer/context/App.tsx', () => { }); describe('authentication methods', () => { - const mockApiRequestAuth = jest.spyOn(apiRequests, 'apiRequestAuth'); + const apiRequestAuthSpy = jest.spyOn(apiRequests, 'apiRequestAuth'); const mockFetchNotifications = jest.fn(); beforeEach(() => { @@ -222,7 +222,7 @@ describe('renderer/context/App.tsx', () => { }); it('should call loginWithPersonalAccessToken', async () => { - mockApiRequestAuth.mockResolvedValueOnce(null); + apiRequestAuthSpy.mockResolvedValueOnce(null); const TestComponent = () => { const { loginWithPersonalAccessToken } = useContext(AppContext); @@ -254,8 +254,8 @@ describe('renderer/context/App.tsx', () => { expect(mockFetchNotifications).toHaveBeenCalledTimes(1), ); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); - expect(mockApiRequestAuth).toHaveBeenCalledWith( + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledWith( 'https://api.github.com/notifications', 'HEAD', 'encrypted', @@ -312,7 +312,7 @@ describe('renderer/context/App.tsx', () => { }); it('should call updateSetting and set auto launch(openAtStartup)', async () => { - const mockSetAutoLaunch = jest.spyOn(comms, 'setAutoLaunch'); + const setAutoLaunchSpy = jest.spyOn(comms, 'setAutoLaunch'); const mockSaveState = jest .spyOn(storage, 'saveState') .mockImplementation(jest.fn()); @@ -340,7 +340,7 @@ describe('renderer/context/App.tsx', () => { fireEvent.click(getByText('Test Case')); }); - expect(mockSetAutoLaunch).toHaveBeenCalledWith(true); + expect(setAutoLaunchSpy).toHaveBeenCalledWith(true); expect(mockSaveState).toHaveBeenCalledWith({ auth: { diff --git a/src/renderer/utils/auth/utils.test.ts b/src/renderer/utils/auth/utils.test.ts index 4ba63c606..384b12bea 100644 --- a/src/renderer/utils/auth/utils.test.ts +++ b/src/renderer/utils/auth/utils.test.ts @@ -1,4 +1,4 @@ -import type { AxiosPromise, AxiosResponse } from 'axios'; +import type { AxiosResponse } from 'axios'; import axios from 'axios'; import nock from 'nock'; @@ -115,11 +115,11 @@ describe('renderer/utils/auth/utils.ts', () => { const apiRequestSpy = jest.spyOn(apiRequests, 'apiRequest'); it('should get a token', async () => { - const requestPromise = new Promise((resolve) => - resolve({ data: { access_token: 'this-is-a-token' } } as AxiosResponse), - ) as AxiosPromise; - - apiRequestSpy.mockResolvedValueOnce(requestPromise); + apiRequestSpy.mockResolvedValueOnce( + Promise.resolve({ + data: { access_token: 'this-is-a-token' }, + } as AxiosResponse), + ); const res = await authUtils.getToken(authCode); diff --git a/src/renderer/utils/helpers.test.ts b/src/renderer/utils/helpers.test.ts index ddfcaaef3..bf8825bcd 100644 --- a/src/renderer/utils/helpers.test.ts +++ b/src/renderer/utils/helpers.test.ts @@ -4,7 +4,7 @@ import { ChevronRightIcon, } from '@primer/octicons-react'; -import type { AxiosPromise, AxiosResponse } from 'axios'; +import type { AxiosResponse } from 'axios'; import { mockPersonalAccessTokenAccount } from '../__mocks__/account-mocks'; import type { Hostname, Link } from '../types'; @@ -74,7 +74,7 @@ describe('renderer/utils/helpers.ts', () => { 'https://github.com/gitify-app/notifications-test/issues/785'; const mockNotificationReferrer = 'notification_referrer_id=MDE4Ok5vdGlmaWNhdGlvblRocmVhZDEzODY2MTA5NjoxMjM0NTY3ODk%3D'; - const mockApiRequestAuth = jest.spyOn(apiRequests, 'apiRequestAuth'); + const apiRequestAuthSpy = jest.spyOn(apiRequests, 'apiRequestAuth'); afterEach(() => { jest.clearAllMocks(); @@ -89,23 +89,19 @@ describe('renderer/utils/helpers.ts', () => { type: 'Issue' as SubjectType, }; - const requestPromise = new Promise((resolve) => - resolve({ - data: { - html_url: mockHtmlUrl, - }, - } as AxiosResponse), - ) as AxiosPromise; - - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue({ + data: { + html_url: mockHtmlUrl, + }, + } as AxiosResponse); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); - expect(mockApiRequestAuth).toHaveBeenCalledWith( + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledWith( subject.latest_comment_url, 'GET', mockPersonalAccessTokenAccount.token, @@ -121,23 +117,19 @@ describe('renderer/utils/helpers.ts', () => { type: 'Issue' as SubjectType, }; - const requestPromise = new Promise((resolve) => - resolve({ - data: { - html_url: mockHtmlUrl, - }, - } as AxiosResponse), - ) as AxiosPromise; - - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue({ + data: { + html_url: mockHtmlUrl, + }, + } as AxiosResponse); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); - expect(mockApiRequestAuth).toHaveBeenCalledWith( + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledWith( subject.url, 'GET', mockPersonalAccessTokenAccount.token, @@ -159,7 +151,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=workflow%3A%22Demo%22+is%3Asuccess+branch%3Amain&${mockNotificationReferrer}`, ); @@ -178,7 +170,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=workflow%3A%22Demo%22+is%3Afailure+branch%3Amain&${mockNotificationReferrer}`, ); @@ -197,7 +189,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=workflow%3A%22Demo%22+is%3Afailure+branch%3Amain&${mockNotificationReferrer}`, ); @@ -216,7 +208,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=workflow%3A%22Demo%22+is%3Askipped+branch%3Amain&${mockNotificationReferrer}`, ); @@ -235,7 +227,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?${mockNotificationReferrer}`, ); @@ -254,7 +246,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=workflow%3A%22Demo%22+branch%3Amain&${mockNotificationReferrer}`, ); @@ -273,7 +265,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?${mockNotificationReferrer}`, ); @@ -289,20 +281,16 @@ describe('renderer/utils/helpers.ts', () => { type: 'Discussion' as SubjectType, }; - const requestPromise = new Promise((resolve) => - resolve({ - data: { data: { search: { nodes: [] } } }, - } as AxiosResponse), - ) as AxiosPromise; - - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue({ + data: { data: { search: { nodes: [] } } }, + } as AxiosResponse); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); expect(result).toBe( `${mockSingleNotification.repository.html_url}/discussions?${mockNotificationReferrer}`, ); @@ -316,22 +304,18 @@ describe('renderer/utils/helpers.ts', () => { type: 'Discussion' as SubjectType, }; - const requestPromise = new Promise((resolve) => - resolve({ - data: { - ...mockGraphQLResponse, - }, - } as AxiosResponse), - ) as AxiosPromise; - - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue({ + data: { + ...mockGraphQLResponse, + }, + } as AxiosResponse); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); expect(result).toBe( `https://github.com/gitify-app/notifications-test/discussions/612?${mockNotificationReferrer}#discussioncomment-2300902`, ); @@ -349,18 +333,14 @@ describe('renderer/utils/helpers.ts', () => { type: 'Discussion' as SubjectType, }; - const requestPromise = new Promise((resolve) => - resolve(null as AxiosResponse), - ) as AxiosPromise; - - mockApiRequestAuth.mockResolvedValue(requestPromise); + apiRequestAuthSpy.mockResolvedValue(null as AxiosResponse); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); expect(result).toBe( `https://github.com/gitify-app/notifications-test/discussions?${mockNotificationReferrer}`, ); @@ -382,7 +362,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/invitations?${mockNotificationReferrer}`, ); @@ -401,7 +381,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/security/dependabot?${mockNotificationReferrer}`, ); @@ -421,7 +401,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?query=is%3Awaiting&${mockNotificationReferrer}`, ); @@ -441,7 +421,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?${mockNotificationReferrer}`, ); @@ -460,7 +440,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `https://github.com/gitify-app/notifications-test/actions?${mockNotificationReferrer}`, ); @@ -481,7 +461,7 @@ describe('renderer/utils/helpers.ts', () => { subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(0); + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(0); expect(result).toBe( `${mockSingleNotification.repository.html_url}?${mockNotificationReferrer}`, ); @@ -502,15 +482,15 @@ describe('renderer/utils/helpers.ts', () => { const mockError = new Error('Test error'); - mockApiRequestAuth.mockRejectedValue(mockError); + apiRequestAuthSpy.mockRejectedValue(mockError); const result = await generateGitHubWebUrl({ ...mockSingleNotification, subject: subject, }); - expect(mockApiRequestAuth).toHaveBeenCalledTimes(1); - expect(mockApiRequestAuth).toHaveBeenCalledWith( + expect(apiRequestAuthSpy).toHaveBeenCalledTimes(1); + expect(apiRequestAuthSpy).toHaveBeenCalledWith( subject.latest_comment_url, 'GET', mockPersonalAccessTokenAccount.token, From 73892c2a06ecfb9075eedbb6728e1836ee65060d Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:40:41 -0500 Subject: [PATCH 4/7] refactor: test suites Signed-off-by: Adam Setch --- .../AccountNotifications.test.tsx | 2 +- .../RepositoryNotifications.test.tsx | 2 +- .../AccountNotifications.test.tsx.snap | 8 ++--- .../RepositoryNotifications.test.tsx.snap | 32 +++++++++---------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/renderer/components/notifications/AccountNotifications.test.tsx b/src/renderer/components/notifications/AccountNotifications.test.tsx index 9375eed23..7e059a497 100644 --- a/src/renderer/components/notifications/AccountNotifications.test.tsx +++ b/src/renderer/components/notifications/AccountNotifications.test.tsx @@ -13,7 +13,7 @@ import * as links from '../../utils/links'; import { AccountNotifications } from './AccountNotifications'; jest.mock('./RepositoryNotifications', () => ({ - RepositoryNotifications: () =>
Repository Notifications
, + RepositoryNotifications: () =>
RepositoryNotifications
, })); describe('renderer/components/notifications/AccountNotifications.tsx', () => { diff --git a/src/renderer/components/notifications/RepositoryNotifications.test.tsx b/src/renderer/components/notifications/RepositoryNotifications.test.tsx index 3e2961414..9849596a3 100644 --- a/src/renderer/components/notifications/RepositoryNotifications.test.tsx +++ b/src/renderer/components/notifications/RepositoryNotifications.test.tsx @@ -10,7 +10,7 @@ import * as comms from '../../utils/comms'; import { RepositoryNotifications } from './RepositoryNotifications'; jest.mock('./NotificationRow', () => ({ - NotificationRow: () =>
Notification Row
, + NotificationRow: () =>
NotificationRow
, })); describe('renderer/components/notifications/RepositoryNotifications.tsx', () => { diff --git a/src/renderer/components/notifications/__snapshots__/AccountNotifications.test.tsx.snap b/src/renderer/components/notifications/__snapshots__/AccountNotifications.test.tsx.snap index 6989e7d7b..10337bfba 100644 --- a/src/renderer/components/notifications/__snapshots__/AccountNotifications.test.tsx.snap +++ b/src/renderer/components/notifications/__snapshots__/AccountNotifications.test.tsx.snap @@ -3078,7 +3078,7 @@ exports[`renderer/components/notifications/AccountNotifications.tsx should rende
- Repository Notifications + RepositoryNotifications
@@ -3279,7 +3279,7 @@ exports[`renderer/components/notifications/AccountNotifications.tsx should rende
- Repository Notifications + RepositoryNotifications
@@ -4275,7 +4275,7 @@ exports[`renderer/components/notifications/AccountNotifications.tsx should toggl
- Repository Notifications + RepositoryNotifications
@@ -4476,7 +4476,7 @@ exports[`renderer/components/notifications/AccountNotifications.tsx should toggl
- Repository Notifications + RepositoryNotifications
diff --git a/src/renderer/components/notifications/__snapshots__/RepositoryNotifications.test.tsx.snap b/src/renderer/components/notifications/__snapshots__/RepositoryNotifications.test.tsx.snap index efa590daf..37c845653 100644 --- a/src/renderer/components/notifications/__snapshots__/RepositoryNotifications.test.tsx.snap +++ b/src/renderer/components/notifications/__snapshots__/RepositoryNotifications.test.tsx.snap @@ -196,10 +196,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should re
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -397,10 +397,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should re
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -655,10 +655,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should re
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -856,10 +856,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should re
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -1322,10 +1322,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should to
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -1530,10 +1530,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should to
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -1795,10 +1795,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should us
- Notification Row + NotificationRow
- Notification Row + NotificationRow
@@ -2003,10 +2003,10 @@ exports[`renderer/components/notifications/RepositoryNotifications.tsx should us
- Notification Row + NotificationRow
- Notification Row + NotificationRow
From 8bcf07dc4b56e76266d03d10500e537a6fc17b1e Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:42:25 -0500 Subject: [PATCH 5/7] refactor: test suites Signed-off-by: Adam Setch --- .../settings/AppearanceSettings.test.tsx | 6 +++++- .../settings/NotificationSettings.test.tsx | 15 +++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/renderer/components/settings/AppearanceSettings.test.tsx b/src/renderer/components/settings/AppearanceSettings.test.tsx index 1491bb5f4..1716c21b7 100644 --- a/src/renderer/components/settings/AppearanceSettings.test.tsx +++ b/src/renderer/components/settings/AppearanceSettings.test.tsx @@ -80,7 +80,11 @@ describe('renderer/components/settings/AppearanceSettings.tsx', () => { await zoomTimeout(); expect(mockUpdateSetting).toHaveBeenCalledTimes(1); - expect(mockUpdateSetting).toHaveBeenCalledWith('zoomPercentage', 90); + expect(mockUpdateSetting).toHaveBeenNthCalledWith( + 1, + 'zoomPercentage', + 90, + ); }); await act(async () => { diff --git a/src/renderer/components/settings/NotificationSettings.test.tsx b/src/renderer/components/settings/NotificationSettings.test.tsx index de3a1e406..a8d867fd4 100644 --- a/src/renderer/components/settings/NotificationSettings.test.tsx +++ b/src/renderer/components/settings/NotificationSettings.test.tsx @@ -55,7 +55,11 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { ); expect(mockUpdateSetting).toHaveBeenCalledTimes(1); - expect(mockUpdateSetting).toHaveBeenCalledWith('fetchInterval', 120000); + expect(mockUpdateSetting).toHaveBeenNthCalledWith( + 1, + 'fetchInterval', + 120000, + ); }); await act(async () => { @@ -119,11 +123,7 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { ); expect(mockUpdateSetting).toHaveBeenCalledTimes(1); - expect(mockUpdateSetting).toHaveBeenNthCalledWith( - 1, - 'fetchInterval', - 60000, - ); + expect(mockUpdateSetting).toHaveBeenCalledWith('fetchInterval', 60000); }); // Attempt to go below the minimum interval, update settings should not be called @@ -155,8 +155,7 @@ describe('renderer/components/settings/NotificationSettings.tsx', () => { ); expect(mockUpdateSetting).toHaveBeenCalledTimes(1); - expect(mockUpdateSetting).toHaveBeenNthCalledWith( - 1, + expect(mockUpdateSetting).toHaveBeenCalledWith( 'fetchInterval', 3600000, ); From 8bf28b02aebed51aea7434c220b533102c9008af Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:44:18 -0500 Subject: [PATCH 6/7] refactor: test suites Signed-off-by: Adam Setch --- .../utils/notifications/handlers/index.test.ts | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/utils/notifications/handlers/index.test.ts b/src/renderer/utils/notifications/handlers/index.test.ts index 68568b309..f06a83e99 100644 --- a/src/renderer/utils/notifications/handlers/index.test.ts +++ b/src/renderer/utils/notifications/handlers/index.test.ts @@ -31,14 +31,13 @@ describe('renderer/utils/notifications/handlers/index.ts', () => { ['WorkflowRun', workflowRunHandler], ]; - it.each(cases)( - 'returns expected handler instance for %s', - (type, expected) => { - const notification = createPartialMockNotification({ type }); - const handler = createNotificationHandler(notification); - expect(handler).toBe(expected); - }, - ); + it.each( + cases, + )('returns expected handler instance for %s', (type, expected) => { + const notification = createPartialMockNotification({ type }); + const handler = createNotificationHandler(notification); + expect(handler).toBe(expected); + }); it('falls back to default handler for unknown type', () => { const notification = createPartialMockNotification({ From df405063fc43b28d37dcfe2c91e0cf4ac37795c2 Mon Sep 17 00:00:00 2001 From: Adam Setch Date: Mon, 17 Nov 2025 11:55:53 -0500 Subject: [PATCH 7/7] refactor: test suites Signed-off-by: Adam Setch --- src/main/first-run.test.ts | 8 +++---- src/main/menu.test.ts | 24 +++++++------------ src/main/updater.test.ts | 4 ++-- .../avatars/AvatarWithFallback.test.tsx | 4 ++-- 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/main/first-run.test.ts b/src/main/first-run.test.ts index f301037a2..84e690cd2 100644 --- a/src/main/first-run.test.ts +++ b/src/main/first-run.test.ts @@ -43,16 +43,16 @@ import { APPLICATION } from '../shared/constants'; import { onFirstRunMaybe } from './first-run'; +function configPath() { + return path.join('/User/Data', 'FirstRun', APPLICATION.FIRST_RUN_FOLDER); +} + describe('main/first-run', () => { beforeEach(() => { jest.clearAllMocks(); mac = true; }); - function configPath() { - return path.join('/User/Data', 'FirstRun', APPLICATION.FIRST_RUN_FOLDER); - } - it('creates first-run marker when not existing and returns true', async () => { existsSync.mockReturnValueOnce(false); // marker absent existsSync.mockReturnValueOnce(false); // folder absent diff --git a/src/main/menu.test.ts b/src/main/menu.test.ts index 31f65605c..b95bc6400 100644 --- a/src/main/menu.test.ts +++ b/src/main/menu.test.ts @@ -62,9 +62,9 @@ describe('main/menu.ts', () => { /** Helper: build menu & return template (first arg passed to buildFromTemplate) */ const buildAndGetTemplate = () => { menuBuilder.buildMenu(); - return (Menu.buildFromTemplate as jest.Mock).mock.calls.slice( + return (Menu.buildFromTemplate as jest.Mock).mock.calls.at( -1, - )[0][0] as TemplateItem[]; + )[0] as TemplateItem[]; }; beforeEach(() => { @@ -179,9 +179,7 @@ describe('main/menu.ts', () => { it('developer submenu click actions execute expected functions', () => { const template = buildAndGetTemplate(); - const devEntry = template.find( - (item) => item?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer'); expect(devEntry).toBeDefined(); const submenu = devEntry.submenu; const clickByLabel = (label: string) => @@ -218,9 +216,7 @@ describe('main/menu.ts', () => { it('developer submenu includes expected static accelerators', () => { const template = buildAndGetTemplate(); - const devEntry = template.find( - (item) => item?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer'); const reloadItem = devEntry.submenu.find((i) => i.role === 'reload'); expect(reloadItem?.accelerator).toBe('CommandOrControl+R'); }); @@ -239,16 +235,14 @@ describe('main/menu.ts', () => { mb.buildMenu(); }); // Return the newest template captured - return (Menu.buildFromTemplate as jest.Mock).mock.calls.slice( + return (Menu.buildFromTemplate as jest.Mock).mock.calls.at( -1, - )[0][0] as TemplateItem[]; + )[0] as TemplateItem[]; }; it('uses mac accelerator for toggleDevTools when on macOS', () => { const template = buildTemplateWithPlatform(true); - const devEntry = template.find( - (i) => i?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer'); const toggleItem = devEntry.submenu.find( (i) => i.role === 'toggleDevTools', ); @@ -257,9 +251,7 @@ describe('main/menu.ts', () => { it('uses non-mac accelerator for toggleDevTools otherwise', () => { const template = buildTemplateWithPlatform(false); - const devEntry = template.find( - (i) => i?.label === 'Developer', - ) as TemplateItem; + const devEntry = template.find((item) => item?.label === 'Developer'); const toggleItem = devEntry.submenu.find( (i) => i.role === 'toggleDevTools', ); diff --git a/src/main/updater.test.ts b/src/main/updater.test.ts index fc858ff48..badcb1eb2 100644 --- a/src/main/updater.test.ts +++ b/src/main/updater.test.ts @@ -46,9 +46,9 @@ jest.mock('electron', () => { // Utility to emit mocked autoUpdater events const emit = (event: string, arg?: ListenerArgs) => { - (listeners[event] || []).forEach((cb) => { + for (const cb of listeners[event] || []) { cb(arg); - }); + } }; // Re-import autoUpdater after mocking diff --git a/src/renderer/components/avatars/AvatarWithFallback.test.tsx b/src/renderer/components/avatars/AvatarWithFallback.test.tsx index b82fffaf9..41dbf5fbd 100644 --- a/src/renderer/components/avatars/AvatarWithFallback.test.tsx +++ b/src/renderer/components/avatars/AvatarWithFallback.test.tsx @@ -48,7 +48,7 @@ describe('renderer/components/avatars/AvatarWithFallback.tsx', () => { renderWithAppContext(); // Find the avatar element by its alt text - const avatar = screen.getByAltText('gitify-app') as HTMLImageElement; + const avatar = screen.getByAltText('gitify-app'); // Simulate image load error (wrapped in act via fireEvent) fireEvent.error(avatar); @@ -60,7 +60,7 @@ describe('renderer/components/avatars/AvatarWithFallback.tsx', () => { renderWithAppContext(); // Find the avatar element by its alt text - const avatar = screen.getByAltText('gitify-app') as HTMLImageElement; + const avatar = screen.getByAltText('gitify-app'); // Simulate image load error (wrapped in act via fireEvent) fireEvent.error(avatar);