@khanacademy/wonder-blocks-testing 12.0.0 → 13.0.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 +25 -0
- package/dist/es/index.js +6 -10
- package/dist/gql/types.d.ts +64 -4
- package/dist/index.js +6 -10
- package/package.json +35 -35
- package/src/gql/__tests__/gql-request-matches-mock.test.ts +0 -234
- package/src/gql/__tests__/mock-gql-fetch.test.tsx +0 -479
- package/src/gql/__tests__/types.typestest.ts +0 -97
- package/src/gql/__tests__/wb-data-integration.test.tsx +0 -269
- package/src/gql/gql-request-matches-mock.ts +0 -71
- package/src/gql/mock-gql-fetch.ts +0 -20
- package/src/gql/types.ts +0 -35
- package/src/harness/adapters/__tests__/__snapshots__/render-state.test.tsx.snap +0 -7
- package/src/harness/adapters/__tests__/data.test.tsx +0 -75
- package/src/harness/adapters/__tests__/render-state.test.tsx +0 -86
- package/src/harness/adapters/data.tsx +0 -48
- package/src/harness/adapters/index.ts +0 -34
- package/src/harness/adapters/render-state.tsx +0 -41
- package/src/index.ts +0 -39
- package/tsconfig-build.json +0 -13
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,269 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen, waitFor} from "@testing-library/react";
|
|
3
|
-
|
|
4
|
-
import {GqlRouter, useGql} from "@khanacademy/wonder-blocks-data";
|
|
5
|
-
import {RespondWith} from "@khanacademy/wonder-blocks-testing-core";
|
|
6
|
-
import {mockGqlFetch} from "../mock-gql-fetch";
|
|
7
|
-
|
|
8
|
-
describe("integrating mockGqlFetch, RespondWith, GqlRouter and useGql", () => {
|
|
9
|
-
it("should reject with error indicating there are no mocks", async () => {
|
|
10
|
-
// Arrange
|
|
11
|
-
const mockFetch = mockGqlFetch();
|
|
12
|
-
const RenderError = () => {
|
|
13
|
-
const [result, setResult] = React.useState<any>(null);
|
|
14
|
-
const gqlFetch = useGql();
|
|
15
|
-
React.useEffect(() => {
|
|
16
|
-
gqlFetch({
|
|
17
|
-
type: "query",
|
|
18
|
-
id: "getMyStuff",
|
|
19
|
-
}).catch((e: any) => {
|
|
20
|
-
setResult(e.message);
|
|
21
|
-
});
|
|
22
|
-
}, [gqlFetch]);
|
|
23
|
-
|
|
24
|
-
return <div data-testid="result">{result}</div>;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
// Act
|
|
28
|
-
render(
|
|
29
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
30
|
-
<RenderError />
|
|
31
|
-
</GqlRouter>,
|
|
32
|
-
);
|
|
33
|
-
const result = screen.getByTestId("result");
|
|
34
|
-
|
|
35
|
-
// Assert
|
|
36
|
-
await waitFor(() =>
|
|
37
|
-
expect(result).toHaveTextContent(
|
|
38
|
-
"No matching mock response found for request",
|
|
39
|
-
),
|
|
40
|
-
);
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should resolve with data for RespondWith.graphQLData", async () => {
|
|
44
|
-
// Arrange
|
|
45
|
-
const mockFetch = mockGqlFetch();
|
|
46
|
-
const query = {
|
|
47
|
-
type: "query",
|
|
48
|
-
id: "getMyStuff",
|
|
49
|
-
} as const;
|
|
50
|
-
const data = {myStuff: "stuff"} as const;
|
|
51
|
-
const RenderData = () => {
|
|
52
|
-
const [result, setResult] = React.useState<any>(null);
|
|
53
|
-
const gqlFetch = useGql();
|
|
54
|
-
React.useEffect(() => {
|
|
55
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
56
|
-
gqlFetch(query).then((r: any) => {
|
|
57
|
-
setResult(JSON.stringify(r ?? "(null)"));
|
|
58
|
-
return;
|
|
59
|
-
});
|
|
60
|
-
}, [gqlFetch]);
|
|
61
|
-
|
|
62
|
-
return <div data-testid="result">{result}</div>;
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
// Act
|
|
66
|
-
mockFetch.mockOperation(
|
|
67
|
-
{operation: query},
|
|
68
|
-
RespondWith.graphQLData(data),
|
|
69
|
-
);
|
|
70
|
-
render(
|
|
71
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
72
|
-
<RenderData />
|
|
73
|
-
</GqlRouter>,
|
|
74
|
-
);
|
|
75
|
-
const result = screen.getByTestId("result");
|
|
76
|
-
|
|
77
|
-
// Assert
|
|
78
|
-
await waitFor(() =>
|
|
79
|
-
expect(result).toHaveTextContent(JSON.stringify(data)),
|
|
80
|
-
);
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it("should reject with AbortError for RespondWith.abortedRequest", async () => {
|
|
84
|
-
// Arrange
|
|
85
|
-
const mockFetch = mockGqlFetch();
|
|
86
|
-
const query = {
|
|
87
|
-
type: "query",
|
|
88
|
-
id: "getMyStuff",
|
|
89
|
-
} as const;
|
|
90
|
-
const RenderError = () => {
|
|
91
|
-
const [result, setResult] = React.useState<any>(null);
|
|
92
|
-
const gqlFetch = useGql();
|
|
93
|
-
React.useEffect(() => {
|
|
94
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
95
|
-
gqlFetch(query).catch((e: any) => {
|
|
96
|
-
setResult(e.message);
|
|
97
|
-
return;
|
|
98
|
-
});
|
|
99
|
-
}, [gqlFetch]);
|
|
100
|
-
|
|
101
|
-
return <div data-testid="result">{result}</div>;
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
// Act
|
|
105
|
-
mockFetch.mockOperation(
|
|
106
|
-
{operation: query},
|
|
107
|
-
RespondWith.abortedRequest(),
|
|
108
|
-
);
|
|
109
|
-
render(
|
|
110
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
111
|
-
<RenderError />
|
|
112
|
-
</GqlRouter>,
|
|
113
|
-
);
|
|
114
|
-
const result = screen.getByTestId("result");
|
|
115
|
-
|
|
116
|
-
// Assert
|
|
117
|
-
await waitFor(() => expect(result).toHaveTextContent("aborted"));
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("should reject with unsuccessful response error for RespondWith.errorStatusCode", async () => {
|
|
121
|
-
// Arrange
|
|
122
|
-
const mockFetch = mockGqlFetch();
|
|
123
|
-
const query = {
|
|
124
|
-
type: "query",
|
|
125
|
-
id: "getMyStuff",
|
|
126
|
-
} as const;
|
|
127
|
-
const RenderError = () => {
|
|
128
|
-
const [result, setResult] = React.useState<any>(null);
|
|
129
|
-
const gqlFetch = useGql();
|
|
130
|
-
React.useEffect(() => {
|
|
131
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
132
|
-
gqlFetch(query).catch((e: any) => {
|
|
133
|
-
setResult(e.message);
|
|
134
|
-
});
|
|
135
|
-
}, [gqlFetch]);
|
|
136
|
-
|
|
137
|
-
return <div data-testid="result">{result}</div>;
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
// Act
|
|
141
|
-
mockFetch.mockOperation(
|
|
142
|
-
{operation: query},
|
|
143
|
-
RespondWith.errorStatusCode(404),
|
|
144
|
-
);
|
|
145
|
-
render(
|
|
146
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
147
|
-
<RenderError />
|
|
148
|
-
</GqlRouter>,
|
|
149
|
-
);
|
|
150
|
-
const result = screen.getByTestId("result");
|
|
151
|
-
|
|
152
|
-
// Assert
|
|
153
|
-
await waitFor(() =>
|
|
154
|
-
expect(result).toHaveTextContent("Response unsuccessful"),
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("should reject with parse error for RespondWith.unparseableBody", async () => {
|
|
159
|
-
// Arrange
|
|
160
|
-
const mockFetch = mockGqlFetch();
|
|
161
|
-
const query = {
|
|
162
|
-
type: "query",
|
|
163
|
-
id: "getMyStuff",
|
|
164
|
-
} as const;
|
|
165
|
-
const RenderError = () => {
|
|
166
|
-
const [result, setResult] = React.useState<any>(null);
|
|
167
|
-
const gqlFetch = useGql();
|
|
168
|
-
React.useEffect(() => {
|
|
169
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
170
|
-
gqlFetch(query).catch((e: any) => {
|
|
171
|
-
setResult(e.message);
|
|
172
|
-
});
|
|
173
|
-
}, [gqlFetch]);
|
|
174
|
-
|
|
175
|
-
return <div data-testid="result">{result}</div>;
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
// Act
|
|
179
|
-
mockFetch.mockOperation(
|
|
180
|
-
{operation: query},
|
|
181
|
-
RespondWith.unparseableBody(),
|
|
182
|
-
);
|
|
183
|
-
render(
|
|
184
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
185
|
-
<RenderError />
|
|
186
|
-
</GqlRouter>,
|
|
187
|
-
);
|
|
188
|
-
const result = screen.getByTestId("result");
|
|
189
|
-
|
|
190
|
-
// Assert
|
|
191
|
-
await waitFor(() =>
|
|
192
|
-
expect(result).toHaveTextContent("Failed to parse response"),
|
|
193
|
-
);
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
it("should reject with missing response error for RespondWith.nonGraphQLBody", async () => {
|
|
197
|
-
// Arrange
|
|
198
|
-
const mockFetch = mockGqlFetch();
|
|
199
|
-
const query = {
|
|
200
|
-
type: "query",
|
|
201
|
-
id: "getMyStuff",
|
|
202
|
-
} as const;
|
|
203
|
-
const RenderError = () => {
|
|
204
|
-
const [result, setResult] = React.useState<any>(null);
|
|
205
|
-
const gqlFetch = useGql();
|
|
206
|
-
React.useEffect(() => {
|
|
207
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
208
|
-
gqlFetch(query).catch((e: any) => {
|
|
209
|
-
setResult(e.message);
|
|
210
|
-
});
|
|
211
|
-
}, [gqlFetch]);
|
|
212
|
-
|
|
213
|
-
return <div data-testid="result">{result}</div>;
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Act
|
|
217
|
-
mockFetch.mockOperation(
|
|
218
|
-
{operation: query},
|
|
219
|
-
RespondWith.nonGraphQLBody(),
|
|
220
|
-
);
|
|
221
|
-
render(
|
|
222
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
223
|
-
<RenderError />
|
|
224
|
-
</GqlRouter>,
|
|
225
|
-
);
|
|
226
|
-
const result = screen.getByTestId("result");
|
|
227
|
-
|
|
228
|
-
// Assert
|
|
229
|
-
await waitFor(() =>
|
|
230
|
-
expect(result).toHaveTextContent("Server response missing"),
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("should reject with GraphQL error for RespondWith.graphQLErrors", async () => {
|
|
235
|
-
// Arrange
|
|
236
|
-
const mockFetch = mockGqlFetch();
|
|
237
|
-
const query = {
|
|
238
|
-
type: "query",
|
|
239
|
-
id: "getMyStuff",
|
|
240
|
-
} as const;
|
|
241
|
-
const RenderError = () => {
|
|
242
|
-
const [result, setResult] = React.useState<any>(null);
|
|
243
|
-
const gqlFetch = useGql();
|
|
244
|
-
React.useEffect(() => {
|
|
245
|
-
// eslint-disable-next-line promise/catch-or-return
|
|
246
|
-
gqlFetch(query).catch((e: any) => {
|
|
247
|
-
setResult(e.message);
|
|
248
|
-
});
|
|
249
|
-
}, [gqlFetch]);
|
|
250
|
-
|
|
251
|
-
return <div data-testid="result">{result}</div>;
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
// Act
|
|
255
|
-
mockFetch.mockOperation(
|
|
256
|
-
{operation: query},
|
|
257
|
-
RespondWith.graphQLErrors(["error 1", "error 2"]),
|
|
258
|
-
);
|
|
259
|
-
render(
|
|
260
|
-
<GqlRouter defaultContext={{}} fetch={mockFetch}>
|
|
261
|
-
<RenderError />
|
|
262
|
-
</GqlRouter>,
|
|
263
|
-
);
|
|
264
|
-
const result = screen.getByTestId("result");
|
|
265
|
-
|
|
266
|
-
// Assert
|
|
267
|
-
await waitFor(() => expect(result).toHaveTextContent("GraphQL errors"));
|
|
268
|
-
});
|
|
269
|
-
});
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type {GqlOperation, GqlContext} from "@khanacademy/wonder-blocks-data";
|
|
2
|
-
import type {GqlMockOperation} from "./types";
|
|
3
|
-
|
|
4
|
-
const safeHasOwnProperty = (obj: any, prop: string): boolean =>
|
|
5
|
-
Object.prototype.hasOwnProperty.call(obj, prop);
|
|
6
|
-
|
|
7
|
-
// TODO(somewhatabstract, FEI-4268): use a third-party library to do this and
|
|
8
|
-
// possibly make it also support the jest `jest.objectContaining` type matching
|
|
9
|
-
// to simplify mock declaration (note that it would need to work in regular
|
|
10
|
-
// tests and stories/fixtures).
|
|
11
|
-
const areObjectsEqual = (a: any, b: any): boolean => {
|
|
12
|
-
if (a === b) {
|
|
13
|
-
return true;
|
|
14
|
-
}
|
|
15
|
-
if (a == null || b == null) {
|
|
16
|
-
return false;
|
|
17
|
-
}
|
|
18
|
-
if (typeof a !== "object" || typeof b !== "object") {
|
|
19
|
-
return false;
|
|
20
|
-
}
|
|
21
|
-
const aKeys = Object.keys(a);
|
|
22
|
-
const bKeys = Object.keys(b);
|
|
23
|
-
if (aKeys.length !== bKeys.length) {
|
|
24
|
-
return false;
|
|
25
|
-
}
|
|
26
|
-
for (let i = 0; i < aKeys.length; i++) {
|
|
27
|
-
const key = aKeys[i];
|
|
28
|
-
if (!safeHasOwnProperty(b, key) || !areObjectsEqual(a[key], b[key])) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
export const gqlRequestMatchesMock = (
|
|
36
|
-
mock: GqlMockOperation<any, any, any>,
|
|
37
|
-
operation: GqlOperation<any, any>,
|
|
38
|
-
variables: Record<any, any> | null | undefined,
|
|
39
|
-
context: GqlContext,
|
|
40
|
-
): boolean => {
|
|
41
|
-
// If they don't represent the same operation, then they can't match.
|
|
42
|
-
// NOTE: Operations can include more fields than id and type, but we only
|
|
43
|
-
// care about id and type. The rest is ignored.
|
|
44
|
-
if (
|
|
45
|
-
mock.operation.id !== operation.id ||
|
|
46
|
-
mock.operation.type !== operation.type
|
|
47
|
-
) {
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// We do a loose match, so if the lhs doesn't define variables,
|
|
52
|
-
// we just assume it matches everything.
|
|
53
|
-
if (mock.variables != null) {
|
|
54
|
-
// Variables have to match.
|
|
55
|
-
if (!areObjectsEqual(mock.variables, variables)) {
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// We do a loose match, so if the lhs doesn't define context,
|
|
61
|
-
// we just assume it matches everything.
|
|
62
|
-
if (mock.context != null) {
|
|
63
|
-
// Context has to match.
|
|
64
|
-
if (!areObjectsEqual(mock.context, context)) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// If we get here, we have a match.
|
|
70
|
-
return true;
|
|
71
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {mockRequester} from "@khanacademy/wonder-blocks-testing-core";
|
|
2
|
-
import {gqlRequestMatchesMock} from "./gql-request-matches-mock";
|
|
3
|
-
import type {GqlFetchMockFn, GqlMockOperation} from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A mock for the fetch function passed to GqlRouter.
|
|
7
|
-
*/
|
|
8
|
-
export const mockGqlFetch = (): GqlFetchMockFn =>
|
|
9
|
-
mockRequester<GqlMockOperation<any, any, any>>(
|
|
10
|
-
gqlRequestMatchesMock,
|
|
11
|
-
// Note that the identation at the start of each line is important.
|
|
12
|
-
// TODO(somewhatabstract): Make a stringify that indents each line of
|
|
13
|
-
// the output properly too.
|
|
14
|
-
(operation, variables, context) =>
|
|
15
|
-
`Operation: ${operation.type} ${operation.id}
|
|
16
|
-
Variables: ${
|
|
17
|
-
variables == null ? "None" : JSON.stringify(variables, null, 2)
|
|
18
|
-
}
|
|
19
|
-
Context: ${JSON.stringify(context, null, 2)}`,
|
|
20
|
-
);
|
package/src/gql/types.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type {GqlOperation, GqlContext} from "@khanacademy/wonder-blocks-data";
|
|
2
|
-
import type {
|
|
3
|
-
GraphQLJson,
|
|
4
|
-
MockResponse,
|
|
5
|
-
} from "@khanacademy/wonder-blocks-testing-core";
|
|
6
|
-
|
|
7
|
-
export type GqlMockOperation<
|
|
8
|
-
TData extends Record<any, any>,
|
|
9
|
-
TVariables extends Record<any, any>,
|
|
10
|
-
TContext extends GqlContext,
|
|
11
|
-
> = {
|
|
12
|
-
operation: GqlOperation<TData, TVariables>;
|
|
13
|
-
variables?: TVariables;
|
|
14
|
-
context?: TContext;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
type GqlMockOperationFn = <
|
|
18
|
-
TData extends Record<any, any>,
|
|
19
|
-
TVariables extends Record<any, any>,
|
|
20
|
-
TContext extends GqlContext,
|
|
21
|
-
TResponseData extends GraphQLJson<TData>,
|
|
22
|
-
>(
|
|
23
|
-
operation: GqlMockOperation<TData, TVariables, TContext>,
|
|
24
|
-
response: MockResponse<TResponseData>,
|
|
25
|
-
) => GqlFetchMockFn;
|
|
26
|
-
|
|
27
|
-
export type GqlFetchMockFn = {
|
|
28
|
-
(
|
|
29
|
-
operation: GqlOperation<any, any>,
|
|
30
|
-
variables: Record<any, any> | null | undefined,
|
|
31
|
-
context: GqlContext,
|
|
32
|
-
): Promise<Response>;
|
|
33
|
-
mockOperation: GqlMockOperationFn;
|
|
34
|
-
mockOperationOnce: GqlMockOperationFn;
|
|
35
|
-
};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`SSR.adapter should throw on bad configuration ({"thisConfig": "isNotValid"}) 1`] = `"Unexpected configuration: set config to null to turn this adapter off"`;
|
|
4
|
-
|
|
5
|
-
exports[`SSR.adapter should throw on bad configuration (false) 1`] = `"Unexpected configuration: set config to null to turn this adapter off"`;
|
|
6
|
-
|
|
7
|
-
exports[`SSR.adapter should throw on bad configuration (string) 1`] = `"Unexpected configuration: set config to null to turn this adapter off"`;
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen, waitFor} from "@testing-library/react";
|
|
3
|
-
import {useCachedEffect} from "@khanacademy/wonder-blocks-data";
|
|
4
|
-
import * as Data from "../data";
|
|
5
|
-
|
|
6
|
-
describe("WonderBlocksData.adapter", () => {
|
|
7
|
-
it("should render children when configuration arrays are empty", () => {
|
|
8
|
-
// Arrange
|
|
9
|
-
const children = <div>CONTENT</div>;
|
|
10
|
-
|
|
11
|
-
// Act
|
|
12
|
-
render(Data.adapter(children, []));
|
|
13
|
-
|
|
14
|
-
// Assert
|
|
15
|
-
expect(screen.getByText("CONTENT")).toBeInTheDocument();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it("should support request interception via configured dataIntercepts", async () => {
|
|
19
|
-
// Arrange
|
|
20
|
-
|
|
21
|
-
const TestFixture = () => {
|
|
22
|
-
const [result] = useCachedEffect("ID", jest.fn());
|
|
23
|
-
|
|
24
|
-
return (
|
|
25
|
-
<div>
|
|
26
|
-
CONTENT:{" "}
|
|
27
|
-
{result.status === "success" ? result.data : undefined}
|
|
28
|
-
</div>
|
|
29
|
-
);
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Act
|
|
33
|
-
const {container} = render(
|
|
34
|
-
Data.adapter(<TestFixture />, () =>
|
|
35
|
-
Promise.resolve("INTERCEPTED!" as any),
|
|
36
|
-
),
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
// Assert
|
|
40
|
-
await waitFor(() => expect(container).toContainHTML("INTERCEPTED!"));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("should render like we expect", () => {
|
|
44
|
-
// Snapshot test is handy to visualize what's going on and help debug
|
|
45
|
-
// test failures of the other cases. The other cases assert specifics.
|
|
46
|
-
// Arrange
|
|
47
|
-
const TestFixture = () => {
|
|
48
|
-
const [result] = useCachedEffect("ID", jest.fn());
|
|
49
|
-
|
|
50
|
-
return (
|
|
51
|
-
<div>
|
|
52
|
-
CONTENT:
|
|
53
|
-
{result.status === "success" ? result.data : undefined}
|
|
54
|
-
</div>
|
|
55
|
-
);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
// Act
|
|
59
|
-
const {container} = render(
|
|
60
|
-
Data.adapter(<TestFixture />, () =>
|
|
61
|
-
Promise.resolve("INTERCEPTED!" as any),
|
|
62
|
-
),
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Assert
|
|
66
|
-
expect(container).toMatchInlineSnapshot(`
|
|
67
|
-
<div>
|
|
68
|
-
<div>
|
|
69
|
-
CONTENT:
|
|
70
|
-
INTERCEPTED!
|
|
71
|
-
</div>
|
|
72
|
-
</div>
|
|
73
|
-
`);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen} from "@testing-library/react";
|
|
3
|
-
import * as WBCore from "@khanacademy/wonder-blocks-core";
|
|
4
|
-
import {makeTestHarness} from "@khanacademy/wonder-blocks-testing-core";
|
|
5
|
-
|
|
6
|
-
import * as RenderState from "../render-state";
|
|
7
|
-
|
|
8
|
-
jest.mock("@khanacademy/wonder-stuff-core", () => {
|
|
9
|
-
const actualCore = jest.requireActual("@khanacademy/wonder-stuff-core");
|
|
10
|
-
return {
|
|
11
|
-
...actualCore,
|
|
12
|
-
RenderStateRoot: (props: any) => (
|
|
13
|
-
<actualCore.RenderStateRoot {...props} />
|
|
14
|
-
),
|
|
15
|
-
};
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe("SSR.adapter", () => {
|
|
19
|
-
it("should render the RenderStateRoot with throwIfNested set to false", () => {
|
|
20
|
-
// Arrange
|
|
21
|
-
const children = <div>CHILDREN!</div>;
|
|
22
|
-
const renderStateRootSpy = jest.spyOn(WBCore, "RenderStateRoot");
|
|
23
|
-
|
|
24
|
-
// Act
|
|
25
|
-
render(RenderState.adapter(children, true));
|
|
26
|
-
|
|
27
|
-
// Assert
|
|
28
|
-
expect(renderStateRootSpy).toHaveBeenCalledWith(
|
|
29
|
-
{
|
|
30
|
-
children,
|
|
31
|
-
throwIfNested: false,
|
|
32
|
-
},
|
|
33
|
-
{},
|
|
34
|
-
);
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it("should render the children correctly", () => {
|
|
38
|
-
// Arrange
|
|
39
|
-
const children = <div>CHILDREN!</div>;
|
|
40
|
-
|
|
41
|
-
// Act
|
|
42
|
-
render(RenderState.adapter(children, true));
|
|
43
|
-
|
|
44
|
-
// Assert
|
|
45
|
-
expect(screen.getByText("CHILDREN!")).toBeInTheDocument();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it("should enable harnessing of components that require RenderStateRoot", () => {
|
|
49
|
-
// Arrange
|
|
50
|
-
const ComponentNeedsSsr = (props: any) => {
|
|
51
|
-
const idf = WBCore.useUniqueIdWithoutMock();
|
|
52
|
-
return <div>{idf?.get("my-id")}</div>;
|
|
53
|
-
};
|
|
54
|
-
const testHarness = makeTestHarness(
|
|
55
|
-
{
|
|
56
|
-
renderState: RenderState.adapter,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
renderState: true,
|
|
60
|
-
},
|
|
61
|
-
);
|
|
62
|
-
const Harnessed = testHarness(ComponentNeedsSsr);
|
|
63
|
-
|
|
64
|
-
// Act
|
|
65
|
-
const underTest = () => render(<Harnessed />);
|
|
66
|
-
|
|
67
|
-
// Assert
|
|
68
|
-
expect(underTest).not.toThrowError();
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it.each`
|
|
72
|
-
config
|
|
73
|
-
${false}
|
|
74
|
-
${"string"}
|
|
75
|
-
${{thisConfig: "isNotValid"}}
|
|
76
|
-
`("should throw on bad configuration ($config)", ({config}) => {
|
|
77
|
-
// Arrange
|
|
78
|
-
const children = <div>CHILDREN!</div>;
|
|
79
|
-
|
|
80
|
-
// Act
|
|
81
|
-
const underTest = () => render(RenderState.adapter(children, config));
|
|
82
|
-
|
|
83
|
-
// Assert
|
|
84
|
-
expect(underTest).toThrowErrorMatchingSnapshot();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {InterceptRequests} from "@khanacademy/wonder-blocks-data";
|
|
3
|
-
import type {TestHarnessAdapter} from "@khanacademy/wonder-blocks-testing-core";
|
|
4
|
-
|
|
5
|
-
type Interceptor = JSX.LibraryManagedAttributes<
|
|
6
|
-
typeof InterceptRequests,
|
|
7
|
-
React.ComponentProps<typeof InterceptRequests>
|
|
8
|
-
>["interceptor"];
|
|
9
|
-
|
|
10
|
-
type Config = Interceptor | Array<Interceptor>;
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Default configuration for the Wonder Blocks Data adapter.
|
|
14
|
-
*/
|
|
15
|
-
export const defaultConfig = [] as Array<Interceptor>;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Test harness adapter to mock Wonder Blocks Data usage.
|
|
19
|
-
*
|
|
20
|
-
* NOTE: Consumers are responsible for properly defining their intercepts.
|
|
21
|
-
* This component does not validate the configuration to ensure interceptors
|
|
22
|
-
* are not overriding one another.
|
|
23
|
-
*/
|
|
24
|
-
export const adapter: TestHarnessAdapter<Config> = (
|
|
25
|
-
children: React.ReactNode,
|
|
26
|
-
config: Config,
|
|
27
|
-
): React.ReactElement<any> => {
|
|
28
|
-
// First we render the cache intercepts.
|
|
29
|
-
let currentChildren = children;
|
|
30
|
-
|
|
31
|
-
const interceptors = Array.isArray(config) ? config : [config];
|
|
32
|
-
|
|
33
|
-
// Then we render the data intercepts.
|
|
34
|
-
for (const interceptor of interceptors) {
|
|
35
|
-
currentChildren = (
|
|
36
|
-
<InterceptRequests interceptor={interceptor}>
|
|
37
|
-
{currentChildren}
|
|
38
|
-
</InterceptRequests>
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* `currentChildren` is a `React.Node` but we need it to be a
|
|
44
|
-
* `React.Element<>`. Return it rendered in a fragment allows us to do
|
|
45
|
-
* that.
|
|
46
|
-
*/
|
|
47
|
-
return <>{currentChildren}</>;
|
|
48
|
-
};
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import {harnessAdapters} from "@khanacademy/wonder-blocks-testing-core";
|
|
2
|
-
import type {TestHarnessConfigs} from "@khanacademy/wonder-blocks-testing-core";
|
|
3
|
-
import * as data from "./data";
|
|
4
|
-
import * as renderState from "./render-state";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* NOTE: We do not type `DefaultAdapters` with `Adapters` here because we want
|
|
8
|
-
* the individual config types of each adapter to remain intact rather than
|
|
9
|
-
* getting changed to `any`.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* The default adapters provided by Wonder Blocks.
|
|
14
|
-
*/
|
|
15
|
-
export const DefaultAdapters = {
|
|
16
|
-
// The error boundary is as close to the component under test as possible,
|
|
17
|
-
// so that other adapters don't soil it with their own errors, if that
|
|
18
|
-
// should happen.
|
|
19
|
-
boundary: harnessAdapters.DefaultAdapters.boundary,
|
|
20
|
-
css: harnessAdapters.DefaultAdapters.css,
|
|
21
|
-
data: data.adapter,
|
|
22
|
-
portal: harnessAdapters.DefaultAdapters.portal,
|
|
23
|
-
router: harnessAdapters.DefaultAdapters.router,
|
|
24
|
-
renderState: renderState.adapter,
|
|
25
|
-
} as const;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* The default configurations to use with the `DefaultAdapters`.
|
|
29
|
-
*/
|
|
30
|
-
export const DefaultConfigs: TestHarnessConfigs<typeof DefaultAdapters> = {
|
|
31
|
-
...harnessAdapters.DefaultConfigs,
|
|
32
|
-
data: data.defaultConfig,
|
|
33
|
-
renderState: renderState.defaultConfig,
|
|
34
|
-
} as const;
|
|
@@ -1,41 +0,0 @@
|
|
|
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 "@khanacademy/wonder-blocks-testing-core";
|
|
6
|
-
|
|
7
|
-
//
|
|
8
|
-
type Config = true;
|
|
9
|
-
|
|
10
|
-
// The default configuration is off since this will likely cause state changes
|
|
11
|
-
// and add Testing Library act/waitFor calls in tests using the harness when
|
|
12
|
-
// its enabled.
|
|
13
|
-
export const defaultConfig: Config | null = null;
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Test harness adapter for supporting render state-based hooks and components.
|
|
17
|
-
*
|
|
18
|
-
* Some components and hooks utilize the render state context to manage what
|
|
19
|
-
* they render and when. In order for this to work, a `RenderStateRoot`
|
|
20
|
-
* component must be present to track the current render state.
|
|
21
|
-
*
|
|
22
|
-
* This adapter wraps the children in a `RenderStateRoot` component to enable
|
|
23
|
-
* the render state context. This adapter should be used when testing components
|
|
24
|
-
* that rely on the render state.
|
|
25
|
-
*/
|
|
26
|
-
export const adapter: TestHarnessAdapter<Config> = (
|
|
27
|
-
children: React.ReactNode,
|
|
28
|
-
config: Config,
|
|
29
|
-
): React.ReactElement<any> => {
|
|
30
|
-
if (config !== true) {
|
|
31
|
-
throw new KindError(
|
|
32
|
-
"Unexpected configuration: set config to null to turn this adapter off",
|
|
33
|
-
Errors.InvalidInput,
|
|
34
|
-
{
|
|
35
|
-
metadata: {config},
|
|
36
|
-
},
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
// We never want to throw if the test harness is nested.
|
|
40
|
-
return <RenderStateRoot throwIfNested={false}>{children}</RenderStateRoot>;
|
|
41
|
-
};
|