@khanacademy/wonder-blocks-testing 5.0.2 → 7.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 +18 -0
- package/dist/es/index.js +19 -191
- package/dist/index.js +87 -462
- package/package.json +2 -2
- package/src/__docs__/_overview_fixtures.stories.mdx +2 -6
- package/src/__docs__/exports.fixtures.stories.mdx +13 -27
- package/src/__docs__/types.get-props-options.stories.mdx +28 -1
- package/src/fixtures/__tests__/fixtures.test.js +75 -470
- package/src/fixtures/fixtures.basic.stories.js +34 -49
- package/src/fixtures/fixtures.defaultwrapper.stories.js +26 -40
- package/src/fixtures/fixtures.js +57 -100
- package/src/fixtures/types.js +7 -188
- package/src/index.js +1 -14
- package/src/__docs__/exports.fixture-adapters.stories.mdx +0 -49
- package/src/__docs__/exports.setup-fixtures.stories.mdx +0 -22
- package/src/__docs__/types.custom-mount-props.stories.mdx +0 -35
- package/src/__docs__/types.fixtures-adapter-factory.stories.mdx +0 -23
- package/src/__docs__/types.fixtures-adapter-fixture-options.stories.mdx +0 -35
- package/src/__docs__/types.fixtures-adapter-group-options.stories.mdx +0 -37
- package/src/__docs__/types.fixtures-adapter-group.stories.mdx +0 -43
- package/src/__docs__/types.fixtures-adapter-options.stories.mdx +0 -21
- package/src/__docs__/types.fixtures-adapter.stories.mdx +0 -35
- package/src/__docs__/types.fixtures-configuration.stories.mdx +0 -35
- package/src/__docs__/types.fixtures-options.stories.mdx +0 -51
- package/src/fixtures/__tests__/combine-options.test.js +0 -65
- package/src/fixtures/__tests__/combine-top-level.test.js +0 -100
- package/src/fixtures/__tests__/setup.test.js +0 -71
- package/src/fixtures/adapters/__tests__/__snapshots__/adapter-group.test.js.snap +0 -9
- package/src/fixtures/adapters/__tests__/__snapshots__/adapter.test.js.snap +0 -13
- package/src/fixtures/adapters/__tests__/adapter-group.test.js +0 -223
- package/src/fixtures/adapters/__tests__/adapter.test.js +0 -97
- package/src/fixtures/adapters/__tests__/storybook.test.js +0 -329
- package/src/fixtures/adapters/adapter-group.js +0 -88
- package/src/fixtures/adapters/adapter.js +0 -63
- package/src/fixtures/adapters/adapters.js +0 -2
- package/src/fixtures/adapters/storybook.js +0 -125
- package/src/fixtures/combine-options.js +0 -25
- package/src/fixtures/combine-top-level.js +0 -44
- package/src/fixtures/setup.js +0 -30
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import type {
|
|
3
|
-
FixturesAdapterGroup,
|
|
4
|
-
FixturesAdapterGroupOptions,
|
|
5
|
-
FixturesAdapterFixtureOptions,
|
|
6
|
-
} from "../types.js";
|
|
7
|
-
|
|
8
|
-
export type CloseGroupFn<TProps: {...}, Options: {...}, Exports: {...}> = (
|
|
9
|
-
options: $ReadOnly<FixturesAdapterGroupOptions>,
|
|
10
|
-
adapterOptions: ?$ReadOnly<Options>,
|
|
11
|
-
declaredFixtures: $ReadOnlyArray<FixturesAdapterFixtureOptions<TProps>>,
|
|
12
|
-
) => ?$ReadOnly<Exports>;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Simple adapter group implementation.
|
|
16
|
-
*/
|
|
17
|
-
export class AdapterGroup<TProps: {...}, Options: {...}, Exports: {...}>
|
|
18
|
-
implements FixturesAdapterGroup<TProps, Options, Exports>
|
|
19
|
-
{
|
|
20
|
-
_closeGroupFn: ?CloseGroupFn<TProps, Options, Exports>;
|
|
21
|
-
+_fixtures: Array<FixturesAdapterFixtureOptions<TProps>>;
|
|
22
|
-
+_options: $ReadOnly<FixturesAdapterGroupOptions>;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create an adapter group.
|
|
26
|
-
*
|
|
27
|
-
* @param {CloseGroupFn<TProps, Options, Exports>} closeGroupFn A function
|
|
28
|
-
* to invoke when the group is closed.
|
|
29
|
-
* @param {AdapterGroupOptions} options The options for the group.
|
|
30
|
-
*/
|
|
31
|
-
constructor(
|
|
32
|
-
closeGroupFn: CloseGroupFn<TProps, Options, Exports>,
|
|
33
|
-
options: $ReadOnly<FixturesAdapterGroupOptions>,
|
|
34
|
-
) {
|
|
35
|
-
if (typeof closeGroupFn !== "function") {
|
|
36
|
-
throw new TypeError("closeGroupFn must be a function");
|
|
37
|
-
}
|
|
38
|
-
if (typeof options !== "object" || options === null) {
|
|
39
|
-
throw new TypeError("options must be an object");
|
|
40
|
-
}
|
|
41
|
-
this._closeGroupFn = closeGroupFn;
|
|
42
|
-
this._options = options;
|
|
43
|
-
this._fixtures = [];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Close the group.
|
|
48
|
-
*
|
|
49
|
-
* This declares that no more fixtures are to be added to the group,
|
|
50
|
-
* and will call the parent adapter with the declared fixtures so that they
|
|
51
|
-
* can be adapted for the target fixture framework, such as Storybook.
|
|
52
|
-
*/
|
|
53
|
-
+closeGroup: (
|
|
54
|
-
adapterOptions: ?$ReadOnly<Partial<Options>>,
|
|
55
|
-
) => ?$ReadOnly<Exports> = (adapterOptions = null) => {
|
|
56
|
-
if (this._closeGroupFn == null) {
|
|
57
|
-
throw new Error("Group already closed");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
try {
|
|
61
|
-
return this._closeGroupFn(
|
|
62
|
-
this._options,
|
|
63
|
-
adapterOptions,
|
|
64
|
-
this._fixtures,
|
|
65
|
-
);
|
|
66
|
-
} finally {
|
|
67
|
-
this._closeGroupFn = null;
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Declare a fixture within the group.
|
|
73
|
-
*
|
|
74
|
-
* @param {FixturesAdapterFixtureOptions<TProps>} fixtureOptions The options
|
|
75
|
-
* describing the fixture.
|
|
76
|
-
*/
|
|
77
|
-
+declareFixture: (
|
|
78
|
-
options: $ReadOnly<FixturesAdapterFixtureOptions<TProps>>,
|
|
79
|
-
) => void = (options) => {
|
|
80
|
-
if (typeof options !== "object" || options === null) {
|
|
81
|
-
throw new TypeError("options must be an object");
|
|
82
|
-
}
|
|
83
|
-
if (this._closeGroupFn == null) {
|
|
84
|
-
throw new Error("Cannot declare fixtures after closing the group");
|
|
85
|
-
}
|
|
86
|
-
this._fixtures.push(options);
|
|
87
|
-
};
|
|
88
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {AdapterGroup, type CloseGroupFn} from "./adapter-group.js";
|
|
3
|
-
import type {
|
|
4
|
-
FixturesAdapter,
|
|
5
|
-
FixturesAdapterGroup,
|
|
6
|
-
FixturesAdapterGroupOptions,
|
|
7
|
-
} from "../types.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Class for implementing a custom adapter.
|
|
11
|
-
*/
|
|
12
|
-
export class Adapter<Options: {...}, Exports: {...}>
|
|
13
|
-
implements FixturesAdapter<Options, Exports>
|
|
14
|
-
{
|
|
15
|
-
+_name: string;
|
|
16
|
-
+_closeGroupFn: CloseGroupFn<any, Options, Exports>;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @param {string} name The name of the adapter.
|
|
20
|
-
* @param {CloseGroupFn<any, Options, Exports>} closeGroupFn The function
|
|
21
|
-
* an adapter group should call when the group is closed. This is invoked
|
|
22
|
-
* by an adapter group when it is closed. This function is where an
|
|
23
|
-
* adapter implements the logic to generate the actual fixtures for the
|
|
24
|
-
* adapter's target framework.
|
|
25
|
-
*/
|
|
26
|
-
constructor(
|
|
27
|
-
name: string,
|
|
28
|
-
closeGroupFn: CloseGroupFn<any, Options, Exports>,
|
|
29
|
-
) {
|
|
30
|
-
if (typeof name !== "string") {
|
|
31
|
-
throw new TypeError("name must be a string");
|
|
32
|
-
}
|
|
33
|
-
if (name.trim() === "") {
|
|
34
|
-
throw new Error("name must be a non-empty string");
|
|
35
|
-
}
|
|
36
|
-
if (typeof closeGroupFn !== "function") {
|
|
37
|
-
throw new TypeError("closeGroupFn must be a function");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
this._name = name;
|
|
41
|
-
this._closeGroupFn = closeGroupFn;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* The name of the adapter.
|
|
46
|
-
*/
|
|
47
|
-
get name(): string {
|
|
48
|
-
return this._name;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Declare a new fixture group.
|
|
53
|
-
*
|
|
54
|
-
* @param {FixturesAdapterGroupOptions} options The options describing the fixture
|
|
55
|
-
* group.
|
|
56
|
-
* @returns {FixturesAdapterGroup} The new fixture group.
|
|
57
|
-
*/
|
|
58
|
-
declareGroup<Config: {...}>(
|
|
59
|
-
options: $ReadOnly<FixturesAdapterGroupOptions>,
|
|
60
|
-
): FixturesAdapterGroup<Config, Options, Exports> {
|
|
61
|
-
return new AdapterGroup(this._closeGroupFn, options);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import {action} from "@storybook/addon-actions";
|
|
4
|
-
import {Adapter} from "./adapter.js";
|
|
5
|
-
|
|
6
|
-
import type {
|
|
7
|
-
FixturesAdapterGroupOptions,
|
|
8
|
-
FixturesAdapterFixtureOptions,
|
|
9
|
-
FixturesAdapterFactory,
|
|
10
|
-
} from "../types.js";
|
|
11
|
-
|
|
12
|
-
type StoryContext = {|
|
|
13
|
-
args: $ReadOnly<any>,
|
|
14
|
-
argTypes: $ReadOnly<any>,
|
|
15
|
-
globals: $ReadOnlyArray<any>,
|
|
16
|
-
hooks: $ReadOnlyArray<any>,
|
|
17
|
-
parameters: $ReadOnly<any>,
|
|
18
|
-
viewMode: mixed,
|
|
19
|
-
|};
|
|
20
|
-
|
|
21
|
-
export type StorybookOptions = {|
|
|
22
|
-
decorators?: Array<
|
|
23
|
-
(story: React.ComponentType<any>, context: StoryContext) => React.Node,
|
|
24
|
-
>,
|
|
25
|
-
parameters?: $ReadOnly<any>,
|
|
26
|
-
|};
|
|
27
|
-
|
|
28
|
-
type DefaultExport = {|
|
|
29
|
-
title?: ?string,
|
|
30
|
-
...StorybookOptions,
|
|
31
|
-
|};
|
|
32
|
-
|
|
33
|
-
type Exports<TProps: {...}> = {|
|
|
34
|
-
default: DefaultExport,
|
|
35
|
-
[story: string]: React.ComponentType<TProps>,
|
|
36
|
-
|};
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get a fixture framework adapter for Storybook support.
|
|
40
|
-
*/
|
|
41
|
-
export const getAdapter: FixturesAdapterFactory<
|
|
42
|
-
StorybookOptions,
|
|
43
|
-
Exports<any>,
|
|
44
|
-
> = (MountingComponent = null) =>
|
|
45
|
-
new Adapter<StorybookOptions, Exports<any>>(
|
|
46
|
-
"storybook",
|
|
47
|
-
<TProps: {...}>(
|
|
48
|
-
{
|
|
49
|
-
title,
|
|
50
|
-
description: groupDescription,
|
|
51
|
-
// We don't use the default title in Storybook as storybook
|
|
52
|
-
// will generate titles for us if we pass a nullish title.
|
|
53
|
-
getDefaultTitle: _,
|
|
54
|
-
}: $ReadOnly<FixturesAdapterGroupOptions>,
|
|
55
|
-
adapterOptions: ?$ReadOnly<StorybookOptions>,
|
|
56
|
-
declaredFixtures: $ReadOnlyArray<
|
|
57
|
-
FixturesAdapterFixtureOptions<TProps>,
|
|
58
|
-
>,
|
|
59
|
-
): ?$ReadOnly<Exports<TProps>> => {
|
|
60
|
-
const templateMap = new WeakMap();
|
|
61
|
-
|
|
62
|
-
const log = (message, ...args) => action(message)(...args);
|
|
63
|
-
|
|
64
|
-
const exports = declaredFixtures.reduce(
|
|
65
|
-
(acc, {description, getProps, component: Component}, i) => {
|
|
66
|
-
const storyName = `${i + 1} ${description}`;
|
|
67
|
-
const exportName = storyName
|
|
68
|
-
// Make word boundaries start with an upper case letter.
|
|
69
|
-
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
70
|
-
// Remove all non-alphanumeric characters.
|
|
71
|
-
.replace(/[^\w]+/g, "")
|
|
72
|
-
// Remove all underscores.
|
|
73
|
-
.replace(/[_]+/g, "");
|
|
74
|
-
|
|
75
|
-
// We create a “template” of how args map to rendering
|
|
76
|
-
// for each type of component as the component here could
|
|
77
|
-
// be the component under test, or wrapped in a wrapper
|
|
78
|
-
// component. We don't use decorators for the wrapper
|
|
79
|
-
// because we may not be in a storybook context and it
|
|
80
|
-
// keeps the framework API simpler this way.
|
|
81
|
-
let Template = templateMap.get(Component);
|
|
82
|
-
if (Template == null) {
|
|
83
|
-
// The MountingComponent is a bit different than just a
|
|
84
|
-
// Storybook decorator. It's a React component that
|
|
85
|
-
// takes over rendering the component in the fixture
|
|
86
|
-
// with the given args, allowing for greater
|
|
87
|
-
// customization in a platform-agnostic manner (i.e.
|
|
88
|
-
// not just story format).
|
|
89
|
-
Template = MountingComponent
|
|
90
|
-
? (args) => (
|
|
91
|
-
<MountingComponent
|
|
92
|
-
component={Component}
|
|
93
|
-
props={args}
|
|
94
|
-
log={log}
|
|
95
|
-
/>
|
|
96
|
-
)
|
|
97
|
-
: (args) => <Component {...args} />;
|
|
98
|
-
templateMap.set(Component, Template);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Each story that shares that component then reuses that
|
|
102
|
-
// template.
|
|
103
|
-
acc[exportName] = Template.bind({});
|
|
104
|
-
acc[exportName].args = getProps({log});
|
|
105
|
-
// Adding a story name here means that we don't have to
|
|
106
|
-
// care about naming the exports correctly, if we don't
|
|
107
|
-
// want (useful if we need to autogenerate or manually
|
|
108
|
-
// expose ESM exports).
|
|
109
|
-
acc[exportName].storyName = storyName;
|
|
110
|
-
|
|
111
|
-
return acc;
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
default: {
|
|
115
|
-
title,
|
|
116
|
-
// TODO(somewhatabstract): Use groupDescription
|
|
117
|
-
// Possibly via a decorator?
|
|
118
|
-
...adapterOptions,
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
return Object.freeze(exports);
|
|
124
|
-
},
|
|
125
|
-
);
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {combineTopLevel} from "./combine-top-level.js";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Combine one or more objects into a single object.
|
|
6
|
-
*
|
|
7
|
-
* Objects later in the argument list take precedence over those that are
|
|
8
|
-
* earlier. Object and array values at the root level are merged.
|
|
9
|
-
*/
|
|
10
|
-
export const combineOptions = <Options: {...}>(
|
|
11
|
-
...toBeCombined: $ReadOnlyArray<Partial<Options>>
|
|
12
|
-
): $ReadOnly<Partial<Options>> => {
|
|
13
|
-
const combined = toBeCombined.filter(Boolean).reduce((acc, cur) => {
|
|
14
|
-
for (const key of Object.keys(cur)) {
|
|
15
|
-
// We always call combine, even if acc[key] is undefined
|
|
16
|
-
// because we need to make sure we clone values.
|
|
17
|
-
acc[key] = combineTopLevel(acc[key], cur[key]);
|
|
18
|
-
}
|
|
19
|
-
return acc;
|
|
20
|
-
}, {});
|
|
21
|
-
|
|
22
|
-
// We know that we are creating a compatible return type.
|
|
23
|
-
// $FlowIgnore[incompatible-return]
|
|
24
|
-
return combined;
|
|
25
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import {clone} from "@khanacademy/wonder-stuff-core";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Combine two values.
|
|
6
|
-
*
|
|
7
|
-
* This method clones val2 before using any of its properties to try to ensure
|
|
8
|
-
* the combined object is not linked back to the original.
|
|
9
|
-
*
|
|
10
|
-
* If the values are objects, it will merge them at the top level. Properties
|
|
11
|
-
* themselves are not merged; val2 properties will overwrite val1 where there
|
|
12
|
-
* are conflicts
|
|
13
|
-
*
|
|
14
|
-
* If the values are arrays, it will concatenate and dedupe them.
|
|
15
|
-
* NOTE: duplicates in either val1 or val2 will also be deduped.
|
|
16
|
-
*
|
|
17
|
-
* If the values are any other type, or val2 has a different type to val1, val2
|
|
18
|
-
* will be returned.
|
|
19
|
-
*/
|
|
20
|
-
export const combineTopLevel = (
|
|
21
|
-
val1: $ReadOnly<any>,
|
|
22
|
-
val2: $ReadOnly<any>,
|
|
23
|
-
): any => {
|
|
24
|
-
const obj2Clone = clone(val2);
|
|
25
|
-
|
|
26
|
-
// Only merge if they're both arrays or both objects.
|
|
27
|
-
// If not, we will just return val2.
|
|
28
|
-
if (
|
|
29
|
-
val1 !== null &&
|
|
30
|
-
val2 !== null &&
|
|
31
|
-
typeof val1 === "object" &&
|
|
32
|
-
typeof val2 === "object"
|
|
33
|
-
) {
|
|
34
|
-
const val1IsArray = Array.isArray(val1);
|
|
35
|
-
const val2IsArray = Array.isArray(val2);
|
|
36
|
-
|
|
37
|
-
if (val1IsArray && val2IsArray) {
|
|
38
|
-
return Array.from(new Set([...val1, ...obj2Clone]));
|
|
39
|
-
} else if (!val1IsArray && !val2IsArray) {
|
|
40
|
-
return {...val1, ...obj2Clone};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return obj2Clone;
|
|
44
|
-
};
|
package/src/fixtures/setup.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
// @flow
|
|
2
|
-
import type {FixturesConfiguration} from "./types.js";
|
|
3
|
-
|
|
4
|
-
let _configuration: ?$ReadOnly<FixturesConfiguration<any, any>> = null;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Setup the fixture framework.
|
|
8
|
-
*/
|
|
9
|
-
export const setup = <TAdapterOptions: {...}, TAdapterExports: {...}>(
|
|
10
|
-
configuration: $ReadOnly<
|
|
11
|
-
FixturesConfiguration<TAdapterOptions, TAdapterExports>,
|
|
12
|
-
>,
|
|
13
|
-
) => {
|
|
14
|
-
_configuration = configuration;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Get the framework configuration.
|
|
19
|
-
*
|
|
20
|
-
* @returns {Configuration} The configuration as provided via setup().
|
|
21
|
-
* @throws {Error} If the configuration has not been set.
|
|
22
|
-
*/
|
|
23
|
-
export const getConfiguration = (): $ReadOnly<
|
|
24
|
-
FixturesConfiguration<any, any>,
|
|
25
|
-
> => {
|
|
26
|
-
if (_configuration == null) {
|
|
27
|
-
throw new Error("Not configured");
|
|
28
|
-
}
|
|
29
|
-
return _configuration;
|
|
30
|
-
};
|