@khanacademy/wonder-blocks-data 13.0.10 → 13.0.12

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 (66) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/package.json +3 -3
  3. package/src/components/__tests__/data.test.tsx +0 -832
  4. package/src/components/__tests__/gql-router.test.tsx +0 -63
  5. package/src/components/__tests__/intercept-requests.test.tsx +0 -57
  6. package/src/components/__tests__/track-data.test.tsx +0 -56
  7. package/src/components/data.ts +0 -73
  8. package/src/components/gql-router.tsx +0 -63
  9. package/src/components/intercept-context.ts +0 -19
  10. package/src/components/intercept-requests.tsx +0 -67
  11. package/src/components/track-data.tsx +0 -28
  12. package/src/hooks/__tests__/__snapshots__/use-shared-cache.test.ts.snap +0 -17
  13. package/src/hooks/__tests__/use-cached-effect.test.tsx +0 -789
  14. package/src/hooks/__tests__/use-gql-router-context.test.tsx +0 -132
  15. package/src/hooks/__tests__/use-gql.test.tsx +0 -204
  16. package/src/hooks/__tests__/use-hydratable-effect.test.ts +0 -708
  17. package/src/hooks/__tests__/use-request-interception.test.tsx +0 -254
  18. package/src/hooks/__tests__/use-server-effect.test.ts +0 -293
  19. package/src/hooks/__tests__/use-shared-cache.test.ts +0 -263
  20. package/src/hooks/use-cached-effect.ts +0 -297
  21. package/src/hooks/use-gql-router-context.ts +0 -49
  22. package/src/hooks/use-gql.ts +0 -58
  23. package/src/hooks/use-hydratable-effect.ts +0 -201
  24. package/src/hooks/use-request-interception.ts +0 -53
  25. package/src/hooks/use-server-effect.ts +0 -75
  26. package/src/hooks/use-shared-cache.ts +0 -107
  27. package/src/index.ts +0 -46
  28. package/src/util/__tests__/__snapshots__/scoped-in-memory-cache.test.ts.snap +0 -19
  29. package/src/util/__tests__/__snapshots__/serializable-in-memory-cache.test.ts.snap +0 -19
  30. package/src/util/__tests__/get-gql-data-from-response.test.ts +0 -186
  31. package/src/util/__tests__/get-gql-request-id.test.ts +0 -132
  32. package/src/util/__tests__/graphql-document-node-parser.test.ts +0 -535
  33. package/src/util/__tests__/hydration-cache-api.test.ts +0 -34
  34. package/src/util/__tests__/merge-gql-context.test.ts +0 -73
  35. package/src/util/__tests__/purge-caches.test.ts +0 -28
  36. package/src/util/__tests__/request-api.test.ts +0 -176
  37. package/src/util/__tests__/request-fulfillment.test.ts +0 -146
  38. package/src/util/__tests__/request-tracking.test.tsx +0 -321
  39. package/src/util/__tests__/result-from-cache-response.test.ts +0 -79
  40. package/src/util/__tests__/scoped-in-memory-cache.test.ts +0 -316
  41. package/src/util/__tests__/serializable-in-memory-cache.test.ts +0 -397
  42. package/src/util/__tests__/ssr-cache.test.ts +0 -636
  43. package/src/util/__tests__/to-gql-operation.test.ts +0 -41
  44. package/src/util/data-error.ts +0 -63
  45. package/src/util/get-gql-data-from-response.ts +0 -65
  46. package/src/util/get-gql-request-id.ts +0 -106
  47. package/src/util/gql-error.ts +0 -43
  48. package/src/util/gql-router-context.ts +0 -9
  49. package/src/util/gql-types.ts +0 -64
  50. package/src/util/graphql-document-node-parser.ts +0 -132
  51. package/src/util/graphql-types.ts +0 -28
  52. package/src/util/hydration-cache-api.ts +0 -30
  53. package/src/util/merge-gql-context.ts +0 -35
  54. package/src/util/purge-caches.ts +0 -14
  55. package/src/util/request-api.ts +0 -65
  56. package/src/util/request-fulfillment.ts +0 -121
  57. package/src/util/request-tracking.ts +0 -211
  58. package/src/util/result-from-cache-response.ts +0 -30
  59. package/src/util/scoped-in-memory-cache.ts +0 -121
  60. package/src/util/serializable-in-memory-cache.ts +0 -44
  61. package/src/util/ssr-cache.ts +0 -193
  62. package/src/util/status.ts +0 -35
  63. package/src/util/to-gql-operation.ts +0 -43
  64. package/src/util/types.ts +0 -145
  65. package/tsconfig-build.json +0 -12
  66. package/tsconfig-build.tsbuildinfo +0 -1
