@khanacademy/wonder-blocks-testing 7.1.6 → 7.1.8

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 CHANGED
@@ -1,5 +1,17 @@
1
1
  # @khanacademy/wonder-blocks-testing
2
2
 
3
+ ## 7.1.8
4
+
5
+ ### Patch Changes
6
+
7
+ - @khanacademy/wonder-blocks-data@10.0.4
8
+
9
+ ## 7.1.7
10
+
11
+ ### Patch Changes
12
+
13
+ - @khanacademy/wonder-blocks-data@10.0.3
14
+
3
15
  ## 7.1.6
4
16
 
5
17
  ### Patch Changes
package/dist/index.js ADDED
@@ -0,0 +1,540 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var addonActions = require('@storybook/addon-actions');
7
+ var _classPrivateFieldLooseBase = require('@babel/runtime/helpers/classPrivateFieldLooseBase');
8
+ var _classPrivateFieldLooseKey = require('@babel/runtime/helpers/classPrivateFieldLooseKey');
9
+ var wonderBlocksData = require('@khanacademy/wonder-blocks-data');
10
+ var reactRouterDom = require('react-router-dom');
11
+ var _extends = require('@babel/runtime/helpers/extends');
12
+
13
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
14
+
15
+ function _interopNamespace(e) {
16
+ if (e && e.__esModule) return e;
17
+ var n = Object.create(null);
18
+ if (e) {
19
+ Object.keys(e).forEach(function (k) {
20
+ if (k !== 'default') {
21
+ var d = Object.getOwnPropertyDescriptor(e, k);
22
+ Object.defineProperty(n, k, d.get ? d : {
23
+ enumerable: true,
24
+ get: function () { return e[k]; }
25
+ });
26
+ }
27
+ });
28
+ }
29
+ n["default"] = e;
30
+ return Object.freeze(n);
31
+ }
32
+
33
+ var React__namespace = /*#__PURE__*/_interopNamespace(React);
34
+ var _classPrivateFieldLooseBase__default = /*#__PURE__*/_interopDefaultLegacy(_classPrivateFieldLooseBase);
35
+ var _classPrivateFieldLooseKey__default = /*#__PURE__*/_interopDefaultLegacy(_classPrivateFieldLooseKey);
36
+ var _extends__default = /*#__PURE__*/_interopDefaultLegacy(_extends);
37
+
38
+ const fixtures = Component => {
39
+ const templateMap = new WeakMap();
40
+ let storyNumber = 1;
41
+ const getPropsOptions = {
42
+ log: (message, ...args) => addonActions.action(message).apply(void 0, args),
43
+ logHandler: addonActions.action
44
+ };
45
+
46
+ const makeStory = (description, props, wrapper = null) => {
47
+ const storyName = `${storyNumber++} ${description}`;
48
+
49
+ const getProps = options => typeof props === "function" ? props(options) : props;
50
+
51
+ const RealComponent = wrapper || Component;
52
+ let Template = templateMap.get(RealComponent);
53
+
54
+ if (Template == null) {
55
+ Template = args => React__namespace.createElement(RealComponent, args);
56
+
57
+ templateMap.set(RealComponent, Template);
58
+ }
59
+
60
+ const story = Template.bind({});
61
+ story.args = getProps(getPropsOptions);
62
+ story.storyName = storyName;
63
+ return story;
64
+ };
65
+
66
+ return makeStory;
67
+ };
68
+
69
+ const getHref = input => {
70
+ if (typeof input === "string") {
71
+ return input;
72
+ } else if (typeof input.url === "string") {
73
+ return input.url;
74
+ } else if (typeof input.href === "string") {
75
+ return input.href;
76
+ } else {
77
+ throw new Error(`Unsupported input type`);
78
+ }
79
+ };
80
+
81
+ const fetchRequestMatchesMock = (mock, input, init) => {
82
+ const href = getHref(input);
83
+
84
+ if (typeof mock === "string") {
85
+ return href === mock;
86
+ } else if (mock instanceof RegExp) {
87
+ return mock.test(href);
88
+ } else {
89
+ throw new Error(`Unsupported mock operation: ${JSON.stringify(mock)}`);
90
+ }
91
+ };
92
+
93
+ const mockRequester = (operationMatcher, operationToString) => {
94
+ const mocks = [];
95
+
96
+ const mockFn = (...args) => {
97
+ for (const mock of mocks) {
98
+ if (mock.onceOnly && mock.used) {
99
+ continue;
100
+ }
101
+
102
+ if (operationMatcher.apply(void 0, [mock.operation].concat(args))) {
103
+ mock.used = true;
104
+ return mock.response();
105
+ }
106
+ }
107
+
108
+ return Promise.reject(new Error(`No matching mock response found for request:
109
+ ${operationToString.apply(void 0, args)}`));
110
+ };
111
+
112
+ const addMockedOperation = (operation, response, onceOnly) => {
113
+ const mockResponse = () => response.toPromise();
114
+
115
+ mocks.push({
116
+ operation,
117
+ response: mockResponse,
118
+ onceOnly,
119
+ used: false
120
+ });
121
+ return mockFn;
122
+ };
123
+
124
+ mockFn.mockOperation = (operation, response) => addMockedOperation(operation, response, false);
125
+
126
+ mockFn.mockOperationOnce = (operation, response) => addMockedOperation(operation, response, true);
127
+
128
+ return mockFn;
129
+ };
130
+
131
+ const mockFetch = () => mockRequester(fetchRequestMatchesMock, (input, init) => `Input: ${typeof input === "string" ? input : JSON.stringify(input, null, 2)}
132
+ Options: ${init == null ? "None" : JSON.stringify(init, null, 2)}`);
133
+
134
+ const safeHasOwnProperty = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
135
+
136
+ const areObjectsEqual = (a, b) => {
137
+ if (a === b) {
138
+ return true;
139
+ }
140
+
141
+ if (a == null || b == null) {
142
+ return false;
143
+ }
144
+
145
+ if (typeof a !== "object" || typeof b !== "object") {
146
+ return false;
147
+ }
148
+
149
+ const aKeys = Object.keys(a);
150
+ const bKeys = Object.keys(b);
151
+
152
+ if (aKeys.length !== bKeys.length) {
153
+ return false;
154
+ }
155
+
156
+ for (let i = 0; i < aKeys.length; i++) {
157
+ const key = aKeys[i];
158
+
159
+ if (!safeHasOwnProperty(b, key) || !areObjectsEqual(a[key], b[key])) {
160
+ return false;
161
+ }
162
+ }
163
+
164
+ return true;
165
+ };
166
+
167
+ const gqlRequestMatchesMock = (mock, operation, variables, context) => {
168
+ if (mock.operation.id !== operation.id || mock.operation.type !== operation.type) {
169
+ return false;
170
+ }
171
+
172
+ if (mock.variables != null) {
173
+ if (!areObjectsEqual(mock.variables, variables)) {
174
+ return false;
175
+ }
176
+ }
177
+
178
+ if (mock.context != null) {
179
+ if (!areObjectsEqual(mock.context, context)) {
180
+ return false;
181
+ }
182
+ }
183
+
184
+ return true;
185
+ };
186
+
187
+ const mockGqlFetch = () => mockRequester(gqlRequestMatchesMock, (operation, variables, context) => `Operation: ${operation.type} ${operation.id}
188
+ Variables: ${variables == null ? "None" : JSON.stringify(variables, null, 2)}
189
+ Context: ${JSON.stringify(context, null, 2)}`);
190
+
191
+ var _settled = _classPrivateFieldLooseKey__default["default"]("settled");
192
+
193
+ class SettleSignal extends EventTarget {
194
+ constructor(setSettleFn = null) {
195
+ super();
196
+ Object.defineProperty(this, _settled, {
197
+ writable: true,
198
+ value: false
199
+ });
200
+ setSettleFn == null ? void 0 : setSettleFn(() => {
201
+ if (_classPrivateFieldLooseBase__default["default"](this, _settled)[_settled]) {
202
+ throw new Error("SettleSignal already settled");
203
+ }
204
+
205
+ _classPrivateFieldLooseBase__default["default"](this, _settled)[_settled] = true;
206
+ this.dispatchEvent(new Event("settled"));
207
+ });
208
+ }
209
+
210
+ static settle() {
211
+ const signal = new SettleSignal();
212
+ _classPrivateFieldLooseBase__default["default"](signal, _settled)[_settled] = true;
213
+ return signal;
214
+ }
215
+
216
+ get settled() {
217
+ return _classPrivateFieldLooseBase__default["default"](this, _settled)[_settled];
218
+ }
219
+
220
+ }
221
+
222
+ const ResponseImpl = typeof Response === "undefined" ? require("node-fetch").Response : Response;
223
+
224
+ const textResponse = (text, statusCode, signal) => ({
225
+ toPromise: () => makeMockResponse({
226
+ type: "text",
227
+ text,
228
+ statusCode,
229
+ signal
230
+ })
231
+ });
232
+
233
+ const rejectResponse = (error, signal) => ({
234
+ toPromise: () => makeMockResponse({
235
+ type: "reject",
236
+ error,
237
+ signal
238
+ })
239
+ });
240
+
241
+ const RespondWith = Object.freeze({
242
+ text: (text, statusCode = 200, signal = null) => textResponse(text, statusCode, signal),
243
+ json: (json, signal = null) => textResponse(() => JSON.stringify(json), 200, signal),
244
+ graphQLData: (data, signal = null) => textResponse(() => JSON.stringify({
245
+ data
246
+ }), 200, signal),
247
+ unparseableBody: (signal = null) => textResponse("INVALID JSON", 200, signal),
248
+ abortedRequest: (signal = null) => rejectResponse(() => {
249
+ const abortError = new Error("Mock request aborted");
250
+ abortError.name = "AbortError";
251
+ return abortError;
252
+ }, signal),
253
+ reject: (error, signal = null) => rejectResponse(error, signal),
254
+ errorStatusCode: (statusCode, signal = null) => {
255
+ if (statusCode < 300) {
256
+ throw new Error(`${statusCode} is not a valid error status code`);
257
+ }
258
+
259
+ return textResponse("{}", statusCode, signal);
260
+ },
261
+ nonGraphQLBody: (signal = null) => textResponse(() => JSON.stringify({
262
+ valid: "json",
263
+ that: "is not a valid graphql response"
264
+ }), 200, signal),
265
+ graphQLErrors: (errorMessages, signal = null) => textResponse(() => JSON.stringify({
266
+ errors: errorMessages.map(e => ({
267
+ message: e
268
+ }))
269
+ }), 200, signal)
270
+ });
271
+
272
+ const callOnSettled = (signal, fn) => {
273
+ if (signal == null || signal.settled) {
274
+ fn();
275
+ return;
276
+ }
277
+
278
+ const onSettled = () => {
279
+ signal.removeEventListener("settled", onSettled);
280
+ fn();
281
+ };
282
+
283
+ signal.addEventListener("settled", onSettled);
284
+ };
285
+
286
+ const makeMockResponse = response => {
287
+ const {
288
+ signal
289
+ } = response;
290
+
291
+ switch (response.type) {
292
+ case "text":
293
+ return new Promise((resolve, reject) => {
294
+ callOnSettled(signal, () => {
295
+ const text = typeof response.text === "function" ? response.text() : response.text;
296
+ resolve(new ResponseImpl(text, {
297
+ status: response.statusCode
298
+ }));
299
+ });
300
+ });
301
+
302
+ case "reject":
303
+ return new Promise((resolve, reject) => {
304
+ callOnSettled(signal, () => reject(response.error instanceof Error ? response.error : response.error()));
305
+ });
306
+
307
+ default:
308
+ if (process.env.NODE_ENV !== "production") {
309
+ throw new Error(`Unknown response type: ${response.type}`);
310
+ }
311
+
312
+ return makeMockResponse({
313
+ type: "reject",
314
+ error: new Error("Unknown response type"),
315
+ signal
316
+ });
317
+ }
318
+ };
319
+
320
+ var _settleFn = _classPrivateFieldLooseKey__default["default"]("settleFn");
321
+
322
+ var _signal = _classPrivateFieldLooseKey__default["default"]("signal");
323
+
324
+ class SettleController {
325
+ constructor() {
326
+ Object.defineProperty(this, _settleFn, {
327
+ writable: true,
328
+ value: void 0
329
+ });
330
+ Object.defineProperty(this, _signal, {
331
+ writable: true,
332
+ value: void 0
333
+ });
334
+ _classPrivateFieldLooseBase__default["default"](this, _signal)[_signal] = new SettleSignal(settleFn => _classPrivateFieldLooseBase__default["default"](this, _settleFn)[_settleFn] = settleFn);
335
+ }
336
+
337
+ get signal() {
338
+ return _classPrivateFieldLooseBase__default["default"](this, _signal)[_signal];
339
+ }
340
+
341
+ settle() {
342
+ _classPrivateFieldLooseBase__default["default"](this, _settleFn)[_settleFn]();
343
+ }
344
+
345
+ }
346
+
347
+ const defaultConfig$3 = null;
348
+
349
+ const normalizeConfig = config => {
350
+ if (typeof config === "string") {
351
+ return {
352
+ classes: [config],
353
+ style: {}
354
+ };
355
+ }
356
+
357
+ if (Array.isArray(config)) {
358
+ return {
359
+ classes: config,
360
+ style: {}
361
+ };
362
+ }
363
+
364
+ if (typeof config === "object") {
365
+ if (config.classes != null && config.style != null) {
366
+ return config;
367
+ }
368
+
369
+ return {
370
+ classes: [],
371
+ style: config
372
+ };
373
+ }
374
+
375
+ throw new Error(`Invalid config: ${config}`);
376
+ };
377
+
378
+ const adapter$3 = (children, config) => {
379
+ const {
380
+ classes,
381
+ style
382
+ } = normalizeConfig(config);
383
+ return React__namespace.createElement("div", {
384
+ "data-test-id": "css-adapter-container",
385
+ className: classes.join(" "),
386
+ style: style
387
+ }, children);
388
+ };
389
+
390
+ const defaultConfig$2 = [];
391
+ const adapter$2 = (children, config) => {
392
+ let currentChildren = children;
393
+ const interceptors = Array.isArray(config) ? config : [config];
394
+
395
+ for (const interceptor of interceptors) {
396
+ currentChildren = React__namespace.createElement(wonderBlocksData.InterceptRequests, {
397
+ interceptor: interceptor
398
+ }, currentChildren);
399
+ }
400
+
401
+ return React__namespace.createElement(React__namespace.Fragment, null, currentChildren);
402
+ };
403
+
404
+ const defaultConfig$1 = null;
405
+ const adapter$1 = (children, config) => React__namespace.createElement(React__namespace.Fragment, null, React__namespace.createElement("div", {
406
+ id: config,
407
+ "data-test-id": config
408
+ }), children);
409
+
410
+ const defaultConfig = {
411
+ location: "/"
412
+ };
413
+
414
+ const maybeWithRoute = (children, path) => {
415
+ if (path == null) {
416
+ return children;
417
+ }
418
+
419
+ return React__namespace.createElement(reactRouterDom.Switch, null, React__namespace.createElement(reactRouterDom.Route, {
420
+ exact: true,
421
+ path: path
422
+ }, children), React__namespace.createElement(reactRouterDom.Route, {
423
+ path: "*",
424
+ render: () => {
425
+ throw new Error("The configured path must match the configured location or your harnessed component will not render.");
426
+ }
427
+ }));
428
+ };
429
+
430
+ const adapter = (children, config) => {
431
+ if (typeof config === "string") {
432
+ config = {
433
+ location: config
434
+ };
435
+ }
436
+
437
+ const wrappedWithRoute = maybeWithRoute(children, config.path);
438
+
439
+ if (config.forceStatic) {
440
+ return React__namespace.createElement(reactRouterDom.StaticRouter, {
441
+ location: config.location,
442
+ context: {}
443
+ }, wrappedWithRoute);
444
+ }
445
+
446
+ if (typeof config.location !== "undefined") {
447
+ return React__namespace.createElement(reactRouterDom.MemoryRouter, {
448
+ initialEntries: [config.location]
449
+ }, wrappedWithRoute);
450
+ }
451
+
452
+ if (typeof config.initialEntries === "undefined") {
453
+ throw new Error("A location or initial history entries must be provided.");
454
+ }
455
+
456
+ const entries = config.initialEntries.length === 0 ? [defaultConfig.location] : config.initialEntries;
457
+ const routerProps = {
458
+ initialEntries: entries
459
+ };
460
+
461
+ if (config.initialIndex != null) {
462
+ routerProps.initialIndex = config.initialIndex;
463
+ }
464
+
465
+ if (config.getUserConfirmation != null) {
466
+ routerProps.getUserConfirmation = config.getUserConfirmation;
467
+ }
468
+
469
+ return React__namespace.createElement(reactRouterDom.MemoryRouter, routerProps, wrappedWithRoute);
470
+ };
471
+
472
+ const DefaultAdapters = {
473
+ css: adapter$3,
474
+ data: adapter$2,
475
+ portal: adapter$1,
476
+ router: adapter
477
+ };
478
+ const DefaultConfigs = {
479
+ css: defaultConfig$3,
480
+ data: defaultConfig$2,
481
+ portal: defaultConfig$1,
482
+ router: defaultConfig
483
+ };
484
+
485
+ var adapters = /*#__PURE__*/Object.freeze({
486
+ __proto__: null,
487
+ DefaultAdapters: DefaultAdapters,
488
+ DefaultConfigs: DefaultConfigs
489
+ });
490
+
491
+ const renderAdapters = (adapters, configs, children) => {
492
+ let currentChildren = children;
493
+
494
+ for (const adapterName of Object.keys(adapters)) {
495
+ const adapter = adapters[adapterName];
496
+ const config = configs[adapterName];
497
+
498
+ if (config != null) {
499
+ currentChildren = adapter(currentChildren, config);
500
+ }
501
+ }
502
+
503
+ return currentChildren;
504
+ };
505
+
506
+ const makeTestHarness = (adapters, defaultConfigs) => {
507
+ return (Component, configs) => {
508
+ const fullConfig = _extends__default["default"]({}, defaultConfigs, configs);
509
+
510
+ const harnessedComponent = React__namespace.forwardRef((props, ref) => renderAdapters(adapters, fullConfig, React__namespace.createElement(Component, _extends__default["default"]({}, props, {
511
+ ref: ref
512
+ }))));
513
+ harnessedComponent.displayName = `testHarness(${Component.displayName || Component.name || "Component"})`;
514
+ return harnessedComponent;
515
+ };
516
+ };
517
+
518
+ const HookHarness = ({
519
+ children
520
+ }) => children;
521
+
522
+ const makeHookHarness = (adapters, defaultConfigs) => {
523
+ const testHarness = makeTestHarness(adapters, defaultConfigs);
524
+ return configs => testHarness(HookHarness, configs);
525
+ };
526
+
527
+ const hookHarness = makeHookHarness(DefaultAdapters, DefaultConfigs);
528
+
529
+ const testHarness = makeTestHarness(DefaultAdapters, DefaultConfigs);
530
+
531
+ exports.RespondWith = RespondWith;
532
+ exports.SettleController = SettleController;
533
+ exports.fixtures = fixtures;
534
+ exports.harnessAdapters = adapters;
535
+ exports.hookHarness = hookHarness;
536
+ exports.makeHookHarness = makeHookHarness;
537
+ exports.makeTestHarness = makeTestHarness;
538
+ exports.mockFetch = mockFetch;
539
+ exports.mockGqlFetch = mockGqlFetch;
540
+ exports.testHarness = testHarness;
@@ -0,0 +1,2 @@
1
+ // @flow
2
+ export * from "../src/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanacademy/wonder-blocks-testing",
3
- "version": "7.1.6",
3
+ "version": "7.1.8",
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.18.6",
17
- "@khanacademy/wonder-blocks-data": "^10.0.2"
17
+ "@khanacademy/wonder-blocks-data": "^10.0.4"
18
18
  },
19
19
  "peerDependencies": {
20
20
  "@khanacademy/wonder-stuff-core": "^0.1.2",
@@ -26,7 +26,7 @@
26
26
  "react-router-dom": "5.3.0"
27
27
  },
28
28
  "devDependencies": {
29
- "wb-dev-build-settings": "^0.5.0"
29
+ "wb-dev-build-settings": "^0.7.0"
30
30
  },
31
31
  "author": "",
32
32
  "license": "MIT"