@khanacademy/wonder-blocks-data 3.2.0 → 4.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/es/index.js +356 -332
  3. package/dist/index.js +507 -456
  4. package/docs.md +17 -35
  5. package/package.json +1 -1
  6. package/src/__tests__/__snapshots__/generated-snapshot.test.js.snap +7 -46
  7. package/src/__tests__/generated-snapshot.test.js +56 -122
  8. package/src/components/__tests__/data.test.js +372 -297
  9. package/src/components/__tests__/intercept-data.test.js +6 -30
  10. package/src/components/data.js +153 -21
  11. package/src/components/data.md +38 -69
  12. package/src/components/intercept-context.js +6 -2
  13. package/src/components/intercept-data.js +40 -51
  14. package/src/components/intercept-data.md +13 -27
  15. package/src/components/track-data.md +9 -23
  16. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.js.snap +17 -0
  17. package/src/hooks/__tests__/use-server-effect.test.js +217 -0
  18. package/src/hooks/__tests__/use-shared-cache.test.js +307 -0
  19. package/src/hooks/use-server-effect.js +45 -0
  20. package/src/hooks/use-shared-cache.js +106 -0
  21. package/src/index.js +15 -19
  22. package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.js.snap +19 -0
  23. package/src/util/__tests__/request-fulfillment.test.js +42 -85
  24. package/src/util/__tests__/request-tracking.test.js +72 -191
  25. package/src/util/__tests__/{result-from-cache-entry.test.js → result-from-cache-response.test.js} +9 -10
  26. package/src/util/__tests__/scoped-in-memory-cache.test.js +396 -0
  27. package/src/util/__tests__/ssr-cache.test.js +639 -0
  28. package/src/util/request-fulfillment.js +36 -44
  29. package/src/util/request-tracking.js +62 -75
  30. package/src/util/{result-from-cache-entry.js → result-from-cache-response.js} +10 -13
  31. package/src/util/scoped-in-memory-cache.js +149 -0
  32. package/src/util/ssr-cache.js +206 -0
  33. package/src/util/types.js +43 -108
  34. package/src/hooks/__tests__/use-data.test.js +0 -826
  35. package/src/hooks/use-data.js +0 -143
  36. package/src/util/__tests__/memory-cache.test.js +0 -446
  37. package/src/util/__tests__/request-handler.test.js +0 -121
  38. package/src/util/__tests__/response-cache.test.js +0 -879
  39. package/src/util/memory-cache.js +0 -187
  40. package/src/util/request-handler.js +0 -42
  41. package/src/util/request-handler.md +0 -51
  42. package/src/util/response-cache.js +0 -213
@@ -5,9 +5,7 @@ import "jest-enzyme";
5
5
 
6
6
  import {Server} from "@khanacademy/wonder-blocks-core";
7
7
  import {RequestTracker, TrackerContext} from "../request-tracking.js";
8
- import {ResponseCache} from "../response-cache.js";
9
-
10
- import type {IRequestHandler} from "../types.js";
8
+ import {SsrCache} from "../ssr-cache.js";
11
9
 
