@khanacademy/wonder-blocks-testing 4.0.0 → 4.0.3
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 +28 -0
- package/dist/es/index.js +20 -276
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-testing
|
|
2
2
|
|
|
3
|
+
## 4.0.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [e5fa4d9e]
|
|
8
|
+
- @khanacademy/wonder-blocks-data@8.0.1
|
|
9
|
+
|
|
10
|
+
## 4.0.2
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- Updated dependencies [1385f468]
|
|
15
|
+
- Updated dependencies [0720470e]
|
|
16
|
+
- Updated dependencies [0720470e]
|
|
17
|
+
- Updated dependencies [cf9ed87f]
|
|
18
|
+
- Updated dependencies [b882b082]
|
|
19
|
+
- Updated dependencies [0720470e]
|
|
20
|
+
- Updated dependencies [75c10036]
|
|
21
|
+
- Updated dependencies [a85f2f3a]
|
|
22
|
+
- Updated dependencies [0720470e]
|
|
23
|
+
- @khanacademy/wonder-blocks-data@8.0.0
|
|
24
|
+
|
|
25
|
+
## 4.0.1
|
|
26
|
+
|
|
27
|
+
### Patch Changes
|
|
28
|
+
|
|
29
|
+
- @khanacademy/wonder-blocks-data@7.0.1
|
|
30
|
+
|
|
3
31
|
## 4.0.0
|
|
4
32
|
|
|
5
33
|
### Major Changes
|
package/dist/es/index.js
CHANGED
|
@@ -3,17 +3,7 @@ import * as React from 'react';
|
|
|
3
3
|
import { action } from '@storybook/addon-actions';
|
|
4
4
|
import { clone } from '@khanacademy/wonder-stuff-core';
|
|
5
5
|
|
|
6
|
-
/**
|
|
7
|
-
* Simple adapter group implementation.
|
|
8
|
-
*/
|
|
9
6
|
class AdapterGroup {
|
|
10
|
-
/**
|
|
11
|
-
* Create an adapter group.
|
|
12
|
-
*
|
|
13
|
-
* @param {CloseGroupFn<TProps, Options, Exports>} closeGroupFn A function
|
|
14
|
-
* to invoke when the group is closed.
|
|
15
|
-
* @param {AdapterGroupOptions} options The options for the group.
|
|
16
|
-
*/
|
|
17
7
|
constructor(closeGroupFn, _options) {
|
|
18
8
|
this.closeGroup = (adapterOptions = null) => {
|
|
19
9
|
if (this._closeGroupFn == null) {
|
|
@@ -51,29 +41,10 @@ class AdapterGroup {
|
|
|
51
41
|
this._options = _options;
|
|
52
42
|
this._fixtures = [];
|
|
53
43
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Close the group.
|
|
56
|
-
*
|
|
57
|
-
* This declares that no more fixtures are to be added to the group,
|
|
58
|
-
* and will call the parent adapter with the declared fixtures so that they
|
|
59
|
-
* can be adapted for the target fixture framework, such as Storybook.
|
|
60
|
-
*/
|
|
61
|
-
|
|
62
44
|
|
|
63
45
|
}
|
|
64
46
|
|
|
65
|
-
/**
|
|
66
|
-
* Class for implementing a custom adapter.
|
|
67
|
-
*/
|
|
68
47
|
class Adapter {
|
|
69
|
-
/**
|
|
70
|
-
* @param {string} name The name of the adapter.
|
|
71
|
-
* @param {CloseGroupFn<any, Options, Exports>} closeGroupFn The function
|
|
72
|
-
* an adapter group should call when the group is closed. This is invoked
|
|
73
|
-
* by an adapter group when it is closed. This function is where an
|
|
74
|
-
* adapter implements the logic to generate the actual fixtures for the
|
|
75
|
-
* adapter's target framework.
|
|
76
|
-
*/
|
|
77
48
|
constructor(name, closeGroupFn) {
|
|
78
49
|
if (typeof name !== "string") {
|
|
79
50
|
throw new TypeError("name must be a string");
|
|
@@ -90,22 +61,10 @@ class Adapter {
|
|
|
90
61
|
this._name = name;
|
|
91
62
|
this._closeGroupFn = closeGroupFn;
|
|
92
63
|
}
|
|
93
|
-
/**
|
|
94
|
-
* The name of the adapter.
|
|
95
|
-
*/
|
|
96
|
-
|
|
97
64
|
|
|
98
65
|
get name() {
|
|
99
66
|
return this._name;
|
|
100
67
|
}
|
|
101
|
-
/**
|
|
102
|
-
* Declare a new fixture group.
|
|
103
|
-
*
|
|
104
|
-
* @param {AdapterGroupOptions} options The options describing the fixture
|
|
105
|
-
* group.
|
|
106
|
-
* @returns {AdapterGroupInterface} The new fixture group.
|
|
107
|
-
*/
|
|
108
|
-
|
|
109
68
|
|
|
110
69
|
declareGroup(options) {
|
|
111
70
|
return new AdapterGroup(this._closeGroupFn, options);
|
|
@@ -113,14 +72,9 @@ class Adapter {
|
|
|
113
72
|
|
|
114
73
|
}
|
|
115
74
|
|
|
116
|
-
/**
|
|
117
|
-
* Get a fixture framework adapter for Storybook support.
|
|
118
|
-
*/
|
|
119
75
|
const getAdapter = (MountingComponent = null) => new Adapter("storybook", ({
|
|
120
76
|
title,
|
|
121
77
|
description: groupDescription,
|
|
122
|
-
// We don't use the default title in Storybook as storybook
|
|
123
|
-
// will generate titles for us if we pass a nullish title.
|
|
124
78
|
getDefaultTitle: _
|
|
125
79
|
}, adapterOptions, declaredFixtures) => {
|
|
126
80
|
const templateMap = new WeakMap();
|
|
@@ -133,43 +87,22 @@ const getAdapter = (MountingComponent = null) => new Adapter("storybook", ({
|
|
|
133
87
|
component: Component
|
|
134
88
|
}, i) => {
|
|
135
89
|
const storyName = `${i + 1} ${description}`;
|
|
136
|
-
const exportName = storyName
|
|
137
|
-
.replace(/\b\w/g, c => c.toUpperCase()) // Remove all non-alphanumeric characters.
|
|
138
|
-
.replace(/[^\w]+/g, "") // Remove all underscores.
|
|
139
|
-
.replace(/[_]+/g, ""); // We create a “template” of how args map to rendering
|
|
140
|
-
// for each type of component as the component here could
|
|
141
|
-
// be the component under test, or wrapped in a wrapper
|
|
142
|
-
// component. We don't use decorators for the wrapper
|
|
143
|
-
// because we may not be in a storybook context and it
|
|
144
|
-
// keeps the framework API simpler this way.
|
|
145
|
-
|
|
90
|
+
const exportName = storyName.replace(/\b\w/g, c => c.toUpperCase()).replace(/[^\w]+/g, "").replace(/[_]+/g, "");
|
|
146
91
|
let Template = templateMap.get(Component);
|
|
147
92
|
|
|
148
93
|
if (Template == null) {
|
|
149
|
-
|
|
150
|
-
// Storybook decorator. It's a React component that
|
|
151
|
-
// takes over rendering the component in the fixture
|
|
152
|
-
// with the given args, allowing for greater
|
|
153
|
-
// customization in a platform-agnostic manner (i.e.
|
|
154
|
-
// not just story format).
|
|
155
|
-
Template = MountingComponent ? args => /*#__PURE__*/React.createElement(MountingComponent, {
|
|
94
|
+
Template = MountingComponent ? args => React.createElement(MountingComponent, {
|
|
156
95
|
component: Component,
|
|
157
96
|
props: args,
|
|
158
97
|
log: log
|
|
159
|
-
}) : args =>
|
|
98
|
+
}) : args => React.createElement(Component, args);
|
|
160
99
|
templateMap.set(Component, Template);
|
|
161
|
-
}
|
|
162
|
-
// template.
|
|
163
|
-
|
|
100
|
+
}
|
|
164
101
|
|
|
165
102
|
acc[exportName] = Template.bind({});
|
|
166
103
|
acc[exportName].args = getProps({
|
|
167
104
|
log
|
|
168
|
-
});
|
|
169
|
-
// care about naming the exports correctly, if we don't
|
|
170
|
-
// want (useful if we need to autogenerate or manually
|
|
171
|
-
// expose ESM exports).
|
|
172
|
-
|
|
105
|
+
});
|
|
173
106
|
acc[exportName].storyName = storyName;
|
|
174
107
|
return acc;
|
|
175
108
|
}, {
|
|
@@ -186,20 +119,9 @@ var adapters = /*#__PURE__*/Object.freeze({
|
|
|
186
119
|
});
|
|
187
120
|
|
|
188
121
|
let _configuration = null;
|
|
189
|
-
/**
|
|
190
|
-
* Setup the fixture framework.
|
|
191
|
-
*/
|
|
192
|
-
|
|
193
122
|
const setup = configuration => {
|
|
194
123
|
_configuration = configuration;
|
|
195
124
|
};
|
|
196
|
-
/**
|
|
197
|
-
* Get the framework configuration.
|
|
198
|
-
*
|
|
199
|
-
* @returns {Configuration} The configuration as provided via setup().
|
|
200
|
-
* @throws {Error} If the configuration has not been set.
|
|
201
|
-
*/
|
|
202
|
-
|
|
203
125
|
const getConfiguration = () => {
|
|
204
126
|
if (_configuration == null) {
|
|
205
127
|
throw new Error("Not configured");
|
|
@@ -208,26 +130,8 @@ const getConfiguration = () => {
|
|
|
208
130
|
return _configuration;
|
|
209
131
|
};
|
|
210
132
|
|
|
211
|
-
/**
|
|
212
|
-
* Combine two values.
|
|
213
|
-
*
|
|
214
|
-
* This method clones val2 before using any of its properties to try to ensure
|
|
215
|
-
* the combined object is not linked back to the original.
|
|
216
|
-
*
|
|
217
|
-
* If the values are objects, it will merge them at the top level. Properties
|
|
218
|
-
* themselves are not merged; val2 properties will overwrite val1 where there
|
|
219
|
-
* are conflicts
|
|
220
|
-
*
|
|
221
|
-
* If the values are arrays, it will concatenate and dedupe them.
|
|
222
|
-
* NOTE: duplicates in either val1 or val2 will also be deduped.
|
|
223
|
-
*
|
|
224
|
-
* If the values are any other type, or val2 has a different type to val1, val2
|
|
225
|
-
* will be returned.
|
|
226
|
-
*/
|
|
227
|
-
|
|
228
133
|
const combineTopLevel = (val1, val2) => {
|
|
229
|
-
const obj2Clone = clone(val2);
|
|
230
|
-
// If not, we will just return val2.
|
|
134
|
+
const obj2Clone = clone(val2);
|
|
231
135
|
|
|
232
136
|
if (val1 !== null && val2 !== null && typeof val1 === "object" && typeof val2 === "object") {
|
|
233
137
|
const val1IsArray = Array.isArray(val1);
|
|
@@ -243,79 +147,26 @@ const combineTopLevel = (val1, val2) => {
|
|
|
243
147
|
return obj2Clone;
|
|
244
148
|
};
|
|
245
149
|
|
|
246
|
-
/**
|
|
247
|
-
* Combine one or more objects into a single object.
|
|
248
|
-
*
|
|
249
|
-
* Objects later in the argument list take precedence over those that are
|
|
250
|
-
* earlier. Object and array values at the root level are merged.
|
|
251
|
-
*/
|
|
252
|
-
|
|
253
150
|
const combineOptions = (...toBeCombined) => {
|
|
254
151
|
const combined = toBeCombined.filter(Boolean).reduce((acc, cur) => {
|
|
255
152
|
for (const key of Object.keys(cur)) {
|
|
256
|
-
// We always call combine, even if acc[key] is undefined
|
|
257
|
-
// because we need to make sure we clone values.
|
|
258
153
|
acc[key] = combineTopLevel(acc[key], cur[key]);
|
|
259
154
|
}
|
|
260
155
|
|
|
261
156
|
return acc;
|
|
262
|
-
}, {});
|
|
263
|
-
// $FlowIgnore[incompatible-return]
|
|
264
|
-
|
|
157
|
+
}, {});
|
|
265
158
|
return combined;
|
|
266
159
|
};
|
|
267
160
|
|
|
268
161
|
const normalizeOptions = componentOrOptions => {
|
|
269
|
-
|
|
270
|
-
// we have to do some type checking.
|
|
271
|
-
//
|
|
272
|
-
// Alternatives I considered were:
|
|
273
|
-
// - Use an additional parameter for the options and then do an arg number
|
|
274
|
-
// check, but that always makes typing a function harder and often breaks
|
|
275
|
-
// types. I didn't want that battle today.
|
|
276
|
-
// - Use a tuple when providing component and options with the first element
|
|
277
|
-
// being the component and the second being the options. However that
|
|
278
|
-
// feels like an obscure API even though it's really easy to do the
|
|
279
|
-
// typing.
|
|
280
|
-
if ( // Most React components, whether functional or class-based, are
|
|
281
|
-
// inherently functions in JavaScript, so a check for functions is
|
|
282
|
-
// usually sufficient.
|
|
283
|
-
typeof componentOrOptions === "function" || // However, the return of React.forwardRef is not a function,
|
|
284
|
-
// so we also have to cope with that.
|
|
285
|
-
// A forwardRef has $$typeof = Symbol(react.forward_ref) and a
|
|
286
|
-
// render function.
|
|
287
|
-
// $FlowIgnore[prop-missing]
|
|
288
|
-
typeof componentOrOptions.render === "function") {
|
|
162
|
+
if (typeof componentOrOptions === "function" || typeof componentOrOptions.render === "function") {
|
|
289
163
|
return {
|
|
290
|
-
// $FlowIgnore[incompatible-return]
|
|
291
164
|
component: componentOrOptions
|
|
292
165
|
};
|
|
293
|
-
}
|
|
294
|
-
// Let's assume our simple heuristic above is sufficient.
|
|
295
|
-
// $FlowIgnore[incompatible-return]
|
|
296
|
-
|
|
166
|
+
}
|
|
297
167
|
|
|
298
168
|
return componentOrOptions;
|
|
299
169
|
};
|
|
300
|
-
/**
|
|
301
|
-
* Describe a group of fixtures for a given component.
|
|
302
|
-
*
|
|
303
|
-
* Only one `fixtures` call should be used per fixture file as it returns
|
|
304
|
-
* the exports for that file.
|
|
305
|
-
*
|
|
306
|
-
* @param {FixtureOptions<TProps>} options Options describing the
|
|
307
|
-
* fixture group.
|
|
308
|
-
* @param {FixtureFn<TProps> => void} fn A function that provides a `fixture`
|
|
309
|
-
* function for defining fixtures.
|
|
310
|
-
* @returns {Exports} The object to be exported as `module.exports`.
|
|
311
|
-
*
|
|
312
|
-
* TODO(somewhatabstract): Determine a way around this requirement so we
|
|
313
|
-
* can support named exports and default exports via the adapters in a
|
|
314
|
-
* deterministic way. Currently this is imposed on us because of how
|
|
315
|
-
* storybook, the popular framework, uses both default and named exports for
|
|
316
|
-
* its interface.
|
|
317
|
-
*/
|
|
318
|
-
|
|
319
170
|
|
|
320
171
|
const fixtures = (componentOrOptions, fn) => {
|
|
321
172
|
var _additionalAdapterOpt;
|
|
@@ -330,13 +181,12 @@ const fixtures = (componentOrOptions, fn) => {
|
|
|
330
181
|
description: groupDescription,
|
|
331
182
|
defaultWrapper,
|
|
332
183
|
additionalAdapterOptions
|
|
333
|
-
} = normalizeOptions(componentOrOptions);
|
|
334
|
-
|
|
184
|
+
} = normalizeOptions(componentOrOptions);
|
|
335
185
|
const group = adapter.declareGroup({
|
|
336
186
|
title,
|
|
337
187
|
description: groupDescription,
|
|
338
188
|
getDefaultTitle: () => component.displayName || component.name || "Component"
|
|
339
|
-
});
|
|
189
|
+
});
|
|
340
190
|
|
|
341
191
|
const addFixture = (description, props, wrapper = null) => {
|
|
342
192
|
var _ref;
|
|
@@ -348,86 +198,38 @@ const fixtures = (componentOrOptions, fn) => {
|
|
|
348
198
|
});
|
|
349
199
|
};
|
|
350
200
|
|
|
351
|
-
fn(addFixture);
|
|
352
|
-
// defaults from our setup.
|
|
353
|
-
|
|
201
|
+
fn(addFixture);
|
|
354
202
|
const groupAdapterOverrides = (_additionalAdapterOpt = additionalAdapterOptions == null ? void 0 : additionalAdapterOptions[adapter.name]) != null ? _additionalAdapterOpt : {};
|
|
355
|
-
const combinedAdapterOptions = combineOptions(defaultAdapterOptions, groupAdapterOverrides);
|
|
356
|
-
|
|
203
|
+
const combinedAdapterOptions = combineOptions(defaultAdapterOptions, groupAdapterOverrides);
|
|
357
204
|
return group.closeGroup(combinedAdapterOptions);
|
|
358
205
|
};
|
|
359
206
|
|
|
360
|
-
// We need a version of Response. When we're in Jest JSDOM environment or a
|
|
361
|
-
// version of Node that supports the fetch API (17 and up, possibly with
|
|
362
|
-
// --experimental-fetch flag), then we're good, but otherwise we need an
|
|
363
|
-
// implementation, so this uses node-fetch as a peer dependency and uses that
|
|
364
|
-
// to provide the implementation if we don't already have one.
|
|
365
207
|
const ResponseImpl = typeof Response === "undefined" ? require("node-fetch").Response : Response;
|
|
366
208
|
|
|
367
|
-
/**
|
|
368
|
-
* Helper for creating a text-based mock response.
|
|
369
|
-
*/
|
|
370
209
|
const textResponse = (text, statusCode = 200) => ({
|
|
371
210
|
type: "text",
|
|
372
211
|
text,
|
|
373
212
|
statusCode
|
|
374
213
|
});
|
|
375
|
-
/**
|
|
376
|
-
* Helper for creating a rejected mock response.
|
|
377
|
-
*/
|
|
378
|
-
|
|
379
214
|
|
|
380
215
|
const rejectResponse = error => ({
|
|
381
216
|
type: "reject",
|
|
382
217
|
error
|
|
383
218
|
});
|
|
384
|
-
/**
|
|
385
|
-
* Helpers to define mock responses for mocked requests.
|
|
386
|
-
*/
|
|
387
|
-
|
|
388
219
|
|
|
389
220
|
const RespondWith = Object.freeze({
|
|
390
|
-
/**
|
|
391
|
-
* Response with text body and status code.
|
|
392
|
-
* Status code defaults to 200.
|
|
393
|
-
*/
|
|
394
221
|
text: (text, statusCode = 200) => textResponse(text, statusCode),
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* Response with JSON body and status code 200.
|
|
398
|
-
*/
|
|
399
222
|
json: json => textResponse(() => JSON.stringify(json)),
|
|
400
|
-
|
|
401
|
-
/**
|
|
402
|
-
* Response with GraphQL data JSON body and status code 200.
|
|
403
|
-
*/
|
|
404
223
|
graphQLData: data => textResponse(() => JSON.stringify({
|
|
405
224
|
data
|
|
406
225
|
})),
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Response with body that will not parse as JSON and status code 200.
|
|
410
|
-
*/
|
|
411
226
|
unparseableBody: () => textResponse("INVALID JSON"),
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Rejects with an AbortError to simulate an aborted request.
|
|
415
|
-
*/
|
|
416
227
|
abortedRequest: () => rejectResponse(() => {
|
|
417
228
|
const abortError = new Error("Mock request aborted");
|
|
418
229
|
abortError.name = "AbortError";
|
|
419
230
|
return abortError;
|
|
420
231
|
}),
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Rejects with the given error.
|
|
424
|
-
*/
|
|
425
232
|
reject: error => rejectResponse(error),
|
|
426
|
-
|
|
427
|
-
/**
|
|
428
|
-
* A non-200 status code with empty text body.
|
|
429
|
-
* Equivalent to calling `ResponseWith.text("", statusCode)`.
|
|
430
|
-
*/
|
|
431
233
|
errorStatusCode: statusCode => {
|
|
432
234
|
if (statusCode < 300) {
|
|
433
235
|
throw new Error(`${statusCode} is not a valid error status code`);
|
|
@@ -435,28 +237,16 @@ const RespondWith = Object.freeze({
|
|
|
435
237
|
|
|
436
238
|
return textResponse("{}", statusCode);
|
|
437
239
|
},
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Response body that is valid JSON but not a valid GraphQL response.
|
|
441
|
-
*/
|
|
442
240
|
nonGraphQLBody: () => textResponse(() => JSON.stringify({
|
|
443
241
|
valid: "json",
|
|
444
242
|
that: "is not a valid graphql response"
|
|
445
243
|
})),
|
|
446
|
-
|
|
447
|
-
/**
|
|
448
|
-
* Response that is a GraphQL errors response with status code 200.
|
|
449
|
-
*/
|
|
450
244
|
graphQLErrors: errorMessages => textResponse(() => JSON.stringify({
|
|
451
245
|
errors: errorMessages.map(e => ({
|
|
452
246
|
message: e
|
|
453
247
|
}))
|
|
454
248
|
}))
|
|
455
249
|
});
|
|
456
|
-
/**
|
|
457
|
-
* Turns a MockResponse value to an actual Response that represents the mock.
|
|
458
|
-
*/
|
|
459
|
-
|
|
460
250
|
const makeMockResponse = response => {
|
|
461
251
|
switch (response.type) {
|
|
462
252
|
case "text":
|
|
@@ -474,13 +264,6 @@ const makeMockResponse = response => {
|
|
|
474
264
|
}
|
|
475
265
|
};
|
|
476
266
|
|
|
477
|
-
/**
|
|
478
|
-
* Get the URL from the given RequestInfo.
|
|
479
|
-
*
|
|
480
|
-
* Since we could be running in Node or in JSDOM, we don't check instance
|
|
481
|
-
* types, but just use a heuristic so that this works without knowing what
|
|
482
|
-
* was polyfilling things.
|
|
483
|
-
*/
|
|
484
267
|
const getHref = input => {
|
|
485
268
|
if (typeof input === "string") {
|
|
486
269
|
return input;
|
|
@@ -492,15 +275,9 @@ const getHref = input => {
|
|
|
492
275
|
throw new Error(`Unsupported input type`);
|
|
493
276
|
}
|
|
494
277
|
};
|
|
495
|
-
/**
|
|
496
|
-
* Determines if a given fetch invocation matches the given mock.
|
|
497
|
-
*/
|
|
498
|
-
|
|
499
278
|
|
|
500
279
|
const fetchRequestMatchesMock = (mock, input, init) => {
|
|
501
|
-
|
|
502
|
-
// This can be a Request, a URL, or a string.
|
|
503
|
-
const href = getHref(input); // Our mock operation is either a string for an exact match, or a regex.
|
|
280
|
+
const href = getHref(input);
|
|
504
281
|
|
|
505
282
|
if (typeof mock === "string") {
|
|
506
283
|
return href === mock;
|
|
@@ -511,21 +288,12 @@ const fetchRequestMatchesMock = (mock, input, init) => {
|
|
|
511
288
|
}
|
|
512
289
|
};
|
|
513
290
|
|
|
514
|
-
/**
|
|
515
|
-
* A generic mock request function for using when mocking fetch or gqlFetch.
|
|
516
|
-
*/
|
|
517
291
|
const mockRequester = (operationMatcher, operationToString) => {
|
|
518
|
-
|
|
519
|
-
// This is the array of mocked operations that we will traverse and
|
|
520
|
-
// manipulate.
|
|
521
|
-
const mocks = []; // What we return has to be a drop in for the fetch function that is
|
|
522
|
-
// provided to `GqlRouter` which is how folks will then use this mock.
|
|
292
|
+
const mocks = [];
|
|
523
293
|
|
|
524
294
|
const mockFn = (...args) => {
|
|
525
|
-
// Iterate our mocked operations and find the first one that matches.
|
|
526
295
|
for (const mock of mocks) {
|
|
527
296
|
if (mock.onceOnly && mock.used) {
|
|
528
|
-
// This is a once-only mock and it has been used, so skip it.
|
|
529
297
|
continue;
|
|
530
298
|
}
|
|
531
299
|
|
|
@@ -533,9 +301,7 @@ const mockRequester = (operationMatcher, operationToString) => {
|
|
|
533
301
|
mock.used = true;
|
|
534
302
|
return mock.response();
|
|
535
303
|
}
|
|
536
|
-
}
|
|
537
|
-
// we rejected.
|
|
538
|
-
|
|
304
|
+
}
|
|
539
305
|
|
|
540
306
|
return Promise.reject(new Error(`No matching mock response found for request:
|
|
541
307
|
${operationToString.apply(void 0, args)}`));
|
|
@@ -560,19 +326,10 @@ const mockRequester = (operationMatcher, operationToString) => {
|
|
|
560
326
|
return mockFn;
|
|
561
327
|
};
|
|
562
328
|
|
|
563
|
-
/**
|
|
564
|
-
* A mock for the fetch function passed to GqlRouter.
|
|
565
|
-
*/
|
|
566
329
|
const mockFetch = () => mockRequester(fetchRequestMatchesMock, (input, init) => `Input: ${typeof input === "string" ? input : JSON.stringify(input, null, 2)}
|
|
567
330
|
Options: ${init == null ? "None" : JSON.stringify(init, null, 2)}`);
|
|
568
331
|
|
|
569
|
-
const safeHasOwnProperty = (obj, prop) =>
|
|
570
|
-
// $FlowFixMe[method-unbinding]
|
|
571
|
-
Object.prototype.hasOwnProperty.call(obj, prop); // TODO(somewhatabstract, FEI-4268): use a third-party library to do this and
|
|
572
|
-
// possibly make it also support the jest `jest.objectContaining` type matching
|
|
573
|
-
// to simplify mock declaration (note that it would need to work in regular
|
|
574
|
-
// tests and stories/fixtures).
|
|
575
|
-
|
|
332
|
+
const safeHasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
|
|
576
333
|
|
|
577
334
|
const areObjectsEqual = (a, b) => {
|
|
578
335
|
if (a === b) {
|
|
@@ -606,38 +363,25 @@ const areObjectsEqual = (a, b) => {
|
|
|
606
363
|
};
|
|
607
364
|
|
|
608
365
|
const gqlRequestMatchesMock = (mock, operation, variables, context) => {
|
|
609
|
-
// If they don't represent the same operation, then they can't match.
|
|
610
|
-
// NOTE: Operations can include more fields than id and type, but we only
|
|
611
|
-
// care about id and type. The rest is ignored.
|
|
612
366
|
if (mock.operation.id !== operation.id || mock.operation.type !== operation.type) {
|
|
613
367
|
return false;
|
|
614
|
-
}
|
|
615
|
-
// we just assume it matches everything.
|
|
616
|
-
|
|
368
|
+
}
|
|
617
369
|
|
|
618
370
|
if (mock.variables != null) {
|
|
619
|
-
// Variables have to match.
|
|
620
371
|
if (!areObjectsEqual(mock.variables, variables)) {
|
|
621
372
|
return false;
|
|
622
373
|
}
|
|
623
|
-
}
|
|
624
|
-
// we just assume it matches everything.
|
|
625
|
-
|
|
374
|
+
}
|
|
626
375
|
|
|
627
376
|
if (mock.context != null) {
|
|
628
|
-
// Context has to match.
|
|
629
377
|
if (!areObjectsEqual(mock.context, context)) {
|
|
630
378
|
return false;
|
|
631
379
|
}
|
|
632
|
-
}
|
|
633
|
-
|
|
380
|
+
}
|
|
634
381
|
|
|
635
382
|
return true;
|
|
636
383
|
};
|
|
637
384
|
|
|
638
|
-
/**
|
|
639
|
-
* A mock for the fetch function passed to GqlRouter.
|
|
640
|
-
*/
|
|
641
385
|
const mockGqlFetch = () => mockRequester(gqlRequestMatchesMock, (operation, variables, context) => `Operation: ${operation.type} ${operation.id}
|
|
642
386
|
Variables: ${variables == null ? "None" : JSON.stringify(variables, null, 2)}
|
|
643
387
|
Context: ${JSON.stringify(context, null, 2)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.3",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@babel/runtime": "^7.16.3",
|
|
17
|
-
"@khanacademy/wonder-blocks-data": "^
|
|
17
|
+
"@khanacademy/wonder-blocks-data": "^8.0.1"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^0.1.2",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"react": "16.14.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"wb-dev-build-settings": "^0.
|
|
27
|
+
"wb-dev-build-settings": "^0.4.0"
|
|
28
28
|
},
|
|
29
29
|
"author": "",
|
|
30
30
|
"license": "MIT"
|