@khanacademy/wonder-blocks-testing 10.1.1 → 11.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.
Files changed (87) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/dist/es/index.js +15 -390
  3. package/dist/gql/types.d.ts +1 -2
  4. package/dist/harness/adapters/data.d.ts +1 -1
  5. package/dist/harness/adapters/index.d.ts +41 -0
  6. package/dist/harness/adapters/ssr.d.ts +1 -1
  7. package/dist/index.d.ts +8 -13
  8. package/dist/index.js +54 -399
  9. package/package.json +9 -7
  10. package/src/gql/__tests__/mock-gql-fetch.test.tsx +1 -1
  11. package/src/gql/__tests__/types.typestest.ts +1 -1
  12. package/src/gql/__tests__/wb-data-integration.test.tsx +1 -1
  13. package/src/gql/mock-gql-fetch.ts +1 -1
  14. package/src/gql/types.ts +4 -2
  15. package/src/harness/adapters/__tests__/ssr.test.tsx +1 -1
  16. package/src/harness/adapters/data.tsx +1 -1
  17. package/src/harness/adapters/{adapters.ts → index.ts} +10 -11
  18. package/src/harness/adapters/ssr.tsx +1 -1
  19. package/src/index.ts +32 -13
  20. package/tsconfig-build.json +1 -0
  21. package/tsconfig-build.tsbuildinfo +1 -1
  22. package/dist/fetch/fetch-request-matches-mock.d.ts +0 -5
  23. package/dist/fetch/mock-fetch.d.ts +0 -5
  24. package/dist/fetch/types.d.ts +0 -9
  25. package/dist/fixtures/fixtures.basic.stories.d.ts +0 -13
  26. package/dist/fixtures/fixtures.d.ts +0 -13
  27. package/dist/fixtures/fixtures.defaultwrapper.stories.d.ts +0 -9
  28. package/dist/fixtures/types.d.ts +0 -36
  29. package/dist/harness/adapt.d.ts +0 -17
  30. package/dist/harness/adapters/adapters.d.ts +0 -36
  31. package/dist/harness/adapters/css.d.ts +0 -12
  32. package/dist/harness/adapters/portal.d.ts +0 -12
  33. package/dist/harness/adapters/router.d.ts +0 -94
  34. package/dist/harness/get-named-adapter-component.d.ts +0 -16
  35. package/dist/harness/hook-harness.d.ts +0 -13
  36. package/dist/harness/make-hook-harness.d.ts +0 -17
  37. package/dist/harness/make-test-harness.d.ts +0 -15
  38. package/dist/harness/test-harness.d.ts +0 -33
  39. package/dist/harness/types.d.ts +0 -36
  40. package/dist/mock-requester.d.ts +0 -5
  41. package/dist/respond-with.d.ts +0 -75
  42. package/dist/response-impl.d.ts +0 -1
  43. package/dist/settle-controller.d.ts +0 -19
  44. package/dist/settle-signal.d.ts +0 -18
  45. package/dist/types.d.ts +0 -25
  46. package/src/__tests__/mock-requester.test.ts +0 -212
  47. package/src/__tests__/respond-with.test.ts +0 -524
  48. package/src/__tests__/response-impl.test.js +0 -47
  49. package/src/__tests__/settle-controller.test.ts +0 -28
  50. package/src/__tests__/settle-signal.test.ts +0 -104
  51. package/src/fetch/__tests__/__snapshots__/mock-fetch.test.ts.snap +0 -29
  52. package/src/fetch/__tests__/fetch-request-matches-mock.test.ts +0 -98
  53. package/src/fetch/__tests__/mock-fetch.test.ts +0 -83
  54. package/src/fetch/fetch-request-matches-mock.ts +0 -42
  55. package/src/fetch/mock-fetch.ts +0 -20
  56. package/src/fetch/types.ts +0 -14
  57. package/src/fixtures/__tests__/fixtures.test.tsx +0 -147
  58. package/src/fixtures/fixtures.basic.stories.tsx +0 -62
  59. package/src/fixtures/fixtures.defaultwrapper.stories.tsx +0 -49
  60. package/src/fixtures/fixtures.tsx +0 -72
  61. package/src/fixtures/types.ts +0 -42
  62. package/src/harness/__tests__/adapt.test.tsx +0 -248
  63. package/src/harness/__tests__/hook-harness.test.ts +0 -73
  64. package/src/harness/__tests__/make-hook-harness.test.tsx +0 -93
  65. package/src/harness/__tests__/make-test-harness.test.tsx +0 -195
  66. package/src/harness/__tests__/test-harness.test.ts +0 -75
  67. package/src/harness/__tests__/types.typestest.tsx +0 -103
  68. package/src/harness/adapt.tsx +0 -41
  69. package/src/harness/adapters/__tests__/__snapshots__/router.test.tsx.snap +0 -5
  70. package/src/harness/adapters/__tests__/css.test.tsx +0 -95
  71. package/src/harness/adapters/__tests__/portal.test.tsx +0 -30
  72. package/src/harness/adapters/__tests__/router.test.tsx +0 -252
  73. package/src/harness/adapters/css.tsx +0 -66
  74. package/src/harness/adapters/portal.tsx +0 -25
  75. package/src/harness/adapters/router.tsx +0 -205
  76. package/src/harness/get-named-adapter-component.tsx +0 -36
  77. package/src/harness/hook-harness.ts +0 -22
  78. package/src/harness/make-hook-harness.tsx +0 -40
  79. package/src/harness/make-test-harness.tsx +0 -60
  80. package/src/harness/test-harness.ts +0 -13
  81. package/src/harness/types.ts +0 -47
  82. package/src/mock-requester.ts +0 -68
  83. package/src/respond-with.ts +0 -263
  84. package/src/response-impl.ts +0 -8
  85. package/src/settle-controller.ts +0 -34
  86. package/src/settle-signal.ts +0 -42
  87. package/src/types.ts +0 -40