12
10
  describe("../request-tracking.js", () => {
13
11
  describe("TrackerContext", () => {
@@ -38,7 +36,7 @@ describe("../request-tracking.js", () => {
38
36
  * We pass our own response cache instance so that the test cases
39
37
  * are not sharing the same default instance.
40
38
  */
41
- new RequestTracker(new ResponseCache());
39
+ new RequestTracker(new SsrCache());
42
40
 
43
41
  describe("@Default", () => {
44
42
  it("should return an instance of RequestTracker", () => {
@@ -64,100 +62,31 @@ describe("../request-tracking.js", () => {
64
62
  });
65
63
 
66
64
  describe("#trackDataRequest", () => {
67
- it("should get the key for the request", () => {
68
- // Arrange
69
- const getKeySpy = jest.fn();
70
- const requestTracker = createRequestTracker();
71
- const fakeHandler: IRequestHandler<any, any> = {
72
- fulfillRequest: jest.fn(() => Promise.resolve(null)),
73
- getKey: getKeySpy,
74
- type: "MY_TYPE",
75
- hydrate: true,
76
- };
77
- const options = {these: "are options"};
78
-
79
- // Act
80
- requestTracker.trackDataRequest(fakeHandler, options);
81
-
82
- // Assert
83
- expect(getKeySpy).toHaveBeenCalledWith(options);
84
- });
85
-
86
65
  it("should track a request", async () => {
87
66
  // Arrange
88
67
  const requestTracker = new RequestTracker();
89
- const fulfillRequestSpy = jest.fn();
90
- const fakeHandler: IRequestHandler<any, any> = {
91
- fulfillRequest: fulfillRequestSpy,
92
- getKey: jest.fn().mockReturnValue("MY_KEY"),
93
- type: "MY_TYPE",
94
- hydrate: true,
95
- };
96
- const options = {these: "are options"};
68
+ const fakeHandler = jest.fn();
97
69
 
98
70
  // Act
99
- requestTracker.trackDataRequest(fakeHandler, options);
71
+ requestTracker.trackDataRequest("ID", fakeHandler, false);
100
72
  await requestTracker.fulfillTrackedRequests();
101
73
 
102
74
  // Assert
103
- expect(fulfillRequestSpy).toHaveBeenCalledWith(options);
104
- expect(fulfillRequestSpy).toHaveBeenCalledTimes(1);
75
+ expect(fakeHandler).toHaveBeenCalledTimes(1);
105
76
  });
106
77
 
107
78
  it("should track each matching request once", async () => {
108
79
  // Arrange
109
80
  const requestTracker = createRequestTracker();
110
- const fulfillRequestSpy = jest.fn().mockResolvedValue(null);
111
- const fakeHandler: IRequestHandler<any, any> = {
112
- fulfillRequest: fulfillRequestSpy,
113
- getKey: (options) => JSON.stringify(options),
114
- type: "MY_TYPE",
115
- hydrate: true,
116
- };
117
- const options1 = {these: "are options"};
118
- const options2 = {these: "are options"};
119
-
120
- // Act
121
- requestTracker.trackDataRequest(fakeHandler, options1);
122
- requestTracker.trackDataRequest(fakeHandler, options2);
123
- await requestTracker.fulfillTrackedRequests();
124
-
125
- // Assert
126
- expect(fulfillRequestSpy).toHaveBeenCalledWith(options1);
127
- expect(fulfillRequestSpy).toHaveBeenCalledTimes(1);
128
- });
129
-
130
- it("should reuse the existing handler for the handler type from the cache", async () => {
131
- // Arrange
132
- const requestTracker = createRequestTracker();
133
- const handlerType = "MY_TYPE";
134
- const fulfillRequestSpy1 = jest.fn();
135
- const fakeHandler1: IRequestHandler<any, any> = {
136
- fulfillRequest: fulfillRequestSpy1,
137
- getKey: jest.fn().mockReturnValue("MY_KEY1"),
138
- type: handlerType,
139
- hydrate: true,
140
- };
141
- const fulfillRequestSpy2 = jest.fn();
142
- const fakeHandler2: IRequestHandler<any, any> = {
143
- fulfillRequest: fulfillRequestSpy2,
144
- getKey: jest.fn().mockReturnValue("MY_KEY2"),
145
- type: handlerType,
146
- hydrate: true,
147
- };
148
- const options1 = {these: "are options"};
149
- const options2 = {these: "are also options"};
81
+ const fakeHandler = jest.fn().mockResolvedValue(null);
150
82
 
151
83
  // Act
152
- requestTracker.trackDataRequest(fakeHandler1, options1);
153
- requestTracker.trackDataRequest(fakeHandler2, options2);
84
+ requestTracker.trackDataRequest("ID", fakeHandler, true);
85
+ requestTracker.trackDataRequest("ID", fakeHandler, true);
154
86
  await requestTracker.fulfillTrackedRequests();
155
87
 
156
88
  // Assert
157
- expect(fulfillRequestSpy1).toHaveBeenCalledTimes(2);
158
- expect(fulfillRequestSpy1).toHaveBeenCalledWith(options1);
159
- expect(fulfillRequestSpy1).toHaveBeenCalledWith(options2);
160
- expect(fulfillRequestSpy2).not.toHaveBeenCalled();
89
+ expect(fakeHandler).toHaveBeenCalledTimes(1);
161
90
  });
162
91
  });
163
92
 
@@ -176,14 +105,8 @@ describe("../request-tracking.js", () => {
176
105
  it("should return true if there are requests waiting to be fulfilled", () => {
177
106
  // Arrange
178
107
  const requestTracker = createRequestTracker();
179
- const fakeHandler: IRequestHandler<any, any> = {
180
- fulfillRequest: jest.fn(),
181
- getKey: jest.fn().mockReturnValue("MY_KEY"),
182
- type: "MY_TYPE",
183
- hydrate: true,
184
- };
185
- const options = {these: "are options"};
186
- requestTracker.trackDataRequest(fakeHandler, options);
108
+ const fakeHandler = jest.fn();
109
+ requestTracker.trackDataRequest("ID", fakeHandler, true);
187
110
 
188
111
  // Act
189
112
  const result = requestTracker.hasUnfulfilledRequests;
@@ -195,14 +118,8 @@ describe("../request-tracking.js", () => {
195
118
  it("should return false if all tracked requests have been fulfilled", () => {
196
119
  // Arrange
197
120
  const requestTracker = createRequestTracker();
198
- const fakeHandler: IRequestHandler<any, any> = {
199
- fulfillRequest: jest.fn().mockResolvedValue(5),
200
- getKey: jest.fn().mockReturnValue("MY_KEY"),
201
- type: "MY_TYPE",
202
- hydrate: true,
203
- };
204
- const options = {these: "are options"};
205
- requestTracker.trackDataRequest(fakeHandler, options);
121
+ const fakeHandler = jest.fn().mockResolvedValue(5);
122
+ requestTracker.trackDataRequest("ID", fakeHandler, false);
206
123
  requestTracker.fulfillTrackedRequests();
207
124
 
208
125
  // Act
@@ -228,25 +145,18 @@ describe("../request-tracking.js", () => {
228
145
  it("should cache errors caused directly by handlers", async () => {
229
146
  // Arrange
230
147
  const requestTracker = createRequestTracker();
231
- const fakeBadHandler: IRequestHandler<any, any> = {
232
- fulfillRequest: () => {
233
- throw new Error("OH NO!");
234
- },
235
- getKey: jest.fn().mockReturnValue("MY_KEY"),
236
- type: "MY_TYPE",
237
- hydrate: true,
148
+ const fakeBadHandler = () => {
149
+ throw new Error("OH NO!");
238
150
  };
239
- requestTracker.trackDataRequest(fakeBadHandler, "OPTIONS");
151
+ requestTracker.trackDataRequest("ID", fakeBadHandler, true);
240
152
 
241
153
  // Act
242
154
  const result = await requestTracker.fulfillTrackedRequests();
243
155
 
244
156
  // Assert
245
157
  expect(result).toStrictEqual({
246
- MY_TYPE: {
247
- MY_KEY: {
248
- error: "OH NO!",
249
- },
158
+ ID: {
159
+ error: "OH NO!",
250
160
  },
251
161
  });
252
162
  });
@@ -254,26 +164,20 @@ describe("../request-tracking.js", () => {
254
164
  it("should cache errors occurring in promises", async () => {
255
165
  // Arrange
256
166
  const requestTracker = createRequestTracker();
257
- const fakeBadRequestHandler: IRequestHandler<string, any> = {
258
- fulfillRequest: () =>
259
- new Promise((resolve, reject) => reject("OH NO!")),
260
- getKey: (o) => o,
261
- type: "BAD_REQUEST",
262
- hydrate: true,
263
- };
167
+ const fakeBadRequestHandler = () =>
168
+ new Promise((resolve, reject) => reject("OH NO!"));
264
169
  requestTracker.trackDataRequest(
170
+ "ID",
265
171
  fakeBadRequestHandler,
266
- "OPTIONS1",
172
+ true,
267
173
  );
268
174
  // Act
269
175
  const result = await requestTracker.fulfillTrackedRequests();
270
176
 
271
177
  // Assert
272
178
  expect(result).toStrictEqual({
273
- BAD_REQUEST: {
274
- OPTIONS1: {
275
- error: "OH NO!",
276
- },
179
+ ID: {
180
+ error: "OH NO!",
277
181
  },
278
182
  });
279
183
  });
@@ -287,40 +191,38 @@ describe("../request-tracking.js", () => {
287
191
  * - Handlers that reject the promise
288
192
  * - Handlers that resolve
289
193
  */
290
- const fakeBadRequestHandler: IRequestHandler<string, any> = {
291
- fulfillRequest: () =>
292
- new Promise((resolve, reject) => reject("OH NO!")),
293
- getKey: (o) => o,
294
- type: "BAD_REQUEST",
295
- hydrate: true,
296
- };
297
- const fakeBadHandler: IRequestHandler<string, any> = {
298
- fulfillRequest: () => {
299
- throw new Error("OH NO!");
300
- },
301
- getKey: (o) => o,
302
- type: "BAD_HANDLER",
303
- hydrate: true,
304
- };
305
- const fakeValidHandler: IRequestHandler<string, any> = {
306
- fulfillRequest: (() => {
307
- let counter = 0;
308
- return (o) => {
309
- counter++;
310
- return Promise.resolve(`DATA:${counter}`);
311
- };
312
- })(),
313
- getKey: (o) => o,
314
- type: "VALID",
315
- hydrate: true,
194
+ const fakeBadRequestHandler = () =>
195
+ new Promise((resolve, reject) => reject("OH NO!"));
196
+ const fakeBadHandler = () => {
197
+ throw new Error("OH NO!");
316
198
  };
199
+ const fakeValidHandler = (() => {
200
+ let counter = 0;
201
+ return (o) => {
202
+ counter++;
203
+ return Promise.resolve(`DATA:${counter}`);
204
+ };
205
+ })();
317
206
  requestTracker.trackDataRequest(
207
+ "BAD_REQUEST",
318
208
  fakeBadRequestHandler,
319
- "OPTIONS1",
209
+ true,
210
+ );
211
+ requestTracker.trackDataRequest(
212
+ "BAD_HANDLER",
213
+ fakeBadHandler,
214
+ true,
215
+ );
216
+ requestTracker.trackDataRequest(
217
+ "VALID_HANDLER1",
218
+ fakeValidHandler,
219
+ true,
220
+ );
221
+ requestTracker.trackDataRequest(
222
+ "VALID_HANDLER2",
223
+ fakeValidHandler,
224
+ true,
320
225
  );
321
- requestTracker.trackDataRequest(fakeBadHandler, "OPTIONS2");
322
- requestTracker.trackDataRequest(fakeValidHandler, "OPTIONS3");
323
- requestTracker.trackDataRequest(fakeValidHandler, "OPTIONS4");
324
226
 
325
227
  // Act
326
228
  const result = await requestTracker.fulfillTrackedRequests();
@@ -328,22 +230,16 @@ describe("../request-tracking.js", () => {
328
230
  // Assert
329
231
  expect(result).toStrictEqual({
330
232
  BAD_REQUEST: {
331
- OPTIONS1: {
332
- error: "OH NO!",
333
- },
233
+ error: "OH NO!",
334
234
  },
335
235
  BAD_HANDLER: {
336
- OPTIONS2: {
337
- error: "OH NO!",
338
- },
236
+ error: "OH NO!",
339
237
  },
340
- VALID: {
341
- OPTIONS3: {
342
- data: "DATA:1",
343
- },
344
- OPTIONS4: {
345
- data: "DATA:2",
346
- },
238
+ VALID_HANDLER1: {
239
+ data: "DATA:1",
240
+ },
241
+ VALID_HANDLER2: {
242
+ data: "DATA:2",
347
243
  },
348
244
  });
349
245
  });
@@ -355,13 +251,8 @@ describe("../request-tracking.js", () => {
355
251
  requestTracker._requestFulfillment,
356
252
  "fulfill",
357
253
  ).mockReturnValue(null);
358
- const fakeValidHandler: IRequestHandler<string, any> = {
359
- fulfillRequest: () => Promise.resolve("DATA"),
360
- getKey: (o) => o,
361
- type: "VALID",
362
- hydrate: true,
363
- };
364
- requestTracker.trackDataRequest(fakeValidHandler, "OPTIONS1");
254
+ const fakeValidHandler = () => Promise.resolve("DATA");
255
+ requestTracker.trackDataRequest("ID", fakeValidHandler, false);
365
256
 
366
257
  // Act
367
258
  const result = await requestTracker.fulfillTrackedRequests();
@@ -373,24 +264,20 @@ describe("../request-tracking.js", () => {
373
264
  it("should clear the tracked requests", async () => {
374
265
  // Arrange
375
266
  const requestTracker = createRequestTracker();
376
- const fakeFulfiller = jest.fn(() => Promise.resolve("DATA"));
377
- const fakeStaticHandler: IRequestHandler<string, any> = {
378
- fulfillRequest: fakeFulfiller,
379
- getKey: (o) => o,
380
- type: "STATIC",
381
- hydrate: true,
382
- };
383
- requestTracker.trackDataRequest(fakeStaticHandler, "1");
384
- requestTracker.trackDataRequest(fakeStaticHandler, "2");
385
- requestTracker.trackDataRequest(fakeStaticHandler, "3");
267
+ const fakeStaticHandler = jest.fn(() =>
268
+ Promise.resolve("DATA"),
269
+ );
270
+ requestTracker.trackDataRequest("1", fakeStaticHandler, true);
271
+ requestTracker.trackDataRequest("2", fakeStaticHandler, true);
272
+ requestTracker.trackDataRequest("3", fakeStaticHandler, true);
386
273
 
387
274
  // Act
388
275
  await requestTracker.fulfillTrackedRequests();
389
- fakeFulfiller.mockClear();
276
+ fakeStaticHandler.mockClear();
390
277
  await requestTracker.fulfillTrackedRequests();
391
278
 
392
279
  // Assert
393
- expect(fakeFulfiller).not.toHaveBeenCalled();
280
+ expect(fakeStaticHandler).not.toHaveBeenCalled();
394
281
  });
395
282
  });
396
283
 
@@ -398,21 +285,15 @@ describe("../request-tracking.js", () => {
398
285
  it("should clear the tracked data requests", async () => {
399
286
  // Arrange
400
287
  const requestTracker = createRequestTracker();
401
- const fulfillRequestSpy = jest.fn().mockResolvedValue(null);
402
- const fakeHandler: IRequestHandler<any, any> = {
403
- fulfillRequest: fulfillRequestSpy,
404
- getKey: jest.fn().mockReturnValue("MY_KEY"),
405
- type: "MY_TYPE",
406
- hydrate: true,
407
- };
408
- requestTracker.trackDataRequest(fakeHandler, "OPTIONS");
288
+ const fakeHandler = jest.fn().mockResolvedValue(null);
289
+ requestTracker.trackDataRequest("ID", fakeHandler, true);
409
290
 
410
291
  // Act
411
292
  requestTracker.reset();
412
293
  await requestTracker.fulfillTrackedRequests();
413
294
 
414
295
  // Assert
415
- expect(fulfillRequestSpy).not.toHaveBeenCalled();
296
+ expect(fakeHandler).not.toHaveBeenCalled();
416
297
  });
417
298
  });
418
299
  });
@@ -1,13 +1,13 @@
1
1
  // @flow
2
- import {resultFromCacheEntry} from "../result-from-cache-entry.js";
2
+ import {resultFromCachedResponse} from "../result-from-cache-response.js";
3
3
 
4
- describe("#resultFromCacheEntry", () => {
4
+ describe("#resultFromCachedResponse", () => {
5
5
  it("should return loading status if cache entry is null", () => {
6
6
  // Arrange
7
7
  const cacheEntry = null;
8
8
 
9
9
  // Act
10
- const result = resultFromCacheEntry(cacheEntry);
10
+ const result = resultFromCachedResponse(cacheEntry);
11
11
 
12
12
  // Assert
13
13
  expect(result).toStrictEqual({
@@ -19,11 +19,11 @@ describe("#resultFromCacheEntry", () => {
19
19
  // Arrange
20
20
  const cacheEntry = {
21
21
  data: "DATA",
22
- error: null,
22
+ error: undefined,
23
23
  };
24
24
 
25
25
  // Act
26
- const result = resultFromCacheEntry(cacheEntry);
26
+ const result = resultFromCachedResponse(cacheEntry);
27
27
 
28
28
  // Assert
29
29
  expect(result).toStrictEqual({
@@ -32,7 +32,7 @@ describe("#resultFromCacheEntry", () => {
32
32
  });
33
33
  });
34
34
 
35
- it("should return error status if cache entry has no data and no error", () => {
35
+ it("should return aborted status if cache entry has no data and no error", () => {
36
36
  // Arrange
37
37
  const cacheEntry: any = {
38
38
  data: null,
@@ -40,12 +40,11 @@ describe("#resultFromCacheEntry", () => {
40
40
  };
41
41
 
42
42
  // Act
43
- const result = resultFromCacheEntry(cacheEntry);
43
+ const result = resultFromCachedResponse(cacheEntry);
44
44
 
45
45
  // Assert
46
46
  expect(result).toStrictEqual({
47
- status: "error",
48
- error: "Loaded result has invalid state where data and error are missing",
47
+ status: "aborted",
49
48
  });
50
49
  });
51
50
 
@@ -57,7 +56,7 @@ describe("#resultFromCacheEntry", () => {
57
56
  };
58
57
 
59
58
  // Act
60
- const result = resultFromCacheEntry(cacheEntry);
59
+ const result = resultFromCachedResponse(cacheEntry);
61
60
 
62
61
  // Assert
63
62
  expect(result).toStrictEqual({