@khanacademy/wonder-blocks-testing 0.0.2
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 +354 -0
- package/dist/index.js +566 -0
- package/dist/index.js.flow +2 -0
- package/docs.md +1 -0
- package/package.json +28 -0
- package/src/fixtures/__tests__/combine-options.test.js +65 -0
- package/src/fixtures/__tests__/combine-top-level.test.js +100 -0
- package/src/fixtures/__tests__/fixtures.test.js +483 -0
- package/src/fixtures/__tests__/setup.test.js +69 -0
- package/src/fixtures/adapters/__tests__/__snapshots__/adapter-group.test.js.snap +9 -0
- package/src/fixtures/adapters/__tests__/__snapshots__/adapter.test.js.snap +13 -0
- package/src/fixtures/adapters/__tests__/adapter-group.test.js +199 -0
- package/src/fixtures/adapters/__tests__/adapter.test.js +91 -0
- package/src/fixtures/adapters/__tests__/storybook.test.js +329 -0
- package/src/fixtures/adapters/adapter-group.js +88 -0
- package/src/fixtures/adapters/adapter.js +63 -0
- package/src/fixtures/adapters/adapters.js +2 -0
- package/src/fixtures/adapters/storybook.js +119 -0
- package/src/fixtures/combine-options.js +25 -0
- package/src/fixtures/combine-top-level.js +44 -0
- package/src/fixtures/fixtures.basic.stories.js +64 -0
- package/src/fixtures/fixtures.defaultwrapper.stories.js +59 -0
- package/src/fixtures/fixtures.js +77 -0
- package/src/fixtures/setup.js +26 -0
- package/src/fixtures/types.js +182 -0
- package/src/index.js +10 -0
- package/src/jest/isolate-modules.js +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
module.exports =
|
|
2
|
+
/******/ (function(modules) { // webpackBootstrap
|
|
3
|
+
/******/ // The module cache
|
|
4
|
+
/******/ var installedModules = {};
|
|
5
|
+
/******/
|
|
6
|
+
/******/ // The require function
|
|
7
|
+
/******/ function __webpack_require__(moduleId) {
|
|
8
|
+
/******/
|
|
9
|
+
/******/ // Check if module is in cache
|
|
10
|
+
/******/ if(installedModules[moduleId]) {
|
|
11
|
+
/******/ return installedModules[moduleId].exports;
|
|
12
|
+
/******/ }
|
|
13
|
+
/******/ // Create a new module (and put it into the cache)
|
|
14
|
+
/******/ var module = installedModules[moduleId] = {
|
|
15
|
+
/******/ i: moduleId,
|
|
16
|
+
/******/ l: false,
|
|
17
|
+
/******/ exports: {}
|
|
18
|
+
/******/ };
|
|
19
|
+
/******/
|
|
20
|
+
/******/ // Execute the module function
|
|
21
|
+
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
|
22
|
+
/******/
|
|
23
|
+
/******/ // Flag the module as loaded
|
|
24
|
+
/******/ module.l = true;
|
|
25
|
+
/******/
|
|
26
|
+
/******/ // Return the exports of the module
|
|
27
|
+
/******/ return module.exports;
|
|
28
|
+
/******/ }
|
|
29
|
+
/******/
|
|
30
|
+
/******/
|
|
31
|
+
/******/ // expose the modules object (__webpack_modules__)
|
|
32
|
+
/******/ __webpack_require__.m = modules;
|
|
33
|
+
/******/
|
|
34
|
+
/******/ // expose the module cache
|
|
35
|
+
/******/ __webpack_require__.c = installedModules;
|
|
36
|
+
/******/
|
|
37
|
+
/******/ // define getter function for harmony exports
|
|
38
|
+
/******/ __webpack_require__.d = function(exports, name, getter) {
|
|
39
|
+
/******/ if(!__webpack_require__.o(exports, name)) {
|
|
40
|
+
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
|
|
41
|
+
/******/ }
|
|
42
|
+
/******/ };
|
|
43
|
+
/******/
|
|
44
|
+
/******/ // define __esModule on exports
|
|
45
|
+
/******/ __webpack_require__.r = function(exports) {
|
|
46
|
+
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
|
47
|
+
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
48
|
+
/******/ }
|
|
49
|
+
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
|
50
|
+
/******/ };
|
|
51
|
+
/******/
|
|
52
|
+
/******/ // create a fake namespace object
|
|
53
|
+
/******/ // mode & 1: value is a module id, require it
|
|
54
|
+
/******/ // mode & 2: merge all properties of value into the ns
|
|
55
|
+
/******/ // mode & 4: return value when already ns object
|
|
56
|
+
/******/ // mode & 8|1: behave like require
|
|
57
|
+
/******/ __webpack_require__.t = function(value, mode) {
|
|
58
|
+
/******/ if(mode & 1) value = __webpack_require__(value);
|
|
59
|
+
/******/ if(mode & 8) return value;
|
|
60
|
+
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
|
|
61
|
+
/******/ var ns = Object.create(null);
|
|
62
|
+
/******/ __webpack_require__.r(ns);
|
|
63
|
+
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
|
|
64
|
+
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
|
|
65
|
+
/******/ return ns;
|
|
66
|
+
/******/ };
|
|
67
|
+
/******/
|
|
68
|
+
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
|
69
|
+
/******/ __webpack_require__.n = function(module) {
|
|
70
|
+
/******/ var getter = module && module.__esModule ?
|
|
71
|
+
/******/ function getDefault() { return module['default']; } :
|
|
72
|
+
/******/ function getModuleExports() { return module; };
|
|
73
|
+
/******/ __webpack_require__.d(getter, 'a', getter);
|
|
74
|
+
/******/ return getter;
|
|
75
|
+
/******/ };
|
|
76
|
+
/******/
|
|
77
|
+
/******/ // Object.prototype.hasOwnProperty.call
|
|
78
|
+
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
|
79
|
+
/******/
|
|
80
|
+
/******/ // __webpack_public_path__
|
|
81
|
+
/******/ __webpack_require__.p = "";
|
|
82
|
+
/******/
|
|
83
|
+
/******/
|
|
84
|
+
/******/ // Load entry module and return exports
|
|
85
|
+
/******/ return __webpack_require__(__webpack_require__.s = 12);
|
|
86
|
+
/******/ })
|
|
87
|
+
/************************************************************************/
|
|
88
|
+
/******/ ([
|
|
89
|
+
/* 0 */
|
|
90
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
91
|
+
|
|
92
|
+
"use strict";
|
|
93
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "b", function() { return setup; });
|
|
94
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getConfiguration; });
|
|
95
|
+
let _configuration = null;
|
|
96
|
+
/**
|
|
97
|
+
* Setup the fixture framework.
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
const setup = configuration => {
|
|
101
|
+
_configuration = configuration;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Get the framework configuration.
|
|
105
|
+
*
|
|
106
|
+
* @returns {Configuration} The configuration as provided via setup().
|
|
107
|
+
* @throws {Error} If the configuration has not been set.
|
|
108
|
+
*/
|
|
109
|
+
|
|
110
|
+
const getConfiguration = () => {
|
|
111
|
+
if (_configuration == null) {
|
|
112
|
+
throw new Error("Not configured");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return _configuration;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
/***/ }),
|
|
119
|
+
/* 1 */
|
|
120
|
+
/***/ (function(module, exports) {
|
|
121
|
+
|
|
122
|
+
module.exports = require("react");
|
|
123
|
+
|
|
124
|
+
/***/ }),
|
|
125
|
+
/* 2 */
|
|
126
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
127
|
+
|
|
128
|
+
"use strict";
|
|
129
|
+
__webpack_require__.r(__webpack_exports__);
|
|
130
|
+
/* harmony import */ var _storybook_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(3);
|
|
131
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "storybook", function() { return _storybook_js__WEBPACK_IMPORTED_MODULE_0__["a"]; });
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
/***/ }),
|
|
136
|
+
/* 3 */
|
|
137
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
138
|
+
|
|
139
|
+
"use strict";
|
|
140
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return getAdapter; });
|
|
141
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
|
|
142
|
+
/* 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__(6);
|
|
144
|
+
/* 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__(7);
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Get a fixture framework adapter for Storybook support.
|
|
152
|
+
*/
|
|
153
|
+
const getAdapter = (MountingComponent = null) => new _adapter_js__WEBPACK_IMPORTED_MODULE_2__[/* Adapter */ "a"]("storybook", ({
|
|
154
|
+
title,
|
|
155
|
+
description: groupDescription
|
|
156
|
+
}, adapterOptions, declaredFixtures) => {
|
|
157
|
+
const templateMap = new WeakMap();
|
|
158
|
+
|
|
159
|
+
const log = (message, ...args) => Object(_storybook_addon_actions__WEBPACK_IMPORTED_MODULE_1__["action"])(message).apply(void 0, args);
|
|
160
|
+
|
|
161
|
+
const exports = declaredFixtures.reduce((acc, {
|
|
162
|
+
description,
|
|
163
|
+
getProps,
|
|
164
|
+
component: Component
|
|
165
|
+
}, i) => {
|
|
166
|
+
const storyName = `${i + 1} ${description}`;
|
|
167
|
+
const exportName = storyName // Make word boundaries start with an upper case letter.
|
|
168
|
+
.replace(/\b\w/g, c => c.toUpperCase()) // Remove all non-alphanumeric characters.
|
|
169
|
+
.replace(/[^\w]+/g, "") // Remove all underscores.
|
|
170
|
+
.replace(/[_]+/g, ""); // We create a “template” of how args map to rendering
|
|
171
|
+
// for each type of component as the component here could
|
|
172
|
+
// be the component under test, or wrapped in a wrapper
|
|
173
|
+
// component. We don't use decorators for the wrapper
|
|
174
|
+
// because we may not be in a storybook context and it
|
|
175
|
+
// keeps the framework API simpler this way.
|
|
176
|
+
|
|
177
|
+
let Template = templateMap.get(Component);
|
|
178
|
+
|
|
179
|
+
if (Template == null) {
|
|
180
|
+
// The MountingComponent is a bit different than just a
|
|
181
|
+
// Storybook decorator. It's a React component that
|
|
182
|
+
// takes over rendering the component in the fixture
|
|
183
|
+
// with the given args, allowing for greater
|
|
184
|
+
// customization in a platform-agnostic manner (i.e.
|
|
185
|
+
// not just story format).
|
|
186
|
+
Template = MountingComponent ? args => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](MountingComponent, {
|
|
187
|
+
component: Component,
|
|
188
|
+
props: args,
|
|
189
|
+
log: log
|
|
190
|
+
}) : args => /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["createElement"](Component, args);
|
|
191
|
+
templateMap.set(Component, Template);
|
|
192
|
+
} // Each story that shares that component then reuses that
|
|
193
|
+
// template.
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
acc[exportName] = Template.bind({});
|
|
197
|
+
acc[exportName].args = getProps({
|
|
198
|
+
log
|
|
199
|
+
}); // Adding a story name here means that we don't have to
|
|
200
|
+
// care about naming the exports correctly, if we don't
|
|
201
|
+
// want (useful if we need to autogenerate or manually
|
|
202
|
+
// expose ESM exports).
|
|
203
|
+
|
|
204
|
+
acc[exportName].storyName = storyName;
|
|
205
|
+
return acc;
|
|
206
|
+
}, {
|
|
207
|
+
default: {
|
|
208
|
+
title,
|
|
209
|
+
// TODO(somewhatabstract): Use groupDescription
|
|
210
|
+
// Possibly via a decorator?
|
|
211
|
+
...adapterOptions
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
return Object.freeze(exports);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
/***/ }),
|
|
218
|
+
/* 4 */
|
|
219
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
220
|
+
|
|
221
|
+
"use strict";
|
|
222
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return fixtures; });
|
|
223
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);
|
|
224
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(react__WEBPACK_IMPORTED_MODULE_0__);
|
|
225
|
+
/* 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__(9);
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Describe a group of fixtures for a given component.
|
|
233
|
+
*
|
|
234
|
+
* Only one `fixtures` call should be used per fixture file as it returns
|
|
235
|
+
* the exports for that file.
|
|
236
|
+
*
|
|
237
|
+
* @param {FixtureOptions<TProps>} options Options describing the
|
|
238
|
+
* fixture group.
|
|
239
|
+
* @param {FixtureFn<TProps> => void} fn A function that provides a `fixture`
|
|
240
|
+
* function for defining fixtures.
|
|
241
|
+
* @returns {Exports} The object to be exported as `module.exports`.
|
|
242
|
+
*
|
|
243
|
+
* TODO(somewhatabstract): Determine a way around this requirement so we
|
|
244
|
+
* can support named exports and default exports via the adapters in a
|
|
245
|
+
* deterministic way. Currently this is imposed on us because of how
|
|
246
|
+
* storybook, the popular framework, uses both default and named exports for
|
|
247
|
+
* its interface.
|
|
248
|
+
*/
|
|
249
|
+
const fixtures = (options, fn) => {
|
|
250
|
+
var _additionalAdapterOpt;
|
|
251
|
+
|
|
252
|
+
const {
|
|
253
|
+
adapter,
|
|
254
|
+
defaultAdapterOptions
|
|
255
|
+
} = Object(_setup_js__WEBPACK_IMPORTED_MODULE_1__[/* getConfiguration */ "a"])();
|
|
256
|
+
const {
|
|
257
|
+
title,
|
|
258
|
+
component,
|
|
259
|
+
description: groupDescription,
|
|
260
|
+
defaultWrapper,
|
|
261
|
+
additionalAdapterOptions
|
|
262
|
+
} = options; // 1. Create a new adapter group.
|
|
263
|
+
|
|
264
|
+
const group = adapter.declareGroup({
|
|
265
|
+
title: title || component.displayName || component.name || "Component",
|
|
266
|
+
description: groupDescription
|
|
267
|
+
}); // 2. Invoke fn with a function that can add a new fixture.
|
|
268
|
+
|
|
269
|
+
const addFixture = (description, props, wrapper = null) => {
|
|
270
|
+
var _ref;
|
|
271
|
+
|
|
272
|
+
group.declareFixture({
|
|
273
|
+
description,
|
|
274
|
+
getProps: options => typeof props === "function" ? props(options) : props,
|
|
275
|
+
component: (_ref = wrapper != null ? wrapper : defaultWrapper) != null ? _ref : component
|
|
276
|
+
});
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
fn(addFixture); // 3. Combine the adapter options from the fixture group with the
|
|
280
|
+
// defaults from our setup.
|
|
281
|
+
|
|
282
|
+
const groupAdapterOverrides = (_additionalAdapterOpt = additionalAdapterOptions == null ? void 0 : additionalAdapterOptions[adapter.name]) != null ? _additionalAdapterOpt : {};
|
|
283
|
+
const combinedAdapterOptions = Object(_combine_options_js__WEBPACK_IMPORTED_MODULE_2__[/* combineOptions */ "a"])(defaultAdapterOptions, groupAdapterOverrides); // 4. Call close on the group and return the result.
|
|
284
|
+
|
|
285
|
+
return group.closeGroup(combinedAdapterOptions);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/***/ }),
|
|
289
|
+
/* 5 */
|
|
290
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
291
|
+
|
|
292
|
+
"use strict";
|
|
293
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return isolateModules; });
|
|
294
|
+
// Opt this file out of coverage because it's super hard to test.
|
|
295
|
+
|
|
296
|
+
/* istanbul ignore file */
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Isolate imports within a given action using jest.isolateModules.
|
|
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.
|
|
309
|
+
*/
|
|
310
|
+
const isolateModules = action => {
|
|
311
|
+
if (typeof jest === "undefined") {
|
|
312
|
+
throw new Error(`jest is not available in global scope`);
|
|
313
|
+
}
|
|
314
|
+
|
|
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
|
+
|
|
323
|
+
return result;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/***/ }),
|
|
327
|
+
/* 6 */
|
|
328
|
+
/***/ (function(module, exports) {
|
|
329
|
+
|
|
330
|
+
module.exports = require("@storybook/addon-actions");
|
|
331
|
+
|
|
332
|
+
/***/ }),
|
|
333
|
+
/* 7 */
|
|
334
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
335
|
+
|
|
336
|
+
"use strict";
|
|
337
|
+
/* 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__(8);
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Class for implementing a custom adapter.
|
|
343
|
+
*/
|
|
344
|
+
class Adapter {
|
|
345
|
+
/**
|
|
346
|
+
* @param {string} name The name of the adapter.
|
|
347
|
+
* @param {CloseGroupFn<any, Options, Exports>} closeGroupFn The function
|
|
348
|
+
* an adapter group should call when the group is closed. This is invoked
|
|
349
|
+
* by an adapter group when it is closed. This function is where an
|
|
350
|
+
* adapter implements the logic to generate the actual fixtures for the
|
|
351
|
+
* adapter's target framework.
|
|
352
|
+
*/
|
|
353
|
+
constructor(name, closeGroupFn) {
|
|
354
|
+
if (typeof name !== "string") {
|
|
355
|
+
throw new TypeError("name must be a string");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (name.trim() === "") {
|
|
359
|
+
throw new Error("name must be a non-empty string");
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (typeof closeGroupFn !== "function") {
|
|
363
|
+
throw new TypeError("closeGroupFn must be a function");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
this._name = name;
|
|
367
|
+
this._closeGroupFn = closeGroupFn;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* The name of the adapter.
|
|
371
|
+
*/
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
get name() {
|
|
375
|
+
return this._name;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Declare a new fixture group.
|
|
379
|
+
*
|
|
380
|
+
* @param {AdapterGroupOptions} options The options describing the fixture
|
|
381
|
+
* group.
|
|
382
|
+
* @returns {AdapterGroupInterface} The new fixture group.
|
|
383
|
+
*/
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
declareGroup(options) {
|
|
387
|
+
return new _adapter_group_js__WEBPACK_IMPORTED_MODULE_0__[/* AdapterGroup */ "a"](this._closeGroupFn, options);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/***/ }),
|
|
393
|
+
/* 8 */
|
|
394
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
395
|
+
|
|
396
|
+
"use strict";
|
|
397
|
+
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return AdapterGroup; });
|
|
398
|
+
/**
|
|
399
|
+
* Simple adapter group implementation.
|
|
400
|
+
*/
|
|
401
|
+
class AdapterGroup {
|
|
402
|
+
/**
|
|
403
|
+
* Create an adapter group.
|
|
404
|
+
*
|
|
405
|
+
* @param {CloseGroupFn<TProps, Options, Exports>} closeGroupFn A function
|
|
406
|
+
* to invoke when the group is closed.
|
|
407
|
+
* @param {AdapterGroupOptions} options The options for the group.
|
|
408
|
+
*/
|
|
409
|
+
constructor(closeGroupFn, _options) {
|
|
410
|
+
this.closeGroup = (adapterOptions = null) => {
|
|
411
|
+
if (this._closeGroupFn == null) {
|
|
412
|
+
throw new Error("Group already closed");
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
try {
|
|
416
|
+
return this._closeGroupFn(this._options, adapterOptions, this._fixtures);
|
|
417
|
+
} finally {
|
|
418
|
+
this._closeGroupFn = null;
|
|
419
|
+
}
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
this.declareFixture = options => {
|
|
423
|
+
if (typeof options !== "object" || options === null) {
|
|
424
|
+
throw new TypeError("options must be an object");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if (this._closeGroupFn == null) {
|
|
428
|
+
throw new Error("Cannot declare fixtures after closing the group");
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
this._fixtures.push(options);
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
if (typeof closeGroupFn !== "function") {
|
|
435
|
+
throw new TypeError("closeGroupFn must be a function");
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (typeof _options !== "object" || _options === null) {
|
|
439
|
+
throw new TypeError("options must be an object");
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
this._closeGroupFn = closeGroupFn;
|
|
443
|
+
this._options = _options;
|
|
444
|
+
this._fixtures = [];
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Close the group.
|
|
448
|
+
*
|
|
449
|
+
* This declares that no more fixtures are to be added to the group,
|
|
450
|
+
* and will call the parent adapter with the declared fixtures so that they
|
|
451
|
+
* can be adapted for the target fixture framework, such as Storybook.
|
|
452
|
+
*/
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/***/ }),
|
|
458
|
+
/* 9 */
|
|
459
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
460
|
+
|
|
461
|
+
"use strict";
|
|
462
|
+
/* 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__(10);
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Combine one or more objects into a single object.
|
|
467
|
+
*
|
|
468
|
+
* Objects later in the argument list take precedence over those that are
|
|
469
|
+
* earlier. Object and array values at the root level are merged.
|
|
470
|
+
*/
|
|
471
|
+
|
|
472
|
+
const combineOptions = (...toBeCombined) => {
|
|
473
|
+
const combined = toBeCombined.filter(Boolean).reduce((acc, cur) => {
|
|
474
|
+
for (const key of Object.keys(cur)) {
|
|
475
|
+
// We always call combine, even if acc[key] is undefined
|
|
476
|
+
// because we need to make sure we clone values.
|
|
477
|
+
acc[key] = Object(_combine_top_level_js__WEBPACK_IMPORTED_MODULE_0__[/* combineTopLevel */ "a"])(acc[key], cur[key]);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return acc;
|
|
481
|
+
}, {}); // We know that we are creating a compatible return type.
|
|
482
|
+
// $FlowIgnore[incompatible-return]
|
|
483
|
+
|
|
484
|
+
return combined;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
/***/ }),
|
|
488
|
+
/* 10 */
|
|
489
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
490
|
+
|
|
491
|
+
"use strict";
|
|
492
|
+
/* 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__(11);
|
|
494
|
+
/* 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
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Combine two values.
|
|
498
|
+
*
|
|
499
|
+
* This method clones val2 before using any of its properties to try to ensure
|
|
500
|
+
* the combined object is not linked back to the original.
|
|
501
|
+
*
|
|
502
|
+
* If the values are objects, it will merge them at the top level. Properties
|
|
503
|
+
* themselves are not merged; val2 properties will overwrite val1 where there
|
|
504
|
+
* are conflicts
|
|
505
|
+
*
|
|
506
|
+
* If the values are arrays, it will concatenate and dedupe them.
|
|
507
|
+
* NOTE: duplicates in either val1 or val2 will also be deduped.
|
|
508
|
+
*
|
|
509
|
+
* If the values are any other type, or val2 has a different type to val1, val2
|
|
510
|
+
* will be returned.
|
|
511
|
+
*/
|
|
512
|
+
|
|
513
|
+
const combineTopLevel = (val1, val2) => {
|
|
514
|
+
const obj2Clone = Object(_khanacademy_wonder_stuff_core__WEBPACK_IMPORTED_MODULE_0__["clone"])(val2); // Only merge if they're both arrays or both objects.
|
|
515
|
+
// If not, we will just return val2.
|
|
516
|
+
|
|
517
|
+
if (val1 !== null && val2 !== null && typeof val1 === "object" && typeof val2 === "object") {
|
|
518
|
+
const val1IsArray = Array.isArray(val1);
|
|
519
|
+
const val2IsArray = Array.isArray(val2);
|
|
520
|
+
|
|
521
|
+
if (val1IsArray && val2IsArray) {
|
|
522
|
+
return Array.from(new Set([].concat(val1, obj2Clone)));
|
|
523
|
+
} else if (!val1IsArray && !val2IsArray) {
|
|
524
|
+
return { ...val1,
|
|
525
|
+
...obj2Clone
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return obj2Clone;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
/***/ }),
|
|
534
|
+
/* 11 */
|
|
535
|
+
/***/ (function(module, exports) {
|
|
536
|
+
|
|
537
|
+
module.exports = require("@khanacademy/wonder-stuff-core");
|
|
538
|
+
|
|
539
|
+
/***/ }),
|
|
540
|
+
/* 12 */
|
|
541
|
+
/***/ (function(module, __webpack_exports__, __webpack_require__) {
|
|
542
|
+
|
|
543
|
+
"use strict";
|
|
544
|
+
__webpack_require__.r(__webpack_exports__);
|
|
545
|
+
/* harmony import */ var _fixtures_adapters_adapters_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2);
|
|
546
|
+
/* 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__(4);
|
|
548
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fixtures", function() { return _fixtures_fixtures_js__WEBPACK_IMPORTED_MODULE_1__["a"]; });
|
|
549
|
+
|
|
550
|
+
/* harmony import */ var _fixtures_setup_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(0);
|
|
551
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "setupFixtures", function() { return _fixtures_setup_js__WEBPACK_IMPORTED_MODULE_2__["b"]; });
|
|
552
|
+
|
|
553
|
+
/* harmony import */ var _jest_isolate_modules_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5);
|
|
554
|
+
/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isolateModules", function() { return _jest_isolate_modules_js__WEBPACK_IMPORTED_MODULE_3__["a"]; });
|
|
555
|
+
|
|
556
|
+
// Fixtures framework
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
// Jest
|
|
561
|
+
// TODO(somewhatabstract): To be moved to wonder stuff
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
/***/ })
|
|
566
|
+
/******/ ]);
|
package/docs.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
`wonder-blocks-testing` provides a utilities to support testing React components
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"design": "v1",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"description": "",
|
|
9
|
+
"main": "dist/index.js",
|
|
10
|
+
"module": "dist/es/index.js",
|
|
11
|
+
"source": "src/index.js",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@khanacademy/wonder-stuff-core": "^0.1.2",
|
|
17
|
+
"@babel/runtime": "^7.16.3"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@storybook/addon-actions": "^6.4.8",
|
|
21
|
+
"react": "16.14.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"wb-dev-build-settings": "^0.2.0"
|
|
25
|
+
},
|
|
26
|
+
"author": "",
|
|
27
|
+
"license": "MIT"
|
|
28
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as CombineTopLevelModule from "../combine-top-level.js";
|
|
3
|
+
|
|
4
|
+
import {combineOptions} from "../combine-options.js";
|
|
5
|
+
|
|
6
|
+
jest.mock("../combine-top-level.js");
|
|
7
|
+
|
|
8
|
+
describe("#combineOptions", () => {
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
jest.clearAllMocks();
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("should call combine once per property per object", () => {
|
|
14
|
+
// Arrange
|
|
15
|
+
const toBeCombined = [
|
|
16
|
+
{a: "test1", b: "test1"},
|
|
17
|
+
{b: "test2"},
|
|
18
|
+
{a: "test3", c: "test3"},
|
|
19
|
+
];
|
|
20
|
+
const combineSpy = jest.spyOn(CombineTopLevelModule, "combineTopLevel");
|
|
21
|
+
|
|
22
|
+
// Act
|
|
23
|
+
combineOptions(...toBeCombined);
|
|
24
|
+
|
|
25
|
+
// Assert
|
|
26
|
+
expect(combineSpy).toHaveBeenCalledTimes(5);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should ignore falsy args", () => {
|
|
30
|
+
// Arrange
|
|
31
|
+
const toBeCombined = [null, {a: "test"}, {b: "test"}, 0, undefined];
|
|
32
|
+
const combineSpy = jest.spyOn(CombineTopLevelModule, "combineTopLevel");
|
|
33
|
+
|
|
34
|
+
// Act
|
|
35
|
+
combineOptions(...toBeCombined);
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(combineSpy).toHaveBeenCalledTimes(2);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return the combined object", () => {
|
|
42
|
+
// Arrange
|
|
43
|
+
const toBeCombined = [
|
|
44
|
+
{a: "test1", b: "test1"},
|
|
45
|
+
{b: "test2"},
|
|
46
|
+
{a: "test3", c: "test3"},
|
|
47
|
+
];
|
|
48
|
+
jest.spyOn(CombineTopLevelModule, "combineTopLevel").mockImplementation(
|
|
49
|
+
// Just for testing, we know the values are strings, so let's
|
|
50
|
+
// combine them with concatenation so we see the order of
|
|
51
|
+
// combination in the result.
|
|
52
|
+
(v1, v2) => `${v1 === undefined ? "" : v1}${v2}`,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Act
|
|
56
|
+
const result = combineOptions(...toBeCombined);
|
|
57
|
+
|
|
58
|
+
// Assert
|
|
59
|
+
expect(result).toEqual({
|
|
60
|
+
a: "test1test3",
|
|
61
|
+
b: "test1test2",
|
|
62
|
+
c: "test3",
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
});
|