@khanacademy/wonder-blocks-testing 1.0.0 → 2.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 +10 -0
- package/dist/es/index.js +200 -24
- package/dist/index.js +249 -52
- package/package.json +4 -3
- package/src/fixtures/__tests__/setup.test.js +6 -4
- package/src/index.js +17 -3
- package/src/jest/isolate-modules.js +0 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-testing
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 274caaac: Remove isolateModules (now implemented by @khanacademy/wonder-stuff-testing), export GQL framework, export fixture framework types
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- @khanacademy/wonder-blocks-data@3.1.2
|
|
12
|
+
|
|
3
13
|
## 1.0.0
|
|
4
14
|
|
|
5
15
|
### Major Changes
|
package/dist/es/index.js
CHANGED
|
@@ -319,36 +319,212 @@ const fixtures = (options, fn) => {
|
|
|
319
319
|
return group.closeGroup(combinedAdapterOptions);
|
|
320
320
|
};
|
|
321
321
|
|
|
322
|
-
|
|
322
|
+
const safeHasOwnProperty = (obj, prop) => // Flow really shouldn't be raising this error here.
|
|
323
|
+
// $FlowFixMe[method-unbinding]
|
|
324
|
+
Object.prototype.hasOwnProperty.call(obj, prop); // TODO(somewhatabstract, FEI-4268): use a third-party library to do this and
|
|
325
|
+
// possibly make it also support the jest `jest.objectContaining` type matching
|
|
326
|
+
// to simplify mock declaration (note that it would need to work in regular
|
|
327
|
+
// tests and stories/fixtures).
|
|
323
328
|
|
|
324
|
-
|
|
329
|
+
|
|
330
|
+
const areObjectsEqual = (a, b) => {
|
|
331
|
+
if (a === b) {
|
|
332
|
+
return true;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (a == null || b == null) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (typeof a !== "object" || typeof b !== "object") {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const aKeys = Object.keys(a);
|
|
344
|
+
const bKeys = Object.keys(b);
|
|
345
|
+
|
|
346
|
+
if (aKeys.length !== bKeys.length) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
351
|
+
const key = aKeys[i];
|
|
352
|
+
|
|
353
|
+
if (!safeHasOwnProperty(b, key) || !areObjectsEqual(a[key], b[key])) {
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return true;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const gqlRequestMatchesMock = (mock, operation, variables, context) => {
|
|
362
|
+
// If they don't represent the same operation, then they can't match.
|
|
363
|
+
// NOTE: Operations can include more fields than id and type, but we only
|
|
364
|
+
// care about id and type. The rest is ignored.
|
|
365
|
+
if (mock.operation.id !== operation.id || mock.operation.type !== operation.type) {
|
|
366
|
+
return false;
|
|
367
|
+
} // We do a loose match, so if the lhs doesn't define variables,
|
|
368
|
+
// we just assume it matches everything.
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
if (mock.variables != null) {
|
|
372
|
+
// Variables have to match.
|
|
373
|
+
if (!areObjectsEqual(mock.variables, variables)) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
} // We do a loose match, so if the lhs doesn't define context,
|
|
377
|
+
// we just assume it matches everything.
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
if (mock.context != null) {
|
|
381
|
+
// Context has to match.
|
|
382
|
+
if (!areObjectsEqual(mock.context, context)) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
} // If we get here, we have a match.
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
return true;
|
|
389
|
+
};
|
|
325
390
|
|
|
326
391
|
/**
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
* This is a helper for the `jest.isolateModules` API, allowing
|
|
330
|
-
* code to avoid the clunky closure syntax in their tests.
|
|
331
|
-
*
|
|
332
|
-
* @param {() => T} action The action that contains the isolated module imports.
|
|
333
|
-
* We do it this way so that any `require` calls are relative to the calling
|
|
334
|
-
* code and not this function. Note that we don't support promises here to
|
|
335
|
-
* discourage dynamic `import` use, which doesn't play well with standard
|
|
336
|
-
* jest yet.
|
|
392
|
+
* Helpers to define rejection states for mocking GQL requests.
|
|
337
393
|
*/
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
394
|
+
const RespondWith = Object.freeze({
|
|
395
|
+
data: data => ({
|
|
396
|
+
type: "data",
|
|
397
|
+
data
|
|
398
|
+
}),
|
|
399
|
+
unparseableBody: () => ({
|
|
400
|
+
type: "parse"
|
|
401
|
+
}),
|
|
402
|
+
abortedRequest: () => ({
|
|
403
|
+
type: "abort"
|
|
404
|
+
}),
|
|
405
|
+
errorStatusCode: statusCode => {
|
|
406
|
+
if (statusCode < 300) {
|
|
407
|
+
throw new Error(`${statusCode} is not a valid error status code`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return {
|
|
411
|
+
type: "status",
|
|
412
|
+
statusCode
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
nonGraphQLBody: () => ({
|
|
416
|
+
type: "invalid"
|
|
417
|
+
}),
|
|
418
|
+
graphQLErrors: errorMessages => ({
|
|
419
|
+
type: "graphql",
|
|
420
|
+
errors: errorMessages
|
|
421
|
+
})
|
|
422
|
+
});
|
|
423
|
+
/**
|
|
424
|
+
* Turns an ErrorResponse value in an actual Response that will invoke
|
|
425
|
+
* that error.
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
const makeGqlMockResponse = response => {
|
|
429
|
+
switch (response.type) {
|
|
430
|
+
case "data":
|
|
431
|
+
return Promise.resolve({
|
|
432
|
+
status: 200,
|
|
433
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
434
|
+
data: response.data
|
|
435
|
+
}))
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
case "parse":
|
|
439
|
+
return Promise.resolve({
|
|
440
|
+
status: 200,
|
|
441
|
+
text: () => Promise.resolve("INVALID JSON")
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
case "abort":
|
|
445
|
+
const abortError = new Error("Mock request aborted");
|
|
446
|
+
abortError.name = "AbortError";
|
|
447
|
+
return Promise.reject(abortError);
|
|
448
|
+
|
|
449
|
+
case "status":
|
|
450
|
+
return Promise.resolve({
|
|
451
|
+
status: response.statusCode,
|
|
452
|
+
text: () => Promise.resolve(JSON.stringify({}))
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
case "invalid":
|
|
456
|
+
return Promise.resolve({
|
|
457
|
+
status: 200,
|
|
458
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
459
|
+
valid: "json",
|
|
460
|
+
that: "is not a valid graphql response"
|
|
461
|
+
}))
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
case "graphql":
|
|
465
|
+
return Promise.resolve({
|
|
466
|
+
status: 200,
|
|
467
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
468
|
+
errors: response.errors.map(e => ({
|
|
469
|
+
message: e
|
|
470
|
+
}))
|
|
471
|
+
}))
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
default:
|
|
475
|
+
throw new Error(`Unknown response type: ${response.type}`);
|
|
341
476
|
}
|
|
477
|
+
};
|
|
342
478
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
// We
|
|
348
|
-
//
|
|
349
|
-
//
|
|
479
|
+
/**
|
|
480
|
+
* A mock for the fetch function passed to GqlRouter.
|
|
481
|
+
*/
|
|
482
|
+
const mockGqlFetch = () => {
|
|
483
|
+
// We want this to work in jest and in fixtures to make life easy for folks.
|
|
484
|
+
// This is the array of mocked operations that we will traverse and
|
|
485
|
+
// manipulate.
|
|
486
|
+
const mocks = []; // What we return has to be a drop in for the fetch function that is
|
|
487
|
+
// provided to `GqlRouter` which is how folks will then use this mock.
|
|
488
|
+
|
|
489
|
+
const gqlFetchMock = (operation, variables, context) => {
|
|
490
|
+
// Iterate our mocked operations and find the first one that matches.
|
|
491
|
+
for (const mock of mocks) {
|
|
492
|
+
if (mock.onceOnly && mock.used) {
|
|
493
|
+
// This is a once-only mock and it has been used, so skip it.
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
if (gqlRequestMatchesMock(mock.operation, operation, variables, context)) {
|
|
498
|
+
mock.used = true;
|
|
499
|
+
return mock.response();
|
|
500
|
+
}
|
|
501
|
+
} // Default is to reject with some helpful info on what request
|
|
502
|
+
// we rejected.
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
return Promise.reject(new Error(`No matching GraphQL mock response found for request:
|
|
506
|
+
Operation: ${operation.type} ${operation.id}
|
|
507
|
+
Variables: ${variables == null ? "None" : JSON.stringify(variables, null, 2)}
|
|
508
|
+
Context: ${JSON.stringify(context, null, 2)}`));
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
const addMockedOperation = (operation, response, onceOnly) => {
|
|
512
|
+
const mockResponse = () => makeGqlMockResponse(response);
|
|
513
|
+
|
|
514
|
+
mocks.push({
|
|
515
|
+
operation,
|
|
516
|
+
response: mockResponse,
|
|
517
|
+
onceOnly,
|
|
518
|
+
used: false
|
|
519
|
+
});
|
|
520
|
+
return gqlFetchMock;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
gqlFetchMock.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
|
|
524
|
+
|
|
525
|
+
gqlFetchMock.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
|
|
350
526
|
|
|
351
|
-
return
|
|
527
|
+
return gqlFetchMock;
|
|
352
528
|
};
|
|
353
529
|
|
|
354
|
-
export { adapters, fixtures,
|
|
530
|
+
export { RespondWith, adapters, fixtures, mockGqlFetch, setup as setupFixtures };
|
package/dist/index.js
CHANGED
|
@@ -82,7 +82,7 @@ module.exports =
|
|
|
82
82
|
/******/
|
|
83
83
|
/******/
|
|
84
84
|
/******/ // Load entry module and return exports
|
|
85
|
-
/******/ return __webpack_require__(__webpack_require__.s =
|
|
85
|
+
/******/ return __webpack_require__(__webpack_require__.s = 14);
|
|
86
86
|
/******/ })
|
|
87
87
|
/************************************************************************/
|
|
88
88
|
/******/ ([
|
|
@@ -117,32 +117,127 @@ const getConfiguration = () => {
|
|
|
117
117
|
|
|
118
118
|
/***/ }),
|
|
119
119
|
/* 1 */
|
|
120
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
121
|
+
|
|
122
|
+
"use strict";
|
|
123
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return RespondWith; });
|
|
124
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return makeGqlMockResponse; });
|
|
125
|
+
/**
|
|
126
|
+
* Helpers to define rejection states for mocking GQL requests.
|
|
127
|
+
*/
|
|
128
|
+
const RespondWith = Object.freeze({
|
|
129
|
+
data: data => ({
|
|
130
|
+
type: "data",
|
|
131
|
+
data
|
|
132
|
+
}),
|
|
133
|
+
unparseableBody: () => ({
|
|
134
|
+
type: "parse"
|
|
135
|
+
}),
|
|
136
|
+
abortedRequest: () => ({
|
|
137
|
+
type: "abort"
|
|
138
|
+
}),
|
|
139
|
+
errorStatusCode: statusCode => {
|
|
140
|
+
if (statusCode < 300) {
|
|
141
|
+
throw new Error(`${statusCode} is not a valid error status code`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
type: "status",
|
|
146
|
+
statusCode
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
nonGraphQLBody: () => ({
|
|
150
|
+
type: "invalid"
|
|
151
|
+
}),
|
|
152
|
+
graphQLErrors: errorMessages => ({
|
|
153
|
+
type: "graphql",
|
|
154
|
+
errors: errorMessages
|
|
155
|
+
})
|
|
156
|
+
});
|
|
157
|
+
/**
|
|
158
|
+
* Turns an ErrorResponse value in an actual Response that will invoke
|
|
159
|
+
* that error.
|
|
160
|
+
*/
|
|
161
|
+
|
|
162
|
+
const makeGqlMockResponse = response => {
|
|
163
|
+
switch (response.type) {
|
|
164
|
+
case "data":
|
|
165
|
+
return Promise.resolve({
|
|
166
|
+
status: 200,
|
|
167
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
168
|
+
data: response.data
|
|
169
|
+
}))
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
case "parse":
|
|
173
|
+
return Promise.resolve({
|
|
174
|
+
status: 200,
|
|
175
|
+
text: () => Promise.resolve("INVALID JSON")
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
case "abort":
|
|
179
|
+
const abortError = new Error("Mock request aborted");
|
|
180
|
+
abortError.name = "AbortError";
|
|
181
|
+
return Promise.reject(abortError);
|
|
182
|
+
|
|
183
|
+
case "status":
|
|
184
|
+
return Promise.resolve({
|
|
185
|
+
status: response.statusCode,
|
|
186
|
+
text: () => Promise.resolve(JSON.stringify({}))
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
case "invalid":
|
|
190
|
+
return Promise.resolve({
|
|
191
|
+
status: 200,
|
|
192
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
193
|
+
valid: "json",
|
|
194
|
+
that: "is not a valid graphql response"
|
|
195
|
+
}))
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
case "graphql":
|
|
199
|
+
return Promise.resolve({
|
|
200
|
+
status: 200,
|
|
201
|
+
text: () => Promise.resolve(JSON.stringify({
|
|
202
|
+
errors: response.errors.map(e => ({
|
|
203
|
+
message: e
|
|
204
|
+
}))
|
|
205
|
+
}))
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
default:
|
|
209
|
+
throw new Error(`Unknown response type: ${response.type}`);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/***/ }),
|
|
214
|
+
/* 2 */
|
|
120
215
|
/***/ (function(module, exports) {
|
|
121
216
|
|
|
122
217
|
module.exports = require("react");
|
|
123
218
|
|
|
124
219
|
/***/ }),
|
|
125
|
-
/*
|
|
220
|
+
/* 3 */
|
|
126
221
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
127
222
|
|
|
128
223
|
"use strict";
|
|
129
224
|
__webpack_require__.r(__webpack_exports__);
|
|
130
|
-
/* harmony import */ var _storybook_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
225
|
+
/* harmony import */ var _storybook_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4);
|
|
131
226
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "storybook", function() { return _storybook_js__WEBPACK_IMPORTED_MODULE_0__["a"]; });
|
|
132
227
|
|
|
133
228
|
|
|
134
229
|
|
|
135
230
|
/***/ }),
|
|
136
|
-
/*
|
|
231
|
+
/* 4 */
|
|
137
232
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
138
233
|
|
|
139
234
|
"use strict";
|
|
140
235
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getAdapter; });
|
|
141
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
236
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
|
|
142
237
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
143
|
-
/* harmony import */ var _storybook_addon_actions__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
238
|
+
/* harmony import */ var _storybook_addon_actions__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(7);
|
|
144
239
|
/* harmony import */ var _storybook_addon_actions__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(_storybook_addon_actions__WEBPACK_IMPORTED_MODULE_1__);
|
|
145
|
-
/* harmony import */ var _adapter_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
240
|
+
/* harmony import */ var _adapter_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(8);
|
|
146
241
|
|
|
147
242
|
|
|
148
243
|
|
|
@@ -215,15 +310,15 @@ const getAdapter = (MountingComponent = null) => new _adapter_js__WEBPACK_IMPORT
|
|
|
215
310
|
});
|
|
216
311
|
|
|
217
312
|
/***/ }),
|
|
218
|
-
/*
|
|
313
|
+
/* 5 */
|
|
219
314
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
220
315
|
|
|
221
316
|
"use strict";
|
|
222
317
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return fixtures; });
|
|
223
|
-
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
318
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
|
|
224
319
|
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
225
320
|
/* harmony import */ var _setup_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(0);
|
|
226
|
-
/* harmony import */ var _combine_options_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(
|
|
321
|
+
/* harmony import */ var _combine_options_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(10);
|
|
227
322
|
|
|
228
323
|
|
|
229
324
|
|
|
@@ -286,56 +381,80 @@ const fixtures = (options, fn) => {
|
|
|
286
381
|
};
|
|
287
382
|
|
|
288
383
|
/***/ }),
|
|
289
|
-
/*
|
|
384
|
+
/* 6 */
|
|
290
385
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
291
386
|
|
|
292
387
|
"use strict";
|
|
293
|
-
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return
|
|
294
|
-
|
|
388
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return mockGqlFetch; });
|
|
389
|
+
/* harmony import */ var _gql_request_matches_mock_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(13);
|
|
390
|
+
/* harmony import */ var _make_gql_mock_response_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(1);
|
|
391
|
+
|
|
295
392
|
|
|
296
|
-
/* istanbul ignore file */
|
|
297
393
|
|
|
298
394
|
/**
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
* This is a helper for the `jest.isolateModules` API, allowing
|
|
302
|
-
* code to avoid the clunky closure syntax in their tests.
|
|
303
|
-
*
|
|
304
|
-
* @param {() => T} action The action that contains the isolated module imports.
|
|
305
|
-
* We do it this way so that any `require` calls are relative to the calling
|
|
306
|
-
* code and not this function. Note that we don't support promises here to
|
|
307
|
-
* discourage dynamic `import` use, which doesn't play well with standard
|
|
308
|
-
* jest yet.
|
|
395
|
+
* A mock for the fetch function passed to GqlRouter.
|
|
309
396
|
*/
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
397
|
+
const mockGqlFetch = () => {
|
|
398
|
+
// We want this to work in jest and in fixtures to make life easy for folks.
|
|
399
|
+
// This is the array of mocked operations that we will traverse and
|
|
400
|
+
// manipulate.
|
|
401
|
+
const mocks = []; // What we return has to be a drop in for the fetch function that is
|
|
402
|
+
// provided to `GqlRouter` which is how folks will then use this mock.
|
|
403
|
+
|
|
404
|
+
const gqlFetchMock = (operation, variables, context) => {
|
|
405
|
+
// Iterate our mocked operations and find the first one that matches.
|
|
406
|
+
for (const mock of mocks) {
|
|
407
|
+
if (mock.onceOnly && mock.used) {
|
|
408
|
+
// This is a once-only mock and it has been used, so skip it.
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (Object(_gql_request_matches_mock_js__WEBPACK_IMPORTED_MODULE_0__[/* gqlRequestMatchesMock */ "a"])(mock.operation, operation, variables, context)) {
|
|
413
|
+
mock.used = true;
|
|
414
|
+
return mock.response();
|
|
415
|
+
}
|
|
416
|
+
} // Default is to reject with some helpful info on what request
|
|
417
|
+
// we rejected.
|
|
314
418
|
|
|
315
|
-
let result = undefined;
|
|
316
|
-
jest.isolateModules(() => {
|
|
317
|
-
result = action();
|
|
318
|
-
}); // We know that we'll have a result of the appropriate type at this point.
|
|
319
|
-
// We could use a promise to make everything happy, but this doesn't need
|
|
320
|
-
// to be async, so why bother.
|
|
321
|
-
// $FlowIgnore[incompatible-return]
|
|
322
419
|
|
|
323
|
-
|
|
420
|
+
return Promise.reject(new Error(`No matching GraphQL mock response found for request:
|
|
421
|
+
Operation: ${operation.type} ${operation.id}
|
|
422
|
+
Variables: ${variables == null ? "None" : JSON.stringify(variables, null, 2)}
|
|
423
|
+
Context: ${JSON.stringify(context, null, 2)}`));
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
const addMockedOperation = (operation, response, onceOnly) => {
|
|
427
|
+
const mockResponse = () => Object(_make_gql_mock_response_js__WEBPACK_IMPORTED_MODULE_1__[/* makeGqlMockResponse */ "b"])(response);
|
|
428
|
+
|
|
429
|
+
mocks.push({
|
|
430
|
+
operation,
|
|
431
|
+
response: mockResponse,
|
|
432
|
+
onceOnly,
|
|
433
|
+
used: false
|
|
434
|
+
});
|
|
435
|
+
return gqlFetchMock;
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
gqlFetchMock.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
|
|
439
|
+
|
|
440
|
+
gqlFetchMock.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
|
|
441
|
+
|
|
442
|
+
return gqlFetchMock;
|
|
324
443
|
};
|
|
325
444
|
|
|
326
445
|
/***/ }),
|
|
327
|
-
/*
|
|
446
|
+
/* 7 */
|
|
328
447
|
/***/ (function(module, exports) {
|
|
329
448
|
|
|
330
449
|
module.exports = require("@storybook/addon-actions");
|
|
331
450
|
|
|
332
451
|
/***/ }),
|
|
333
|
-
/*
|
|
452
|
+
/* 8 */
|
|
334
453
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
335
454
|
|
|
336
455
|
"use strict";
|
|
337
456
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return Adapter; });
|
|
338
|
-
/* harmony import */ var _adapter_group_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
457
|
+
/* harmony import */ var _adapter_group_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9);
|
|
339
458
|
|
|
340
459
|
|
|
341
460
|
/**
|
|
@@ -390,7 +509,7 @@ class Adapter {
|
|
|
390
509
|
}
|
|
391
510
|
|
|
392
511
|
/***/ }),
|
|
393
|
-
/*
|
|
512
|
+
/* 9 */
|
|
394
513
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
395
514
|
|
|
396
515
|
"use strict";
|
|
@@ -455,12 +574,12 @@ class AdapterGroup {
|
|
|
455
574
|
}
|
|
456
575
|
|
|
457
576
|
/***/ }),
|
|
458
|
-
/*
|
|
577
|
+
/* 10 */
|
|
459
578
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
460
579
|
|
|
461
580
|
"use strict";
|
|
462
581
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return combineOptions; });
|
|
463
|
-
/* harmony import */ var _combine_top_level_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
582
|
+
/* harmony import */ var _combine_top_level_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(11);
|
|
464
583
|
|
|
465
584
|
/**
|
|
466
585
|
* Combine one or more objects into a single object.
|
|
@@ -485,12 +604,12 @@ const combineOptions = (...toBeCombined) => {
|
|
|
485
604
|
};
|
|
486
605
|
|
|
487
606
|
/***/ }),
|
|
488
|
-
/*
|
|
607
|
+
/* 11 */
|
|
489
608
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
490
609
|
|
|
491
610
|
"use strict";
|
|
492
611
|
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return combineTopLevel; });
|
|
493
|
-
/* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
612
|
+
/* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12);
|
|
494
613
|
/* harmony import */ var _khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__);
|
|
495
614
|
|
|
496
615
|
/**
|
|
@@ -531,34 +650,112 @@ const combineTopLevel = (val1, val2) => {
|
|
|
531
650
|
};
|
|
532
651
|
|
|
533
652
|
/***/ }),
|
|
534
|
-
/*
|
|
653
|
+
/* 12 */
|
|
535
654
|
/***/ (function(module, exports) {
|
|
536
655
|
|
|
537
656
|
module.exports = require("@khanacademy/wonder-stuff-core");
|
|
538
657
|
|
|
539
658
|
/***/ }),
|
|
540
|
-
/*
|
|
659
|
+
/* 13 */
|
|
660
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
661
|
+
|
|
662
|
+
"use strict";
|
|
663
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return gqlRequestMatchesMock; });
|
|
664
|
+
const safeHasOwnProperty = (obj, prop) => // Flow really shouldn't be raising this error here.
|
|
665
|
+
// $FlowFixMe[method-unbinding]
|
|
666
|
+
Object.prototype.hasOwnProperty.call(obj, prop); // TODO(somewhatabstract, FEI-4268): use a third-party library to do this and
|
|
667
|
+
// possibly make it also support the jest `jest.objectContaining` type matching
|
|
668
|
+
// to simplify mock declaration (note that it would need to work in regular
|
|
669
|
+
// tests and stories/fixtures).
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
const areObjectsEqual = (a, b) => {
|
|
673
|
+
if (a === b) {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
if (a == null || b == null) {
|
|
678
|
+
return false;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
if (typeof a !== "object" || typeof b !== "object") {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const aKeys = Object.keys(a);
|
|
686
|
+
const bKeys = Object.keys(b);
|
|
687
|
+
|
|
688
|
+
if (aKeys.length !== bKeys.length) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
693
|
+
const key = aKeys[i];
|
|
694
|
+
|
|
695
|
+
if (!safeHasOwnProperty(b, key) || !areObjectsEqual(a[key], b[key])) {
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return true;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const gqlRequestMatchesMock = (mock, operation, variables, context) => {
|
|
704
|
+
// If they don't represent the same operation, then they can't match.
|
|
705
|
+
// NOTE: Operations can include more fields than id and type, but we only
|
|
706
|
+
// care about id and type. The rest is ignored.
|
|
707
|
+
if (mock.operation.id !== operation.id || mock.operation.type !== operation.type) {
|
|
708
|
+
return false;
|
|
709
|
+
} // We do a loose match, so if the lhs doesn't define variables,
|
|
710
|
+
// we just assume it matches everything.
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
if (mock.variables != null) {
|
|
714
|
+
// Variables have to match.
|
|
715
|
+
if (!areObjectsEqual(mock.variables, variables)) {
|
|
716
|
+
return false;
|
|
717
|
+
}
|
|
718
|
+
} // We do a loose match, so if the lhs doesn't define context,
|
|
719
|
+
// we just assume it matches everything.
|
|
720
|
+
|
|
721
|
+
|
|
722
|
+
if (mock.context != null) {
|
|
723
|
+
// Context has to match.
|
|
724
|
+
if (!areObjectsEqual(mock.context, context)) {
|
|
725
|
+
return false;
|
|
726
|
+
}
|
|
727
|
+
} // If we get here, we have a match.
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
return true;
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
/***/ }),
|
|
734
|
+
/* 14 */
|
|
541
735
|
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
542
736
|
|
|
543
737
|
"use strict";
|
|
544
738
|
__webpack_require__.r(__webpack_exports__);
|
|
545
|
-
/* harmony import */ var _fixtures_adapters_adapters_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
|
|
739
|
+
/* harmony import */ var _fixtures_adapters_adapters_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
|
|
546
740
|
/* harmony reexport (module object) */ __webpack_require__.d(__webpack_exports__, "adapters", function() { return _fixtures_adapters_adapters_js__WEBPACK_IMPORTED_MODULE_0__; });
|
|
547
|
-
/* harmony import */ var _fixtures_fixtures_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(
|
|
741
|
+
/* harmony import */ var _fixtures_fixtures_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(5);
|
|
548
742
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fixtures", function() { return _fixtures_fixtures_js__WEBPACK_IMPORTED_MODULE_1__["a"]; });
|
|
549
743
|
|
|
550
744
|
/* harmony import */ var _fixtures_setup_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(0);
|
|
551
745
|
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "setupFixtures", function() { return _fixtures_setup_js__WEBPACK_IMPORTED_MODULE_2__["b"]; });
|
|
552
746
|
|
|
553
|
-
/* harmony import */ var
|
|
554
|
-
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "
|
|
747
|
+
/* harmony import */ var _gql_mock_gql_fetch_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6);
|
|
748
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mockGqlFetch", function() { return _gql_mock_gql_fetch_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
|
|
749
|
+
|
|
750
|
+
/* harmony import */ var _gql_make_gql_mock_response_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(1);
|
|
751
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "RespondWith", function() { return _gql_make_gql_mock_response_js__WEBPACK_IMPORTED_MODULE_4__["a"]; });
|
|
555
752
|
|
|
556
753
|
// Fixtures framework
|
|
557
754
|
|
|
558
755
|
|
|
559
756
|
|
|
560
|
-
|
|
561
|
-
//
|
|
757
|
+
|
|
758
|
+
// GraphQL framework
|
|
562
759
|
|
|
563
760
|
|
|
564
761
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"design": "v1",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
16
|
"@babel/runtime": "^7.16.3",
|
|
17
|
-
"@khanacademy/wonder-blocks-data": "^3.1.
|
|
17
|
+
"@khanacademy/wonder-blocks-data": "^3.1.2"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^0.1.2",
|
|
21
|
+
"@khanacademy/wonder-stuff-testing": "^0.0.2",
|
|
21
22
|
"@storybook/addon-actions": "^6.4.8",
|
|
22
23
|
"react": "16.14.0"
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
|
-
"wb-dev-build-settings": "^0.
|
|
26
|
+
"wb-dev-build-settings": "^0.3.0"
|
|
26
27
|
},
|
|
27
28
|
"author": "",
|
|
28
29
|
"license": "MIT"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// @flow
|
|
2
|
-
import {
|
|
2
|
+
import {jest as wst} from "@khanacademy/wonder-stuff-testing";
|
|
3
3
|
|
|
4
4
|
describe("#getConfiguration", () => {
|
|
5
5
|
it("should return the configuration passed during setup", () => {
|
|
6
6
|
// Arrange
|
|
7
|
-
const {setup, getConfiguration} = isolateModules(() =>
|
|
7
|
+
const {setup, getConfiguration} = wst.isolateModules(() =>
|
|
8
8
|
require("../setup.js"),
|
|
9
9
|
);
|
|
10
10
|
const configuration = {
|
|
@@ -25,7 +25,9 @@ describe("#getConfiguration", () => {
|
|
|
25
25
|
|
|
26
26
|
it("should throw if setup has not been performed", () => {
|
|
27
27
|
// Arrange
|
|
28
|
-
const {getConfiguration} = isolateModules(() =>
|
|
28
|
+
const {getConfiguration} = wst.isolateModules(() =>
|
|
29
|
+
require("../setup.js"),
|
|
30
|
+
);
|
|
29
31
|
|
|
30
32
|
// Act
|
|
31
33
|
const underTest = () => getConfiguration();
|
|
@@ -40,7 +42,7 @@ describe("#getConfiguration", () => {
|
|
|
40
42
|
describe("#setup", () => {
|
|
41
43
|
it("should set the configuration returned by getConfiguration", () => {
|
|
42
44
|
// Arrange
|
|
43
|
-
const {setup, getConfiguration} = isolateModules(() =>
|
|
45
|
+
const {setup, getConfiguration} = wst.isolateModules(() =>
|
|
44
46
|
require("../setup.js"),
|
|
45
47
|
);
|
|
46
48
|
const configuration1 = {
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,21 @@
|
|
|
4
4
|
export * as adapters from "./fixtures/adapters/adapters.js";
|
|
5
5
|
export {fixtures} from "./fixtures/fixtures.js";
|
|
6
6
|
export {setup as setupFixtures} from "./fixtures/setup.js";
|
|
7
|
+
export type {
|
|
8
|
+
Adapter,
|
|
9
|
+
AdapterFactory,
|
|
10
|
+
AdapterFixtureOptions,
|
|
11
|
+
AdapterGroup,
|
|
12
|
+
AdapterGroupOptions,
|
|
13
|
+
AdapterOptions,
|
|
14
|
+
Configuration,
|
|
15
|
+
CustomWrapperProps,
|
|
16
|
+
GetPropsOptions,
|
|
17
|
+
FixturesOptions,
|
|
18
|
+
} from "./fixtures/types.js";
|
|
7
19
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
export {
|
|
20
|
+
// GraphQL framework
|
|
21
|
+
export {mockGqlFetch} from "./gql/mock-gql-fetch.js";
|
|
22
|
+
export type {GqlMockResponse} from "./gql/make-gql-mock-response.js";
|
|
23
|
+
export {RespondWith} from "./gql/make-gql-mock-response.js";
|
|
24
|
+
export type {GqlFetchMockFn, GqlMock, GqlMockOperation} from "./gql/types.js";
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
// Opt this file out of coverage because it's super hard to test.
|
|
3
|
-
/* istanbul ignore file */
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Isolate imports within a given action using jest.isolateModules.
|
|
7
|
-
*
|
|
8
|
-
* This is a helper for the `jest.isolateModules` API, allowing
|
|
9
|
-
* code to avoid the clunky closure syntax in their tests.
|
|
10
|
-
*
|
|
11
|
-
* @param {() => T} action The action that contains the isolated module imports.
|
|
12
|
-
* We do it this way so that any `require` calls are relative to the calling
|
|
13
|
-
* code and not this function. Note that we don't support promises here to
|
|
14
|
-
* discourage dynamic `import` use, which doesn't play well with standard
|
|
15
|
-
* jest yet.
|
|
16
|
-
*/
|
|
17
|
-
export const isolateModules = <T>(action: () => T): T => {
|
|
18
|
-
if (typeof jest === "undefined") {
|
|
19
|
-
throw new Error(`jest is not available in global scope`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
let result = undefined;
|
|
23
|
-
jest.isolateModules(() => {
|
|
24
|
-
result = action();
|
|
25
|
-
});
|
|
26
|
-
// We know that we'll have a result of the appropriate type at this point.
|
|
27
|
-
// We could use a promise to make everything happy, but this doesn't need
|
|
28
|
-
// to be async, so why bother.
|
|
29
|
-
// $FlowIgnore[incompatible-return]
|
|
30
|
-
return result;
|
|
31
|
-
};
|