@khanacademy/wonder-blocks-data 3.0.0 → 3.0.1

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.
package/CHANGELOG.md CHANGED
@@ -1,7 +1,14 @@
1
1
  # @khanacademy/wonder-blocks-data
2
2
 
3
+ ## 3.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - d281dac8: Ensure server-side request fulfillments can be intercepted
8
+
3
9
  ## 3.0.0
10
+
4
11
  ### Major Changes
5
12
 
6
- - b252d9c8: Remove client-side caching
7
- - b252d9c8: Introduce useData hook
13
+ - b252d9c8: Remove client-side caching
14
+ - b252d9c8: Introduce useData hook
package/dist/es/index.js CHANGED
@@ -582,25 +582,49 @@ const useData = (handler, options) => {
582
582
  // this will have cached data in those cases as it will be present on the
583
583
  // initial render - and subsequent renders on the client it will be null.
584
584
  const cachedResult = ResponseCache.Default.getEntry(handler, options);
585
- const [result, setResult] = useState(cachedResult); // We only track data requests when we are server-side and we don't
585
+ const [result, setResult] = useState(cachedResult); // Lookup to see if there's an interceptor for the handler.
586
+ // If we have one, we need to replace the handler with one that
587
+ // uses the interceptor.
588
+
589
+ const interceptorMap = useContext(InterceptContext);
590
+ const interceptor = interceptorMap[handler.type]; // If we have an interceptor, we need to replace the handler with one that
591
+ // uses the interceptor. This helper function generates a new handler.
592
+ // We need this before we track the request as we want the interceptor
593
+ // to also work for tracked requests to simplify testing the server-side
594
+ // request fulfillment.
595
+
596
+ const getMaybeInterceptedHandler = () => {
597
+ if (interceptor == null) {
598
+ return handler;
599
+ }
600
+
601
+ const fulfillRequestFn = options => {
602
+ var _interceptor$fulfillR;
603
+
604
+ return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
605
+ };
606
+
607
+ return {
608
+ fulfillRequest: fulfillRequestFn,
609
+ getKey: options => handler.getKey(options),
610
+ type: handler.type,
611
+ hydrate: handler.hydrate
612
+ };
613
+ }; // We only track data requests when we are server-side and we don't
586
614
  // already have a result, as given by the cachedData (which is also the
587
615
  // initial value for the result state).
588
616
 
617
+
589
618
  const maybeTrack = useContext(TrackerContext);
590
619
 
591
620
  if (result == null && Server.isServerSide()) {
592
- maybeTrack == null ? void 0 : maybeTrack(handler, options);
593
- } // Lookup to see if there's an interceptor for the handler.
594
- // If we have one, we need to replace the handler with one that
595
- // uses the interceptor.
596
-
597
-
598
- const interceptorMap = useContext(InterceptContext);
599
- const interceptor = interceptorMap[handler.type]; // We need to update our request when the handler changes or the key
621
+ maybeTrack == null ? void 0 : maybeTrack(getMaybeInterceptedHandler(), options);
622
+ } // We need to update our request when the handler changes or the key
600
623
  // to the options change, so we keep track of those.
601
624
  // However, even if we are hydrating from cache, we still need to make the
602
625
  // request at least once, so we do not initialize these references.
603
626
 
627
+
604
628
  const handlerRef = useRef();
605
629
  const keyRef = useRef();
606
630
  const interceptorRef = useRef(); // This effect will ensure that we fulfill the request as desired.
@@ -625,26 +649,7 @@ const useData = (handler, options) => {
625
649
  if (cachedResult == null) {
626
650
  // Mark ourselves as loading.
627
651
  setResult(null);
628
- }
629
-
630
- const getMaybeInterceptedHandler = () => {
631
- if (interceptor == null) {
632
- return handler;
633
- }
634
-
635
- const fulfillRequestFn = options => {
636
- var _interceptor$fulfillR;
637
-
638
- return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
639
- };
640
-
641
- return {
642
- fulfillRequest: fulfillRequestFn,
643
- getKey: options => handler.getKey(options),
644
- type: handler.type,
645
- hydrate: handler.hydrate
646
- };
647
- }; // We aren't server-side, so let's make the request.
652
+ } // We aren't server-side, so let's make the request.
648
653
  // The request handler is in control of whether that request actually
