@khanacademy/wonder-blocks-testing 5.0.0 → 6.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 +20 -0
- package/package.json +2 -2
- package/src/fixtures/__tests__/fixtures.test.js +12 -12
- package/src/fixtures/fixtures.basic.stories.js +12 -7
- package/src/fixtures/fixtures.defaultwrapper.stories.js +1 -1
- package/src/fixtures/fixtures.js +14 -8
- package/src/fixtures/types.js +5 -2
- package/src/harness/__tests__/types.flowtest.js +115 -0
- package/src/harness/types.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# @khanacademy/wonder-blocks-testing
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- af459222: Improve typing for fixtures call
|
|
8
|
+
|
|
9
|
+
## 5.0.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- Updated dependencies [5f4a4297]
|
|
14
|
+
- Updated dependencies [2b96fd59]
|
|
15
|
+
- @khanacademy/wonder-blocks-data@8.0.3
|
|
16
|
+
|
|
17
|
+
## 5.0.1
|
|
18
|
+
|
|
19
|
+
### Patch Changes
|
|
20
|
+
|
|
21
|
+
- 5b1c80d2: Fix test harness types
|
|
22
|
+
|
|
3
23
|
## 5.0.0
|
|
4
24
|
|
|
5
25
|
### Major Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@khanacademy/wonder-blocks-testing",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0",
|
|
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": "^8.0.
|
|
17
|
+
"@khanacademy/wonder-blocks-data": "^8.0.3"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
20
|
"@khanacademy/wonder-stuff-core": "^0.1.2",
|
|
@@ -48,7 +48,7 @@ describe("#fixtures", () => {
|
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
// Act
|
|
51
|
-
fixtures(
|
|
51
|
+
fixtures<any, _>(
|
|
52
52
|
{
|
|
53
53
|
title: "TITLE",
|
|
54
54
|
description: "DESCRIPTION",
|
|
@@ -81,7 +81,7 @@ describe("#fixtures", () => {
|
|
|
81
81
|
component.displayName = "DISPLAYNAME";
|
|
82
82
|
|
|
83
83
|
// Act
|
|
84
|
-
fixtures(
|
|
84
|
+
fixtures<any, _>(
|
|
85
85
|
{
|
|
86
86
|
component,
|
|
87
87
|
},
|
|
@@ -157,7 +157,7 @@ describe("#fixtures", () => {
|
|
|
157
157
|
const fn = jest.fn();
|
|
158
158
|
|
|
159
159
|
// Act
|
|
160
|
-
fixtures(
|
|
160
|
+
fixtures<any, _>(
|
|
161
161
|
{
|
|
162
162
|
title: "GROUP_TITLE",
|
|
163
163
|
description: "GROUP_DESCRIPTION",
|
|
@@ -196,7 +196,7 @@ describe("#fixtures", () => {
|
|
|
196
196
|
};
|
|
197
197
|
|
|
198
198
|
// Act
|
|
199
|
-
fixtures(
|
|
199
|
+
fixtures<any, _>(
|
|
200
200
|
{
|
|
201
201
|
component: () => "COMPONENT",
|
|
202
202
|
additionalAdapterOptions: {
|
|
@@ -241,7 +241,7 @@ describe("#fixtures", () => {
|
|
|
241
241
|
};
|
|
242
242
|
|
|
243
243
|
// Act
|
|
244
|
-
fixtures(
|
|
244
|
+
fixtures<any, _>(
|
|
245
245
|
{
|
|
246
246
|
component: () => "COMPONENT",
|
|
247
247
|
additionalAdapterOptions: {
|
|
@@ -273,7 +273,7 @@ describe("#fixtures", () => {
|
|
|
273
273
|
});
|
|
274
274
|
|
|
275
275
|
// Act
|
|
276
|
-
const result = fixtures(
|
|
276
|
+
const result = fixtures<any, _>(
|
|
277
277
|
{
|
|
278
278
|
component: () => "COMPONENT",
|
|
279
279
|
},
|
|
@@ -301,7 +301,7 @@ describe("#fixtures", () => {
|
|
|
301
301
|
const component = () => "COMPONENT";
|
|
302
302
|
|
|
303
303
|
// Act
|
|
304
|
-
fixtures(
|
|
304
|
+
fixtures<any, _>(
|
|
305
305
|
{
|
|
306
306
|
title: "GROUP_TITLE",
|
|
307
307
|
description: "GROUP_DESCRIPTION",
|
|
@@ -367,7 +367,7 @@ describe("#fixtures", () => {
|
|
|
367
367
|
const wrapper = () => "WRAPPER";
|
|
368
368
|
|
|
369
369
|
// Act
|
|
370
|
-
fixtures(
|
|
370
|
+
fixtures<any, _>(
|
|
371
371
|
{
|
|
372
372
|
title: "GROUP_TITLE",
|
|
373
373
|
description: "GROUP_DESCRIPTION",
|
|
@@ -407,7 +407,7 @@ describe("#fixtures", () => {
|
|
|
407
407
|
const defaultWrapper = () => "DEFAULT_WRAPPER";
|
|
408
408
|
|
|
409
409
|
// Act
|
|
410
|
-
fixtures(
|
|
410
|
+
fixtures<any, _>(
|
|
411
411
|
{
|
|
412
412
|
title: "GROUP_TITLE",
|
|
413
413
|
description: "GROUP_DESCRIPTION",
|
|
@@ -443,7 +443,7 @@ describe("#fixtures", () => {
|
|
|
443
443
|
});
|
|
444
444
|
|
|
445
445
|
// Act
|
|
446
|
-
fixtures(
|
|
446
|
+
fixtures<any, _>(
|
|
447
447
|
{
|
|
448
448
|
component: () => "COMPONENT",
|
|
449
449
|
},
|
|
@@ -475,7 +475,7 @@ describe("#fixtures", () => {
|
|
|
475
475
|
const props = jest.fn().mockReturnValue({these: "areProps"});
|
|
476
476
|
|
|
477
477
|
// Act
|
|
478
|
-
fixtures(
|
|
478
|
+
fixtures<any, _>(
|
|
479
479
|
{
|
|
480
480
|
component: () => "COMPONENT",
|
|
481
481
|
},
|
|
@@ -507,7 +507,7 @@ describe("#fixtures", () => {
|
|
|
507
507
|
const props = jest.fn().mockReturnValue({these: "areProps"});
|
|
508
508
|
|
|
509
509
|
// Act
|
|
510
|
-
fixtures(
|
|
510
|
+
fixtures<any, _>(
|
|
511
511
|
{
|
|
512
512
|
component: () => "COMPONENT",
|
|
513
513
|
},
|
|
@@ -12,7 +12,12 @@ setupFixtures({
|
|
|
12
12
|
adapter: adapters.storybook(),
|
|
13
13
|
});
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
type Props = {|
|
|
16
|
+
propA: string,
|
|
17
|
+
propB?: string,
|
|
18
|
+
|};
|
|
19
|
+
|
|
20
|
+
const MyComponent = (props: Props) =>
|
|
16
21
|
`I am a component. Here are my props: ${JSON.stringify(props, null, 2)}`;
|
|
17
22
|
|
|
18
23
|
const Wrapper = (props) => (
|
|
@@ -24,15 +29,15 @@ const Wrapper = (props) => (
|
|
|
24
29
|
);
|
|
25
30
|
|
|
26
31
|
const stories: Array<mixed> = Object.values(
|
|
27
|
-
fixtures(
|
|
32
|
+
fixtures<typeof MyComponent, _>(
|
|
28
33
|
{
|
|
29
34
|
component: MyComponent,
|
|
30
35
|
title: "Testing / Fixtures / Basic",
|
|
31
36
|
},
|
|
32
37
|
(fixture) => {
|
|
33
38
|
fixture("This is a fixture with some regular props", {
|
|
34
|
-
|
|
35
|
-
|
|
39
|
+
propA: "this is a prop",
|
|
40
|
+
propB: "this is another",
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
fixture(
|
|
@@ -45,7 +50,7 @@ const stories: Array<mixed> = Object.values(
|
|
|
45
50
|
},
|
|
46
51
|
);
|
|
47
52
|
return {
|
|
48
|
-
|
|
53
|
+
propA: "prop was made from a function",
|
|
49
54
|
};
|
|
50
55
|
},
|
|
51
56
|
);
|
|
@@ -53,8 +58,8 @@ const stories: Array<mixed> = Object.values(
|
|
|
53
58
|
fixture(
|
|
54
59
|
"This fixture uses a custom wrapper",
|
|
55
60
|
{
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
propA: "some props again",
|
|
62
|
+
propB: "this one",
|
|
58
63
|
},
|
|
59
64
|
Wrapper,
|
|
60
65
|
);
|
package/src/fixtures/fixtures.js
CHANGED
|
@@ -9,11 +9,14 @@ type FixtureProps<TProps: {...}> =
|
|
|
9
9
|
| $ReadOnly<TProps>
|
|
10
10
|
| ((options: $ReadOnly<GetPropsOptions>) => $ReadOnly<TProps>);
|
|
11
11
|
|
|
12
|
-
const normalizeOptions = <
|
|
12
|
+
const normalizeOptions = <
|
|
13
|
+
TComponent: React.ComponentType<any>,
|
|
14
|
+
TProps: React.ElementConfig<TComponent>,
|
|
15
|
+
>(
|
|
13
16
|
componentOrOptions:
|
|
14
|
-
|
|
|
15
|
-
| $ReadOnly<FixturesOptions<TProps>>,
|
|
16
|
-
): $ReadOnly<FixturesOptions<TProps>> => {
|
|
17
|
+
| TComponent
|
|
18
|
+
| $ReadOnly<FixturesOptions<TComponent, TProps>>,
|
|
19
|
+
): $ReadOnly<FixturesOptions<TComponent, TProps>> => {
|
|
17
20
|
// To differentiate between a React component and a FixturesOptions object,
|
|
18
21
|
// we have to do some type checking.
|
|
19
22
|
//
|
|
@@ -66,10 +69,13 @@ const normalizeOptions = <TProps: {...}>(
|
|
|
66
69
|
* storybook, the popular framework, uses both default and named exports for
|
|
67
70
|
* its interface.
|
|
68
71
|
*/
|
|
69
|
-
export const fixtures = <
|
|
72
|
+
export const fixtures = <
|
|
73
|
+
TComponent: React.ComponentType<any>,
|
|
74
|
+
TProps: React.ElementConfig<TComponent>,
|
|
75
|
+
>(
|
|
70
76
|
componentOrOptions:
|
|
71
|
-
|
|
|
72
|
-
| $ReadOnly<FixturesOptions<TProps>>,
|
|
77
|
+
| TComponent
|
|
78
|
+
| $ReadOnly<FixturesOptions<TComponent, TProps>>,
|
|
73
79
|
fn: (
|
|
74
80
|
fixture: (
|
|
75
81
|
description: string,
|
|
@@ -86,7 +92,7 @@ export const fixtures = <TProps: {...}>(
|
|
|
86
92
|
description: groupDescription,
|
|
87
93
|
defaultWrapper,
|
|
88
94
|
additionalAdapterOptions,
|
|
89
|
-
} = normalizeOptions(componentOrOptions);
|
|
95
|
+
} = normalizeOptions<TComponent, TProps>(componentOrOptions);
|
|
90
96
|
|
|
91
97
|
// 1. Create a new adapter group.
|
|
92
98
|
const group = adapter.declareGroup<TProps>({
|
package/src/fixtures/types.js
CHANGED
|
@@ -23,11 +23,14 @@ export type FixturesAdapterOptions = {|
|
|
|
23
23
|
/**
|
|
24
24
|
* Options to describe a collection of fixtures.
|
|
25
25
|
*/
|
|
26
|
-
export type FixturesOptions<
|
|
26
|
+
export type FixturesOptions<
|
|
27
|
+
TComponent: React.ComponentType<any>,
|
|
28
|
+
TProps: React.ElementConfig<TComponent>,
|
|
29
|
+
> = {|
|
|
27
30
|
/**
|
|
28
31
|
* The component being tested by the fixtures.
|
|
29
32
|
*/
|
|
30
|
-
component:
|
|
33
|
+
component: TComponent,
|
|
31
34
|
|
|
32
35
|
/**
|
|
33
36
|
* Optional title of the fixture collection.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
import type {
|
|
5
|
+
TestHarnessAdapter,
|
|
6
|
+
TestHarnessAdapters,
|
|
7
|
+
TestHarnessConfig,
|
|
8
|
+
TestHarnessConfigs,
|
|
9
|
+
} from "../types.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* TestHarnessAdapter<TConfig>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
//> should assert type of config.
|
|
16
|
+
((
|
|
17
|
+
children: React.Node,
|
|
18
|
+
// TConfig is string, but we typed this arg as a number
|
|
19
|
+
// $FlowExpectedError[incompatible-cast]
|
|
20
|
+
config: number,
|
|
21
|
+
): React.Element<any> => <div />: TestHarnessAdapter<string>);
|
|
22
|
+
//<
|
|
23
|
+
|
|
24
|
+
//> should work for correct definition
|
|
25
|
+
((children: React.Node, config: string): React.Element<any> => (
|
|
26
|
+
<div />
|
|
27
|
+
): TestHarnessAdapter<string>);
|
|
28
|
+
//<
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* TestHarnessAdapters
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
//> should work for empty case
|
|
35
|
+
({}: TestHarnessAdapters);
|
|
36
|
+
//<
|
|
37
|
+
|
|
38
|
+
//> should assert if adapter is not Adapter<TConfig>
|
|
39
|
+
({
|
|
40
|
+
// String is not a adapter function
|
|
41
|
+
// $FlowExpectedError[incompatible-cast]
|
|
42
|
+
adapterString: "string",
|
|
43
|
+
}: TestHarnessAdapters);
|
|
44
|
+
//<
|
|
45
|
+
|
|
46
|
+
//> should work for a function matching Adapter<TConfig>
|
|
47
|
+
({
|
|
48
|
+
adapterA: (children, config) => <div>test</div>,
|
|
49
|
+
}: TestHarnessAdapters);
|
|
50
|
+
//<
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* TestHarnessConfig<TAdapter>
|
|
54
|
+
*/
|
|
55
|
+
//> should give the config type of an adapter
|
|
56
|
+
("string": TestHarnessConfig<TestHarnessAdapter<string>>);
|
|
57
|
+
//<
|
|
58
|
+
|
|
59
|
+
//> should error if the config type is wrong
|
|
60
|
+
// 45 is not a string
|
|
61
|
+
// $FlowExpectedError[incompatible-cast]
|
|
62
|
+
(45: TestHarnessConfig<TestHarnessAdapter<string>>);
|
|
63
|
+
//<
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* TestHarnessConfigs<TAdapters>
|
|
67
|
+
*
|
|
68
|
+
* NOTE: This only works if the properties of the passed THarnasses type
|
|
69
|
+
* are explicitly typed as `TestHarnessAdapter<TConfig>` so if passing in a
|
|
70
|
+
* non-Adapters type (which we should be, to get strong TConfig types instead
|
|
71
|
+
* of `any`), then that object should make sure that each adapter is strongly
|
|
72
|
+
* marked as `TestHarnessAdapter<TConfig>` - flow does not appear to pattern
|
|
73
|
+
* match against the type definition when invoking the ExtractConfig type and I
|
|
74
|
+
* haven't worked out how to get it to multi-dispatch so that it matches
|
|
75
|
+
* functions too. Even worse, if the type doesn't match, it just allows `any`
|
|
76
|
+
* in the configs object, rather than indicating any kind of problem.
|
|
77
|
+
*/
|
|
78
|
+
const notadapters = "this is wrong";
|
|
79
|
+
const adapterA: TestHarnessAdapter<string> = (
|
|
80
|
+
children: React.Node,
|
|
81
|
+
config: ?string,
|
|
82
|
+
): React.Element<any> => <div />;
|
|
83
|
+
const adapterB: TestHarnessAdapter<number> = (
|
|
84
|
+
children: React.Node,
|
|
85
|
+
config: ?number,
|
|
86
|
+
): React.Element<any> => <div />;
|
|
87
|
+
const adapters = {
|
|
88
|
+
adapterA,
|
|
89
|
+
adapterB,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
//> should assert if parameterized type is not valid Adapters
|
|
93
|
+
// string is not a valid Adapter
|
|
94
|
+
// $FlowExpectedError[incompatible-use]
|
|
95
|
+
// $FlowExpectedError[incompatible-type-arg]
|
|
96
|
+
({}: TestHarnessConfigs<typeof notadapters>);
|
|
97
|
+
//<
|
|
98
|
+
|
|
99
|
+
//> should expect one config per adapter
|
|
100
|
+
// both adapter configs missing
|
|
101
|
+
// $FlowExpectedError[incompatible-exact]
|
|
102
|
+
({}: TestHarnessConfigs<typeof adapters>);
|
|
103
|
+
// adapterB config missing
|
|
104
|
+
// $FlowExpectedError[prop-missing]
|
|
105
|
+
({adapterA: "test"}: TestHarnessConfigs<typeof adapters>);
|
|
106
|
+
//<
|
|
107
|
+
|
|
108
|
+
//> should assert if config does not match adapter config
|
|
109
|
+
({
|
|
110
|
+
adapterA: "a string, this is correct",
|
|
111
|
+
// the config type here is a number, not a string
|
|
112
|
+
// $FlowExpectedError[incompatible-cast]
|
|
113
|
+
adapterB: "a string, but it should be a number",
|
|
114
|
+
}: TestHarnessConfigs<typeof adapters>);
|
|
115
|
+
//<
|
package/src/harness/types.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as React from "react";
|
|
|
4
4
|
/**
|
|
5
5
|
* A adapter to be composed with our test harnass infrastructure.
|
|
6
6
|
*/
|
|
7
|
-
export type TestHarnessAdapter
|
|
7
|
+
export type TestHarnessAdapter<TConfig> = (
|
|
8
8
|
children: React.Node,
|
|
9
9
|
config: TConfig,
|
|
10
10
|
) => React.Element<any>;
|
|
@@ -36,7 +36,7 @@ type ExtractMaybeConfig = <TConfig>(TestHarnessAdapter<TConfig>) => ?TConfig;
|
|
|
36
36
|
*
|
|
37
37
|
* This is the `TestHarnessAdapter` equivalent of `React.ElementConfig`.
|
|
38
38
|
*/
|
|
39
|
-
export type TestHarnessConfig
|
|
39
|
+
export type TestHarnessConfig<TAdapter> = $Call<ExtractConfig, TAdapter>;
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* The `TestHarnessConfigs` type as defined by parsing a given set of adapters.
|
|
@@ -51,7 +51,7 @@ export type TestHarnessConfig<-TAdapter> = $Call<ExtractConfig, TAdapter>;
|
|
|
51
51
|
* functions too. Even worse, if the type doesn't match, it just allows `any`
|
|
52
52
|
* in the `Configs` object, rather than indicating any kind of problem.
|
|
53
53
|
*/
|
|
54
|
-
export type TestHarnessConfigs
|
|
54
|
+
export type TestHarnessConfigs<TAdapters: TestHarnessAdapters> = $ObjMap<
|
|
55
55
|
TAdapters,
|
|
56
56
|
ExtractMaybeConfig,
|
|
57
57
|
>;
|