Skip to content

Commit afa29db

Browse files
committed
refactor tests
1 parent 0c2fa34 commit afa29db

File tree

1 file changed

+119
-102
lines changed

1 file changed

+119
-102
lines changed

src/__tests__/render-hook.test.tsx

Lines changed: 119 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,7 @@ afterEach(() => {
1212
console.error = originalConsoleError;
1313
});
1414

15-
function useSuspendingHook(promise: Promise<string>) {
16-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
17-
// @ts-ignore: React 18 does not have `use` hook
18-
return React.use(promise);
19-
}
20-
21-
test('gives committed result', async () => {
15+
test('renders hook and waits for effects to complete', async () => {
2216
const { result } = await renderHook(() => {
2317
const [state, setState] = React.useState(1);
2418

@@ -32,7 +26,41 @@ test('gives committed result', async () => {
3226
expect(result.current).toEqual([2, expect.any(Function)]);
3327
});
3428

35-
test('allows rerendering', async () => {
29+
test('handles multiple state updates in single effect', async () => {
30+
function useTestHook() {
31+
const [first, setFirst] = React.useState(1);
32+
const [second, setSecond] = React.useState(2);
33+
34+
React.useEffect(() => {
35+
setFirst(10);
36+
setSecond(20);
37+
}, []);
38+
39+
return { first, second };
40+
}
41+
42+
const { result } = await renderHook(useTestHook);
43+
expect(result.current).toEqual({ first: 10, second: 20 });
44+
});
45+
46+
test('renders hook with initialProps', async () => {
47+
function useTestHook(props: { value: number }) {
48+
const [state, setState] = React.useState(props.value);
49+
50+
React.useEffect(() => {
51+
setState(props.value * 2);
52+
}, [props.value]);
53+
54+
return state;
55+
}
56+
57+
const { result } = await renderHook(useTestHook, {
58+
initialProps: { value: 5 },
59+
});
60+
expect(result.current).toEqual(10);
61+
});
62+
63+
test('rerenders hook with new props', async () => {
3664
const { result, rerender } = await renderHook(
3765
(props: { branch: 'left' | 'right' }) => {
3866
const [left, setLeft] = React.useState('left');
@@ -57,49 +85,7 @@ test('allows rerendering', async () => {
5785
expect(result.current).toEqual(['right', expect.any(Function)]);
5886
});
5987

60-
test('allows wrapper components', async () => {
61-
const Context = React.createContext('default');
62-
function Wrapper({ children }: { children: ReactNode }) {
63-
return <Context.Provider value="provided">{children}</Context.Provider>;
64-
}
65-
const { result } = await renderHook(
66-
() => {
67-
return React.useContext(Context);
68-
},
69-
{
70-
wrapper: Wrapper,
71-
},
72-
);
73-
74-
expect(result.current).toEqual('provided');
75-
});
76-
77-
function useMyHook<T>(param: T) {
78-
return { param };
79-
}
80-
81-
test('props type is inferred correctly when initial props is defined', async () => {
82-
const { result, rerender } = await renderHook((num: number) => useMyHook(num), {
83-
initialProps: 5,
84-
});
85-
expect(result.current.param).toBe(5);
86-
87-
await rerender(6);
88-
expect(result.current.param).toBe(6);
89-
});
90-
91-
test('props type is inferred correctly when initial props is explicitly undefined', async () => {
92-
const { result, rerender } = await renderHook((num: number | undefined) => useMyHook(num), {
93-
initialProps: undefined,
94-
});
95-
96-
expect(result.current.param).toBeUndefined();
97-
98-
await rerender(6);
99-
expect(result.current.param).toBe(6);
100-
});
101-
102-
test('rerender function updates hook asynchronously', async () => {
88+
test('rerender updates hook state based on props', async () => {
10389
function useTestHook(props: { value: number }) {
10490
const [state, setState] = React.useState(props.value);
10591

@@ -119,7 +105,24 @@ test('rerender function updates hook asynchronously', async () => {
119105
expect(result.current).toEqual(20);
120106
});
121107

122-
test('unmount function unmounts hook asynchronously', async () => {
108+
test('supports wrapper option for context providers', async () => {
109+
const Context = React.createContext('default');
110+
function Wrapper({ children }: { children: ReactNode }) {
111+
return <Context.Provider value="provided">{children}</Context.Provider>;
112+
}
113+
const { result } = await renderHook(
114+
() => {
115+
return React.useContext(Context);
116+
},
117+
{
118+
wrapper: Wrapper,
119+
},
120+
);
121+
122+
expect(result.current).toEqual('provided');
123+
});
124+
125+
test('unmount triggers cleanup effects', async () => {
123126
let cleanupCalled = false;
124127

125128
function useTestHook() {
@@ -139,24 +142,75 @@ test('unmount function unmounts hook asynchronously', async () => {
139142
expect(cleanupCalled).toBe(true);
140143
});
141144

142-
test('handles multiple state updates in effects', async () => {
143-
function useTestHook() {
144-
const [first, setFirst] = React.useState(1);
145-
const [second, setSecond] = React.useState(2);
145+
test('handles cleanup effects on rerender and unmount', async () => {
146+
let effectCount = 0;
147+
let cleanupCount = 0;
148+
149+
function useTestHook(props: { key: string }) {
150+
const [value, setValue] = React.useState(props.key);
146151

147152
React.useEffect(() => {
148-
setFirst(10);
149-
setSecond(20);
150-
}, []);
153+
effectCount++;
154+
setValue(`${props.key}-effect`);
151155

152-
return { first, second };
156+
return () => {
157+
cleanupCount++;
158+
};
159+
}, [props.key]);
160+
161+
return value;
153162
}
154163

155-
const { result } = await renderHook(useTestHook);
156-
expect(result.current).toEqual({ first: 10, second: 20 });
164+
const { result, rerender, unmount } = await renderHook(useTestHook, {
165+
initialProps: { key: 'initial' },
166+
});
167+
168+
expect(result.current).toBe('initial-effect');
169+
expect(effectCount).toBe(1);
170+
expect(cleanupCount).toBe(0);
171+
172+
await rerender({ key: 'updated' });
173+
expect(result.current).toBe('updated-effect');
174+
expect(effectCount).toBe(2);
175+
expect(cleanupCount).toBe(1);
176+
177+
await unmount();
178+
expect(effectCount).toBe(2);
179+
expect(cleanupCount).toBe(2);
180+
});
181+
182+
function useMyHook<T>(param: T) {
183+
return { param };
184+
}
185+
186+
test('infers props type from initialProps', async () => {
187+
const { result, rerender } = await renderHook((num: number) => useMyHook(num), {
188+
initialProps: 5,
189+
});
190+
expect(result.current.param).toBe(5);
191+
192+
await rerender(6);
193+
expect(result.current.param).toBe(6);
157194
});
158195

159-
test('handles hook with suspense', async () => {
196+
test('infers props type when initialProps is undefined', async () => {
197+
const { result, rerender } = await renderHook((num: number | undefined) => useMyHook(num), {
198+
initialProps: undefined,
199+
});
200+
201+
expect(result.current.param).toBeUndefined();
202+
203+
await rerender(6);
204+
expect(result.current.param).toBe(6);
205+
});
206+
207+
function useSuspendingHook(promise: Promise<string>) {
208+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
209+
// @ts-ignore: React 18 does not have `use` hook
210+
return React.use(promise);
211+
}
212+
213+
test('supports React Suspense', async () => {
160214
let resolvePromise: (value: string) => void;
161215
const promise = new Promise<string>((resolve) => {
162216
resolvePromise = resolve;
@@ -195,7 +249,7 @@ class ErrorBoundary extends React.Component<
195249
}
196250
}
197251

198-
test('handles hook suspense with error boundary', async () => {
252+
test('handles Suspense errors with error boundary', async () => {
199253
const ERROR_MESSAGE = 'Hook Promise Rejected In Test';
200254
// eslint-disable-next-line no-console
201255
console.error = excludeConsoleMessage(console.error, ERROR_MESSAGE);
@@ -224,7 +278,7 @@ test('handles hook suspense with error boundary', async () => {
224278
expect(result.current).toBeNull();
225279
});
226280

227-
test('handles custom hooks with complex logic', async () => {
281+
test('handles hooks with callbacks and complex state', async () => {
228282
function useCounter(initialValue: number) {
229283
const [count, setCount] = React.useState(initialValue);
230284

@@ -264,40 +318,3 @@ test('handles custom hooks with complex logic', async () => {
264318
});
265319
expect(result.current.count).toBe(4);
266320
});
267-
268-
test('handles hook with cleanup and re-initialization', async () => {
269-
let effectCount = 0;
270-
let cleanupCount = 0;
271-
272-
function useTestHook(props: { key: string }) {
273-
const [value, setValue] = React.useState(props.key);
274-
275-
React.useEffect(() => {
276-
effectCount++;
277-
setValue(`${props.key}-effect`);
278-
279-
return () => {
280-
cleanupCount++;
281-
};
282-
}, [props.key]);
283-
284-
return value;
285-
}
286-
287-
const { result, rerender, unmount } = await renderHook(useTestHook, {
288-
initialProps: { key: 'initial' },
289-
});
290-
291-
expect(result.current).toBe('initial-effect');
292-
expect(effectCount).toBe(1);
293-
expect(cleanupCount).toBe(0);
294-
295-
await rerender({ key: 'updated' });
296-
expect(result.current).toBe('updated-effect');
297-
expect(effectCount).toBe(2);
298-
expect(cleanupCount).toBe(1);
299-
300-
await unmount();
301-
expect(effectCount).toBe(2);
302-
expect(cleanupCount).toBe(2);
303-
});

0 commit comments

Comments
 (0)