649
654
  // happens or not.
650
655
 
package/dist/index.js CHANGED
@@ -424,25 +424,49 @@ const useData = (handler, options) => {
424
424
  // this will have cached data in those cases as it will be present on the
425
425
  // initial render - and subsequent renders on the client it will be null.
426
426
  const cachedResult = _util_response_cache_js__WEBPACK_IMPORTED_MODULE_6__[/* ResponseCache */ "a"].Default.getEntry(handler, options);
427
- const [result, setResult] = Object(react__WEBPACK_IMPORTED_MODULE_1__["useState"])(cachedResult); // We only track data requests when we are server-side and we don't
427
+ const [result, setResult] = Object(react__WEBPACK_IMPORTED_MODULE_1__["useState"])(cachedResult); // Lookup to see if there's an interceptor for the handler.
428
+ // If we have one, we need to replace the handler with one that
429
+ // uses the interceptor.
430
+
431
+ const interceptorMap = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__[/* default */ "a"]);
432
+ const interceptor = interceptorMap[handler.type]; // If we have an interceptor, we need to replace the handler with one that
433
+ // uses the interceptor. This helper function generates a new handler.
434
+ // We need this before we track the request as we want the interceptor
435
+ // to also work for tracked requests to simplify testing the server-side
436
+ // request fulfillment.
437
+
438
+ const getMaybeInterceptedHandler = () => {
439
+ if (interceptor == null) {
440
+ return handler;
441
+ }
442
+
443
+ const fulfillRequestFn = options => {
444
+ var _interceptor$fulfillR;
445
+
446
+ return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
447
+ };
448
+
449
+ return {
450
+ fulfillRequest: fulfillRequestFn,
451
+ getKey: options => handler.getKey(options),
452
+ type: handler.type,
453
+ hydrate: handler.hydrate
454
+ };
455
+ }; // We only track data requests when we are server-side and we don't
428
456
  // already have a result, as given by the cachedData (which is also the
429
457
  // initial value for the result state).
430
458
 
459
+
431
460
  const maybeTrack = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_util_request_tracking_js__WEBPACK_IMPORTED_MODULE_4__[/* TrackerContext */ "b"]);
432
461
 
433
462
  if (result == null && _khanacademy_wonder_blocks_core__WEBPACK_IMPORTED_MODULE_0__["Server"].isServerSide()) {
434
- maybeTrack == null ? void 0 : maybeTrack(handler, options);
435
- } // Lookup to see if there's an interceptor for the handler.
436
- // If we have one, we need to replace the handler with one that
437
- // uses the interceptor.
438
-
439
-
440
- const interceptorMap = Object(react__WEBPACK_IMPORTED_MODULE_1__["useContext"])(_components_intercept_context_js__WEBPACK_IMPORTED_MODULE_3__[/* default */ "a"]);
441
- const interceptor = interceptorMap[handler.type]; // We need to update our request when the handler changes or the key
463
+ maybeTrack == null ? void 0 : maybeTrack(getMaybeInterceptedHandler(), options);
464
+ } // We need to update our request when the handler changes or the key
442
465
  // to the options change, so we keep track of those.
443
466
  // However, even if we are hydrating from cache, we still need to make the
444
467
  // request at least once, so we do not initialize these references.
445
468
 
469
+
446
470
  const handlerRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
447
471
  const keyRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])();
448
472
  const interceptorRef = Object(react__WEBPACK_IMPORTED_MODULE_1__["useRef"])(); // This effect will ensure that we fulfill the request as desired.