@@ -1,63 +0,0 @@
1
- import * as React from "react";
2
- import {render} from "@testing-library/react";
3
-
4
- import {GqlRouterContext} from "../../util/gql-router-context";
5
- import {GqlRouter} from "../gql-router";
6
-
7
- describe("GqlRouter", () => {
8
- it("should provide the GqlRouterContext as configured", async () => {
9
- // Arrange
10
- const defaultContext = {
11
- foo: "bar",
12
- } as const;
13
- const fetch = jest.fn();
14
- const CaptureContext = ({captureFn}: any) => {
15
- captureFn(React.useContext(GqlRouterContext));
16
- return null;
17
- };
18
-
19
- // Act
20
- const result = await new Promise((resolve: any, reject: any) => {
21
- render(
22
- <GqlRouter defaultContext={defaultContext} fetch={fetch}>
23
- <CaptureContext captureFn={resolve} />
24
- </GqlRouter>,
25
- );
26
- });
27
-
28
- // Assert
29
- expect(result).toStrictEqual({
30
- defaultContext,
31
- fetch,
32
- });
33
- });
34
-
35
- it("should not render React.memo-ized children if props remain the same", () => {
36
- // Arrange
37
- const defaultContext = {
38
- foo: "bar",
39
- } as const;
40
- const fetch = jest.fn();
41
- let renderCount = 0;
42
- const Child = React.memo(() => {
43
- const context = React.useContext(GqlRouterContext);
44
- renderCount++;
45
- return <div>{JSON.stringify(context)}</div>;
46
- });
47
-
48
- // Act
49
- const {rerender} = render(
50
- <GqlRouter defaultContext={defaultContext} fetch={fetch}>
51
- <Child />
52
- </GqlRouter>,
53
- );
54
- rerender(
55
- <GqlRouter defaultContext={defaultContext} fetch={fetch}>
56
- <Child />
57
- </GqlRouter>,
58
- );
59
-
60
- // Assert
61
- expect(renderCount).toBe(1);
62
- });
63
- });
@@ -1,57 +0,0 @@
1
- import * as React from "react";
2
- import {render} from "@testing-library/react";
3
-
4
- import InterceptContext from "../intercept-context";
5
- import InterceptRequests from "../intercept-requests";
6
-
7
- describe("InterceptRequests", () => {
8
- afterEach(() => {
9
- jest.resetAllMocks();
10
- });
11
-
12
- it("should update context with fulfillRequest method", () => {
13
- // Arrange
14
- const fakeHandler = (requestId: any): Promise<string> =>
15
- Promise.resolve("data");
16
- const props = {
17
- interceptor: fakeHandler,
18
- } as const;
19
- const captureContextFn = jest.fn();
20
-
21
- // Act
22
- render(
23
- <InterceptRequests {...props}>
24
- <InterceptContext.Consumer>
25
- {captureContextFn}
26
- </InterceptContext.Consumer>
27
- </InterceptRequests>,
28
- );
29
-
30
- // Assert
31
- expect(captureContextFn).toHaveBeenCalledWith([fakeHandler]);
32
- });
33
-
34
- it("should override parent InterceptRequests", () => {
35
- // Arrange
36
- const fakeHandler1 = jest.fn();
37
- const fakeHandler2 = jest.fn();
38
- const captureContextFn = jest.fn();
39
-
40
- // Act
41
- render(
42
- <InterceptRequests interceptor={fakeHandler1}>
43
- <InterceptRequests interceptor={fakeHandler2}>
44
- <InterceptContext.Consumer>
45
- {captureContextFn}
46
- </InterceptContext.Consumer>
47
- </InterceptRequests>
48
- </InterceptRequests>,
49
- );
50
-
51
- // Assert
52
- expect(captureContextFn).toHaveBeenCalledWith([
53
- fakeHandler1,
54
- fakeHandler2,
55
- ]);
56
- });
57
- });
@@ -1,56 +0,0 @@
1
- import * as React from "react";
2
- import {Server} from "@khanacademy/wonder-blocks-core";
3
- import {render, screen} from "@testing-library/react";
4
-
5
- import TrackData from "../track-data";
6
- import {RequestTracker, TrackerContext} from "../../util/request-tracking";
7
-
8
- describe("TrackData", () => {
9
- afterEach(() => {
10
- jest.resetAllMocks();
11
- });
12
-
13
- it("should throw if used when server-side mode is off", () => {
14
- // Arrange
15
- jest.spyOn(Server, "isServerSide").mockReturnValue(false);
16
-
17
- // Act
18
- const underTest = () => render(<TrackData>SOME CHILDREN</TrackData>);
19
-
20
- // Assert
21
- expect(underTest).toThrowErrorMatchingInlineSnapshot(
22
- `"This component is not for use during client-side rendering"`,
23
- );
24
- });
25
-
26
- it("should render children when server-side mode is on", () => {
27
- // Arrange
28
- jest.spyOn(Server, "isServerSide").mockReturnValue(true);
29
-
30
- // Act
31
- render(<TrackData>SOME CHILDREN</TrackData>);
32
- const result = screen.getByText("SOME CHILDREN");
33
-
34
- // Assert
35
- expect(result).toBeInTheDocument();
36
- });
37
-
38
- it("should provide tracker function for tracking context", async () => {
39
- // Arrange
40
- jest.spyOn(Server, "isServerSide").mockReturnValue(true);
41
-
42
- // Act
43
- const result = await new Promise((resolve: any, reject: any) => {
44
- render(
45
- <TrackData>
46
- <TrackerContext.Consumer>
47
- {(fn: any) => resolve(fn)}
48
- </TrackerContext.Consumer>
49
- </TrackData>,
50
- );
51
- });
52
-
53
- // Assert
54
- expect(result).toBe(RequestTracker.Default.trackDataRequest);
55
- });
56
- });
@@ -1,73 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {
4
- useHydratableEffect,
5
- WhenClientSide,
6
- } from "../hooks/use-hydratable-effect";
7
-
8
- import type {Result, ValidCacheData} from "../util/types";
9
-
10
- type Props<
11
- /**
12
- * The type of data resolved by the handler's fulfillRequest method.
13
- */
14
- TData extends ValidCacheData,
15
- > = {
16
- /**
17
- * A unique identifier for the request.
18
- *
19
- * This should not be shared by other uses of this component.
20
- */
21
- requestId: string;
22
- /**
23
- * This defines how the request is fulfilled.
24
- *
25
- * If this is changed without changing the ID, there are cases where the
26
- * old handler result may be given. This is not a supported mode of
27
- * operation.
28
- */
29
- handler: () => Promise<TData>;
30
- /**
31
- * How the hook should behave when rendering client-side for the first time.
32
- *
33
- * This controls how the hook hydrates and executes when client-side.
34
- *
35
- * Default is `OnClientRender.ExecuteWhenNoSuccessResult`.
36
- */
37
- clientBehavior?: typeof WhenClientSide[keyof typeof WhenClientSide];
38
- /**
39
- * When true, the children will be rendered with the existing result
40
- * until the pending load is completed. Otherwise, the children will be
41
- * given a loading state until the request is fulfilled.
42
- *
43
- * Defaults to false.
44
- */
45
- retainResultOnChange?: boolean;
46
- /**
47
- * A function that will render the content of this component using the
48
- * loading state and data or error that gets retrieved from cache or loaded
49
- * via the request if no cached value is available.
50
- */
51
- children: (result: Result<TData>) => React.ReactElement | null;
52
- };
53
-
54
- /**
55
- * This component is the main component of Wonder Blocks Data. With this, data
56
- * requirements can be placed in a React application in a manner that will
57
- * support server-side rendering and efficient caching.
58
- */
59
- const Data = <TData extends ValidCacheData>({
60
- requestId,
61
- handler,
62
- children,
63
- retainResultOnChange = false,
64
- clientBehavior = WhenClientSide.ExecuteWhenNoSuccessResult,
65
- }: Props<TData>): React.ReactElement | null => {
66
- const result = useHydratableEffect(requestId, handler, {
67
- retainResultOnChange,
68
- clientBehavior,
69
- });
70
- return children(result);
71
- };
72
-
73
- export default Data;
@@ -1,63 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {GqlRouterContext} from "../util/gql-router-context";
4
-
5
- import type {
6
- GqlContext,
7
- GqlFetchFn,
8
- GqlRouterConfiguration,
9
- } from "../util/gql-types";
10
-
11
- type Props<TContext extends GqlContext> = {
12
- /**
13
- * The default context to be used by operations when no context is provided.
14
- */
15
- defaultContext: TContext;
16
- /**
17
- * The function to use when fetching requests.
18
- */
19
- fetch: GqlFetchFn<any, any, TContext>;
20
- /**
21
- * The children to be rendered inside the router.
22
- */
23
- children: React.ReactNode;
24
- };
25
-
26
- /**
27
- * Configure GraphQL routing for GraphQL hooks and components.
28
- *
29
- * These can be nested. Components and hooks relying on the GraphQL routing
30
- * will use the configuration from their closest ancestral GqlRouter.
31
- */
32
- export const GqlRouter = <TContext extends GqlContext>({
33
- defaultContext: thisDefaultContext,
34
- fetch: thisFetch,
35
- children,
36
- }: Props<TContext>): React.ReactElement => {
37
- // We don't care if we're nested. We always force our callers to define
38
- // everything. It makes for a clearer API and requires less error checking
39
- // code (assuming our TypeScript types are correct). We also don't default
40
- // fetch to anything - our callers can tell us what function to use quite
41
- // easily. If code that consumes this wants more nuanced nesting, it can
42
- // implement it within its own GqlRouter than then defers to this one.
43
-
44
- // We want to always use the same object if things haven't changed to avoid
45
- // over-rendering consumers of our context, let's memoize the configuration.
46
- // By doing this, if a component under children that uses this context
47
- // uses React.memo, we won't force it to re-render every time we render
48
- // because we'll only change the context value if something has actually
49
- // changed.
50
- const configuration: GqlRouterConfiguration<TContext> = React.useMemo(
51
- () => ({
52
- fetch: thisFetch,
53
- defaultContext: thisDefaultContext,
54
- }),
55
- [thisDefaultContext, thisFetch],
56
- );
57
-
58
- return (
59
- <GqlRouterContext.Provider value={configuration}>
60
- {children}
61
- </GqlRouterContext.Provider>
62
- );
63
- };
@@ -1,19 +0,0 @@
1
- import * as React from "react";
2
- import type {ValidCacheData} from "../util/types";
3
-
4
- type InterceptContextData = ReadonlyArray<
5
- (
6
- requestId: string,
7
- ) => Promise<ValidCacheData | null | undefined> | null | undefined
8
- >;
9
-
10
- /**
11
- * InterceptContext defines a map from request ID to interception methods.
12
- *
13
- * INTERNAL USE ONLY
14
- */
15
- const InterceptContext: React.Context<InterceptContextData> =
16
- React.createContext<InterceptContextData>([]);
17
- InterceptContext.displayName = "InterceptContext";
18
-
19
- export default InterceptContext;
@@ -1,67 +0,0 @@
1
- import * as React from "react";
2
-
3
- import InterceptContext from "./intercept-context";
4
-
5
- import type {ValidCacheData} from "../util/types";
6
-
7
- type Props<TData extends ValidCacheData> = {
8
- /**
9
- * Called to intercept and possibly handle the request.
10
- * If this returns null, the request will be handled by ancestor
11
- * any ancestor interceptors, and ultimately, the original request
12
- * handler, otherwise, this interceptor is handling the request.
13
- *
14
- * Interceptors are called in ancestor precedence, with the closest
15
- * interceptor ancestor being called first, and the furthest ancestor
16
- * being called last.
17
- *
18
- * Beware: Interceptors do not care about what data they are intercepting,
19
- * so make sure to only intercept requests that you recognize from the
20
- * identifier.
21
- */
22
- interceptor: (requestId: string) => Promise<TData> | null | undefined;
23
- /**
24
- * The children to render within this component. Any requests by `Data`
25
- * components that use same ID as this component will be intercepted.
26
- * If `InterceptRequests` is used within `children`, that interception will
27
- * be given a chance to intercept first.
28
- */
29
- children: React.ReactNode;
30
- };
31
-
32
- /**
33
- * This component provides a mechanism to intercept data requests.
34
- * This is for use in testing.
35
- *
36
- * This component is not recommended for use in production code as it
37
- * can prevent predictable functioning of the Wonder Blocks Data framework.
38
- * One possible side-effect is that inflight requests from the interceptor could
39
- * be picked up by `Data` component requests from outside the children of this
40
- * component.
41
- *
42
- * Interceptions within the same component tree are chained such that the
43
- * interceptor closest to the intercepted request is called first, and the
44
- * furthest interceptor is called last.
45
- */
46
- const InterceptRequests = <TData extends ValidCacheData>({
47
- interceptor,
48
- children,
49
- }: Props<TData>): React.ReactElement => {
50
- const interceptors = React.useContext(InterceptContext);
51
-
52
- const updatedInterceptors = React.useMemo(
53
- // We could build this in reverse order so that our hook that does
54
- // the interception didn't have to use reduceRight, but I think it
55
- // is easier to think about if we do this in component tree order.
56
- () => [...interceptors, interceptor],
57
- [interceptors, interceptor],
58
- );
59
-
60
- return (
61
- <InterceptContext.Provider value={updatedInterceptors}>
62
- {children}
63
- </InterceptContext.Provider>
64
- );
65
- };
66
-
67
- export default InterceptRequests;
@@ -1,28 +0,0 @@
1
- import * as React from "react";
2
- import {Server} from "@khanacademy/wonder-blocks-core";
3
-
4
- import {RequestTracker, TrackerContext} from "../util/request-tracking";
5
-
6
- type TrackDataProps = {
7
- children: React.ReactNode;
8
- };
9
-
10
- /**
11
- * Component to enable data request tracking when server-side rendering.
12
- */
13
- export default class TrackData extends React.Component<TrackDataProps> {
14
- render(): React.ReactNode {
15
- if (!Server.isServerSide()) {
16
- throw new Error(
17
- "This component is not for use during client-side rendering",
18
- );
19
- }
20
- return (
21
- <TrackerContext.Provider
22
- value={RequestTracker.Default.trackDataRequest}
23
- >
24
- {this.props.children}
25
- </TrackerContext.Provider>
26
- );
27
- }
28
- }
@@ -1,17 +0,0 @@
1
- // Jest Snapshot v1, https://goo.gl/fbAQLP
2
-
3
- exports[`#useSharedCache should throw if the id is 1`] = `[InvalidInputDataError: id must be a non-empty string]`;
4
-
5
- exports[`#useSharedCache should throw if the id is [Function anonymous] 1`] = `[InvalidInputDataError: id must be a non-empty string]`;
6
-
7
- exports[`#useSharedCache should throw if the id is 5 1`] = `[InvalidInputDataError: id must be a non-empty string]`;
8
-
9
- exports[`#useSharedCache should throw if the id is null 1`] = `[InvalidInputDataError: id must be a non-empty string]`;
10
-
11
- exports[`#useSharedCache should throw if the scope is 1`] = `[InvalidInputDataError: scope must be a non-empty string]`;
12
-
13
- exports[`#useSharedCache should throw if the scope is [Function anonymous] 1`] = `[InvalidInputDataError: scope must be a non-empty string]`;
14
-
15
- exports[`#useSharedCache should throw if the scope is 5 1`] = `[InvalidInputDataError: scope must be a non-empty string]`;
16
-
17
- exports[`#useSharedCache should throw if the scope is null 1`] = `[InvalidInputDataError: scope must be a non-empty string]`;