@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.
- package/CHANGELOG.md +17 -0
- package/dist/es/index.js +450 -33
- package/dist/harness/adapt.d.ts +17 -0
- package/dist/harness/adapter.d.ts +16 -0
- package/dist/harness/adapters/adapters.d.ts +1 -0
- package/dist/harness/adapters/ssr.d.ts +12 -0
- package/dist/harness/test-harness.d.ts +1 -0
- package/dist/harness/types.d.ts +1 -1
- package/dist/index.js +449 -33
- package/dist/mock-requester.d.ts +2 -2
- package/package.json +2 -2
- package/src/fetch/fetch-request-matches-mock.ts +2 -4
- package/src/fetch/mock-fetch.ts +1 -1
- package/src/fixtures/__tests__/fixtures.test.tsx +9 -14
- package/src/fixtures/fixtures.basic.stories.tsx +9 -3
- package/src/fixtures/fixtures.tsx +1 -2
- package/src/gql/mock-gql-fetch.ts +1 -1
- package/src/harness/__tests__/adapt.test.tsx +200 -0
- package/src/harness/__tests__/adapter.test.tsx +67 -0
- package/src/harness/__tests__/hook-harness.test.ts +4 -2
- package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
- package/src/harness/__tests__/test-harness.test.ts +4 -2
- package/src/harness/__tests__/types.typestest.tsx +6 -13
- package/src/harness/adapt.tsx +55 -0
- package/src/harness/adapter.tsx +27 -0
- package/src/harness/adapters/__tests__/data.test.tsx +12 -4
- package/src/harness/adapters/__tests__/ssr.test.tsx +82 -0
- package/src/harness/adapters/adapters.ts +3 -0
- package/src/harness/adapters/css.tsx +1 -3
- package/src/harness/adapters/router.tsx +5 -17
- package/src/harness/adapters/ssr.tsx +37 -0
- package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
- package/src/harness/make-test-harness.tsx +6 -8
- package/src/harness/types.ts +1 -1
- package/src/mock-requester.ts +4 -11
- package/src/respond-with.ts +2 -1
- package/src/settle-controller.ts +2 -3
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/harness/render-adapters.d.ts +0 -6
- package/src/harness/__tests__/render-adapters.test.tsx +0 -87
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
-
|
|
8
|
-
|
|
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 {
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
package/src/harness/types.ts
CHANGED
package/src/mock-requester.ts
CHANGED
|
@@ -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
|
|
23
|
-
//
|
|
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:
|
package/src/respond-with.ts
CHANGED
|
@@ -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
|
|
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.
|
package/src/settle-controller.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|