@@ -467,26 +491,7 @@ const useData = (handler, options) => {
467
491
  if (cachedResult == null) {
468
492
  // Mark ourselves as loading.
469
493
  setResult(null);
470
- }
471
-
472
- const getMaybeInterceptedHandler = () => {
473
- if (interceptor == null) {
474
- return handler;
475
- }
476
-
477
- const fulfillRequestFn = options => {
478
- var _interceptor$fulfillR;
479
-
480
- return (_interceptor$fulfillR = interceptor.fulfillRequest(options)) != null ? _interceptor$fulfillR : handler.fulfillRequest(options);
481
- };
482
-
483
- return {
484
- fulfillRequest: fulfillRequestFn,
485
- getKey: options => handler.getKey(options),
486
- type: handler.type,
487
- hydrate: handler.hydrate
488
- };
489
- }; // We aren't server-side, so let's make the request.
494
+ } // We aren't server-side, so let's make the request.
490
495
  // The request handler is in control of whether that request actually
491
496
  // happens or not.
492
497
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-data",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "design": "v1",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -155,6 +155,42 @@ describe("#useData", () => {
155
155
  error: "ERROR",
156
156
  });
157
157
  });
158
+
159
+ it("should track the intercepted request", async () => {
160
+ // Arrange
161
+ const intercepted = Promise.resolve("INTERCEPTED");
162
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
163
+ const fakeHandler: IRequestHandler<string, string> = {
164
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
165
+ getKey: (o) => o,
166
+ type: "MY_HANDLER",
167
+ hydrate: true,
168
+ };
169
+ const trackDataRequestSpy = jest.spyOn(
170
+ RequestTracker.Default,
171
+ "trackDataRequest",
172
+ );
173
+ const wrapper = ({children}) => (
174
+ <TrackData>
175
+ <InterceptData
176
+ fulfillRequest={() => intercepted}
177
+ handler={fakeHandler}
178
+ >
179
+ {children}
180
+ </InterceptData>
181
+ </TrackData>
182
+ );
183
+
184
+ // Act
185
+ serverRenderHook(() => useData(fakeHandler, "options"), {
186
+ wrapper,
187
+ });
188
+ const trackedHandler = trackDataRequestSpy.mock.calls[0][0];
189
+ const result = await trackedHandler.fulfillRequest();
190
+
191
+ // Assert
192
+ expect(result).toBe("INTERCEPTED");
193
+ });
158
194
  });
159
195
 
160
196
  describe("when client-side", () => {
@@ -668,122 +704,122 @@ describe("#useData", () => {
668
704
  data: "DATA",
669
705
  });
670
706
  });
671
- });
672
707
 
