@khanacademy/wonder-blocks-testing 2.0.7 → 3.0.1
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 +23 -0
- package/dist/es/index.js +43 -5
- package/dist/index.js +43 -5
- package/package.json +2 -2
- package/src/fixtures/__tests__/fixtures.test.js +66 -22
- package/src/fixtures/adapters/__tests__/adapter-group.test.js +24 -0
- package/src/fixtures/adapters/__tests__/adapter.test.js +6 -0
- package/src/fixtures/adapters/storybook.js +4 -1
- package/src/fixtures/fixtures.basic.stories.js +1 -1
- package/src/fixtures/fixtures.defaultwrapper.stories.js +1 -1
- package/src/fixtures/fixtures.js +47 -3
- package/src/fixtures/types.js +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-testing
|
|
2
2
|
|
|
3
|
+
## 3.0.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 6e4fbeed: Make sure simplified fixtures call copes with return values from React.forwardRef
|
|
8
|
+
|
|
9
|
+
## 3.0.0
|
|
10
|
+
|
|
11
|
+
### Major Changes
|
|
12
|
+
|
|
13
|
+
- 9a43cc06: Allow for autogenerating titles in Storybook
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 222cb8db: Add simplified signature for common usage of `fixtures` function
|
|
18
|
+
|
|
19
|
+
## 2.0.8
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies [34407c4a]
|
|
24
|
+
- @khanacademy/wonder-blocks-data@7.0.0
|
|
25
|
+
|
|
3
26
|
## 2.0.7
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/dist/es/index.js
CHANGED
|
@@ -118,7 +118,10 @@ class Adapter {
|
|
|
118
118
|
*/
|
|
119
119
|
const getAdapter = (MountingComponent = null) => new Adapter("storybook", ({
|
|
120
120
|
title,
|
|
121
|
-
description: groupDescription
|
|
121
|
+
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
|
+
getDefaultTitle: _
|
|
122
125
|
}, adapterOptions, declaredFixtures) => {
|
|
123
126
|
const templateMap = new WeakMap();
|
|
124
127
|
|
|
@@ -262,6 +265,38 @@ const combineOptions = (...toBeCombined) => {
|
|
|
262
265
|
return combined;
|
|
263
266
|
};
|
|
264
267
|
|
|
268
|
+
const normalizeOptions = componentOrOptions => {
|
|
269
|
+
// To differentiate between a React component and a FixturesOptions object,
|
|
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") {
|
|
289
|
+
return {
|
|
290
|
+
// $FlowIgnore[incompatible-return]
|
|
291
|
+
component: componentOrOptions
|
|
292
|
+
};
|
|
293
|
+
} // We can't test for React.ComponentType at runtime.
|
|
294
|
+
// Let's assume our simple heuristic above is sufficient.
|
|
295
|
+
// $FlowIgnore[incompatible-return]
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
return componentOrOptions;
|
|
299
|
+
};
|
|
265
300
|
/**
|
|
266
301
|
* Describe a group of fixtures for a given component.
|
|
267
302
|
*
|
|
@@ -280,7 +315,9 @@ const combineOptions = (...toBeCombined) => {
|
|
|
280
315
|
* storybook, the popular framework, uses both default and named exports for
|
|
281
316
|
* its interface.
|
|
282
317
|
*/
|
|
283
|
-
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
const fixtures = (componentOrOptions, fn) => {
|
|
284
321
|
var _additionalAdapterOpt;
|
|
285
322
|
|
|
286
323
|
const {
|
|
@@ -293,11 +330,12 @@ const fixtures = (options, fn) => {
|
|
|
293
330
|
description: groupDescription,
|
|
294
331
|
defaultWrapper,
|
|
295
332
|
additionalAdapterOptions
|
|
296
|
-
} =
|
|
333
|
+
} = normalizeOptions(componentOrOptions); // 1. Create a new adapter group.
|
|
297
334
|
|
|
298
335
|
const group = adapter.declareGroup({
|
|
299
|
-
title
|
|
300
|
-
description: groupDescription
|
|
336
|
+
title,
|
|
337
|
+
description: groupDescription,
|
|
338
|
+
getDefaultTitle: () => component.displayName || component.name || "Component"
|
|
301
339
|
}); // 2. Invoke fn with a function that can add a new fixture.
|
|
302
340
|
|
|
303
341
|
const addFixture = (description, props, wrapper = null) => {
|
package/dist/index.js
CHANGED
|
@@ -247,7 +247,10 @@ __webpack_require__.r(__webpack_exports__);
|
|
|
247
247
|
*/
|
|
248
248
|
const getAdapter = (MountingComponent = null) => new _adapter_js__WEBPACK_IMPORTED_MODULE_2__[/* Adapter */ "a"]("storybook", ({
|
|
249
249
|
title,
|
|
250
|
-
description: groupDescription
|
|
250
|
+
description: groupDescription,
|
|
251
|
+
// We don't use the default title in Storybook as storybook
|
|
252
|
+
// will generate titles for us if we pass a nullish title.
|
|
253
|
+
getDefaultTitle: _
|
|
251
254
|
}, adapterOptions, declaredFixtures) => {
|
|
252
255
|
const templateMap = new WeakMap();
|
|
253
256
|
|
|
@@ -323,6 +326,38 @@ const getAdapter = (MountingComponent = null) => new _adapter_js__WEBPACK_IMPORT
|
|
|
323
326
|
|
|
324
327
|
|
|
325
328
|
|
|
329
|
+
const normalizeOptions = componentOrOptions => {
|
|
330
|
+
// To differentiate between a React component and a FixturesOptions object,
|
|
331
|
+
// we have to do some type checking.
|
|
332
|
+
//
|
|
333
|
+
// Alternatives I considered were:
|
|
334
|
+
// - Use an additional parameter for the options and then do an arg number
|
|
335
|
+
// check, but that always makes typing a function harder and often breaks
|
|
336
|
+
// types. I didn't want that battle today.
|
|
337
|
+
// - Use a tuple when providing component and options with the first element
|
|
338
|
+
// being the component and the second being the options. However that
|
|
339
|
+
// feels like an obscure API even though it's really easy to do the
|
|
340
|
+
// typing.
|
|
341
|
+
if ( // Most React components, whether functional or class-based, are
|
|
342
|
+
// inherently functions in JavaScript, so a check for functions is
|
|
343
|
+
// usually sufficient.
|
|
344
|
+
typeof componentOrOptions === "function" || // However, the return of React.forwardRef is not a function,
|
|
345
|
+
// so we also have to cope with that.
|
|
346
|
+
// A forwardRef has $$typeof = Symbol(react.forward_ref) and a
|
|
347
|
+
// render function.
|
|
348
|
+
// $FlowIgnore[prop-missing]
|
|
349
|
+
typeof componentOrOptions.render === "function") {
|
|
350
|
+
return {
|
|
351
|
+
// $FlowIgnore[incompatible-return]
|
|
352
|
+
component: componentOrOptions
|
|
353
|
+
};
|
|
354
|
+
} // We can't test for React.ComponentType at runtime.
|
|
355
|
+
// Let's assume our simple heuristic above is sufficient.
|
|
356
|
+
// $FlowIgnore[incompatible-return]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
return componentOrOptions;
|
|
360
|
+
};
|
|
326
361
|
/**
|
|
327
362
|
* Describe a group of fixtures for a given component.
|
|
328
363
|
*
|
|
@@ -341,7 +376,9 @@ const getAdapter = (MountingComponent = null) => new _adapter_js__WEBPACK_IMPORT
|
|
|
341
376
|
* storybook, the popular framework, uses both default and named exports for
|
|
342
377
|
* its interface.
|
|
343
378
|
*/
|
|
344
|
-
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
const fixtures = (componentOrOptions, fn) => {
|
|
345
382
|
var _additionalAdapterOpt;
|
|
346
383
|
|
|
347
384
|
const {
|
|
@@ -354,11 +391,12 @@ const fixtures = (options, fn) => {
|
|
|
354
391
|
description: groupDescription,
|
|
355
392
|
defaultWrapper,
|
|
356
393
|
additionalAdapterOptions
|
|
357
|
-
} =
|
|
394
|
+
} = normalizeOptions(componentOrOptions); // 1. Create a new adapter group.
|
|
358
395
|
|
|
359
396
|
const group = adapter.declareGroup({
|
|
360
|
-
title
|
|
361
|
-
description: groupDescription
|
|
397
|
+
title,
|
|
398
|
+
description: groupDescription,
|
|
399
|
+
getDefaultTitle: () => component.displayName || component.name || "Component"
|
|
362
400
|
}); // 2. Invoke fn with a function that can add a new fixture.
|
|
363
401
|
|
|
364
402
|
const addFixture = (description, props, wrapper = null) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
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": "^7.0.0"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^0.1.2",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @flow
|
|
2
|
+
import * as React from "react";
|
|
2
3
|
import * as SetupModule from "../setup.js";
|
|
3
4
|
import * as CombineOptionsModule from "../combine-options.js";
|
|
4
5
|
import {fixtures} from "../fixtures.js";
|
|
@@ -11,6 +12,28 @@ describe("#fixtures", () => {
|
|
|
11
12
|
jest.clearAllMocks();
|
|
12
13
|
});
|
|
13
14
|
|
|
15
|
+
it("should declare a group on the configured adapter based off the given component", () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
const fakeGroup = {
|
|
18
|
+
closeGroup: jest.fn(),
|
|
19
|
+
};
|
|
20
|
+
const adapter = {
|
|
21
|
+
declareGroup: jest.fn().mockReturnValue(fakeGroup),
|
|
22
|
+
name: "testadapter",
|
|
23
|
+
};
|
|
24
|
+
jest.spyOn(SetupModule, "getConfiguration").mockReturnValue({
|
|
25
|
+
adapter,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Act
|
|
29
|
+
fixtures(() => "COMPONENT", jest.fn());
|
|
30
|
+
|
|
31
|
+
// Assert
|
|
32
|
+
expect(adapter.declareGroup).toHaveBeenCalledWith({
|
|
33
|
+
getDefaultTitle: expect.any(Function),
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
14
37
|
it("should declare a group on the configured adapter with the given title and description", () => {
|
|
15
38
|
// Arrange
|
|
16
39
|
const fakeGroup = {
|
|
@@ -38,6 +61,7 @@ describe("#fixtures", () => {
|
|
|
38
61
|
expect(adapter.declareGroup).toHaveBeenCalledWith({
|
|
39
62
|
title: "TITLE",
|
|
40
63
|
description: "DESCRIPTION",
|
|
64
|
+
getDefaultTitle: expect.any(Function),
|
|
41
65
|
});
|
|
42
66
|
});
|
|
43
67
|
|
|
@@ -63,11 +87,11 @@ describe("#fixtures", () => {
|
|
|
63
87
|
},
|
|
64
88
|
jest.fn(),
|
|
65
89
|
);
|
|
90
|
+
const {getDefaultTitle} = adapter.declareGroup.mock.calls[0][0];
|
|
91
|
+
const result = getDefaultTitle();
|
|
66
92
|
|
|
67
93
|
// Assert
|
|
68
|
-
expect(
|
|
69
|
-
title: "DISPLAYNAME",
|
|
70
|
-
});
|
|
94
|
+
expect(result).toBe("DISPLAYNAME");
|
|
71
95
|
});
|
|
72
96
|
|
|
73
97
|
it("should default the title to the component.name in the absence of component.displayName", () => {
|
|
@@ -87,17 +111,12 @@ describe("#fixtures", () => {
|
|
|
87
111
|
};
|
|
88
112
|
|
|
89
113
|
// Act
|
|
90
|
-
fixtures(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
},
|
|
94
|
-
jest.fn(),
|
|
95
|
-
);
|
|
114
|
+
fixtures(component, jest.fn());
|
|
115
|
+
const {getDefaultTitle} = adapter.declareGroup.mock.calls[0][0];
|
|
116
|
+
const result = getDefaultTitle();
|
|
96
117
|
|
|
97
118
|
// Assert
|
|
98
|
-
expect(
|
|
99
|
-
title: "FUNCTIONNAME",
|
|
100
|
-
});
|
|
119
|
+
expect(result).toBe("FUNCTIONNAME");
|
|
101
120
|
});
|
|
102
121
|
|
|
103
122
|
it("should default the title to 'Component' in the absence of component.name", () => {
|
|
@@ -114,17 +133,12 @@ describe("#fixtures", () => {
|
|
|
114
133
|
});
|
|
115
134
|
|
|
116
135
|
// Act
|
|
117
|
-
fixtures(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
},
|
|
121
|
-
jest.fn(),
|
|
122
|
-
);
|
|
136
|
+
fixtures(() => "test", jest.fn());
|
|
137
|
+
const {getDefaultTitle} = adapter.declareGroup.mock.calls[0][0];
|
|
138
|
+
const result = getDefaultTitle();
|
|
123
139
|
|
|
124
140
|
// Assert
|
|
125
|
-
expect(
|
|
126
|
-
title: "Component",
|
|
127
|
-
});
|
|
141
|
+
expect(result).toBe("Component");
|
|
128
142
|
});
|
|
129
143
|
|
|
130
144
|
it("should invoke the passed fn with function argument", () => {
|
|
@@ -271,7 +285,7 @@ describe("#fixtures", () => {
|
|
|
271
285
|
});
|
|
272
286
|
|
|
273
287
|
describe("injected fixture fn", () => {
|
|
274
|
-
it("should call group.declareFixture with description
|
|
288
|
+
it("should call group.declareFixture with description, props getter, and component", () => {
|
|
275
289
|
// Arrange
|
|
276
290
|
const fakeGroup = {
|
|
277
291
|
declareFixture: jest.fn(),
|
|
@@ -306,6 +320,36 @@ describe("#fixtures", () => {
|
|
|
306
320
|
});
|
|
307
321
|
});
|
|
308
322
|
|
|
323
|
+
it("should call group.declareFixture with component if component is forward ref", () => {
|
|
324
|
+
// Arrange
|
|
325
|
+
const fakeGroup = {
|
|
326
|
+
declareFixture: jest.fn(),
|
|
327
|
+
closeGroup: jest.fn(),
|
|
328
|
+
};
|
|
329
|
+
const adapter = {
|
|
330
|
+
declareGroup: jest.fn().mockReturnValue(fakeGroup),
|
|
331
|
+
name: "testadapter",
|
|
332
|
+
};
|
|
333
|
+
jest.spyOn(SetupModule, "getConfiguration").mockReturnValue({
|
|
334
|
+
adapter,
|
|
335
|
+
});
|
|
336
|
+
const component = React.forwardRef((props, ref) => (
|
|
337
|
+
<div {...props} ref={ref} />
|
|
338
|
+
));
|
|
339
|
+
|
|
340
|
+
// Act
|
|
341
|
+
fixtures(component, (fixture) => {
|
|
342
|
+
fixture("FIXTURE_DESCRIPTION", {these: "areProps"});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// Assert
|
|
346
|
+
expect(fakeGroup.declareFixture).toHaveBeenCalledWith({
|
|
347
|
+
description: "FIXTURE_DESCRIPTION",
|
|
348
|
+
getProps: expect.any(Function),
|
|
349
|
+
component,
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
309
353
|
it("should pass wrapper component to group.declareFixture", () => {
|
|
310
354
|
// Arrange
|
|
311
355
|
const fakeGroup = {
|
|
@@ -12,6 +12,9 @@ describe("AdapterGroup", () => {
|
|
|
12
12
|
new AdapterGroup(badCloseGroupFn, {
|
|
13
13
|
title: "TITLE",
|
|
14
14
|
description: null,
|
|
15
|
+
getDefaultTitle: () => {
|
|
16
|
+
throw new Error("NOT IMPLEMENTED");
|
|
17
|
+
},
|
|
15
18
|
});
|
|
16
19
|
|
|
17
20
|
expect(act).toThrowErrorMatchingInlineSnapshot(
|
|
@@ -40,6 +43,9 @@ describe("AdapterGroup", () => {
|
|
|
40
43
|
const groupOptions = {
|
|
41
44
|
title: "TITLE",
|
|
42
45
|
description: null,
|
|
46
|
+
getDefaultTitle: () => {
|
|
47
|
+
throw new Error("NOT IMPLEMENTED");
|
|
48
|
+
},
|
|
43
49
|
};
|
|
44
50
|
const adapterGroup = new AdapterGroup(closeGroupFn, groupOptions);
|
|
45
51
|
|
|
@@ -56,6 +62,9 @@ describe("AdapterGroup", () => {
|
|
|
56
62
|
const groupOptions = {
|
|
57
63
|
title: "TITLE",
|
|
58
64
|
description: null,
|
|
65
|
+
getDefaultTitle: () => {
|
|
66
|
+
throw new Error("NOT IMPLEMENTED");
|
|
67
|
+
},
|
|
59
68
|
};
|
|
60
69
|
const adapterSpecificOptions = {
|
|
61
70
|
adapterSpecificOption: "adapterSpecificOption",
|
|
@@ -79,6 +88,9 @@ describe("AdapterGroup", () => {
|
|
|
79
88
|
const groupOptions = {
|
|
80
89
|
title: "TITLE",
|
|
81
90
|
description: "DESCRIPTION",
|
|
91
|
+
getDefaultTitle: () => {
|
|
92
|
+
throw new Error("NOT IMPLEMENTED");
|
|
93
|
+
},
|
|
82
94
|
};
|
|
83
95
|
const adapterGroup = new AdapterGroup(closeGroupFn, groupOptions);
|
|
84
96
|
const fixture = {
|
|
@@ -103,6 +115,9 @@ describe("AdapterGroup", () => {
|
|
|
103
115
|
const groupOptions = {
|
|
104
116
|
title: "TITLE",
|
|
105
117
|
description: null,
|
|
118
|
+
getDefaultTitle: () => {
|
|
119
|
+
throw new Error("NOT IMPLEMENTED");
|
|
120
|
+
},
|
|
106
121
|
};
|
|
107
122
|
const adapterGroup = new AdapterGroup(closeGroupFn, groupOptions);
|
|
108
123
|
adapterGroup.closeGroup();
|
|
@@ -126,6 +141,9 @@ describe("AdapterGroup", () => {
|
|
|
126
141
|
const groupOptions = {
|
|
127
142
|
title: "TITLE",
|
|
128
143
|
description: null,
|
|
144
|
+
getDefaultTitle: () => {
|
|
145
|
+
throw new Error("NOT IMPLEMENTED");
|
|
146
|
+
},
|
|
129
147
|
};
|
|
130
148
|
const adapterGroup = new AdapterGroup(
|
|
131
149
|
closeGroupFn,
|
|
@@ -147,6 +165,9 @@ describe("AdapterGroup", () => {
|
|
|
147
165
|
const groupOptions = {
|
|
148
166
|
title: "TITLE",
|
|
149
167
|
description: "DESCRIPTION",
|
|
168
|
+
getDefaultTitle: () => {
|
|
169
|
+
throw new Error("NOT IMPLEMENTED");
|
|
170
|
+
},
|
|
150
171
|
};
|
|
151
172
|
const adapterGroup = new AdapterGroup(closeGroupFn, groupOptions);
|
|
152
173
|
const fixture1 = {
|
|
@@ -178,6 +199,9 @@ describe("AdapterGroup", () => {
|
|
|
178
199
|
const groupOptions = {
|
|
179
200
|
title: "TITLE",
|
|
180
201
|
description: null,
|
|
202
|
+
getDefaultTitle: () => {
|
|
203
|
+
throw new Error("NOT IMPLEMENTED");
|
|
204
|
+
},
|
|
181
205
|
};
|
|
182
206
|
const adapterGroup = new AdapterGroup(closeGroupFn, groupOptions);
|
|
183
207
|
adapterGroup.closeGroup();
|
|
@@ -56,6 +56,9 @@ describe("Adapter", () => {
|
|
|
56
56
|
const options = {
|
|
57
57
|
title: "group_title",
|
|
58
58
|
description: "group_description",
|
|
59
|
+
getDefaultTitle: () => {
|
|
60
|
+
throw new Error("NOT IMPLEMENTED");
|
|
61
|
+
},
|
|
59
62
|
};
|
|
60
63
|
const adapterGroupSpy = jest
|
|
61
64
|
.spyOn(AdapterGroupModule, "AdapterGroup")
|
|
@@ -82,6 +85,9 @@ describe("Adapter", () => {
|
|
|
82
85
|
const result = adapter.declareGroup({
|
|
83
86
|
title: "group_title",
|
|
84
87
|
description: "group_description",
|
|
88
|
+
getDefaultTitle: () => {
|
|
89
|
+
throw new Error("NOT IMPLEMENTED");
|
|
90
|
+
},
|
|
85
91
|
});
|
|
86
92
|
|
|
87
93
|
// Assert
|
|
@@ -26,7 +26,7 @@ export type StorybookOptions = {|
|
|
|
26
26
|
|};
|
|
27
27
|
|
|
28
28
|
type DefaultExport = {|
|
|
29
|
-
title
|
|
29
|
+
title?: ?string,
|
|
30
30
|
...StorybookOptions,
|
|
31
31
|
|};
|
|
32
32
|
|
|
@@ -47,6 +47,9 @@ export const getAdapter: AdapterFactory<StorybookOptions, Exports<any>> = (
|
|
|
47
47
|
{
|
|
48
48
|
title,
|
|
49
49
|
description: groupDescription,
|
|
50
|
+
// We don't use the default title in Storybook as storybook
|
|
51
|
+
// will generate titles for us if we pass a nullish title.
|
|
52
|
+
getDefaultTitle: _,
|
|
50
53
|
}: $ReadOnly<AdapterGroupOptions>,
|
|
51
54
|
adapterOptions: ?$ReadOnly<StorybookOptions>,
|
|
52
55
|
declaredFixtures: $ReadOnlyArray<AdapterFixtureOptions<TProps>>,
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
|
|
4
4
|
import {setupFixtures, fixtures, adapters} from "../index.js";
|
|
5
5
|
|
|
6
|
-
// Normally would call setup from the storybook.
|
|
6
|
+
// Normally would call setup from the storybook.preview.js for a project.
|
|
7
7
|
setupFixtures({
|
|
8
8
|
adapter: adapters.storybook(),
|
|
9
9
|
});
|
|
@@ -3,7 +3,7 @@ import * as React from "react";
|
|
|
3
3
|
|
|
4
4
|
import {setupFixtures, fixtures, adapters} from "../index.js";
|
|
5
5
|
|
|
6
|
-
// Normally would call setup from the storybook.
|
|
6
|
+
// Normally would call setup from the storybook.preview.js for a project.
|
|
7
7
|
setupFixtures({
|
|
8
8
|
adapter: adapters.storybook(),
|
|
9
9
|
});
|
package/src/fixtures/fixtures.js
CHANGED
|
@@ -9,6 +9,45 @@ type FixtureProps<TProps: {...}> =
|
|
|
9
9
|
| $ReadOnly<TProps>
|
|
10
10
|
| ((options: $ReadOnly<GetPropsOptions>) => $ReadOnly<TProps>);
|
|
11
11
|
|
|
12
|
+
const normalizeOptions = <TProps: {...}>(
|
|
13
|
+
componentOrOptions:
|
|
14
|
+
| React.ComponentType<TProps>
|
|
15
|
+
| $ReadOnly<FixturesOptions<TProps>>,
|
|
16
|
+
): $ReadOnly<FixturesOptions<TProps>> => {
|
|
17
|
+
// To differentiate between a React component and a FixturesOptions object,
|
|
18
|
+
// we have to do some type checking.
|
|
19
|
+
//
|
|
20
|
+
// Alternatives I considered were:
|
|
21
|
+
// - Use an additional parameter for the options and then do an arg number
|
|
22
|
+
// check, but that always makes typing a function harder and often breaks
|
|
23
|
+
// types. I didn't want that battle today.
|
|
24
|
+
// - Use a tuple when providing component and options with the first element
|
|
25
|
+
// being the component and the second being the options. However that
|
|
26
|
+
// feels like an obscure API even though it's really easy to do the
|
|
27
|
+
// typing.
|
|
28
|
+
if (
|
|
29
|
+
// Most React components, whether functional or class-based, are
|
|
30
|
+
// inherently functions in JavaScript, so a check for functions is
|
|
31
|
+
// usually sufficient.
|
|
32
|
+
typeof componentOrOptions === "function" ||
|
|
33
|
+
// However, the return of React.forwardRef is not a function,
|
|
34
|
+
// so we also have to cope with that.
|
|
35
|
+
// A forwardRef has $$typeof = Symbol(react.forward_ref) and a
|
|
36
|
+
// render function.
|
|
37
|
+
// $FlowIgnore[prop-missing]
|
|
38
|
+
typeof componentOrOptions.render === "function"
|
|
39
|
+
) {
|
|
40
|
+
return {
|
|
41
|
+
// $FlowIgnore[incompatible-return]
|
|
42
|
+
component: componentOrOptions,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// We can't test for React.ComponentType at runtime.
|
|
46
|
+
// Let's assume our simple heuristic above is sufficient.
|
|
47
|
+
// $FlowIgnore[incompatible-return]
|
|
48
|
+
return componentOrOptions;
|
|
49
|
+
};
|
|
50
|
+
|
|
12
51
|
/**
|
|
13
52
|
* Describe a group of fixtures for a given component.
|
|
14
53
|
*
|
|
@@ -28,7 +67,9 @@ type FixtureProps<TProps: {...}> =
|
|
|
28
67
|
* its interface.
|
|
29
68
|
*/
|
|
30
69
|
export const fixtures = <TProps: {...}>(
|
|
31
|
-
|
|
70
|
+
componentOrOptions:
|
|
71
|
+
| React.ComponentType<TProps>
|
|
72
|
+
| $ReadOnly<FixturesOptions<TProps>>,
|
|
32
73
|
fn: (
|
|
33
74
|
fixture: (
|
|
34
75
|
description: string,
|
|
@@ -38,18 +79,21 @@ export const fixtures = <TProps: {...}>(
|
|
|
38
79
|
) => void,
|
|
39
80
|
): ?$ReadOnly<mixed> => {
|
|
40
81
|
const {adapter, defaultAdapterOptions} = getConfiguration();
|
|
82
|
+
|
|
41
83
|
const {
|
|
42
84
|
title,
|
|
43
85
|
component,
|
|
44
86
|
description: groupDescription,
|
|
45
87
|
defaultWrapper,
|
|
46
88
|
additionalAdapterOptions,
|
|
47
|
-
} =
|
|
89
|
+
} = normalizeOptions(componentOrOptions);
|
|
48
90
|
|
|
49
91
|
// 1. Create a new adapter group.
|
|
50
92
|
const group = adapter.declareGroup<TProps>({
|
|
51
|
-
title
|
|
93
|
+
title,
|
|
52
94
|
description: groupDescription,
|
|
95
|
+
getDefaultTitle: () =>
|
|
96
|
+
component.displayName || component.name || "Component",
|
|
53
97
|
});
|
|
54
98
|
|
|
55
99
|
// 2. Invoke fn with a function that can add a new fixture.
|
package/src/fixtures/types.js
CHANGED
|
@@ -81,13 +81,22 @@ export type AdapterFixtureOptions<TProps: {...}> = {|
|
|
|
81
81
|
export type AdapterGroupOptions = {|
|
|
82
82
|
/**
|
|
83
83
|
* The title of the group.
|
|
84
|
+
*
|
|
85
|
+
* If omitted, the adapter is free to generate a default or ask for one
|
|
86
|
+
* using the passed getDefaultTitle() function.
|
|
84
87
|
*/
|
|
85
|
-
+title: string,
|
|
88
|
+
+title: ?string,
|
|
86
89
|
|
|
87
90
|
/**
|
|
88
91
|
* Description of the group.
|
|
89
92
|
*/
|
|
90
93
|
+description: ?string,
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Function that will generate a default title if an adapter cannot
|
|
97
|
+
* generate its own.
|
|
98
|
+
*/
|
|
99
|
+
+getDefaultTitle: () => string,
|
|
91
100
|
|};
|
|
92
101
|
|
|
93
102
|
/**
|