@khanacademy/wonder-blocks-testing 8.0.21 → 9.1.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 (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/es/index.js +450 -33
  3. package/dist/harness/adapt.d.ts +17 -0
  4. package/dist/harness/adapter.d.ts +16 -0
  5. package/dist/harness/adapters/adapters.d.ts +1 -0
  6. package/dist/harness/adapters/ssr.d.ts +12 -0
  7. package/dist/harness/test-harness.d.ts +1 -0
  8. package/dist/harness/types.d.ts +1 -1
  9. package/dist/index.js +449 -33
  10. package/dist/mock-requester.d.ts +2 -2
  11. package/package.json +2 -2
  12. package/src/fetch/fetch-request-matches-mock.ts +2 -4
  13. package/src/fetch/mock-fetch.ts +1 -1
  14. package/src/fixtures/__tests__/fixtures.test.tsx +9 -14
  15. package/src/fixtures/fixtures.basic.stories.tsx +9 -3
  16. package/src/fixtures/fixtures.tsx +1 -2
  17. package/src/gql/mock-gql-fetch.ts +1 -1
  18. package/src/harness/__tests__/adapt.test.tsx +200 -0
  19. package/src/harness/__tests__/adapter.test.tsx +67 -0
  20. package/src/harness/__tests__/hook-harness.test.ts +4 -2
  21. package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
  22. package/src/harness/__tests__/test-harness.test.ts +4 -2
  23. package/src/harness/__tests__/types.typestest.tsx +6 -13
  24. package/src/harness/adapt.tsx +55 -0
  25. package/src/harness/adapter.tsx +27 -0
  26. package/src/harness/adapters/__tests__/data.test.tsx +12 -4
  27. package/src/harness/adapters/__tests__/ssr.test.tsx +82 -0
  28. package/src/harness/adapters/adapters.ts +3 -0
  29. package/src/harness/adapters/css.tsx +1 -3
  30. package/src/harness/adapters/router.tsx +5 -17
  31. package/src/harness/adapters/ssr.tsx +37 -0
  32. package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
  33. package/src/harness/make-test-harness.tsx +6 -8
  34. package/src/harness/types.ts +1 -1
  35. package/src/mock-requester.ts +4 -11
  36. package/src/respond-with.ts +2 -1
  37. package/src/settle-controller.ts +2 -3
  38. package/tsconfig-build.tsbuildinfo +1 -1
  39. package/dist/harness/render-adapters.d.ts +0 -6
  40. package/src/harness/__tests__/render-adapters.test.tsx +0 -87
  41. package/src/harness/render-adapters.ts +0 -27
@@ -98,8 +98,7 @@ const maybeWithRoute = (
98
98
  path?: string | null,
99
99
  ): React.ReactElement => {
100
100
  if (path == null) {
101
- // @ts-expect-error [FEI-5019] - TS2322 - Type 'ReactNode' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>>'.
102
- return children;
101
+ return <>{children}</>;
103
102
  }
104
103
 
105
104
  return (
@@ -142,14 +141,12 @@ export const adapter: TestHarnessAdapter<Config> = (
142
141
 
143
142
  // Wrap children with the various contexts and routes, as per the config.
144
143
  const wrappedWithRoute = maybeWithRoute(children, config.path);
145
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'forceStatic' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
146
- if (config.forceStatic) {
144
+ if ("forceStatic" in config && config.forceStatic) {
147
145
  /**
148
146
  * There may be times (SSR testing comes to mind) where we will be
149
147
  * really strict about not permitting client-side navigation events.
150
148
  */
151
149
  return (
152
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'location' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
153
150
  <StaticRouter location={config.location} context={{}}>
154
151
  {wrappedWithRoute}
155
152
  </StaticRouter>
@@ -164,10 +161,8 @@ export const adapter: TestHarnessAdapter<Config> = (
164
161
  *
165
162
  * First, the easy one.
166
163
  */
167
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'location' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
168
- if (typeof config.location !== "undefined") {
164
+ if ("location" in config && config.location !== undefined) {
169
165
  return (
170
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'location' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
171
166
  <MemoryRouter initialEntries={[config.location]}>
172
167
  {wrappedWithRoute}
173
168
  </MemoryRouter>
@@ -178,8 +173,7 @@ export const adapter: TestHarnessAdapter<Config> = (
178
173
  * If it's not the easy one, it should be the complex one.
179
174
  * Let's make sure we have good data (also keeps TypeScript happy).
180
175
  */
181
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'initialEntries' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
182
- if (typeof config.initialEntries === "undefined") {
176
+ if (!("initialEntries" in config) || config.initialEntries === undefined) {
183
177
  throw new Error(
184
178
  "A location or initial history entries must be provided.",
185
179
  );
@@ -191,25 +185,19 @@ export const adapter: TestHarnessAdapter<Config> = (
191
185
  * we want, so let's ensure we always have our default location at least.
192
186
  */
193
187
  const entries =
194
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'initialEntries' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
195
188
  config.initialEntries.length === 0
196
189
  ? [defaultConfig.location]
197
- : // @ts-expect-error [FEI-5019] - TS2339 - Property 'initialEntries' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
198
- config.initialEntries;
190
+ : config.initialEntries;
199
191
 
200
192
  // Memory router doesn't allow us to pass maybe types in its TypeScript types.
201
193
  // So let's build props then spread them.
202
194
  const routerProps: MemoryRouterProps = {
203
195
  initialEntries: entries,
204
196
  };
205
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'initialIndex' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
206
197
  if (config.initialIndex != null) {
207
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'initialIndex' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
208
198
  routerProps.initialIndex = config.initialIndex;
209
199
  }
210
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'getUserConfirmation' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
211
200
  if (config.getUserConfirmation != null) {
212
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'getUserConfirmation' does not exist on type 'Readonly<{ initialEntries: LocationDescriptor<unknown>[] | undefined; initialIndex?: number | undefined; getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined; path?: string | undefined; } | { ...; } | { ...; }>'.
213
201
  routerProps.getUserConfirmation = config.getUserConfirmation;
214
202
  }
215
203
 
@@ -0,0 +1,37 @@
1
+ import * as React from "react";
2
+ import {KindError, Errors} from "@khanacademy/wonder-stuff-core";
3
+ import {RenderStateRoot} from "@khanacademy/wonder-blocks-core";
4
+
5
+ import type {TestHarnessAdapter} from "../types";
6
+
7
+ // We only support one configuration for this adapter; the default
8
+ // behavior - it's either on or off.
9
+ type Config = true | null;
10
+
11
+ // The default configuration is off since this will likely cause state changes
12
+ // and add Testing Library act/waitFor calls in tests using the harness when
13
+ // its enabled.
14
+ export const defaultConfig: Config = null;
15
+
16
+ /**
17
+ * Test harness adapter for supporting portals.
18
+ *
19
+ * Some components rely on rendering with a React Portal. This adapter ensures
20
+ * that the DOM contains a mounting point for the portal with the expected
21
+ * identifier.
22
+ */
23
+ export const adapter: TestHarnessAdapter<Config> = (
24
+ children: React.ReactNode,
25
+ config: Config,
26
+ ): React.ReactElement<any> => {
27
+ if (config !== true) {
28
+ throw new KindError(
29
+ "Unexpected configuration: set config to null to turn this adapter off",
30
+ Errors.InvalidInput,
31
+ {
32
+ metadata: {config},
33
+ },
34
+ );
35
+ }
36
+ return <RenderStateRoot>{children}</RenderStateRoot>;
37
+ };
@@ -4,8 +4,9 @@ import {makeTestHarness} from "./make-test-harness";
4
4
 
5
5
  import type {TestHarnessAdapters, TestHarnessConfigs} from "./types";
6
6
 
7
- // @ts-expect-error [FEI-5019] - TS7031 - Binding element 'children' implicitly has an 'any' type.
8
- const HookHarness = ({children}) => children;
7
+ const HookHarness = ({
8
+ children,
9
+ }: React.PropsWithChildren<unknown>): React.ReactElement => <>{children}</>;
9
10
 
10
11
  /**
11
12
  * Create a test harness method for use with React hooks.
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
 
3
- import {renderAdapters} from "./render-adapters";
3
+ import {Adapt} from "./adapt";
4
4
 
5
5
  import type {TestHarnessAdapters, TestHarnessConfigs} from "./types";
6
6
 
@@ -43,13 +43,11 @@ export const makeTestHarness = <TAdapters extends TestHarnessAdapters>(
43
43
  ...defaultConfigs,
44
44
  ...configs,
45
45
  };
46
- const harnessedComponent = React.forwardRef((props: TProps, ref) =>
47
- renderAdapters<TAdapters>(
48
- adapters,
49
- fullConfig,
50
- <Component {...(props as TProps)} ref={ref} />,
51
- ),
52
- );
46
+ const harnessedComponent = React.forwardRef((props: TProps, ref) => (
47
+ <Adapt adapters={adapters} configs={fullConfig}>
48
+ <Component {...(props as TProps)} ref={ref} />
49
+ </Adapt>
50
+ ));
53
51
 
54
52
  // We add a name for the component here so that we can detect that
55
53
  // later and also see it in traces and what have you.
@@ -6,7 +6,7 @@ import * as React from "react";
6
6
  export type TestHarnessAdapter<TConfig> = (
7
7
  children: React.ReactNode,
8
8
  config: TConfig,
9
- ) => React.ReactElement<any>;
9
+ ) => React.ReactElement;
10
10
 
11
11
  /**
12
12
  * A general map of adapters by their identifiers.
@@ -4,23 +4,17 @@ import type {OperationMock, OperationMatcher, MockFn} from "./types";
4
4
  /**
5
5
  * A generic mock request function for using when mocking fetch or gqlFetch.
6
6
  */
7
- export const mockRequester = <
8
- TOperationType,
9
- TOperationMock extends OperationMock<TOperationType> = OperationMock<TOperationType>,
10
- >(
7
+ export const mockRequester = <TOperationType>(
11
8
  operationMatcher: OperationMatcher<any>,
12
- operationToString: (
13
- operationMock: TOperationMock,
14
- ...args: Array<any>
15
- ) => string,
9
+ operationToString: (...args: Array<any>) => string,
16
10
  ): MockFn<TOperationType> => {
17
11
  // We want this to work in jest and in fixtures to make life easy for folks.
18
12
  // This is the array of mocked operations that we will traverse and
19
13
  // manipulate.
20
14
  const mocks: Array<OperationMock<any>> = [];
21
15
 
22
- // What we return has to be a drop in for the fetch function that is
23
- // provided to `GqlRouter` which is how folks will then use this mock.
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.
24
18
  const mockFn: MockFn<TOperationType> = (
25
19
  ...args: Array<any>
26
20
  ): Promise<Response> => {
@@ -38,7 +32,6 @@ export const mockRequester = <
38
32
 
39
33
  // Default is to reject with some helpful info on what request
40
34
  // we rejected.
41
- // @ts-expect-error [FEI-5019] - TS2556 - A spread argument must either have a tuple type or be passed to a rest parameter.
42
35
  const operation = operationToString(...args);
43
36
  return Promise.reject(
44
37
  new Error(`No matching mock response found for request:
@@ -228,7 +228,8 @@ const makeMockResponse = (
228
228
  if (process.env.NODE_ENV !== "production") {
229
229
  // If we're not in production, give an immediate signal that the
230
230
  // dev forgot to support this new type.
231
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'type' does not exist on type 'never'.
231
+ // @ts-expect-error TS knows we can't get here and so sees
232
+ // `response` as `never`.
232
233
  throw new Error(`Unknown response type: ${response.type}`);
233
234
  }
234
235
  // Production; assume a rejection.
@@ -4,8 +4,7 @@ import {SettleSignal} from "./settle-signal";
4
4
  * A controller for the `RespondWith` API to control response settlement.
5
5
  */
6
6
  export class SettleController {
7
- // @ts-expect-error [FEI-5019] - TS2564 - Property '#settleFn' has no initializer and is not definitely assigned in the constructor.
8
- private _settleFn: () => void;
7
+ private _settleFn: undefined | (() => void);
9
8
  private _signal: SettleSignal;
10
9
 
11
10
  constructor() {
@@ -30,6 +29,6 @@ export class SettleController {
30
29
  * @throws {Error} if the signal has already been settled.
31
30
  */
32
31
  settle(): void {
33
- this._settleFn();
32
+ this._settleFn?.();
34
33
  }
35
34
  }