673
- describe("with interceptor", () => {
674
- it("should return the result of the interceptor request resolution", async () => {
675
- // Arrange
676
- const intercepted = Promise.resolve("INTERCEPTED");
677
- const notIntercepted = Promise.resolve("NOT INTERCEPTED");
678
- const fakeHandler: IRequestHandler<string, string> = {
679
- fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
680
- getKey: (o) => o,
681
- type: "MY_HANDLER",
682
- hydrate: true,
683
- };
684
- const wrapper = ({children}) => (
685
- <InterceptData
686
- fulfillRequest={() => intercepted}
687
- handler={fakeHandler}
688
- >
689
- {children}
690
- </InterceptData>
691
- );
708
+ describe("with interceptor", () => {
709
+ it("should return the result of the interceptor request resolution", async () => {
710
+ // Arrange
711
+ const intercepted = Promise.resolve("INTERCEPTED");
712
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
713
+ const fakeHandler: IRequestHandler<string, string> = {
714
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
715
+ getKey: (o) => o,
716
+ type: "MY_HANDLER",
717
+ hydrate: true,
718
+ };
719
+ const wrapper = ({children}) => (
720
+ <InterceptData
721
+ fulfillRequest={() => intercepted}
722
+ handler={fakeHandler}
723
+ >
724
+ {children}
725
+ </InterceptData>
726
+ );
692
727
 
693
- // Act
694
- const render = clientRenderHook(
695
- () => useData(fakeHandler, "options"),
696
- {
697
- wrapper,
698
- },
699
- );
700
- await act((): Promise<mixed> =>
701
- Promise.all([notIntercepted, intercepted]),
702
- );
703
- const result = render.result.current;
728
+ // Act
729
+ const render = clientRenderHook(
730
+ () => useData(fakeHandler, "options"),
731
+ {
732
+ wrapper,
733
+ },
734
+ );
735
+ await act((): Promise<mixed> =>
736
+ Promise.all([notIntercepted, intercepted]),
737
+ );
738
+ const result = render.result.current;
704
739
 
705
- // Assert
706
- expect(result).toEqual({
707
- status: "success",
708
- data: "INTERCEPTED",
740
+ // Assert
741
+ expect(result).toEqual({
742
+ status: "success",
743
+ data: "INTERCEPTED",
744
+ });
709
745
  });
710
- });
711
-
712
- it("should return the result of the interceptor request rejection", async () => {
713
- // Arrange
714
- const intercepted = Promise.reject("INTERCEPTED");
715
- const notIntercepted = Promise.resolve("NOT INTERCEPTED");
716
- const fakeHandler: IRequestHandler<string, string> = {
717
- fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
718
- getKey: (o) => o,
719
- type: "MY_HANDLER",
720
- hydrate: true,
721
- };
722
- const wrapper = ({children}) => (
723
- <InterceptData
724
- fulfillRequest={() => intercepted}
725
- handler={fakeHandler}
726
- >
727
- {children}
728
- </InterceptData>
729
- );
730
746
 
731
- // Act
732
- const render = clientRenderHook(
733
- () => useData(fakeHandler, "options"),
734
- {
735
- wrapper,
736
- },
737
- );
738
- await notIntercepted;
739
- await act(async (): Promise<mixed> => {
740
- try {
741
- await intercepted;
742
- } catch (e) {
743
- /* ignore, it's ok */
744
- }
745
- });
746
- const result = render.result.current;
747
+ it("should return the result of the interceptor request rejection", async () => {
748
+ // Arrange
749
+ const intercepted = Promise.reject("INTERCEPTED");
750
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
751
+ const fakeHandler: IRequestHandler<string, string> = {
752
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
753
+ getKey: (o) => o,
754
+ type: "MY_HANDLER",
755
+ hydrate: true,
756
+ };
757
+ const wrapper = ({children}) => (
758
+ <InterceptData
759
+ fulfillRequest={() => intercepted}
760
+ handler={fakeHandler}
761
+ >
762
+ {children}
763
+ </InterceptData>
764
+ );
747
765
 
748
- // Assert
749
- expect(result).toEqual({
750
- status: "error",
751
- error: "INTERCEPTED",
766
+ // Act
767
+ const render = clientRenderHook(
768
+ () => useData(fakeHandler, "options"),
769
+ {
770
+ wrapper,
771
+ },
772
+ );
773
+ await notIntercepted;
774
+ await act(async (): Promise<mixed> => {
775
+ try {
776
+ await intercepted;
777
+ } catch (e) {
778
+ /* ignore, it's ok */
779
+ }
780
+ });
781
+ const result = render.result.current;
782
+
783
+ // Assert
784
+ expect(result).toEqual({
785
+ status: "error",
786
+ error: "INTERCEPTED",
787
+ });
752
788
  });
753
- });
754
789
 
755
- it("should return the result of the handler if the interceptor returns null", async () => {
756
- // Arrange
757
- const notIntercepted = Promise.resolve("NOT INTERCEPTED");
758
- const fakeHandler: IRequestHandler<string, string> = {
759
- fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
760
- getKey: (o) => o,
761
- type: "MY_HANDLER",
762
- hydrate: true,
763
- };
764
- const wrapper = ({children}) => (
765
- <InterceptData
766
- fulfillRequest={() => null}
767
- handler={fakeHandler}
768
- >
769
- {children}
770
- </InterceptData>
771
- );
790
+ it("should return the result of the handler if the interceptor returns null", async () => {
791
+ // Arrange
792
+ const notIntercepted = Promise.resolve("NOT INTERCEPTED");
793
+ const fakeHandler: IRequestHandler<string, string> = {
794
+ fulfillRequest: jest.fn().mockReturnValue(notIntercepted),
795
+ getKey: (o) => o,
796
+ type: "MY_HANDLER",
797
+ hydrate: true,
798
+ };
799
+ const wrapper = ({children}) => (
800
+ <InterceptData
801
+ fulfillRequest={() => null}
802
+ handler={fakeHandler}
803
+ >
804
+ {children}
805
+ </InterceptData>
806
+ );
772
807
 
773
- // Act
774
- const render = clientRenderHook(
775
- () => useData(fakeHandler, "options"),
776
- {
777
- wrapper,
778
- },
779
- );
780
- await act((): Promise<mixed> => notIntercepted);
781
- const result = render.result.current;
808
+ // Act
809
+ const render = clientRenderHook(
810
+ () => useData(fakeHandler, "options"),
811
+ {
812
+ wrapper,
813
+ },
814
+ );
815
+ await act((): Promise<mixed> => notIntercepted);
816
+ const result = render.result.current;
782
817
 
783
- // Assert
784
- expect(result).toEqual({
785
- status: "success",
786
- data: "NOT INTERCEPTED",
818
+ // Assert
819
+ expect(result).toEqual({
820
+ status: "success",
821
+ data: "NOT INTERCEPTED",
822
+ });
787
823
  });
788
824
  });
789
825
  });
@@ -29,20 +29,41 @@ export const useData = <TOptions, TData: ValidData>(
29
29
  );
30
30
  const [result, setResult] = useState<?CacheEntry<TData>>(cachedResult);
31
31
 
32
+ // Lookup to see if there's an interceptor for the handler.
33
+ // If we have one, we need to replace the handler with one that
34
+ // uses the interceptor.
35
+ const interceptorMap = useContext(InterceptContext);
36
+ const interceptor = interceptorMap[handler.type];
37
+
38
+ // If we have an interceptor, we need to replace the handler with one that
39
+ // uses the interceptor. This helper function generates a new handler.
40
+ // We need this before we track the request as we want the interceptor
41
+ // to also work for tracked requests to simplify testing the server-side
42
+ // request fulfillment.
43
+ const getMaybeInterceptedHandler = () => {
44
+ if (interceptor == null) {
45
+ return handler;
46
+ }
47
+
48
+ const fulfillRequestFn = (options) =>
49
+ interceptor.fulfillRequest(options) ??
50
+ handler.fulfillRequest(options);
51
+ return {
52
+ fulfillRequest: fulfillRequestFn,
53
+ getKey: (options) => handler.getKey(options),
54
+ type: handler.type,
55
+ hydrate: handler.hydrate,
56
+ };
57
+ };
58
+
32
59
  // We only track data requests when we are server-side and we don't
33
60
  // already have a result, as given by the cachedData (which is also the
34
61
  // initial value for the result state).
35
62
  const maybeTrack = useContext(TrackerContext);
36
63
  if (result == null && Server.isServerSide()) {
37
- maybeTrack?.(handler, options);
64
+ maybeTrack?.(getMaybeInterceptedHandler(), options);
38
65
  }
39
66
 
40
- // Lookup to see if there's an interceptor for the handler.
41
- // If we have one, we need to replace the handler with one that
42
- // uses the interceptor.
43
- const interceptorMap = useContext(InterceptContext);
44
- const interceptor = interceptorMap[handler.type];
45
-
46
67
  // We need to update our request when the handler changes or the key
47
68
  // to the options change, so we keep track of those.
48
69
  // However, even if we are hydrating from cache, we still need to make the
@@ -74,22 +95,6 @@ export const useData = <TOptions, TData: ValidData>(
74
95
  setResult(null);
75
96
  }
76
97
 
77
- const getMaybeInterceptedHandler = () => {
78
- if (interceptor == null) {
79
- return handler;
80
- }
81
-
82
- const fulfillRequestFn = (options) =>
83
- interceptor.fulfillRequest(options) ??
84
- handler.fulfillRequest(options);
85
- return {
86
- fulfillRequest: fulfillRequestFn,
87
- getKey: (options) => handler.getKey(options),
88
- type: handler.type,
89
- hydrate: handler.hydrate,
90
- };
91
- };
92
-
93
98
  // We aren't server-side, so let's make the request.
94
99
  // The request handler is in control of whether that request actually
95
100
  // happens or not.