Skip to content

Commit d53b892

Browse files
committed
feat(useWorkerLoad): add new hook
fix #11
1 parent 3aafa98 commit d53b892

2 files changed

Lines changed: 428 additions & 0 deletions

File tree

src/__tests__/useWorkerLoad.ts

Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
import { renderHook, act } from '@testing-library/react-hooks';
2+
3+
import { useWorkerLoad } from '..';
4+
5+
let callbackFn: jest.Mock;
6+
let initialProps: any;
7+
const thrownError = new Error('error');
8+
9+
beforeEach(() => {
10+
callbackFn = jest.fn();
11+
initialProps = { worker: callbackFn };
12+
});
13+
14+
const useHookHelper = ({ worker, initialValue }: any) =>
15+
useWorkerLoad(worker, initialValue);
16+
17+
describe('initial values', () => {
18+
it('starts without error', async () => {
19+
const {
20+
result: {
21+
current: { error },
22+
},
23+
waitForNextUpdate,
24+
} = renderHook(useHookHelper, { initialProps });
25+
26+
await waitForNextUpdate();
27+
28+
expect(error).toBeUndefined();
29+
});
30+
31+
it('starts without data', async () => {
32+
const {
33+
result: {
34+
current: { data },
35+
},
36+
waitForNextUpdate,
37+
} = renderHook(useHookHelper, { initialProps });
38+
39+
await waitForNextUpdate();
40+
41+
expect(data).toBeUndefined();
42+
});
43+
44+
it('starts with loading', async () => {
45+
const {
46+
result: {
47+
current: { isLoading },
48+
},
49+
waitForNextUpdate,
50+
} = renderHook(useHookHelper, { initialProps });
51+
52+
await waitForNextUpdate();
53+
54+
expect(isLoading).toBe(true);
55+
});
56+
57+
it('starts with initial value', async () => {
58+
const initialValue = 'foobar';
59+
const {
60+
result: {
61+
current: { data },
62+
},
63+
waitForNextUpdate,
64+
} = renderHook(useHookHelper, {
65+
initialProps: {
66+
...initialProps,
67+
initialValue,
68+
},
69+
});
70+
71+
await waitForNextUpdate();
72+
73+
expect(data).toBe(initialValue);
74+
});
75+
});
76+
77+
it('stops loading when effect resolves', async () => {
78+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
79+
initialProps,
80+
});
81+
82+
await waitForNextUpdate();
83+
84+
expect(result.current.isLoading).toEqual(false);
85+
});
86+
87+
it('stops loading on failure and returns the error', async () => {
88+
callbackFn.mockRejectedValue(thrownError);
89+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
90+
initialProps,
91+
});
92+
93+
await waitForNextUpdate();
94+
95+
expect(result.current.error?.value).toBe(thrownError);
96+
expect(result.current.error?.retry).not.toBeUndefined();
97+
expect(result.current.isLoading).toEqual(false);
98+
});
99+
100+
it('updates data with returned value', async () => {
101+
const val = 'foobar';
102+
callbackFn.mockResolvedValue(val);
103+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
104+
initialProps,
105+
});
106+
107+
await waitForNextUpdate();
108+
109+
expect(result.current.data).toEqual(val);
110+
});
111+
112+
it('can set error', async () => {
113+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
114+
initialProps,
115+
});
116+
117+
await waitForNextUpdate();
118+
expect(result.current.error).toBeUndefined();
119+
act(() => {
120+
result.current.setError(thrownError);
121+
});
122+
123+
expect(result.current.error?.value).toEqual(thrownError);
124+
expect(result.current.error?.retry).not.toBeUndefined();
125+
});
126+
127+
it('can set isLoading', async () => {
128+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
129+
initialProps,
130+
});
131+
132+
await waitForNextUpdate();
133+
expect(result.current.isLoading).toEqual(false);
134+
act(() => {
135+
result.current.setIsLoading(true);
136+
});
137+
138+
expect(result.current.isLoading).toEqual(true);
139+
});
140+
141+
describe('retry', () => {
142+
it('sets loading true', async () => {
143+
callbackFn.mockRejectedValue(thrownError);
144+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
145+
initialProps,
146+
});
147+
148+
await waitForNextUpdate();
149+
expect(result.current.isLoading).toBe(false);
150+
151+
act(() => {
152+
result.current.error!.retry();
153+
});
154+
155+
expect(result.current.isLoading).toBe(true);
156+
157+
await waitForNextUpdate(); // stops errors from being logged
158+
});
159+
160+
it('resets error after promise resolves', async () => {
161+
callbackFn.mockRejectedValueOnce(thrownError);
162+
163+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
164+
initialProps,
165+
});
166+
167+
await waitForNextUpdate();
168+
expect(result.current.error?.value).toBe(thrownError);
169+
170+
await act(result.current.error!.retry);
171+
172+
expect(result.current.error).toBeUndefined();
173+
});
174+
175+
it('updates data after retry on error', async () => {
176+
const val = 'foobar';
177+
callbackFn.mockResolvedValue(val);
178+
callbackFn.mockRejectedValueOnce(thrownError);
179+
180+
const { result, waitForNextUpdate } = renderHook(useHookHelper, {
181+
initialProps,
182+
});
183+
184+
await waitForNextUpdate();
185+
expect(result.current.error?.value).toBe(thrownError);
186+
expect(result.current.data).toBeUndefined();
187+
188+
await act(result.current.error!.retry);
189+
190+
expect(result.current.data).toBe(val);
191+
expect(result.current.error).toBeUndefined();
192+
});
193+
});
194+
195+
/// These tests won't fail their execution, but the type-checking instead.
196+
describe('test types', () => {
197+
type Assert<T, Expected> = T extends Expected
198+
? Expected extends T
199+
? true
200+
: never
201+
: never;
202+
203+
describe('`initialValue` is unset', () => {
204+
it('infers `data` may be undefined when `worker` returns mandatory type', async () => {
205+
const worker = async () => 'string';
206+
const {
207+
result: {
208+
current: { data },
209+
},
210+
waitForNextUpdate,
211+
} = renderHook(() => useWorkerLoad(worker));
212+
await waitForNextUpdate();
213+
214+
const assert1: Assert<ReturnType<typeof worker>, Promise<string>> = true;
215+
expect(assert1).toBe(true);
216+
217+
const assert2: Assert<typeof data, string | undefined> = true;
218+
expect(assert2).toBe(true);
219+
});
220+
221+
it('infers `data` is undefined when `worker` returns undefined type', async () => {
222+
const worker = async () => undefined;
223+
const {
224+
result: {
225+
current: { data },
226+
},
227+
waitForNextUpdate,
228+
} = renderHook(() => useWorkerLoad(worker));
229+
await waitForNextUpdate();
230+
231+
const assert1: Assert<
232+
ReturnType<typeof worker>,
233+
Promise<undefined>
234+
> = true;
235+
expect(assert1).toBe(true);
236+
237+
const assert2: Assert<typeof data, undefined> = true;
238+
expect(assert2).toBe(true);
239+
});
240+
241+
it('forces `data` to be undefined even when type is explicitly set', async () => {
242+
const worker = async () => undefined;
243+
const {
244+
result: {
245+
current: { data },
246+
},
247+
waitForNextUpdate,
248+
} = renderHook(() => useWorkerLoad<number>(worker));
249+
await waitForNextUpdate();
250+
251+
const assert1: Assert<
252+
ReturnType<typeof worker>,
253+
Promise<undefined>
254+
> = true;
255+
expect(assert1).toBe(true);
256+
257+
const assert2: Assert<typeof data, undefined> = true;
258+
expect(assert2).toBe(true);
259+
});
260+
});
261+
262+
describe('has `initialValue`', () => {
263+
it('infers `data` from `initialValue', async () => {
264+
const worker = async () => 0;
265+
const {
266+
result: {
267+
current: { data },
268+
},
269+
waitForNextUpdate,
270+
} = renderHook(() => useWorkerLoad(worker, -1));
271+
await waitForNextUpdate();
272+
273+
const assert1: Assert<ReturnType<typeof worker>, Promise<number>> = true;
274+
expect(assert1).toBe(true);
275+
276+
const assert2: Assert<typeof data, number> = true;
277+
expect(assert2).toBe(true);
278+
});
279+
280+
it("infers `data` may be undefined from `worker`'s return type", async () => {
281+
const worker = async () => undefined;
282+
const {
283+
result: {
284+
current: { data },
285+
},
286+
waitForNextUpdate,
287+
} = renderHook(() => useWorkerLoad(worker, -1 as number));
288+
await waitForNextUpdate();
289+
290+
const assert1: Assert<
291+
ReturnType<typeof worker>,
292+
Promise<undefined>
293+
> = true;
294+
expect(assert1).toBe(true);
295+
296+
const assert2: Assert<typeof data, number | undefined> = true;
297+
expect(assert2).toBe(true);
298+
});
299+
300+
it('infers `data` might be undefined from `initialValue`s type', async () => {
301+
const worker = async () => 1;
302+
const {
303+
result: {
304+
current: { data },
305+
},
306+
waitForNextUpdate,
307+
} = renderHook(() => useWorkerLoad(worker, undefined));
308+
await waitForNextUpdate();
309+
310+
const assert1: Assert<ReturnType<typeof worker>, Promise<number>> = true;
311+
expect(assert1).toBe(true);
312+
313+
const assert2: Assert<typeof data, number | undefined> = true;
314+
expect(assert2).toBe(true);
315+
});
316+
317+
it('allows `data` to be undefined when type is explicitly set', async () => {
318+
const worker = async () => 0;
319+
const {
320+
result: {
321+
current: { data },
322+
},
323+
waitForNextUpdate,
324+
} = renderHook(() => useWorkerLoad<number | undefined>(worker, -1));
325+
await waitForNextUpdate();
326+
327+
const assert1: Assert<ReturnType<typeof worker>, Promise<number>> = true;
328+
expect(assert1).toBe(true);
329+
330+
const assert2: Assert<typeof data, number | undefined> = true;
331+
expect(assert2).toBe(true);
332+
});
333+
});
334+
});

0 commit comments

Comments
 (0)