@khanacademy/wonder-blocks-testing-core 0.0.0-PR2266-20240710215230
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 +7 -0
- package/dist/es/index.js +455 -0
- package/dist/fetch/fetch-request-matches-mock.d.ts +5 -0
- package/dist/fetch/mock-fetch.d.ts +5 -0
- package/dist/fetch/types.d.ts +9 -0
- package/dist/fixtures/fixtures.basic.stories.d.ts +13 -0
- package/dist/fixtures/fixtures.d.ts +13 -0
- package/dist/fixtures/fixtures.defaultwrapper.stories.d.ts +9 -0
- package/dist/fixtures/types.d.ts +36 -0
- package/dist/harness/adapt.d.ts +17 -0
- package/dist/harness/adapters/adapters.d.ts +41 -0
- package/dist/harness/adapters/css.d.ts +12 -0
- package/dist/harness/adapters/error-boundary.d.ts +15 -0
- package/dist/harness/adapters/portal.d.ts +12 -0
- package/dist/harness/adapters/router.d.ts +94 -0
- package/dist/harness/get-named-adapter-component.d.ts +16 -0
- package/dist/harness/hook-harness.d.ts +13 -0
- package/dist/harness/make-hook-harness.d.ts +17 -0
- package/dist/harness/make-test-harness.d.ts +15 -0
- package/dist/harness/test-harness.d.ts +34 -0
- package/dist/harness/types.d.ts +36 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +489 -0
- package/dist/mock-requester.d.ts +5 -0
- package/dist/render-hook-static.d.ts +33 -0
- package/dist/respond-with.d.ts +75 -0
- package/dist/response-impl.d.ts +1 -0
- package/dist/settle-controller.d.ts +19 -0
- package/dist/settle-signal.d.ts +18 -0
- package/dist/types.d.ts +25 -0
- package/package.json +33 -0
- package/src/__tests__/mock-requester.test.ts +212 -0
- package/src/__tests__/render-hook-static.test.ts +48 -0
- package/src/__tests__/respond-with.test.ts +524 -0
- package/src/__tests__/response-impl.test.js +47 -0
- package/src/__tests__/settle-controller.test.ts +28 -0
- package/src/__tests__/settle-signal.test.ts +104 -0
- package/src/fetch/__tests__/__snapshots__/mock-fetch.test.ts.snap +29 -0
- package/src/fetch/__tests__/fetch-request-matches-mock.test.ts +98 -0
- package/src/fetch/__tests__/mock-fetch.test.ts +83 -0
- package/src/fetch/fetch-request-matches-mock.ts +42 -0
- package/src/fetch/mock-fetch.ts +20 -0
- package/src/fetch/types.ts +14 -0
- package/src/fixtures/__tests__/fixtures.test.tsx +147 -0
- package/src/fixtures/fixtures.basic.stories.tsx +62 -0
- package/src/fixtures/fixtures.defaultwrapper.stories.tsx +49 -0
- package/src/fixtures/fixtures.tsx +72 -0
- package/src/fixtures/types.ts +42 -0
- package/src/harness/__tests__/adapt.test.tsx +248 -0
- package/src/harness/__tests__/hook-harness.test.ts +73 -0
- package/src/harness/__tests__/make-hook-harness.test.tsx +93 -0
- package/src/harness/__tests__/make-test-harness.test.tsx +195 -0
- package/src/harness/__tests__/test-harness.test.ts +75 -0
- package/src/harness/__tests__/types.typestest.tsx +103 -0
- package/src/harness/adapt.tsx +41 -0
- package/src/harness/adapters/__tests__/__snapshots__/router.test.tsx.snap +5 -0
- package/src/harness/adapters/__tests__/css.test.tsx +95 -0
- package/src/harness/adapters/__tests__/error-boundary.test.tsx +121 -0
- package/src/harness/adapters/__tests__/portal.test.tsx +30 -0
- package/src/harness/adapters/__tests__/router.test.tsx +252 -0
- package/src/harness/adapters/adapters.ts +35 -0
- package/src/harness/adapters/css.tsx +66 -0
- package/src/harness/adapters/error-boundary.tsx +56 -0
- package/src/harness/adapters/portal.tsx +25 -0
- package/src/harness/adapters/router.tsx +205 -0
- package/src/harness/get-named-adapter-component.tsx +36 -0
- package/src/harness/hook-harness.ts +22 -0
- package/src/harness/make-hook-harness.tsx +40 -0
- package/src/harness/make-test-harness.tsx +60 -0
- package/src/harness/test-harness.ts +13 -0
- package/src/harness/types.ts +47 -0
- package/src/index.ts +29 -0
- package/src/mock-requester.ts +68 -0
- package/src/render-hook-static.tsx +60 -0
- package/src/respond-with.ts +263 -0
- package/src/response-impl.ts +8 -0
- package/src/settle-controller.ts +34 -0
- package/src/settle-signal.ts +42 -0
- package/src/types.ts +40 -0
- package/tsconfig-build.json +10 -0
- package/tsconfig-build.tsbuildinfo +1 -0
package/CHANGELOG.md
ADDED
package/dist/es/index.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { action } from '@storybook/addon-actions';
|
|
3
|
+
import { StaticRouter, MemoryRouter, Switch, Route } from 'react-router-dom';
|
|
4
|
+
|
|
5
|
+
const fixtures = Component => {
|
|
6
|
+
const templateMap = new WeakMap();
|
|
7
|
+
let storyNumber = 1;
|
|
8
|
+
const getPropsOptions = {
|
|
9
|
+
log: (message, ...args) => action(message)(...args),
|
|
10
|
+
logHandler: action
|
|
11
|
+
};
|
|
12
|
+
const makeStory = (description, props, wrapper = null) => {
|
|
13
|
+
const storyName = `${storyNumber++} ${description}`;
|
|
14
|
+
const getProps = options => typeof props === "function" ? props(options) : props;
|
|
15
|
+
const RealComponent = wrapper || Component;
|
|
16
|
+
let Template = templateMap.get(RealComponent);
|
|
17
|
+
if (Template == null) {
|
|
18
|
+
Template = args => React.createElement(RealComponent, args);
|
|
19
|
+
templateMap.set(RealComponent, Template);
|
|
20
|
+
}
|
|
21
|
+
const story = Template.bind({});
|
|
22
|
+
story.args = getProps(getPropsOptions);
|
|
23
|
+
story.storyName = storyName;
|
|
24
|
+
return story;
|
|
25
|
+
};
|
|
26
|
+
return makeStory;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const getHref = input => {
|
|
30
|
+
if (typeof input === "string") {
|
|
31
|
+
return input;
|
|
32
|
+
} else if (typeof input.url === "string") {
|
|
33
|
+
return input.url;
|
|
34
|
+
} else if (typeof input.href === "string") {
|
|
35
|
+
return input.href;
|
|
36
|
+
} else {
|
|
37
|
+
throw new Error(`Unsupported input type`);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const fetchRequestMatchesMock = (mock, input, init) => {
|
|
41
|
+
const href = getHref(input);
|
|
42
|
+
if (typeof mock === "string") {
|
|
43
|
+
return href === mock;
|
|
44
|
+
} else if (mock instanceof RegExp) {
|
|
45
|
+
return mock.test(href);
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error(`Unsupported mock operation: ${JSON.stringify(mock)}`);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const mockRequester = (operationMatcher, operationToString) => {
|
|
52
|
+
const mocks = [];
|
|
53
|
+
const mockFn = (...args) => {
|
|
54
|
+
for (const mock of mocks) {
|
|
55
|
+
if (mock.onceOnly && mock.used) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (operationMatcher(mock.operation, ...args)) {
|
|
59
|
+
mock.used = true;
|
|
60
|
+
return mock.response();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
const operation = operationToString(...args);
|
|
64
|
+
return Promise.reject(new Error(`No matching mock response found for request:
|
|
65
|
+
${operation}`));
|
|
66
|
+
};
|
|
67
|
+
const addMockedOperation = (operation, response, onceOnly) => {
|
|
68
|
+
const mockResponse = () => response.toPromise();
|
|
69
|
+
mocks.push({
|
|
70
|
+
operation,
|
|
71
|
+
response: mockResponse,
|
|
72
|
+
onceOnly,
|
|
73
|
+
used: false
|
|
74
|
+
});
|
|
75
|
+
return mockFn;
|
|
76
|
+
};
|
|
77
|
+
mockFn.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
|
|
78
|
+
mockFn.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
|
|
79
|
+
return mockFn;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const mockFetch = () => mockRequester(fetchRequestMatchesMock, (input, init) => `Input: ${typeof input === "string" ? input : JSON.stringify(input, null, 2)}
|
|
83
|
+
Options: ${init == null ? "None" : JSON.stringify(init, null, 2)}`);
|
|
84
|
+
|
|
85
|
+
const ResponseImpl = typeof Response === "undefined" ? require("node-fetch").Response : Response;
|
|
86
|
+
|
|
87
|
+
const textResponse = (text, statusCode, signal) => ({
|
|
88
|
+
toPromise: () => makeMockResponse({
|
|
89
|
+
type: "text",
|
|
90
|
+
text,
|
|
91
|
+
statusCode,
|
|
92
|
+
signal
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
const rejectResponse = (error, signal) => ({
|
|
96
|
+
toPromise: () => makeMockResponse({
|
|
97
|
+
type: "reject",
|
|
98
|
+
error,
|
|
99
|
+
signal
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
const RespondWith = Object.freeze({
|
|
103
|
+
text: (text, statusCode = 200, signal = null) => textResponse(text, statusCode, signal),
|
|
104
|
+
json: (json, signal = null) => textResponse(() => JSON.stringify(json), 200, signal),
|
|
105
|
+
graphQLData: (data, signal = null) => textResponse(() => JSON.stringify({
|
|
106
|
+
data
|
|
107
|
+
}), 200, signal),
|
|
108
|
+
unparseableBody: (signal = null) => textResponse("INVALID JSON", 200, signal),
|
|
109
|
+
abortedRequest: (signal = null) => rejectResponse(() => {
|
|
110
|
+
const abortError = new Error("Mock request aborted");
|
|
111
|
+
abortError.name = "AbortError";
|
|
112
|
+
return abortError;
|
|
113
|
+
}, signal),
|
|
114
|
+
reject: (error, signal = null) => rejectResponse(error, signal),
|
|
115
|
+
errorStatusCode: (statusCode, signal = null) => {
|
|
116
|
+
if (statusCode < 300) {
|
|
117
|
+
throw new Error(`${statusCode} is not a valid error status code`);
|
|
118
|
+
}
|
|
119
|
+
return textResponse("{}", statusCode, signal);
|
|
120
|
+
},
|
|
121
|
+
nonGraphQLBody: (signal = null) => textResponse(() => JSON.stringify({
|
|
122
|
+
valid: "json",
|
|
123
|
+
that: "is not a valid graphql response"
|
|
124
|
+
}), 200, signal),
|
|
125
|
+
graphQLErrors: (errorMessages, signal = null) => textResponse(() => JSON.stringify({
|
|
126
|
+
errors: errorMessages.map(e => ({
|
|
127
|
+
message: e
|
|
128
|
+
}))
|
|
129
|
+
}), 200, signal)
|
|
130
|
+
});
|
|
131
|
+
const callOnSettled = (signal, fn) => {
|
|
132
|
+
if (signal == null || signal.settled) {
|
|
133
|
+
fn();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const onSettled = () => {
|
|
137
|
+
signal.removeEventListener("settled", onSettled);
|
|
138
|
+
fn();
|
|
139
|
+
};
|
|
140
|
+
signal.addEventListener("settled", onSettled);
|
|
141
|
+
};
|
|
142
|
+
const makeMockResponse = response => {
|
|
143
|
+
const {
|
|
144
|
+
signal
|
|
145
|
+
} = response;
|
|
146
|
+
switch (response.type) {
|
|
147
|
+
case "text":
|
|
148
|
+
return new Promise((resolve, reject) => {
|
|
149
|
+
callOnSettled(signal, () => {
|
|
150
|
+
const text = typeof response.text === "function" ? response.text() : response.text;
|
|
151
|
+
resolve(new ResponseImpl(text, {
|
|
152
|
+
status: response.statusCode
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
case "reject":
|
|
157
|
+
return new Promise((resolve, reject) => {
|
|
158
|
+
callOnSettled(signal, () => reject(response.error instanceof Error ? response.error : response.error()));
|
|
159
|
+
});
|
|
160
|
+
default:
|
|
161
|
+
if (process.env.NODE_ENV !== "production") {
|
|
162
|
+
throw new Error(`Unknown response type: ${response.type}`);
|
|
163
|
+
}
|
|
164
|
+
return makeMockResponse({
|
|
165
|
+
type: "reject",
|
|
166
|
+
error: new Error("Unknown response type"),
|
|
167
|
+
signal
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
class SettleSignal extends EventTarget {
|
|
173
|
+
constructor(setSettleFn = null) {
|
|
174
|
+
super();
|
|
175
|
+
this._settled = false;
|
|
176
|
+
setSettleFn == null ? void 0 : setSettleFn(() => {
|
|
177
|
+
if (this._settled) {
|
|
178
|
+
throw new Error("SettleSignal already settled");
|
|
179
|
+
}
|
|
180
|
+
this._settled = true;
|
|
181
|
+
this.dispatchEvent(new Event("settled"));
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
static settle() {
|
|
185
|
+
const signal = new SettleSignal();
|
|
186
|
+
signal._settled = true;
|
|
187
|
+
return signal;
|
|
188
|
+
}
|
|
189
|
+
get settled() {
|
|
190
|
+
return this._settled;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
class SettleController {
|
|
195
|
+
constructor() {
|
|
196
|
+
this._settleFn = void 0;
|
|
197
|
+
this._signal = void 0;
|
|
198
|
+
this._signal = new SettleSignal(settleFn => this._settleFn = settleFn);
|
|
199
|
+
}
|
|
200
|
+
get signal() {
|
|
201
|
+
return this._signal;
|
|
202
|
+
}
|
|
203
|
+
settle() {
|
|
204
|
+
var _this$_settleFn;
|
|
205
|
+
(_this$_settleFn = this._settleFn) == null ? void 0 : _this$_settleFn.call(this);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const defaultConfig$3 = null;
|
|
210
|
+
const normalizeConfig = config => {
|
|
211
|
+
if (typeof config === "string") {
|
|
212
|
+
return {
|
|
213
|
+
classes: [config],
|
|
214
|
+
style: {}
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (Array.isArray(config)) {
|
|
218
|
+
return {
|
|
219
|
+
classes: config,
|
|
220
|
+
style: {}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
if (typeof config === "object") {
|
|
224
|
+
if ("classes" in config && config.classes != null && "style" in config && config.style != null) {
|
|
225
|
+
return config;
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
classes: [],
|
|
229
|
+
style: config
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
throw new Error(`Invalid config: ${config}`);
|
|
233
|
+
};
|
|
234
|
+
const adapter$3 = (children, config) => {
|
|
235
|
+
const {
|
|
236
|
+
classes,
|
|
237
|
+
style
|
|
238
|
+
} = normalizeConfig(config);
|
|
239
|
+
return React.createElement("div", {
|
|
240
|
+
"data-testid": "css-adapter-container",
|
|
241
|
+
className: classes.join(" "),
|
|
242
|
+
style: style
|
|
243
|
+
}, children);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const defaultConfig$2 = null;
|
|
247
|
+
class ErrorBoundary extends React.Component {
|
|
248
|
+
constructor(...args) {
|
|
249
|
+
super(...args);
|
|
250
|
+
this.state = {
|
|
251
|
+
renderError: false
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
static getDerivedStateFromError(error) {
|
|
255
|
+
return {
|
|
256
|
+
renderError: true
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
componentDidCatch(error, errorInfo) {
|
|
260
|
+
const lastErrorUx = this.props.onError(error, errorInfo);
|
|
261
|
+
this.setState({
|
|
262
|
+
lastErrorUx
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
render() {
|
|
266
|
+
var _this$state$lastError;
|
|
267
|
+
return this.state.renderError ? (_this$state$lastError = this.state.lastErrorUx) != null ? _this$state$lastError : "An error occurred" : this.props.children;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
const adapter$2 = (children, config) => React.createElement(ErrorBoundary, {
|
|
271
|
+
onError: config
|
|
272
|
+
}, children);
|
|
273
|
+
|
|
274
|
+
const defaultConfig$1 = null;
|
|
275
|
+
const adapter$1 = (children, config) => React.createElement(React.Fragment, null, React.createElement("div", {
|
|
276
|
+
id: config,
|
|
277
|
+
"data-testid": config
|
|
278
|
+
}), children);
|
|
279
|
+
|
|
280
|
+
const defaultConfig = {
|
|
281
|
+
location: "/"
|
|
282
|
+
};
|
|
283
|
+
const maybeWithRoute = (children, path) => {
|
|
284
|
+
if (path == null) {
|
|
285
|
+
return React.createElement(React.Fragment, null, children);
|
|
286
|
+
}
|
|
287
|
+
return React.createElement(Switch, null, React.createElement(Route, {
|
|
288
|
+
exact: true,
|
|
289
|
+
path: path
|
|
290
|
+
}, children), React.createElement(Route, {
|
|
291
|
+
path: "*",
|
|
292
|
+
render: () => {
|
|
293
|
+
throw new Error("The configured path must match the configured location or your harnessed component will not render.");
|
|
294
|
+
}
|
|
295
|
+
}));
|
|
296
|
+
};
|
|
297
|
+
const adapter = (children, config) => {
|
|
298
|
+
if (typeof config === "string") {
|
|
299
|
+
config = {
|
|
300
|
+
location: config
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
const wrappedWithRoute = maybeWithRoute(children, config.path);
|
|
304
|
+
if ("forceStatic" in config && config.forceStatic) {
|
|
305
|
+
return React.createElement(StaticRouter, {
|
|
306
|
+
location: config.location,
|
|
307
|
+
context: {}
|
|
308
|
+
}, wrappedWithRoute);
|
|
309
|
+
}
|
|
310
|
+
if ("location" in config && config.location !== undefined) {
|
|
311
|
+
return React.createElement(MemoryRouter, {
|
|
312
|
+
initialEntries: [config.location]
|
|
313
|
+
}, wrappedWithRoute);
|
|
314
|
+
}
|
|
315
|
+
if (!("initialEntries" in config) || config.initialEntries === undefined) {
|
|
316
|
+
throw new Error("A location or initial history entries must be provided.");
|
|
317
|
+
}
|
|
318
|
+
const entries = config.initialEntries.length === 0 ? [defaultConfig.location] : config.initialEntries;
|
|
319
|
+
const routerProps = {
|
|
320
|
+
initialEntries: entries
|
|
321
|
+
};
|
|
322
|
+
if (config.initialIndex != null) {
|
|
323
|
+
routerProps.initialIndex = config.initialIndex;
|
|
324
|
+
}
|
|
325
|
+
if (config.getUserConfirmation != null) {
|
|
326
|
+
routerProps.getUserConfirmation = config.getUserConfirmation;
|
|
327
|
+
}
|
|
328
|
+
return React.createElement(MemoryRouter, routerProps, wrappedWithRoute);
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
const DefaultAdapters = {
|
|
332
|
+
boundary: adapter$2,
|
|
333
|
+
css: adapter$3,
|
|
334
|
+
portal: adapter$1,
|
|
335
|
+
router: adapter
|
|
336
|
+
};
|
|
337
|
+
const DefaultConfigs = {
|
|
338
|
+
boundary: defaultConfig$2,
|
|
339
|
+
css: defaultConfig$3,
|
|
340
|
+
portal: defaultConfig$1,
|
|
341
|
+
router: defaultConfig
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
var adapters = /*#__PURE__*/Object.freeze({
|
|
345
|
+
__proto__: null,
|
|
346
|
+
DefaultAdapters: DefaultAdapters,
|
|
347
|
+
DefaultConfigs: DefaultConfigs
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
function _extends() {
|
|
351
|
+
_extends = Object.assign ? Object.assign.bind() : function (target) {
|
|
352
|
+
for (var i = 1; i < arguments.length; i++) {
|
|
353
|
+
var source = arguments[i];
|
|
354
|
+
for (var key in source) {
|
|
355
|
+
if (Object.prototype.hasOwnProperty.call(source, key)) {
|
|
356
|
+
target[key] = source[key];
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
return target;
|
|
361
|
+
};
|
|
362
|
+
return _extends.apply(this, arguments);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const componentCache = new Map();
|
|
366
|
+
const getNamedAdapterComponent = name => {
|
|
367
|
+
const existing = componentCache.get(name);
|
|
368
|
+
if (existing != null) {
|
|
369
|
+
return existing;
|
|
370
|
+
}
|
|
371
|
+
const newComponent = ({
|
|
372
|
+
children,
|
|
373
|
+
config,
|
|
374
|
+
adapter
|
|
375
|
+
}) => adapter(children, config);
|
|
376
|
+
newComponent.displayName = `Adapter(${name})`;
|
|
377
|
+
componentCache.set(name, newComponent);
|
|
378
|
+
return newComponent;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const Adapt = ({
|
|
382
|
+
children,
|
|
383
|
+
adapters,
|
|
384
|
+
configs
|
|
385
|
+
}) => {
|
|
386
|
+
return Object.entries(adapters).reduce((newChildren, [name, adapter]) => {
|
|
387
|
+
const theConfig = configs[name];
|
|
388
|
+
if (theConfig == null) {
|
|
389
|
+
return newChildren;
|
|
390
|
+
}
|
|
391
|
+
const Adapter = getNamedAdapterComponent(name);
|
|
392
|
+
return React.createElement(Adapter, {
|
|
393
|
+
key: name,
|
|
394
|
+
adapter: adapter,
|
|
395
|
+
config: theConfig
|
|
396
|
+
}, newChildren);
|
|
397
|
+
}, React.createElement(React.Fragment, null, children));
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const makeTestHarness = (adapters, defaultConfigs) => {
|
|
401
|
+
return (Component, configs) => {
|
|
402
|
+
const fullConfig = _extends({}, defaultConfigs, configs);
|
|
403
|
+
const harnessedComponent = React.forwardRef((props, ref) => React.createElement(Adapt, {
|
|
404
|
+
adapters: adapters,
|
|
405
|
+
configs: fullConfig
|
|
406
|
+
}, React.createElement(Component, _extends({}, props, {
|
|
407
|
+
ref: ref
|
|
408
|
+
}))));
|
|
409
|
+
harnessedComponent.displayName = `testHarness(${Component.displayName || Component.name || "Component"})`;
|
|
410
|
+
return harnessedComponent;
|
|
411
|
+
};
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const HookHarness = ({
|
|
415
|
+
children
|
|
416
|
+
}) => React.createElement(React.Fragment, null, children);
|
|
417
|
+
const makeHookHarness = (adapters, defaultConfigs) => {
|
|
418
|
+
const testHarness = makeTestHarness(adapters, defaultConfigs);
|
|
419
|
+
return configs => testHarness(HookHarness, configs);
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const hookHarness = makeHookHarness(DefaultAdapters, DefaultConfigs);
|
|
423
|
+
|
|
424
|
+
const testHarness = makeTestHarness(DefaultAdapters, DefaultConfigs);
|
|
425
|
+
|
|
426
|
+
if (process.env.NODE_ENV === 'production') {
|
|
427
|
+
module.exports = require('./cjs/react-dom-server.browser.production.min.js');
|
|
428
|
+
} else {
|
|
429
|
+
module.exports = require('./cjs/react-dom-server.browser.development.js');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const renderHookStatic = (render, {
|
|
433
|
+
wrapper,
|
|
434
|
+
initialProps
|
|
435
|
+
} = {}) => {
|
|
436
|
+
let result;
|
|
437
|
+
function TestComponent({
|
|
438
|
+
renderCallbackProps
|
|
439
|
+
}) {
|
|
440
|
+
result = render(renderCallbackProps);
|
|
441
|
+
return null;
|
|
442
|
+
}
|
|
443
|
+
const component = React.createElement(TestComponent, {
|
|
444
|
+
renderCallbackProps: initialProps
|
|
445
|
+
});
|
|
446
|
+
const componentWithWrapper = wrapper == null ? component : React.createElement(wrapper, null, component);
|
|
447
|
+
undefined(componentWithWrapper);
|
|
448
|
+
return {
|
|
449
|
+
result: {
|
|
450
|
+
current: result
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
export { RespondWith, SettleController, fixtures, adapters as harnessAdapters, hookHarness, makeHookHarness, makeTestHarness, mockFetch, mockRequester, renderHookStatic, testHarness };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { MockResponse } from "../respond-with";
|
|
2
|
+
export type FetchMockOperation = RegExp | string;
|
|
3
|
+
type FetchMockOperationFn = (operation: FetchMockOperation, response: MockResponse<any>) => FetchMockFn;
|
|
4
|
+
export type FetchMockFn = {
|
|
5
|
+
(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
|
6
|
+
mockOperation: FetchMockOperationFn;
|
|
7
|
+
mockOperationOnce: FetchMockOperationFn;
|
|
8
|
+
};
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type Props = {
|
|
3
|
+
propA: string;
|
|
4
|
+
propB?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare const F1: unknown;
|
|
7
|
+
export declare const F2: unknown;
|
|
8
|
+
export declare const F3: unknown;
|
|
9
|
+
declare const _default: {
|
|
10
|
+
title: string;
|
|
11
|
+
component: (props: Props) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
12
|
+
};
|
|
13
|
+
export default _default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { FixtureFn } from "./types";
|
|
3
|
+
/**
|
|
4
|
+
* Describe a group of fixtures for a given component.
|
|
5
|
+
*
|
|
6
|
+
* Only one `fixtures` call should be used per fixture file as it returns
|
|
7
|
+
* the exports for that file.
|
|
8
|
+
*
|
|
9
|
+
* @param {React.ComponentType<any>} Component The component we want to create
|
|
10
|
+
* stories for.
|
|
11
|
+
* @returns {FixtureFn<TProps>} A function to create a CSF compatible story.
|
|
12
|
+
*/
|
|
13
|
+
export declare const fixtures: <TComponent extends React.ComponentType<any>, TProps extends JSX.LibraryManagedAttributes<TComponent, React.ComponentProps<TComponent>>>(Component: TComponent) => FixtureFn<TProps>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
type Props = Record<any, any>;
|
|
3
|
+
export declare const F1: unknown;
|
|
4
|
+
export declare const F2: unknown;
|
|
5
|
+
declare const _default: {
|
|
6
|
+
title: string;
|
|
7
|
+
component: (props: Props) => React.ReactElement<any, string | React.JSXElementConstructor<any>>;
|
|
8
|
+
};
|
|
9
|
+
export default _default;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Options injected to the function that returns the fixture props.
|
|
4
|
+
*/
|
|
5
|
+
export type GetPropsOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* A function to call that will log output.
|
|
8
|
+
*/
|
|
9
|
+
log: (message: string, ...args: Array<any>) => void;
|
|
10
|
+
/**
|
|
11
|
+
* A function to make a handler that will log all arguments with the given
|
|
12
|
+
* name or message. Useful for logging events as it avoids the boilerplate
|
|
13
|
+
* of the `log` function.
|
|
14
|
+
*/
|
|
15
|
+
logHandler: (name: string) => (...args: Array<any>) => void;
|
|
16
|
+
};
|
|
17
|
+
export type FixtureProps<TProps extends object> = Readonly<TProps> | ((options: Readonly<GetPropsOptions>) => Readonly<TProps>);
|
|
18
|
+
/**
|
|
19
|
+
* A function for defining a fixture.
|
|
20
|
+
*/
|
|
21
|
+
export type FixtureFn<TProps extends object> = (
|
|
22
|
+
/**
|
|
23
|
+
* The name of the fixture.
|
|
24
|
+
*/
|
|
25
|
+
description: string,
|
|
26
|
+
/**
|
|
27
|
+
* The props for the fixture or a function that returns the props.
|
|
28
|
+
* The function is injected with an API to facilitate logging.
|
|
29
|
+
*/
|
|
30
|
+
props: FixtureProps<TProps>,
|
|
31
|
+
/**
|
|
32
|
+
* An alternative component to render for the fixture.
|
|
33
|
+
* Useful if the fixture requires some additional setup for testing the
|
|
34
|
+
* component.
|
|
35
|
+
*/
|
|
36
|
+
wrapper?: React.ComponentType<TProps>) => unknown;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { TestHarnessConfigs, TestHarnessAdapters } from "./types";
|
|
3
|
+
type Props<TAdapters extends TestHarnessAdapters> = {
|
|
4
|
+
children: React.ReactNode;
|
|
5
|
+
adapters: TAdapters;
|
|
6
|
+
configs: TestHarnessConfigs<TAdapters>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Render a set of adapters around the given children.
|
|
10
|
+
*
|
|
11
|
+
* Adapters are rendered with the last adapter being the outermost and the first
|
|
12
|
+
* adapter being the innermost, with children being the innermost of all. This
|
|
13
|
+
* ensures that we are backwards compatible with previous releases of the
|
|
14
|
+
* test harness.
|
|
15
|
+
*/
|
|
16
|
+
export declare const Adapt: <TAdapters extends TestHarnessAdapters>({ children, adapters, configs, }: Props<TAdapters>) => React.ReactElement;
|
|
17
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/// <reference path="../../../types/aphrodite.d.ts" />
|
|
2
|
+
/// <reference types="react" />
|
|
3
|
+
import type { TestHarnessConfigs } from "../types";
|
|
4
|
+
/**
|
|
5
|
+
* NOTE: We do not type `DefaultAdapters` with `Adapters` here because we want
|
|
6
|
+
* the individual config types of each adapter to remain intact rather than
|
|
7
|
+
* getting changed to `any`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* The default adapters provided by Wonder Blocks.
|
|
11
|
+
*/
|
|
12
|
+
export declare const DefaultAdapters: {
|
|
13
|
+
readonly boundary: import("../types").TestHarnessAdapter<(error: Error | null | undefined, errorInfo: {
|
|
14
|
+
componentStack: string;
|
|
15
|
+
}) => import("react").ReactNode>;
|
|
16
|
+
readonly css: import("../types").TestHarnessAdapter<string | string[] | import("aphrodite").CSSProperties | {
|
|
17
|
+
/**
|
|
18
|
+
* The default adapters provided by Wonder Blocks.
|
|
19
|
+
*/
|
|
20
|
+
classes: string[];
|
|
21
|
+
style: import("aphrodite").CSSProperties;
|
|
22
|
+
}>;
|
|
23
|
+
readonly portal: import("../types").TestHarnessAdapter<string>;
|
|
24
|
+
readonly router: import("../types").TestHarnessAdapter<string | Readonly<{
|
|
25
|
+
initialEntries: import("history").LocationDescriptor<unknown>[] | undefined;
|
|
26
|
+
initialIndex?: number | undefined;
|
|
27
|
+
getUserConfirmation?: ((message: string, callback: (ok: boolean) => void) => void) | undefined;
|
|
28
|
+
path?: string | undefined;
|
|
29
|
+
} | {
|
|
30
|
+
location: import("history").LocationDescriptor<unknown>;
|
|
31
|
+
forceStatic: true;
|
|
32
|
+
path?: string | undefined;
|
|
33
|
+
} | {
|
|
34
|
+
location: import("history").LocationDescriptor<unknown>;
|
|
35
|
+
path?: string | undefined;
|
|
36
|
+
}>>;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The default configurations to use with the `DefaultAdapters`.
|
|
40
|
+
*/
|
|
41
|
+
export declare const DefaultConfigs: TestHarnessConfigs<typeof DefaultAdapters>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { CSSProperties } from "aphrodite";
|
|
2
|
+
import type { TestHarnessAdapter } from "../types";
|
|
3
|
+
type Config = string | Array<string> | CSSProperties | {
|
|
4
|
+
classes: Array<string>;
|
|
5
|
+
style: CSSProperties;
|
|
6
|
+
};
|
|
7
|
+
export declare const defaultConfig: Config | null | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Test harness adapter for adding CSS to the harnessed component wrapper.
|
|
10
|
+
*/
|
|
11
|
+
export declare const adapter: TestHarnessAdapter<Config>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { TestHarnessAdapter } from "../types";
|
|
3
|
+
type ErrorInfo = {
|
|
4
|
+
componentStack: string;
|
|
5
|
+
};
|
|
6
|
+
type Config = (error: Error | null | undefined, errorInfo: ErrorInfo) => React.ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Default configuration is to not use this adapter.
|
|
9
|
+
*/
|
|
10
|
+
export declare const defaultConfig: Config | null;
|
|
11
|
+
/**
|
|
12
|
+
* Test harness adapter to add error boundary to capture errors during render.
|
|
13
|
+
*/
|
|
14
|
+
export declare const adapter: TestHarnessAdapter<Config>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TestHarnessAdapter } from "../types";
|
|
2
|
+
type Config = string;
|
|
3
|
+
export declare const defaultConfig: Config | null | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Test harness adapter for supporting portals.
|
|
6
|
+
*
|
|
7
|
+
* Some components rely on rendering with a React Portal. This adapter ensures
|
|
8
|
+
* that the DOM contains a mounting point for the portal with the expected
|
|
9
|
+
* identifier.
|
|
10
|
+
*/
|
|
11
|
+
export declare const adapter: TestHarnessAdapter<Config>;
|
|
12
|
+
export {};
|