Skip to content

Commit de87f0c

Browse files
committed
test: refactor render with app context
Signed-off-by: Adam Setch <[email protected]>
1 parent 15f40e9 commit de87f0c

File tree

93 files changed

+23668
-25993
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

93 files changed

+23668
-25993
lines changed
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import type { RenderOptions } from '@testing-library/react';
2+
import { render } from '@testing-library/react';
3+
import type { ReactElement, ReactNode } from 'react';
4+
import { useMemo } from 'react';
5+
6+
import { BaseStyles, ThemeProvider } from '@primer/react';
7+
8+
import { mockAuth, mockSettings } from '../__mocks__/state-mocks';
9+
import type { AppContextState } from '../context/App';
10+
import { AppContext } from '../context/App';
11+
12+
/**
13+
* Props for the AppContextProvider wrapper
14+
*/
15+
interface AppContextProviderProps {
16+
readonly children: ReactNode;
17+
readonly value?: Partial<AppContextState>;
18+
}
19+
20+
/**
21+
* Wrapper component that provides ThemeProvider, BaseStyles, and AppContext
22+
* with sensible defaults for testing.
23+
*/
24+
export function AppContextProvider({
25+
children,
26+
value = {},
27+
}: AppContextProviderProps) {
28+
const defaultValue: Partial<AppContextState> = useMemo(() => {
29+
return {
30+
auth: mockAuth,
31+
isLoggedIn: false,
32+
loginWithGitHubApp: async () => {},
33+
loginWithOAuthApp: async () => {},
34+
loginWithPersonalAccessToken: async () => {},
35+
logoutFromAccount: async () => {},
36+
37+
status: 'success',
38+
globalError: { title: '', descriptions: [], emojis: [] },
39+
40+
notifications: [],
41+
notificationCount: 0,
42+
unreadNotificationCount: 0,
43+
hasNotifications: false,
44+
hasUnreadNotifications: false,
45+
46+
fetchNotifications: async () => {},
47+
removeAccountNotifications: async () => {},
48+
49+
markNotificationsAsRead: async () => {},
50+
markNotificationsAsDone: async () => {},
51+
unsubscribeNotification: async () => {},
52+
53+
settings: mockSettings,
54+
clearFilters: () => {},
55+
resetSettings: () => {},
56+
updateSetting: () => {},
57+
updateFilter: () => {},
58+
59+
...value,
60+
} as Partial<AppContextState>;
61+
}, [value]);
62+
63+
return (
64+
<ThemeProvider>
65+
<BaseStyles>
66+
<AppContext.Provider value={defaultValue}>
67+
{children}
68+
</AppContext.Provider>
69+
</BaseStyles>
70+
</ThemeProvider>
71+
);
72+
}
73+
74+
/**
75+
* Custom render function that wraps components with AppContextProvider by default.
76+
*
77+
* Usage (simplified):
78+
* renderWithAppContext(<MyComponent />, { auth, settings })
79+
*
80+
* Legacy (still supported):
81+
* renderWithAppContext(<MyComponent />, { appContext: { auth, settings } })
82+
*/
83+
type RenderWithAppContextOptions = Omit<RenderOptions, 'wrapper'> &
84+
Partial<AppContextState> & {
85+
appContext?: Partial<AppContextState>;
86+
};
87+
88+
export function renderWithAppContext(
89+
ui: ReactElement,
90+
options: RenderWithAppContextOptions = {},
91+
) {
92+
const CONTEXT_KEYS: Array<keyof AppContextState> = [
93+
'auth',
94+
'isLoggedIn',
95+
'loginWithGitHubApp',
96+
'loginWithOAuthApp',
97+
'loginWithPersonalAccessToken',
98+
'logoutFromAccount',
99+
'status',
100+
'globalError',
101+
'notifications',
102+
'notificationCount',
103+
'unreadNotificationCount',
104+
'hasNotifications',
105+
'hasUnreadNotifications',
106+
'fetchNotifications',
107+
'removeAccountNotifications',
108+
'markNotificationsAsRead',
109+
'markNotificationsAsDone',
110+
'unsubscribeNotification',
111+
'settings',
112+
'clearFilters',
113+
'resetSettings',
114+
'updateSetting',
115+
'updateFilter',
116+
];
117+
118+
const { appContext, ...rest } = options as Partial<Record<string, unknown>> & {
119+
appContext?: Partial<AppContextState>;
120+
};
121+
122+
const ctxFromTopLevel: Partial<AppContextState> = {};
123+
for (const key of CONTEXT_KEYS) {
124+
if (key in rest && rest[key] !== undefined) {
125+
(ctxFromTopLevel as Partial<Record<string, unknown>>)[key] = rest[key];
126+
}
127+
}
128+
129+
const value: Partial<AppContextState> = { ...ctxFromTopLevel };
130+
if (appContext) {
131+
Object.assign(value, appContext);
132+
}
133+
134+
return render(ui, {
135+
wrapper: ({ children }) => (
136+
<AppContextProvider value={value}>{children}</AppContextProvider>
137+
),
138+
// No additional render options by default
139+
});
140+
}

