@khanacademy/wonder-blocks-testing 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,469 @@
1
+ // @flow
2
+ import * as React from "react";
3
+ import {render, screen, waitFor} from "@testing-library/react";
4
+
5
+ import {GqlRouter, useGql} from "@khanacademy/wonder-blocks-data";
6
+ import {RespondWith} from "../make-gql-mock-response.js";
7
+ import {mockGqlFetch} from "../mock-gql-fetch.js";
8
+
9
+ describe("#mockGqlFetch", () => {
10
+ describe("with GqlRouter and useGql", () => {
11
+ it("should reject when there are no mocks", async () => {
12
+ // Arrange
13
+ const mockFetch = mockGqlFetch();
14
+ const RenderError = () => {
15
+ const [result, setResult] = React.useState(null);
16
+ const gqlFetch = useGql();
17
+ React.useEffect(() => {
18
+ gqlFetch({
19
+ type: "query",
20
+ id: "getMyStuff",
21
+ }).catch((e) => {
22
+ setResult(e.message);
23
+ });
24
+ }, [gqlFetch]);
25
+
26
+ return <div data-test-id="result">{result}</div>;
27
+ };
28
+
29
+ // Act
30
+ render(
31
+ <GqlRouter defaultContext={{}} fetch={mockFetch}>
32
+ <RenderError />
33
+ </GqlRouter>,
34
+ );
35
+ const result = screen.getByTestId("result");
36
+
37
+ // Assert
38
+ await waitFor(() =>
39
+ expect(result).toHaveTextContent(
40
+ "No matching GraphQL mock response found for request",
41
+ ),
42
+ );
43
+ });
44
+
45
+ it("should provide data when response gives data", async () => {
46
+ // Arrange
47
+ const mockFetch = mockGqlFetch();
48
+ const query = {
49
+ type: "query",
50
+ id: "getMyStuff",
51
+ };
52
+ const data = {myStuff: "stuff"};
53
+ const RenderError = () => {
54
+ const [result, setResult] = React.useState(null);
55
+ const gqlFetch = useGql();
56
+ React.useEffect(() => {
57
+ // eslint-disable-next-line promise/catch-or-return
58
+ gqlFetch(query).then((r) => {
59
+ setResult(JSON.stringify(r ?? "(null)"));
60
+ return;
61
+ });
62
+ }, [gqlFetch]);
63
+
64
+ return <div data-test-id="result">{result}</div>;
65
+ };
66
+
67
+ // Act
68
+ mockFetch.mockOperation({operation: query}, RespondWith.data(data));
69
+ render(
70
+ <GqlRouter defaultContext={{}} fetch={mockFetch}>
71
+ <RenderError />
72
+ </GqlRouter>,
73
+ );
74
+ const result = screen.getByTestId("result");
75
+
76
+ // Assert
77
+ await waitFor(() =>
78
+ expect(result).toHaveTextContent(JSON.stringify(data)),
79
+ );
80
+ });
81
+
82
+ it("should provide null data if aborted response", async () => {
83
+ // Arrange
84
+ const mockFetch = mockGqlFetch();
85
+ const query = {
86
+ type: "query",
87
+ id: "getMyStuff",
88
+ };
89
+ const RenderError = () => {
90
+ const [result, setResult] = React.useState(null);
91
+ const gqlFetch = useGql();
92
+ React.useEffect(() => {
93
+ // eslint-disable-next-line promise/catch-or-return
94
+ gqlFetch(query).then((r) => {
95
+ setResult(JSON.stringify(r ?? "(null)"));
96
+ return;
97
+ });
98
+ }, [gqlFetch]);
99
+
100
+ return <div data-test-id="result">{result}</div>;
101
+ };
102
+
103
+ // Act
104
+ mockFetch.mockOperation(
105
+ {operation: query},
106
+ RespondWith.abortedRequest(),
107
+ );
108
+ render(
109
+ <GqlRouter defaultContext={{}} fetch={mockFetch}>
110
+ <RenderError />
111
+ </GqlRouter>,
112
+ );
113
+ const result = screen.getByTestId("result");
114
+
115
+ // Assert
116
+ await waitFor(() => expect(result).toHaveTextContent('"(null)"'));
117
+ });
118
+
119
+ it("should reject when request gives failed error code", async () => {
120
+ // Arrange
121
+ const mockFetch = mockGqlFetch();
122
+ const query = {
123
+ type: "query",
124
+ id: "getMyStuff",
125
+ };
126
+ const RenderError = () => {
127
+ const [result, setResult] = React.useState(null);
128
+ const gqlFetch = useGql();
129
+ React.useEffect(() => {
130
+ // eslint-disable-next-line promise/catch-or-return
131
+ gqlFetch(query).catch((e) => {
132
+ setResult(e.message);
133
+ });
134
+ }, [gqlFetch]);
135
+
136
+ return <div data-test-id="result">{result}</div>;
137
+ };
138
+
139
+ // Act
140
+ mockFetch.mockOperation(
141
+ {operation: query},
142
+ RespondWith.errorStatusCode(404),
143
+ );
144
+ render(
145
+ <GqlRouter defaultContext={{}} fetch={mockFetch}>
146
+ <RenderError />
147
+ </GqlRouter>,
148
+ );
149
+ const result = screen.getByTestId("result");
150
+
151
+ // Assert
152
+ await waitFor(() =>
153
+ expect(result).toHaveTextContent("Response unsuccessful"),
154
+ );
155
+ });
156
+ });
157
+
158
+ it("should reject with a useful error when there are no matching mocks", async () => {
159
+ // Arrange
160
+ const mockFetch = mockGqlFetch();
161
+
162
+ // Act
163
+ const result = mockFetch(
164
+ {
165
+ type: "query",
166
+ id: "getMyStuff",
167
+ },
168
+ {a: "variable"},
169
+ {my: "context"},
170
+ );
171
+
172
+ // Assert
173
+ await expect(result).rejects.toThrowErrorMatchingInlineSnapshot(`
174
+ "No matching GraphQL mock response found for request:
175
+ Operation: query getMyStuff
176
+ Variables: {
177
+ \\"a\\": \\"variable\\"
178
+ }
179
+ Context: {
180
+ \\"my\\": \\"context\\"
181
+ }"
182
+ `);
183
+ });
184
+
185
+ describe("mockOperation", () => {
186
+ it("should match a similar operation when no variables or context in mock", async () => {
187
+ // Arrange
188
+ const mockFetch = mockGqlFetch();
189
+ const operation = {
190
+ type: "query",
191
+ id: "getMyStuff",
192
+ };
193
+ const data = {
194
+ myStuff: "stuff",
195
+ };
196
+
197
+ // Act
198
+ mockFetch.mockOperation({operation}, RespondWith.data(data));
199
+ const result = mockFetch(
200
+ operation,
201
+ {a: "variable"},
202
+ {my: "context"},
203
+ );
204
+
205
+ // Assert
206
+ await expect(result).resolves.toBeDefined();
207
+ });
208
+
209
+ it("should not match a different operation when no variables or context in mock", async () => {
210
+ // Arrange
211
+ const mockFetch = mockGqlFetch();
212
+ const operation = {
213
+ type: "query",
214
+ id: "getMyStuff",
215
+ };
216
+ const data = {
217
+ myStuff: "stuff",
218
+ };
219
+
220
+ // Act
221
+ mockFetch.mockOperation({operation}, RespondWith.data(data));
222
+ const result = mockFetch(
223
+ {type: "mutation", id: "putMyStuff"},
224
+ {a: "variable"},
225
+ {my: "context"},
226
+ );
227
+
228
+ // Assert
229
+ await expect(result).rejects.toThrowError();
230
+ });
231
+
232
+ it("should match a similar operation when variables also match and no context in mock", async () => {
233
+ // Arrange
234
+ const mockFetch = mockGqlFetch();
235
+ const operation = {
236
+ type: "query",
237
+ id: "getMyStuff",
238
+ };
239
+ const variables = {
240
+ a: "variable",
241
+ };
242
+ const data = {
243
+ myStuff: "stuff",
244
+ };
245
+
246
+ // Act
247
+ mockFetch.mockOperation(
248
+ {operation, variables},
249
+ RespondWith.data(data),
250
+ );
251
+ const result = mockFetch(operation, variables, {my: "context"});
252
+
253
+ // Assert
254
+ await expect(result).resolves.toBeDefined();
255
+ });
256
+
257
+ it("should not match a similar operation when variables do not match and no context in mock", async () => {
258
+ // Arrange
259
+ const mockFetch = mockGqlFetch();
260
+ const operation = {
261
+ type: "query",
262
+ id: "getMyStuff",
263
+ };
264
+ const variables = {
265
+ a: "variable",
266
+ };
267
+ const data = {
268
+ myStuff: "stuff",
269
+ };
270
+
271
+ // Act
272
+ mockFetch.mockOperation(
273
+ {operation, variables},
274
+ RespondWith.data(data),
275
+ );
276
+ const result = mockFetch(
277
+ operation,
278
+ {b: "different variable"},
279
+ {my: "context"},
280
+ );
281
+
282
+ // Assert
283
+ await expect(result).rejects.toThrowError();
284
+ });
285
+
286
+ it("should match any similar operation when context also matches and no variables in mock", async () => {
287
+ // Arrange
288
+ const mockFetch = mockGqlFetch();
289
+ const operation = {
290
+ type: "query",
291
+ id: "getMyStuff",
292
+ };
293
+ const context = {
294
+ my: "context",
295
+ };
296
+ const data = {
297
+ myStuff: "stuff",
298
+ };
299
+
300
+ // Act
301
+ mockFetch.mockOperation(
302
+ {operation, context},
303
+ RespondWith.data(data),
304
+ );
305
+ const result = mockFetch(operation, {a: "variable"}, context);
306
+
307
+ // Assert
308
+ await expect(result).resolves.toBeDefined();
309
+ });
310
+
311
+ it("should not match any similar operation when context does not match and no variables in mock", async () => {
312
+ // Arrange
313
+ const mockFetch = mockGqlFetch();
314
+ const operation = {
315
+ type: "query",
316
+ id: "getMyStuff",
317
+ };
318
+ const context = {
319
+ my: "context",
320
+ };
321
+ const data = {
322
+ myStuff: "stuff",
323
+ };
324
+
325
+ // Act
326
+ mockFetch.mockOperation(
327
+ {operation, context},
328
+ RespondWith.data(data),
329
+ );
330
+ const result = mockFetch(
331
+ operation,
332
+ {a: "variable"},
333
+ {different: "context"},
334
+ );
335
+
336
+ // Assert
337
+ await expect(result).rejects.toThrowError();
338
+ });
339
+
340
+ it("should match operation when variables and context match the mock", async () => {
341
+ // Arrange
342
+ const mockFetch = mockGqlFetch();
343
+ const operation = {
344
+ type: "query",
345
+ id: "getMyStuff",
346
+ };
347
+ const variables = {
348
+ a: "variable",
349
+ };
350
+ const context = {
351
+ my: "context",
352
+ };
353
+ const data = {
354
+ myStuff: "stuff",
355
+ };
356
+
357
+ // Act
358
+ mockFetch.mockOperation(
359
+ {operation, variables, context},
360
+ RespondWith.data(data),
361
+ );
362
+ const result = mockFetch(operation, variables, context);
363
+
364
+ // Assert
365
+ await expect(result).resolves.toBeDefined();
366
+ });
367
+
368
+ it("should resolve to provide the matching data", async () => {
369
+ // Arrange
370
+ const mockFetch = mockGqlFetch();
371
+ const operation = {
372
+ type: "query",
373
+ id: "getMyStuff",
374
+ };
375
+ const variables = {
376
+ a: "variable",
377
+ };
378
+ const context = {
379
+ my: "context",
380
+ };
381
+ const data = {
382
+ myStuff: "stuff",
383
+ };
384
+
385
+ // Act
386
+ mockFetch.mockOperation(
387
+ {operation, variables, context},
388
+ RespondWith.data(data),
389
+ );
390
+ const response = await mockFetch(operation, variables, context);
391
+ const result = await response.text();
392
+
393
+ // Assert
394
+ expect(result).toBe(JSON.stringify({data}));
395
+ });
396
+
397
+ it("should match all call matches", async () => {
398
+ // Arrange
399
+ const mockFetch = mockGqlFetch();
400
+ const operation = {
401
+ type: "query",
402
+ id: "getMyStuff",
403
+ };
404
+ const data = {
405
+ myStuff: "stuff",
406
+ };
407
+
408
+ // Act
409
+ mockFetch.mockOperation({operation}, RespondWith.data(data));
410
+ const result = Promise.all([
411
+ mockFetch(operation, {a: "variable"}, {my: "context"}),
412
+ mockFetch(operation, {b: "variable"}, {another: "context"}),
413
+ ]);
414
+
415
+ // Assert
416
+ await expect(result).resolves.toStrictEqual([
417
+ expect.any(Object),
418
+ expect.any(Object),
419
+ ]);
420
+ });
421
+ });
422
+
423
+ describe("mockOperationOnce", () => {
424
+ it("should match once", async () => {
425
+ // Arrange
426
+ const mockFetch = mockGqlFetch();
427
+ const operation = {
428
+ type: "query",
429
+ id: "getMyStuff",
430
+ };
431
+ const data = {
432
+ myStuff: "stuff",
433
+ };
434
+
435
+ // Act
436
+ mockFetch.mockOperationOnce({operation}, RespondWith.data(data));
437
+ const result = mockFetch(
438
+ operation,
439
+ {a: "variable"},
440
+ {my: "context"},
441
+ );
442
+
443
+ // Assert
444
+ await expect(result).resolves.toBeDefined();
445
+ });
446
+
447
+ it("should only match once", async () => {
448
+ // Arrange
449
+ const mockFetch = mockGqlFetch();
450
+ const operation = {
451
+ type: "query",
452
+ id: "getMyStuff",
453
+ };
454
+ const data = {
455
+ myStuff: "stuff",
456
+ };
457
+
458
+ // Act
459
+ mockFetch.mockOperationOnce({operation}, RespondWith.data(data));
460
+ const result = Promise.all([
461
+ mockFetch(operation, {a: "variable"}, {my: "context"}),
462
+ mockFetch(operation, {b: "variable"}, {another: "context"}),
463
+ ]);
464
+
465
+ // Assert
466
+ await expect(result).rejects.toThrowError();
467
+ });
468
+ });
469
+ });