@@ -1,205 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {StaticRouter, MemoryRouter, Route, Switch} from "react-router-dom";
4
-
5
- import type {LocationDescriptor} from "history";
6
- import type {TestHarnessAdapter} from "../types";
7
-
8
- type MemoryRouterProps = JSX.LibraryManagedAttributes<
9
- typeof MemoryRouter,
10
- React.ComponentProps<typeof MemoryRouter>
11
- >;
12
-
13
- /**
14
- * Configuration for the withLocation test harness adapter.
15
- */
16
- type Config = // The initial location to use.
17
-
18
- | Readonly<
19
- | {
20
- /**
21
- * See MemoryRouter prop for initialEntries.
22
- */
23
- initialEntries: MemoryRouterProps["initialEntries"];
24
- /**
25
- * See MemoryRouter prop for initialIndex.
26
- */
27
- initialIndex?: MemoryRouterProps["initialIndex"];
28
- /**
29
- * See MemoryRouter prop for getUserConfirmation.
30
- */
31
- getUserConfirmation?: MemoryRouterProps["getUserConfirmation"];
32
- /**
33
- * A path match to use.
34
- *
35
- * When this is specified, the harnessed component will be
36
- * rendered inside a `Route` handler with this path.
37
- *
38
- * If the path matches the location, then the route will
39
- * render the component.
40
- *
41
- * If the path does not match the location, then the route
42
- * will not render the component.
43
- */
44
- path?: string;
45
- }
46
- | {
47
- /**
48
- * The location to use.
49
- */
50
- location: LocationDescriptor;
51
- /**
52
- * Force the use of a StaticRouter, instead of MemoryRouter.
53
- */
54
- forceStatic: true;
55
- /**
56
- * A path match to use.
57
- *
58
- * When this is specified, the harnessed component will be
59
- * rendered inside a `Route` handler with this path.
60
- *
61
- * If the path matches the location, then the route will
62
- * render the component.
63
- *
64
- * If the path does not match the location, then the route
65
- * will not render the component.
66
- */
67
- path?: string;
68
- }
69
- | {
70
- /**
71
- * The initial location to use.
72
- */
73
- location: LocationDescriptor;
74
- /**
75
- * A path match to use.
76
- *
77
- * When this is specified, the harnessed component will be
78
- * rendered inside a `Route` handler with this path.
79
- *
80
- * If the path matches the location, then the route will
81
- * render the component.
82
- *
83
- * If the path does not match the location, then the route
84
- * will not render the component.
85
- */
86
- path?: string;
87
- }
88
- >
89
- | string;
90
-
91
- /**
92
- * The default configuration for this adapter.
93
- */
94
- export const defaultConfig = {location: "/"} as const;
95
-
96
- const maybeWithRoute = (
97
- children: React.ReactNode,
98
- path?: string | null,
99
- ): React.ReactElement => {
100
- if (path == null) {
101
- return <>{children}</>;
102
- }
103
-
104
- return (
105
- <Switch>
106
- <Route exact={true} path={path}>
107
- {children}
108
- </Route>
109
- <Route
110
- path="*"
111
- render={() => {
112
- throw new Error(
113
- "The configured path must match the configured location or your harnessed component will not render.",
114
- );
115
- }}
116
- />
117
- </Switch>
118
- );
119
- };
120
-
121
- /**
122
- * Adapter that sets up a router and AppShell location-specific contexts.
123
- *
124
- * This allows you to ensure that components are being tested in the
125
- * AppShell world.
126
- *
127
- * NOTE(somewhatabstract): The AppShell component itself already does
128
- * the work of setting up routing and the AppShellContext and so using this
129
- * adapter with the App component will have zero-effect since AppShell will
130
- * override it.
131
- */
132
- export const adapter: TestHarnessAdapter<Config> = (
133
- children: React.ReactNode,
134
- config: Config,
135
- ): React.ReactElement<any> => {
136
- if (typeof config === "string") {
137
- config = {
138
- location: config,
139
- };
140
- }
141
-
142
- // Wrap children with the various contexts and routes, as per the config.
143
- const wrappedWithRoute = maybeWithRoute(children, config.path);
144
- if ("forceStatic" in config && config.forceStatic) {
145
- /**
146
- * There may be times (SSR testing comes to mind) where we will be
147
- * really strict about not permitting client-side navigation events.
148
- */
149
- return (
150
- <StaticRouter location={config.location} context={{}}>
151
- {wrappedWithRoute}
152
- </StaticRouter>
153
- );
154
- }
155
-
156
- /**
157
- * OK, we must be OK with a memory router.
158
- *
159
- * There are two flavors of config for this. The easy one with just a
160
- * location, and the complex one for those gnarlier setups.
161
- *
162
- * First, the easy one.
163
- */
164
- if ("location" in config && config.location !== undefined) {
165
- return (
166
- <MemoryRouter initialEntries={[config.location]}>
167
- {wrappedWithRoute}
168
- </MemoryRouter>
169
- );
170
- }
171
-
172
- /**
173
- * If it's not the easy one, it should be the complex one.
174
- * Let's make sure we have good data (also keeps TypeScript happy).
175
- */
176
- if (!("initialEntries" in config) || config.initialEntries === undefined) {
177
- throw new Error(
178
- "A location or initial history entries must be provided.",
179
- );
180
- }
181
-
182
- /**
183
- * What should happen if no entries were in the array?
184
- * It likely uses the root one anyway, but a consistent API is what
185
- * we want, so let's ensure we always have our default location at least.
186
- */
187
- const entries =
188
- config.initialEntries.length === 0
189
- ? [defaultConfig.location]
190
- : config.initialEntries;
191
-
192
- // Memory router doesn't allow us to pass maybe types in its TypeScript types.
193
- // So let's build props then spread them.
194
- const routerProps: MemoryRouterProps = {
195
- initialEntries: entries,
196
- };
197
- if (config.initialIndex != null) {
198
- routerProps.initialIndex = config.initialIndex;
199
- }
200
- if (config.getUserConfirmation != null) {
201
- routerProps.getUserConfirmation = config.getUserConfirmation;
202
- }
203
-
204
- return <MemoryRouter {...routerProps}>{wrappedWithRoute}</MemoryRouter>;
205
- };
@@ -1,36 +0,0 @@
1
- import * as React from "react";
2
-
3
- import type {TestHarnessAdapter} from "./types";
4
-
5
- type Props<TConfig = any> = {
6
- children: React.ReactNode;
7
- config: TConfig;
8
- adapter: TestHarnessAdapter<TConfig>;
9
- };
10
-
11
- const componentCache = new Map<string, React.FunctionComponent<Props>>();
12
-
13
- /**
14
- * Get a component tagged with the given name for rendering an adapter.
15
- *
16
- * We can share these across invocations because only the name is used.
17
- * The rest is configured at render time. This way we don't recreate new
18
- * components on the fly and cause remounting to occur.
19
- */
20
- export const getNamedAdapterComponent = (name: string) => {
21
- // If we already have this component, just return it.
22
- const existing = componentCache.get(name);
23
- if (existing != null) {
24
- return existing;
25
- }
26
-
27
- // Otherwise, create a new component, name it, cache it, and return it.
28
- const newComponent: React.FunctionComponent<Props> = ({
29
- children,
30
- config,
31
- adapter,
32
- }: any) => adapter(children, config);
33
- newComponent.displayName = `Adapter(${name})`;
34
- componentCache.set(name, newComponent);
35
- return newComponent;
36
- };
@@ -1,22 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {makeHookHarness} from "./make-hook-harness";
4
- import {DefaultAdapters, DefaultConfigs} from "./adapters/adapters";
5
-
6
- import type {TestHarnessConfigs} from "./types";
7
-
8
- /**
9
- * Create test wrapper for hook testing with Wonder Blocks default adapters.
10
- *
11
- * This is primarily useful for tests within Wonder Blocks.
12
- *
13
- * If you want to expand the range of adapters or change the default
14
- * configurations, use `makeHookHarness` to create a new `hookHarness`
15
- * function.
16
- */
17
- export const hookHarness: (
18
- configs?: Partial<TestHarnessConfigs<typeof DefaultAdapters>>,
19
- ) => React.ForwardRefExoticComponent<any> = makeHookHarness(
20
- DefaultAdapters,
21
- DefaultConfigs,
22
- );
@@ -1,40 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {makeTestHarness} from "./make-test-harness";
4
-
5
- import type {TestHarnessAdapters, TestHarnessConfigs} from "./types";
6
-
7
- const HookHarness = ({
8
- children,
9
- }: React.PropsWithChildren<unknown>): React.ReactElement => <>{children}</>;
10
-
11
- /**
12
- * Create a test harness method for use with React hooks.
13
- *
14
- * This returns a test harness method that applies the default configurations
15
- * to the given adapters, wrapping a given component.
16
- *
17
- * @param {TAdapters} adapters All the adapters to be supported by the returned
18
- * test harness.
19
- * @param {TestHarnessConfigs<TAdapters>} defaultConfigs Default configuration values for
20
- * the adapters.
21
- * @returns {(
22
- * configs?: $Shape<TestHarnessConfigs<TAdapters>>,
23
- * ) => React.AbstractComponent<any, any>} A test harness.
24
- */
25
- export const makeHookHarness = <TAdapters extends TestHarnessAdapters>(
26
- adapters: TAdapters,
27
- defaultConfigs: TestHarnessConfigs<TAdapters>,
28
- ): ((
29
- configs?: Partial<TestHarnessConfigs<TAdapters>>,
30
- ) => React.ForwardRefExoticComponent<any>) => {
31
- const testHarness = makeTestHarness<TAdapters>(adapters, defaultConfigs);
32
- /**
33
- * Create a harness to use as a wrapper when rendering hooks.
34
- *
35
- * @param {$Shape<Configs<typeof DefaultAdapters>>} [configs] Any adapter
36
- * configuration that you want to override from the DefaultConfigs values.
37
- */
38
- return (configs?: Partial<TestHarnessConfigs<TAdapters>>) =>
39
- testHarness<any>(HookHarness, configs);
40
- };
@@ -1,60 +0,0 @@
1
- import * as React from "react";
2
-
3
- import {Adapt} from "./adapt";
4
-
5
- import type {TestHarnessAdapters, TestHarnessConfigs} from "./types";
6
-
7
- /**
8
- * Create a test harness method for use with React components.
9
- *
10
- * This returns a test harness method that applies the default configurations
11
- * to the given adapters, wrapping a given component.
12
- *
13
- * @param {TAdapters} adapters All the adapters to be supported by the returned
14
- * test harness.
15
- * @param {Configs<TAdapters>} defaultConfigs Default configuration values for
16
- * the adapters.
17
- * @returns A test harness.
18
- */
19
- export const makeTestHarness = <TAdapters extends TestHarnessAdapters>(
20
- adapters: TAdapters,
21
- defaultConfigs: TestHarnessConfigs<TAdapters>,
22
- ): (<TProps extends object>(
23
- Component: React.ComponentType<TProps>,
24
- configs?: Partial<TestHarnessConfigs<TAdapters>>,
25
- ) => React.ForwardRefExoticComponent<
26
- React.PropsWithoutRef<TProps> & React.RefAttributes<unknown>
27
- >) => {
28
- /**
29
- * Create a harnessed version of the given component.
30
- *
31
- * @param {React.ComponentType<TProps>} component The
32
- * component to be wrapped.
33
- * @param {Partial<TestHarnessConfigs<TAdapters>>} [configs] Any adapter
34
- * configuration that you want to override from the `defaultConfigs` values.
35
- */
36
- return <TProps extends object>(
37
- Component: React.ComponentType<TProps>,
38
- configs?: Partial<TestHarnessConfigs<TAdapters>>,
39
- ): React.ForwardRefExoticComponent<
40
- React.PropsWithoutRef<TProps> & React.RefAttributes<unknown>
41
- > => {
42
- const fullConfig: TestHarnessConfigs<TAdapters> = {
43
- ...defaultConfigs,
44
- ...configs,
45
- };
46
- const harnessedComponent = React.forwardRef((props: TProps, ref) => (
47
- <Adapt adapters={adapters} configs={fullConfig}>
48
- <Component {...(props as TProps)} ref={ref} />
49
- </Adapt>
50
- ));
51
-
52
- // We add a name for the component here so that we can detect that
53
- // later and also see it in traces and what have you.
54
- harnessedComponent.displayName = `testHarness(${
55
- Component.displayName || Component.name || "Component"
56
- })`;
57
-
58
- return harnessedComponent;
59
- };
60
- };
@@ -1,13 +0,0 @@
1
- import {makeTestHarness} from "./make-test-harness";
2
- import {DefaultAdapters, DefaultConfigs} from "./adapters/adapters";
3
-
4
- /**
5
- * Wrap a component with a test harness using Wonder Blocks default adapters.
6
- *
7
- * This is primarily useful for tests within Wonder Blocks.
8
- *
9
- * If you want to expand the range of adapters or change the default
10
- * configurations, use `makeTestHarness` to create a new `testHarness`
11
- * function.
12
- */
13
- export const testHarness = makeTestHarness(DefaultAdapters, DefaultConfigs);
@@ -1,47 +0,0 @@
1
- import * as React from "react";
2
-
3
- /**
4
- * A adapter to be composed with our test harness infrastructure.
5
- */
6
- export type TestHarnessAdapter<TConfig> = (
7
- children: React.ReactNode,
8
- config: TConfig,
9
- ) => React.ReactElement;
10
-
11
- /**
12
- * A general map of adapters by their identifiers.
13
- *
14
- * It's OK that this has `any` for the config type as this is the very base
15
- * version of a adapter set. In reality, a more specific type will be used
16
- * with the harness functions that use more specific definitions of known
17
- * adapters. This is just to support the base reality of not knowing.
18
- *
19
- * Use this on input positions only. Output positions for adapters
20
- * should infer their type in most cases to ensure the strongest typing of
21
- * the adapters.
22
- */
23
- export type TestHarnessAdapters = {
24
- readonly [adapterID: string]: TestHarnessAdapter<any>;
25
- };
26
-
27
- /**
28
- * Type for easily defining an adapter's config type.
29
- */
30
- export type TestHarnessConfig<TAdapter> = TAdapter extends TestHarnessAdapter<
31
- infer TConfig
32
- >
33
- ? TConfig
34
- : never;
35
-
36
- /**
37
- * The `TestHarnessConfigs` type as defined by parsing a given set of adapters.
38
- *
39
- * NOTE: This only works if the properties of the passed `TAdapters` type
40
- * are explicitly typed as `TestHarnessAdapter<TConfig>` so if passing in a
41
- * non-Adapters type (which we should be, to get strong `TConfig` types instead
42
- * of `any`), then that object should make sure that each adapter is strongly
43
- * marked as `TestHarnessAdapter<TConfig>`
44
- */
45
- export type TestHarnessConfigs<TAdapters extends TestHarnessAdapters> = {
46
- [K in keyof TAdapters]: TestHarnessConfig<TAdapters[K]> | null | undefined;
47
- };
@@ -1,68 +0,0 @@
1
- import type {MockResponse} from "./respond-with";
2
- import type {OperationMock, OperationMatcher, MockFn} from "./types";
3
-
4
- /**
5
- * A generic mock request function for using when mocking fetch or gqlFetch.
6
- */
7
- export const mockRequester = <TOperationType>(
8
- operationMatcher: OperationMatcher<any>,
9
- operationToString: (...args: Array<any>) => string,
10
- ): MockFn<TOperationType> => {
11
- // We want this to work in jest and in fixtures to make life easy for folks.
12
- // This is the array of mocked operations that we will traverse and
13
- // manipulate.
14
- const mocks: Array<OperationMock<any>> = [];
15
-
16
- // What we return has to be a drop in replacement for the mocked function
17
- // which is how folks will then use this mock.
18
- const mockFn: MockFn<TOperationType> = (
19
- ...args: Array<any>
20
- ): Promise<Response> => {
21
- // Iterate our mocked operations and find the first one that matches.
22
- for (const mock of mocks) {
23
- if (mock.onceOnly && mock.used) {
24
- // This is a once-only mock and it has been used, so skip it.
25
- continue;
26
- }
27
- if (operationMatcher(mock.operation, ...args)) {
28
- mock.used = true;
29
- return mock.response();
30
- }
31
- }
32
-
33
- // Default is to reject with some helpful info on what request
34
- // we rejected.
35
- const operation = operationToString(...args);
36
- return Promise.reject(
37
- new Error(`No matching mock response found for request:
38
- ${operation}`),
39
- );
40
- };
41
-
42
- const addMockedOperation = <TOperation>(
43
- operation: TOperation,
44
- response: MockResponse<any>,
45
- onceOnly: boolean,
46
- ): MockFn<TOperationType> => {
47
- const mockResponse = () => response.toPromise();
48
- mocks.push({
49
- operation,
50
- response: mockResponse,
51
- onceOnly,
52
- used: false,
53
- });
54
- return mockFn;
55
- };
56
-
57
- mockFn.mockOperation = <TOperation>(
58
- operation: TOperation,
59
- response: MockResponse<any>,
60
- ): MockFn<TOperationType> => addMockedOperation(operation, response, false);
61
-
62
- mockFn.mockOperationOnce = <TOperation>(
63
- operation: TOperation,
64
- response: MockResponse<any>,
65
- ): MockFn<TOperationType> => addMockedOperation(operation, response, true);
66
-
67
- return mockFn;
68
- };