src/renderer/components/AllRead.test.tsx

Lines changed: 13 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { act, render } from '@testing-library/react';
1+
import { act } from '@testing-library/react';
22

3+
import { renderWithAppContext } from '../__helpers__/test-utils';
34
import { mockSettings } from '../__mocks__/state-mocks';
45
import { ensureStableEmojis } from '../__mocks__/utils';
5-
import { AppContext } from '../context/App';
66
import { AllRead } from './AllRead';
77

88
describe('renderer/components/AllRead.tsx', () => {
@@ -11,41 +11,27 @@ describe('renderer/components/AllRead.tsx', () => {
1111
});
1212

1313
it('should render itself & its children - no filters', async () => {
14-
let tree: ReturnType<typeof render> | null = null;
14+
let tree: ReturnType<typeof renderWithAppContext> | null = null;
1515

1616
await act(async () => {
17-
tree = render(
18-
<AppContext.Provider
19-
value={{
20-
settings: {
21-
...mockSettings,
22-
},
23-
}}
24-
>
25-
<AllRead />
26-
</AppContext.Provider>,
27-
);
17+
tree = renderWithAppContext(<AllRead />, {
18+
19+
settings: {
20+
...mockSettings } });
2821
});
2922

3023
expect(tree).toMatchSnapshot();
3124
});
3225

3326
it('should render itself & its children - with filters', async () => {
34-
let tree: ReturnType<typeof render> | null = null;
27+
let tree: ReturnType<typeof renderWithAppContext> | null = null;
3528

3629
await act(async () => {
37-
tree = render(
38-
<AppContext.Provider
39-
value={{
40-
settings: {
41-
...mockSettings,
42-
filterReasons: ['author'],
43-
},
44-
}}
45-
>
46-
<AllRead />
47-
</AppContext.Provider>,
48-
);
30+
tree = renderWithAppContext(<AllRead />, {
31+
32+
settings: {
33+
...mockSettings,
34+
filterReasons: ['author'] } });
4935
});
5036

5137
expect(tree).toMatchSnapshot();

src/renderer/components/Oops.test.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { act, render } from '@testing-library/react';
1+
import { act } from '@testing-library/react';
22

3+
import { renderWithAppContext } from '../__helpers__/test-utils';
34
import { ensureStableEmojis } from '../__mocks__/utils';
45
import { Oops } from './Oops';
56

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

18-
let tree: ReturnType<typeof render> | null = null;
19+
let tree: ReturnType<typeof renderWithAppContext> | null = null;
1920

2021
await act(async () => {
21-
tree = render(<Oops error={mockError} />);
22+
tree = renderWithAppContext(<Oops error={mockError} />);
2223
});
2324

2425
expect(tree).toMatchSnapshot();
2526
});
2627

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

3031
await act(async () => {
31-
tree = render(<Oops error={null} />);
32+
tree = renderWithAppContext(<Oops error={null} />);
3233
});
3334

3435
expect(tree).toMatchSnapshot();

0 commit comments

Comments
 (0)