Passed
Push — feature/data-request-hooks ( ad0abd...ffe931 )
by Tristan
06:29
created

resources/assets/js/hooks/webResourceHooks/indexResourceHook.test.ts   A

Complexity

Total Complexity 1
Complexity/F 1

Size

Lines of Code 394
Function Count 1

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 1
eloc 105
mnd 0
bc 0
fnc 1
dl 0
loc 394
rs 10
bpm 0
cpm 1
noi 0
c 0
b 0
f 0

1 Function

Rating   Name   Duplication   Size   Complexity  
A indexResourceHook.test.ts ➔ arrayToIndexedObj 0 3 1
1
import fetchMock from "fetch-mock";
2
import { act, renderHook } from "@testing-library/react-hooks";
3
import { FetchError } from "../../helpers/httpRequests";
4
import useResourceIndex from "./indexResourceHook";
5
import { getId, mapToObject } from "../../helpers/queries";
6
7
interface TestResource {
8
  id: number;
9
  name: string;
10
}
11
12
function arrayToIndexedObj(arr) {
13
  return mapToObject(arr, getId);
14
}
15
16
describe("indexResourceHook", () => {
17
  afterEach((): void => {
18
    fetchMock.reset();
19
    fetchMock.restore();
20
  });
21
22
  const endpoint = "https://talent.test/api/test";
23
24
  it("Initially returns no values and a status of 'pending'", () => {
25
    fetchMock.mock(
26
      "*",
27
      {},
28
      {
29
        delay: 10,
30
      },
31
    );
32
    const { result } = renderHook(() => useResourceIndex(endpoint));
33
    expect(result.current.values).toEqual({});
34
    expect(result.current.indexStatus).toEqual("pending");
35
  });
36
  it("If initial value is set, returns that value and does not automatically fetch.", () => {
37
    fetchMock.mock("*", {});
38
    const initialValue = [
39
      { id: 1, name: "one" },
40
      { id: 2, name: "two" },
41
    ];
42
    const { result } = renderHook(() =>
43
      useResourceIndex(endpoint, { initialValue }),
44
    );
45
    expect(result.current.values).toEqual({
46
      1: initialValue[0],
47
      2: initialValue[1],
48
    });
49
    expect(result.current.indexStatus).toEqual("initial");
50
    expect(fetchMock.called()).toBe(false);
51
  });
52
  it("If initial value is set, but forceInitialRefresh is true, returns the initial value but also fetches.", async () => {
53
    const initialValue = [
54
      { id: 1, name: "one" },
55
      { id: 2, name: "two" },
56
    ];
57
    const updatedValue = [
58
      { id: 1, name: "one NEW" },
59
      { id: 2, name: "two NEW" },
60
      { id: 3, name: "three NEW" },
61
    ];
62
    fetchMock.mock("*", updatedValue, { delay: 10 });
63
    const { result, waitFor } = renderHook(() =>
64
      useResourceIndex(endpoint, { initialValue, forceInitialRefresh: true }),
65
    );
66
    expect(result.current.values).toEqual(arrayToIndexedObj(initialValue));
67
    expect(result.current.indexStatus).toEqual("pending");
68
    expect(fetchMock.called()).toBe(true);
69
    await waitFor(() => result.current.indexStatus === "fulfilled");
70
    expect(result.current.values).toEqual(arrayToIndexedObj(updatedValue));
71
    expect(result.current.indexStatus).toEqual("fulfilled");
72
  });
73
  it("Returns new values and status of 'fulfilled' after initial fetch succeeds", async () => {
74
    const initialValue = [
75
      { id: 1, name: "one" },
76
      { id: 2, name: "two" },
77
    ];
78
    fetchMock.mock("*", initialValue, {
79
      delay: 10,
80
    });
81
82
    const { result, waitFor } = renderHook(() => useResourceIndex(endpoint));
83
84
    expect(result.current.values).toEqual({});
85
    expect(result.current.indexStatus).toEqual("pending");
86
    await waitFor(
87
      () => {
88
        return result.current.indexStatus === "fulfilled";
89
      },
90
      { timeout: 100 },
91
    );
92
    expect(result.current.values).toEqual(arrayToIndexedObj(initialValue));
93
  });
94
  it("parseIndexResponse (if set) transforms values returns by index requests", async () => {
95
    const responseValue = [
96
      { id: 1, name: "one" },
97
      { id: 2, name: "two" },
98
    ];
99
    const parseIndexResponse = (arr) =>
100
      arr.map((x) => ({ ...x, name: `${x.name} PARSED` }));
101
    const expectValue = [
102
      { id: 1, name: "one PARSED" },
103
      { id: 2, name: "two PARSED" },
104
    ];
105
    fetchMock.mock("*", responseValue);
106
    const { result, waitFor } = renderHook(() =>
107
      useResourceIndex(endpoint, { parseIndexResponse }),
108
    );
109
    expect(result.current.values).toEqual({});
110
    expect(result.current.indexStatus).toEqual("pending");
111
    await waitFor(() => result.current.indexStatus === "fulfilled");
112
    expect(result.current.values).toEqual(arrayToIndexedObj(expectValue));
113
  });
114
  it("Initial fetch is a GET request to the provided endpoint", async () => {
115
    fetchMock.getOnce(endpoint, {});
116
    const { result, waitFor } = renderHook(() => useResourceIndex(endpoint));
117
    await waitFor(() => result.current.indexStatus === "fulfilled");
118
    expect(fetchMock.called()).toBe(true);
119
  });
120
  it("After initial fetch, all values have entityStatus of 'fulfilled'", async () => {
121
    const responseValue = [
122
      { id: 1, name: "one" },
123
      { id: 2, name: "two" },
124
    ];
125
    fetchMock.getOnce(endpoint, responseValue);
126
    const { result, waitFor } = renderHook(() => useResourceIndex(endpoint));
127
    expect(result.current.entityStatus).toEqual({});
128
    await waitFor(() => result.current.indexStatus === "fulfilled");
129
    expect(result.current.entityStatus).toEqual({
130
      1: "fulfilled",
131
      2: "fulfilled",
132
    });
133
  });
134
  // it("Status changes to 'pending' when refresh() is called", async () => {
135
  //   fetchMock.mock("*", {});
136
  //   const initialValue = { name: "Talent Cloud", age: 3 };
137
  //   const parseResponse = () => ({ name: "Talent Cloud 2", age: 100 });
138
  //   const { result, waitForNextUpdate } = renderHook(() =>
139
  //     useResource(endpoint, initialValue, {
140
  //       parseResponse,
141
  //       skipInitialFetch: true,
142
  //     }),
143
  //   );
144
  //   expect(result.current.status).toEqual("initial");
145
  //   await act(async () => {
146
  //     result.current.refresh();
147
  //     await waitForNextUpdate();
148
  //     expect(result.current.status).toEqual("pending");
149
  //   });
150
  // });
151
  // it("refresh() triggers a GET request to endpoint", async () => {
152
  //   fetchMock.getOnce(endpoint, {});
153
  //   const { result } = renderHook(() =>
154
  //     useResource(endpoint, null, { skipInitialFetch: true }),
155
  //   );
156
  //   await act(async () => {
157
  //     await result.current.refresh();
158
  //   });
159
  //   expect(fetchMock.called()).toBe(true);
160
  // });
161
  // it("refresh() returns fetch result and updates hook value", async () => {
162
  //   const initialValue = { name: "Talent Cloud", age: 3 };
163
  //   const newValue = { name: "Talent Cloud 2", age: 100 };
164
  //   fetchMock.mock(endpoint, newValue);
165
  //   const { result } = renderHook(() =>
166
  //     useResource(endpoint, initialValue, { skipInitialFetch: true }),
167
  //   );
168
  //   expect(result.current.value).toEqual(initialValue);
169
  //   expect(result.current.status).toEqual("initial");
170
  //   await act(async () => {
171
  //     const refreshValue = await result.current.refresh();
172
  //     expect(refreshValue).toEqual(newValue);
173
  //   });
174
  //   expect(result.current.status).toEqual("fulfilled");
175
  //   expect(result.current.value).toEqual(newValue);
176
  // });
177
  // it("Returns an error and 'rejected' status when fetch returns a server error", async () => {
178
  //   const initialValue = { name: "Talent Cloud", age: 3 };
179
  //   fetchMock.once(endpoint, 404);
180
  //   const { result, waitForNextUpdate } = renderHook(() =>
181
  //     useResource(endpoint, initialValue),
182
  //   );
183
  //   expect(result.current.value).toEqual(initialValue);
184
  //   expect(result.current.status).toEqual("pending");
185
  //   await waitForNextUpdate();
186
  //   expect(result.current.value).toEqual(initialValue);
187
  //   expect(result.current.status).toEqual("rejected");
188
  //   expect(result.current.error instanceof FetchError).toBe(true);
189
  // });
190
  // it("refresh() rejects with an error when fetch returns a server error", async () => {
191
  //   const initialValue = { name: "Talent Cloud", age: 3 };
192
  //   fetchMock.once(endpoint, 404);
193
  //   const { result } = renderHook(() =>
194
  //     useResource(endpoint, initialValue, { skipInitialFetch: true }),
195
  //   );
196
  //   expect(result.current.value).toEqual(initialValue);
197
  //   expect(result.current.status).toEqual("initial");
198
  //   await act(async () => {
199
  //     await expect(result.current.refresh()).rejects.toBeInstanceOf(FetchError);
200
  //   });
201
  //   expect(result.current.value).toEqual(initialValue);
202
  //   expect(result.current.status).toEqual("rejected");
203
  //   expect(result.current.error instanceof FetchError).toBe(true);
204
  // });
205
  // it("If refresh() is called twice, and one request returns, status remains pending", async () => {
206
  //   fetchMock.once(
207
  //     endpoint,
208
  //     {},
209
  //     {
210
  //       delay: 10,
211
  //     },
212
  //   );
213
  //   // Second call will take longer.
214
  //   fetchMock.mock(
215
  //     "*",
216
  //     {},
217
  //     {
218
  //       delay: 20,
219
  //     },
220
  //   );
221
  //   const { result } = renderHook(() =>
222
  //     useResource(endpoint, null, { skipInitialFetch: true }),
223
  //   );
224
  //   await act(async () => {
225
  //     const refreshPromise1 = result.current.refresh();
226
  //     const refreshPromise2 = result.current.refresh();
227
  //     await refreshPromise1;
228
  //     expect(result.current.status).toEqual("pending");
229
  //     await refreshPromise2;
230
  //     expect(result.current.status).toEqual("fulfilled");
231
  //   });
232
  // });
233
  // it("update() changes status to pending", async () => {
234
  //   fetchMock.mock("*", {});
235
  //   const { result, waitForNextUpdate } = renderHook(() =>
236
  //     useResource(endpoint, null, {
237
  //       skipInitialFetch: true,
238
  //     }),
239
  //   );
240
  //   expect(result.current.status).toEqual("initial");
241
  //   await act(async () => {
242
  //     result.current.update(null);
243
  //     await waitForNextUpdate();
244
  //     expect(result.current.status).toEqual("pending");
245
  //   });
246
  // });
247
  // it("update() triggers a PUT request to endpoint", async () => {
248
  //   fetchMock.putOnce(endpoint, {});
249
  //   const { result } = renderHook(() =>
250
  //     useResource(endpoint, null, { skipInitialFetch: true }),
251
  //   );
252
  //   await act(async () => {
253
  //     await result.current.update(null);
254
  //   });
255
  //   expect(fetchMock.called()).toBe(true);
256
  // });
257
  // it("update() returns fetch result and updates hook value with value from response when it completes", async () => {
258
  //   const initialValue = { name: "Talent Cloud", age: 3 };
259
  //   const updateValue = { name: "Talent Cloud 2", age: 100 };
260
  //   // The value returned from server is slightly different from what we send it.
261
  //   const responseValue = { name: updateValue.name, age: updateValue.age + 1 };
262
  //   fetchMock.mock(endpoint, responseValue);
263
  //   const { result } = renderHook(() =>
264
  //     useResource(endpoint, initialValue, { skipInitialFetch: true }),
265
  //   );
266
  //   expect(result.current.value).toEqual(initialValue);
267
  //   expect(result.current.status).toEqual("initial");
268
  //   await act(async () => {
269
  //     const updateResponseValue = await result.current.update(updateValue);
270
  //     expect(updateResponseValue).toEqual(responseValue);
271
  //   });
272
  //   // Ensure the new value is the one returned from request, not what we tried to send.
273
  //   expect(result.current.status).toEqual("fulfilled");
274
  //   expect(result.current.value).toEqual(responseValue);
275
  // });
276
  // it("update() rejects with an error when fetch returns a server error", async () => {
277
  //   const initialValue = { name: "Talent Cloud", age: 3 };
278
  //   const updateValue = { name: "Talent Cloud 2", age: 100 };
279
  //   fetchMock.once(endpoint, 404);
280
  //   const { result } = renderHook(() =>
281
  //     useResource(endpoint, initialValue, { skipInitialFetch: true }),
282
  //   );
283
  //   expect(result.current.value).toEqual(initialValue);
284
  //   expect(result.current.status).toEqual("initial");
285
  //   await act(async () => {
286
  //     await expect(result.current.update(updateValue)).rejects.toBeInstanceOf(
287
  //       FetchError,
288
  //     );
289
  //   });
290
  //   expect(result.current.value).toEqual(initialValue);
291
  //   expect(result.current.status).toEqual("rejected");
292
  //   expect(result.current.error instanceof FetchError).toBe(true);
293
  // });
294
  // it("If multiple update requests are started, status remains pending until all are complete", async () => {
295
  //   fetchMock.once(
296
  //     endpoint,
297
  //     {},
298
  //     {
299
  //       delay: 10,
300
  //     },
301
  //   );
302
  //   // Second call will take longer.
303
  //   fetchMock.mock(
304
  //     "*",
305
  //     {},
306
  //     {
307
  //       delay: 20,
308
  //     },
309
  //   );
310
  //   const { result } = renderHook(() =>
311
  //     useResource(endpoint, null, { skipInitialFetch: true }),
312
  //   );
313
  //   await act(async () => {
314
  //     const updatePromise1 = result.current.update(null);
315
  //     const updatePromise2 = result.current.update(null);
316
  //     await updatePromise1;
317
  //     expect(result.current.status).toEqual("pending");
318
  //     await updatePromise2;
319
  //     expect(result.current.status).toEqual("fulfilled");
320
  //   });
321
  // });
322
  // it("handleError is called on any fetch errors, if its passed in", async () => {
323
  //   fetchMock.get(endpoint, 404);
324
  //   fetchMock.putOnce(endpoint, 500);
325
  //   const handleError = jest.fn();
326
  //   const { result, waitFor } = renderHook(() =>
327
  //     useResource(endpoint, null, { handleError }),
328
  //   );
329
  //   await waitFor(() => result.current.status === "rejected");
330
  //   // handleError was called once on initial fetch.
331
  //   expect(handleError.mock.calls.length).toBe(1);
332
  //   // Get error from argument to mocked function.
333
  //   const initialError = handleError.mock.calls[0][0];
334
  //   expect(initialError).toBeInstanceOf(FetchError);
335
  //   expect(initialError.response.status).toBe(404);
336
337
  //   await act(async () => {
338
  //     await result.current.refresh().catch(() => {
339
  //       /* Do nothing */
340
  //     });
341
  //   });
342
  //   // handleError was called again on refresh.
343
  //   expect(handleError.mock.calls.length).toBe(2);
344
  //   const refreshError = handleError.mock.calls[1][0];
345
  //   expect(refreshError).toBeInstanceOf(FetchError);
346
  //   expect(refreshError.response.status).toBe(404);
347
348
  //   await act(async () => {
349
  //     await result.current.update(null).catch(() => {
350
  //       /* Do nothing */
351
  //     });
352
  //   });
353
  //   // handleError was called again on update.
354
  //   expect(handleError.mock.calls.length).toBe(3);
355
  //   const updateError = handleError.mock.calls[2][0];
356
  //   expect(updateError).toBeInstanceOf(FetchError);
357
  //   expect(updateError.response.status).toBe(500);
358
  // });
359
  // it("A successful refresh will overwrite a previous error state", async () => {
360
  //   const responseValue = { name: "Talent Cloud", age: 3 };
361
  //   // First request fails, second succeeds
362
  //   fetchMock.once(endpoint, 404);
363
  //   fetchMock.mock("*", responseValue);
364
  //   const { result, waitFor } = renderHook(() => useResource(endpoint, null));
365
  //   await waitFor(() => result.current.status === "rejected");
366
  //   expect(result.current.value).toBe(null);
367
  //   expect(result.current.error).not.toBeUndefined();
368
369
  //   await act(async () => {
370
  //     await result.current.refresh();
371
  //   });
372
  //   expect(result.current.status).toBe("fulfilled");
373
  //   expect(result.current.value).toEqual(responseValue);
374
  //   expect(result.current.error).toBeUndefined();
375
  // });
376
  // it("A successful update will overwrite a previous error state", async () => {
377
  //   const responseValue = { name: "Talent Cloud", age: 3 };
378
  //   // First request fails, second succeeds
379
  //   fetchMock.once(endpoint, 404);
380
  //   fetchMock.mock("*", responseValue);
381
  //   const { result, waitFor } = renderHook(() => useResource(endpoint, null));
382
  //   await waitFor(() => result.current.status === "rejected");
383
  //   expect(result.current.value).toBe(null);
384
  //   expect(result.current.error).not.toBeUndefined();
385
386
  //   await act(async () => {
387
  //     await result.current.update(null);
388
  //   });
389
  //   expect(result.current.status).toBe("fulfilled");
390
  //   expect(result.current.value).toEqual(responseValue);
391
  //   expect(result.current.error).toBeUndefined();
392
  // });
393
});
394