Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 17 additions & 9 deletions src/renderer/__helpers__/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,23 @@ import { mockAuth, mockSettings } from '../__mocks__/state-mocks';

import { AppContext, type AppContextState } from '../context/App';

import { type FiltersStore, useFiltersStore } from '../stores';

export { navigateMock } from './vitest.setup';
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> };

const EMPTY_APP_CONTEXT: TestAppContext = {};

interface RenderOptions extends Partial<AppContextState> {
initialEntries?: string[];
}

/**
* Test context
*/
type TestAppContext = Partial<AppContextState>;

interface RenderOptions extends TestAppContext {
initialEntries?: string[];
filters?: Partial<FiltersStore>;
}

/**
* Props for the AppContextProvider wrapper
*/
Expand Down Expand Up @@ -83,7 +86,7 @@ function AppContextProvider({
<MemoryRouter initialEntries={initialEntries}>
<ThemeProvider>
<BaseStyles>
<AppContext.Provider value={defaultValue}>
<AppContext.Provider value={defaultValue as AppContextState}>
{children}
</AppContext.Provider>
</BaseStyles>
Expand All @@ -93,15 +96,20 @@ function AppContextProvider({
}

/**
* Custom render that wraps components with AppContextProvider by default.
* Custom render that wraps components with all providers needed for testing:
* MemoryRouter, AppContext, and Zustand stores.
*
* Usage:
* renderWithAppContext(<MyComponent />, { auth, settings, ... })
* renderWithProviders(<MyComponent />, { notifications, accounts, settings, filters, ... })
*/
export function renderWithAppContext(
export function renderWithProviders(
ui: ReactElement,
{ initialEntries, ...context }: RenderOptions = {},
{ initialEntries, filters, ...context }: RenderOptions = {},
) {
if (filters) {
useFiltersStore.setState(filters);
}

return render(ui, {
wrapper: ({ children }) => (
<AppContextProvider initialEntries={initialEntries} value={context}>
Expand Down
16 changes: 8 additions & 8 deletions src/renderer/components/AllRead.test.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { act } from '@testing-library/react';

import { renderWithAppContext } from '../__helpers__/test-utils';
import { renderWithProviders } from '../__helpers__/test-utils';
import { mockSettings } from '../__mocks__/state-mocks';

import { useFiltersStore } from '../stores';
import { AllRead } from './AllRead';

describe('renderer/components/AllRead.tsx', () => {
it('should render itself & its children - no filters', async () => {
let tree: ReturnType<typeof renderWithAppContext> | null = null;
let tree: ReturnType<typeof renderWithProviders> | null = null;

await act(async () => {
tree = renderWithAppContext(<AllRead />, {
tree = renderWithProviders(<AllRead />, {
settings: {
...mockSettings,
},
Expand All @@ -22,15 +21,16 @@ describe('renderer/components/AllRead.tsx', () => {
});

it('should render itself & its children - with filters', async () => {
useFiltersStore.setState({ reasons: ['author'] });

let tree: ReturnType<typeof renderWithAppContext> | null = null;
let tree: ReturnType<typeof renderWithProviders> | null = null;

await act(async () => {
tree = renderWithAppContext(<AllRead />, {
tree = renderWithProviders(<AllRead />, {
settings: {
...mockSettings,
},
filters: {
reasons: ['author'],
},
});
});

Expand Down
50 changes: 25 additions & 25 deletions src/renderer/components/GlobalShortcuts.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import userEvent from '@testing-library/user-event';

import { navigateMock, renderWithAppContext } from '../__helpers__/test-utils';
import { navigateMock, renderWithProviders } from '../__helpers__/test-utils';

import * as comms from '../utils/system/comms';
import * as links from '../utils/system/links';
Expand All @@ -14,7 +14,7 @@ describe('components/GlobalShortcuts.tsx', () => {
describe('key bindings', () => {
describe('ignores keys that are not valid', () => {
it('ignores B key', async () => {
renderWithAppContext(<GlobalShortcuts />);
renderWithProviders(<GlobalShortcuts />);

await userEvent.keyboard('b');

Expand All @@ -24,7 +24,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('home', () => {
it('navigates home when pressing H key', async () => {
renderWithAppContext(<GlobalShortcuts />);
renderWithProviders(<GlobalShortcuts />);

await userEvent.keyboard('h');

Expand All @@ -38,7 +38,7 @@ describe('components/GlobalShortcuts.tsx', () => {
.mockImplementation(vi.fn());

it('opens primary account GitHub notifications webpage when pressing N while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -48,7 +48,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not open primary account GitHub notifications webpage when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: false,
});

Expand All @@ -60,7 +60,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('focus mode', () => {
it('toggles focus when pressing W while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
updateSetting: updateSettingMock,
isLoggedIn: true,
});
Expand All @@ -71,7 +71,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not toggle focus mode when loading', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
updateSetting: updateSettingMock,
status: 'loading',
isLoggedIn: true,
Expand All @@ -83,7 +83,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not toggle focus mode when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
updateSetting: updateSettingMock,
isLoggedIn: false,
});
Expand All @@ -96,7 +96,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('filters', () => {
it('toggles filters when pressing F while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -106,7 +106,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not toggle filters when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: false,
});

Expand All @@ -122,7 +122,7 @@ describe('components/GlobalShortcuts.tsx', () => {
.mockImplementation(vi.fn());

it('opens primary account GitHub issues webpage when pressing I while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -132,7 +132,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not open primary account GitHub issues webpage when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: false,
});

Expand All @@ -148,7 +148,7 @@ describe('components/GlobalShortcuts.tsx', () => {
.mockImplementation(vi.fn());

it('opens primary account GitHub pull requests webpage when pressing N while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -158,7 +158,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not open primary account GitHub pull requests webpage when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: false,
});

Expand All @@ -170,7 +170,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('refresh', () => {
it('refreshes notifications when pressing R key', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
fetchNotifications: fetchNotificationsMock,
});

Expand All @@ -181,7 +181,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not refresh when status is loading', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
status: 'loading',
});

Expand All @@ -193,7 +193,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('settings', () => {
it('toggles settings when pressing S while logged in', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -203,7 +203,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not toggle settings when logged out', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: false,
});

Expand All @@ -215,7 +215,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('accounts', () => {
it('navigates to accounts when pressing A on settings route', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
initialEntries: ['/settings'],
isLoggedIn: true,
});
Expand All @@ -226,7 +226,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not trigger accounts when not on settings route', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -238,7 +238,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('quit app', () => {
it('quits the app when pressing Q on settings route', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
initialEntries: ['/settings'],
isLoggedIn: true,
});
Expand All @@ -249,7 +249,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('does not quit the app when not on settings route', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand All @@ -261,7 +261,7 @@ describe('components/GlobalShortcuts.tsx', () => {

describe('modifiers', () => {
it('ignores shortcuts when typing in an input', async () => {
renderWithAppContext(
renderWithProviders(
<>
<GlobalShortcuts />
<input id="test-input" />
Expand All @@ -281,7 +281,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('ignores shortcuts when typing in a textarea', async () => {
renderWithAppContext(
renderWithProviders(
<>
<GlobalShortcuts />
<textarea id="test-textarea" />
Expand All @@ -301,7 +301,7 @@ describe('components/GlobalShortcuts.tsx', () => {
});

it('ignores shortcuts when modifier keys are pressed', async () => {
renderWithAppContext(<GlobalShortcuts />, {
renderWithProviders(<GlobalShortcuts />, {
isLoggedIn: true,
});

Expand Down
12 changes: 6 additions & 6 deletions src/renderer/components/Oops.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';

import { PersonIcon } from '@primer/octicons-react';

import { navigateMock, renderWithAppContext } from '../__helpers__/test-utils';
import { navigateMock, renderWithProviders } from '../__helpers__/test-utils';

import { Oops } from './Oops';

Expand All @@ -15,20 +15,20 @@ describe('renderer/components/Oops.tsx', () => {
emojis: ['🔥'],
};

let tree: ReturnType<typeof renderWithAppContext> | null = null;
let tree: ReturnType<typeof renderWithProviders> | null = null;

await act(async () => {
tree = renderWithAppContext(<Oops error={mockError} />);
tree = renderWithProviders(<Oops error={mockError} />);
});

expect(tree.container).toMatchSnapshot();
});

it('should render itself & its children - fallback to unknown error', async () => {
let tree: ReturnType<typeof renderWithAppContext> | null = null;
let tree: ReturnType<typeof renderWithProviders> | null = null;

await act(async () => {
tree = renderWithAppContext(<Oops error={null} />);
tree = renderWithProviders(<Oops error={null} />);
});

expect(tree.container).toMatchSnapshot();
Expand All @@ -49,7 +49,7 @@ describe('renderer/components/Oops.tsx', () => {
],
};

renderWithAppContext(<Oops error={mockError} />);
renderWithProviders(<Oops error={mockError} />);

await userEvent.click(screen.getByText('Go somewhere'));

Expand Down
Loading
Loading