@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 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": "5.0.0",
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.2"
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
- const MyComponent = (props) =>
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
- see: "this is a prop",
35
- and: "this is another",
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
- this: "prop was made from a function",
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
- just: "some props again",
57
- like: "this one",
61
+ propA: "some props again",
62
+ propB: "this one",
58
63
  },
59
64
  Wrapper,
60
65
  );
@@ -31,7 +31,7 @@ const DefaultWrapper = (props) => (
31
31
  );
32
32
 
33
33
  const stories: Array<mixed> = Object.values(
34
- fixtures(
34
+ fixtures<typeof MyComponent, _>(
35
35
  {
36
36
  component: MyComponent,
37
37
  title: "Testing / Fixtures / DefaultWrapper",
@@ -9,11 +9,14 @@ type FixtureProps<TProps: {...}> =
9
9
  | $ReadOnly<TProps>
10
10
  | ((options: $ReadOnly<GetPropsOptions>) => $ReadOnly<TProps>);
11
11
 
12
- const normalizeOptions = <TProps: {...}>(
12
+ const normalizeOptions = <
13
+ TComponent: React.ComponentType<any>,
14
+ TProps: React.ElementConfig<TComponent>,
15
+ >(
13
16
  componentOrOptions:
14
- | React.ComponentType<TProps>
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 = <TProps: {...}>(
72
+ export const fixtures = <
73
+ TComponent: React.ComponentType<any>,
74
+ TProps: React.ElementConfig<TComponent>,
75
+ >(
70
76
  componentOrOptions:
71
- | React.ComponentType<TProps>
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>({
@@ -23,11 +23,14 @@ export type FixturesAdapterOptions = {|
23
23
  /**
24
24
  * Options to describe a collection of fixtures.
25
25
  */
26
- export type FixturesOptions<TProps: {...}> = {|
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: React.ComponentType<TProps>,
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
+ //<
@@ -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<-TConfig> = (
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<-TAdapter> = $Call<ExtractConfig, TAdapter>;
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<-TAdapters: TestHarnessAdapters> = $ObjMap<
54
+ export type TestHarnessConfigs<TAdapters: TestHarnessAdapters> = $ObjMap<
55
55
  TAdapters,
56
56
  ExtractMaybeConfig,
57
57
  >;