@khanacademy/wonder-blocks-testing 8.0.21 → 9.1.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.
Files changed (41) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/es/index.js +450 -33
  3. package/dist/harness/adapt.d.ts +17 -0
  4. package/dist/harness/adapter.d.ts +16 -0
  5. package/dist/harness/adapters/adapters.d.ts +1 -0
  6. package/dist/harness/adapters/ssr.d.ts +12 -0
  7. package/dist/harness/test-harness.d.ts +1 -0
  8. package/dist/harness/types.d.ts +1 -1
  9. package/dist/index.js +449 -33
  10. package/dist/mock-requester.d.ts +2 -2
  11. package/package.json +2 -2
  12. package/src/fetch/fetch-request-matches-mock.ts +2 -4
  13. package/src/fetch/mock-fetch.ts +1 -1
  14. package/src/fixtures/__tests__/fixtures.test.tsx +9 -14
  15. package/src/fixtures/fixtures.basic.stories.tsx +9 -3
  16. package/src/fixtures/fixtures.tsx +1 -2
  17. package/src/gql/mock-gql-fetch.ts +1 -1
  18. package/src/harness/__tests__/adapt.test.tsx +200 -0
  19. package/src/harness/__tests__/adapter.test.tsx +67 -0
  20. package/src/harness/__tests__/hook-harness.test.ts +4 -2
  21. package/src/harness/__tests__/make-test-harness.test.tsx +16 -10
  22. package/src/harness/__tests__/test-harness.test.ts +4 -2
  23. package/src/harness/__tests__/types.typestest.tsx +6 -13
  24. package/src/harness/adapt.tsx +55 -0
  25. package/src/harness/adapter.tsx +27 -0
  26. package/src/harness/adapters/__tests__/data.test.tsx +12 -4
  27. package/src/harness/adapters/__tests__/ssr.test.tsx +82 -0
  28. package/src/harness/adapters/adapters.ts +3 -0
  29. package/src/harness/adapters/css.tsx +1 -3
  30. package/src/harness/adapters/router.tsx +5 -17
  31. package/src/harness/adapters/ssr.tsx +37 -0
  32. package/src/harness/{make-hook-harness.ts → make-hook-harness.tsx} +3 -2
  33. package/src/harness/make-test-harness.tsx +6 -8
  34. package/src/harness/types.ts +1 -1
  35. package/src/mock-requester.ts +4 -11
  36. package/src/respond-with.ts +2 -1
  37. package/src/settle-controller.ts +2 -3
  38. package/tsconfig-build.tsbuildinfo +1 -1
  39. package/dist/harness/render-adapters.d.ts +0 -6
  40. package/src/harness/__tests__/render-adapters.test.tsx +0 -87
  41. package/src/harness/render-adapters.ts +0 -27
@@ -27,8 +27,7 @@ export const fixtures = <
27
27
  let storyNumber = 1;
28
28
 
29
29
  const getPropsOptions = {
30
- // @ts-expect-error [FEI-5019] - TS7006 - Parameter 'message' implicitly has an 'any' type. | TS7019 - Rest parameter 'args' implicitly has an 'any[]' type.
31
- log: (message, ...args) => action(message)(...args),
30
+ log: (message: string, ...args: Array<any>) => action(message)(...args),
32
31
  logHandler: action,
33
32
  } as const;
34
33
 
@@ -6,7 +6,7 @@ import type {GqlFetchMockFn, GqlMockOperation} from "./types";
6
6
  * A mock for the fetch function passed to GqlRouter.
7
7
  */
8
8
  export const mockGqlFetch = (): GqlFetchMockFn =>
9
- mockRequester<GqlMockOperation<any, any, any>, any>(
9
+ mockRequester<GqlMockOperation<any, any, any>>(
10
10
  gqlRequestMatchesMock,
11
11
  // Note that the identation at the start of each line is important.
12
12
  // TODO(somewhatabstract): Make a stringify that indents each line of
@@ -0,0 +1,200 @@
1
+ import * as React from "react";
2
+ import {render} from "@testing-library/react";
3
+
4
+ import {Adapt} from "../adapt";
5
+
6
+ import type {TestHarnessAdapter, TestHarnessConfigs} from "../types";
7
+
8
+ describe("Adapt", () => {
9
+ it("should render children if no adapters", () => {
10
+ // Arrange
11
+ const children = <div>Adapt me!</div>;
12
+
13
+ // Act
14
+ const {container: result} = render(
15
+ <Adapt adapters={{}} configs={{}}>
16
+ {children}
17
+ </Adapt>,
18
+ );
19
+
20
+ // Assert
21
+ expect(result).toMatchInlineSnapshot(`
22
+ <div>
23
+ <div>
24
+ Adapt me!
25
+ </div>
26
+ </div>
27
+ `);
28
+ });
29
+
30
+ it("should invoke the adapter with its corresponding config", () => {
31
+ // Arrange
32
+ const children = <div>Adapt me!</div>;
33
+ const adapters = {
34
+ adapterA: jest
35
+ .fn()
36
+ .mockReturnValue("ADAPTER A") as TestHarnessAdapter<string>,
37
+ } as const;
38
+ const configs: TestHarnessConfigs<typeof adapters> = {
39
+ adapterA: "APPLY A CONFIG",
40
+ };
41
+
42
+ // Act
43
+ render(
44
+ <Adapt adapters={adapters} configs={configs}>
45
+ {children}
46
+ </Adapt>,
47
+ );
48
+
49
+ // Assert
50
+ expect(adapters.adapterA).toHaveBeenCalledWith(
51
+ expect.anything(),
52
+ "APPLY A CONFIG",
53
+ );
54
+ });
55
+
56
+ it("should render each adapter and the children", () => {
57
+ // Arrange
58
+ const children = "Adapt me!";
59
+ const adapter: TestHarnessAdapter<string> = (c: any, conf: any) => (
60
+ <>
61
+ {conf}
62
+ {c}
63
+ </>
64
+ );
65
+ const adapters = {
66
+ adapterA: adapter,
67
+ adapterB: adapter,
68
+ adapterC: adapter,
69
+ } as const;
70
+ const configs: TestHarnessConfigs<typeof adapters> = {
71
+ adapterA: "A",
72
+ adapterB: "B",
73
+ adapterC: "C",
74
+ };
75
+
76
+ // Act
77
+ const {container: result} = render(
78
+ <Adapt adapters={adapters} configs={configs}>
79
+ {children}
80
+ </Adapt>,
81
+ );
82
+
83
+ // Assert
84
+ expect(result).toMatchInlineSnapshot(`
85
+ <div>
86
+ C
87
+ B
88
+ A
89
+ Adapt me!
90
+ </div>
91
+ `);
92
+ });
93
+
94
+ it("should skip adapters where the corresponding config is null", () => {
95
+ // Arrange
96
+ const children = "Adapt me!";
97
+ const adapter: TestHarnessAdapter<string> = (c: any, conf: any) => (
98
+ <>
99
+ {conf}
100
+ {c}
101
+ </>
102
+ );
103
+ const adapters = {
104
+ adapterA: adapter,
105
+ adapterB: adapter,
106
+ adapterC: adapter,
107
+ } as const;
108
+ const configs: TestHarnessConfigs<typeof adapters> = {
109
+ adapterA: "A",
110
+ adapterB: null,
111
+ adapterC: "C",
112
+ };
113
+
114
+ // Act
115
+ const {container: result} = render(
116
+ <Adapt adapters={adapters} configs={configs}>
117
+ {children}
118
+ </Adapt>,
119
+ );
120
+
121
+ // Assert
122
+ expect(result).toMatchInlineSnapshot(`
123
+ <div>
124
+ C
125
+ A
126
+ Adapt me!
127
+ </div>
128
+ `);
129
+ });
130
+
131
+ it("should render such that contexts are properly setup in order", () => {
132
+ // Arrange
133
+ const MyContext = React.createContext("root");
134
+ const ContextRoot = ({children}: any): React.ReactElement => {
135
+ const value = React.useContext(MyContext);
136
+ if (value !== "root") {
137
+ throw new Error("Don't nest this");
138
+ }
139
+ return (
140
+ <MyContext.Provider value={"other"}>
141
+ {children}
142
+ </MyContext.Provider>
143
+ );
144
+ };
145
+ const adapterNoContext: TestHarnessAdapter<string> = (
146
+ c: any,
147
+ conf: any,
148
+ ) => (
149
+ <>
150
+ {conf}
151
+ {c}
152
+ </>
153
+ );
154
+ const adapterWithContext: TestHarnessAdapter<string> = (
155
+ c: any,
156
+ conf: string,
157
+ ) => (
158
+ <ContextRoot>
159
+ {conf}
160
+ {c}
161
+ </ContextRoot>
162
+ );
163
+ const adapters = {
164
+ adapterA: adapterNoContext,
165
+ adapterB: adapterWithContext,
166
+ adapterC: adapterNoContext,
167
+ } as const;
168
+ const configs: TestHarnessConfigs<typeof adapters> = {
169
+ adapterA: "A",
170
+ adapterB: "B",
171
+ adapterC: "C",
172
+ };
173
+ const ChildComponent = (props: any): React.ReactElement => {
174
+ const value = React.useContext(MyContext);
175
+ if (value === "default") {
176
+ throw new Error("Context not setup properly");
177
+ }
178
+ return <div>{value}</div>;
179
+ };
180
+
181
+ // Act
182
+ const {container: result} = render(
183
+ <Adapt adapters={adapters} configs={configs}>
184
+ <ChildComponent />
185
+ </Adapt>,
186
+ );
187
+
188
+ // Assert
189
+ expect(result).toMatchInlineSnapshot(`
190
+ <div>
191
+ C
192
+ B
193
+ A
194
+ <div>
195
+ other
196
+ </div>
197
+ </div>
198
+ `);
199
+ });
200
+ });
@@ -0,0 +1,67 @@
1
+ import * as React from "react";
2
+ import {render} from "@testing-library/react";
3
+
4
+ import {TestHarnessAdapter} from "../types";
5
+
6
+ import {Adapter} from "../adapter";
7
+
8
+ describe("Adapter", () => {
9
+ it("should render only the children if the adapter config is nullish", () => {
10
+ // Arrange
11
+ const adapter: TestHarnessAdapter<string> = (children, config) => (
12
+ <div id="adapter">
13
+ {config}
14
+ {children}
15
+ </div>
16
+ );
17
+ const children = "Adapt me!";
18
+
19
+ // Act
20
+ const {container: result} = render(
21
+ <Adapter
22
+ adapter={adapter}
23
+ config={null as null | undefined | string}
24
+ >
25
+ {children}
26
+ </Adapter>,
27
+ );
28
+
29
+ // Assert
30
+ expect(result).toMatchInlineSnapshot(`
31
+ <div>
32
+ Adapt me!
33
+ </div>
34
+ `);
35
+ });
36
+
37
+ it("should render the adapter around the children if the config is not nullish", () => {
38
+ // Arrange
39
+ const adapter: TestHarnessAdapter<string> = (children, config) => (
40
+ <div id="adapter">
41
+ {config}
42
+ {children}
43
+ </div>
44
+ );
45
+ const children = "Adapt me!";
46
+ const config = "this-is-the-config";
47
+
48
+ // Act
49
+ const {container: result} = render(
50
+ <Adapter adapter={adapter} config={config}>
51
+ {children}
52
+ </Adapter>,
53
+ );
54
+
55
+ // Assert
56
+ expect(result).toMatchInlineSnapshot(`
57
+ <div>
58
+ <div
59
+ id="adapter"
60
+ >
61
+ this-is-the-config
62
+ Adapt me!
63
+ </div>
64
+ </div>
65
+ `);
66
+ });
67
+ });
@@ -34,7 +34,8 @@ describe("#hookHarness", () => {
34
34
  const config = {
35
35
  router: "/boo",
36
36
  } as const;
37
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'harnessFake' does not exist on type 'typeof import("/Users/kevinbarabash/khan/wonder-blocks/packages/wonder-blocks-testing/src/harness/make-hook-harness")'.
37
+ // @ts-expect-error We know harnessFake isn't real, we add it in the
38
+ // mocks at the top of this file.
38
39
  const [{harnessFake}, {hookHarness}] = await ws.isolateModules(() =>
39
40
  Promise.all([
40
41
  import("../make-hook-harness"),
@@ -54,7 +55,8 @@ describe("#hookHarness", () => {
54
55
  const config = {
55
56
  router: "/boo",
56
57
  } as const;
57
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'returnValueFake' does not exist on type 'typeof import("/Users/kevinbarabash/khan/wonder-blocks/packages/wonder-blocks-testing/src/harness/make-hook-harness")'.
58
+ // @ts-expect-error We know harnessFake isn't real, we add it in the
59
+ // mocks at the top of this file.
58
60
  const [{returnValueFake}, {hookHarness}] = await ws.isolateModules(() =>
59
61
  Promise.all([
60
62
  import("../make-hook-harness"),
@@ -2,7 +2,7 @@ import * as React from "react";
2
2
  import {Route} from "react-router-dom";
3
3
  import {render} from "@testing-library/react";
4
4
 
5
- import * as RA from "../render-adapters";
5
+ import * as RA from "../adapt";
6
6
  import {makeTestHarness} from "../make-test-harness";
7
7
  import {DefaultConfigs, DefaultAdapters} from "../adapters/adapters";
8
8
 
@@ -75,7 +75,7 @@ describe("#makeTestHarness", () => {
75
75
  DefaultConfigs,
76
76
  );
77
77
  const Component = () => <div>test</div>;
78
- const renderSpy = jest.spyOn(RA, "renderAdapters");
78
+ const renderSpy = jest.spyOn(RA, "Adapt");
79
79
 
80
80
  // Act
81
81
  const HarnessedComponent = testHarness(Component);
@@ -83,9 +83,12 @@ describe("#makeTestHarness", () => {
83
83
 
84
84
  // Assert
85
85
  expect(renderSpy).toHaveBeenCalledWith(
86
- DefaultAdapters,
87
- DefaultConfigs,
88
- expect.anything(),
86
+ {
87
+ adapters: DefaultAdapters,
88
+ configs: DefaultConfigs,
89
+ children: expect.anything(),
90
+ },
91
+ {},
89
92
  );
90
93
  });
91
94
 
@@ -132,7 +135,7 @@ describe("#makeTestHarness", () => {
132
135
  router: "/mysecretplace",
133
136
  };
134
137
  const Component = () => <div>test</div>;
135
- const renderSpy = jest.spyOn(RA, "renderAdapters");
138
+ const renderSpy = jest.spyOn(RA, "Adapt");
136
139
 
137
140
  // Act
138
141
  const HarnessedComponent = testHarness(
@@ -143,12 +146,15 @@ describe("#makeTestHarness", () => {
143
146
 
144
147
  // Assert
145
148
  expect(renderSpy).toHaveBeenCalledWith(
146
- DefaultAdapters,
147
149
  {
148
- ...DefaultConfigs,
149
- router: configOverrides.router,
150
+ adapters: DefaultAdapters,
151
+ configs: {
152
+ ...DefaultConfigs,
153
+ router: configOverrides.router,
154
+ },
155
+ children: expect.anything(),
150
156
  },
151
- expect.anything(),
157
+ {},
152
158
  );
153
159
  });
154
160
 
@@ -35,7 +35,8 @@ describe("#testHarness", () => {
35
35
  const config = {
36
36
  router: "/boo",
37
37
  } as const;
38
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'harnessFake' does not exist on type 'typeof import("/Users/kevinbarabash/khan/wonder-blocks/packages/wonder-blocks-testing/src/harness/make-test-harness")'.
38
+ // @ts-expect-error We know harnessFake isn't real, we add it in the
39
+ // mocks at the top of this file.
39
40
  const [{harnessFake}, {testHarness}] = await ws.isolateModules(() =>
40
41
  Promise.all([
41
42
  import("../make-test-harness"),
@@ -56,7 +57,8 @@ describe("#testHarness", () => {
56
57
  const config = {
57
58
  router: "/boo",
58
59
  } as const;
59
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'returnValueFake' does not exist on type 'typeof import("/Users/kevinbarabash/khan/wonder-blocks/packages/wonder-blocks-testing/src/harness/make-test-harness")'.
60
+ // @ts-expect-error We know harnessFake isn't real, we add it in the
61
+ // mocks at the top of this file.
60
62
  const [{returnValueFake}, {testHarness}] = await ws.isolateModules(() =>
61
63
  Promise.all([
62
64
  import("../make-test-harness"),
@@ -12,13 +12,10 @@ import type {
12
12
  */
13
13
 
14
14
  //> should assert type of config.
15
- // @ts-expect-error [FEI-5019] - TS2352 - Conversion of type '(children: React.ReactNode, config: number) => React.ReactElement<any>' to type 'TestHarnessAdapter<string>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
16
- ((
17
- children: React.ReactNode,
18
- // TConfig is string, but we typed this arg as a number
19
- // $FlowExpectedError[incompatible-cast]
20
- config: number,
21
- ): React.ReactElement<any> => <div />) as TestHarnessAdapter<string>;
15
+ // @ts-expect-error TConfig is string, but we typed config as a number
16
+ ((children: React.ReactNode, config: number): React.ReactElement<any> => (
17
+ <div />
18
+ )) as TestHarnessAdapter<string>;
22
19
  //<
23
20
 
24
21
  //> should work for correct definition
@@ -36,10 +33,8 @@ import type {
36
33
  //<
37
34
 
38
35
  //> should assert if adapter is not Adapter<TConfig>
39
- // @ts-expect-error [FEI-5019] - TS2352 - Conversion of type '{ adapterString: string; }' to type 'TestHarnessAdapters' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
36
+ // @ts-expect-error String is not a adapter function
40
37
  ({
41
- // String is not a adapter function
42
- // $FlowExpectedError[incompatible-cast]
43
38
  adapterString: "string",
44
39
  } as TestHarnessAdapters);
45
40
  //<
@@ -100,11 +95,9 @@ const adapters = {
100
95
  //<
101
96
 
102
97
  //> should assert if config does not match adapter config
103
- // @ts-expect-error: Conversion of type '{ adapterA: string; adapterB: string; }' to type 'TestHarnessConfigs<{ readonly adapterA: TestHarnessAdapter<string>; readonly adapterB: TestHarnessAdapter<number>; }>' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Types of property 'adapterB' are incompatible. Type 'string' is not comparable to type 'number'.
98
+ // @ts-expect-error: the config type here is a number, not a string
104
99
  ({
105
100
  adapterA: "a string, this is correct",
106
- // the config type here is a number, not a string
107
- // $FlowExpectedError[incompatible-cast]
108
101
  adapterB: "a string, but it should be a number",
109
102
  } as TestHarnessConfigs<typeof adapters>);
110
103
  //<
@@ -0,0 +1,55 @@
1
+ import * as React from "react";
2
+
3
+ import {Adapter} from "./adapter";
4
+
5
+ import type {TestHarnessConfigs, TestHarnessAdapters} from "./types";
6
+
7
+ type Props<TAdapters extends TestHarnessAdapters> = {
8
+ children: React.ReactNode;
9
+ adapters: TAdapters;
10
+ configs: TestHarnessConfigs<TAdapters>;
11
+ };
12
+
13
+ /**
14
+ * Render a set of adapters around the given children.
15
+ *
16
+ * Adapters are rendered with the last adapter being the outermost and the first
17
+ * adapter being the innermost, with children being the innermost of all. This
18
+ * ensures that we are backwards compatible with previous releases of the
19
+ * test harness.
20
+ */
21
+ export const Adapt = <TAdapters extends TestHarnessAdapters>({
22
+ children,
23
+ adapters,
24
+ configs,
25
+ }: Props<TAdapters>): React.ReactElement => {
26
+ // We start at the end of the adapter list and work backwards to be
27
+ // compatible with previous releases.
28
+ const thisAdapterName = Object.keys(adapters).at(-1);
29
+ if (thisAdapterName == null) {
30
+ // There are no adapters. Just render the children.
31
+ return <>{children}</>;
32
+ }
33
+
34
+ const thisAdapter = adapters[thisAdapterName];
35
+ const thisConfig = configs[thisAdapterName];
36
+
37
+ // NOTE: We could simplify this by using an array of tuples as the input
38
+ // prop, but that complicates other things like testing and I think
39
+ // for simplicities sake elsewhere, this is good enough.
40
+ const restAdapters = Object.fromEntries(
41
+ Object.entries(adapters).slice(0, -1),
42
+ );
43
+ const restConfigs = Object.fromEntries(
44
+ // The config object may not be ordered the same as the adapters so
45
+ // we must filter by adapter name here.
46
+ Object.entries(configs).filter(([name]) => name !== thisAdapterName),
47
+ );
48
+ return (
49
+ <Adapter adapter={thisAdapter} config={thisConfig}>
50
+ <Adapt adapters={restAdapters} configs={restConfigs}>
51
+ {children}
52
+ </Adapt>
53
+ </Adapter>
54
+ );
55
+ };
@@ -0,0 +1,27 @@
1
+ import * as React from "react";
2
+
3
+ import type {TestHarnessAdapter} from "./types";
4
+
5
+ type Props<TConfig, TAdapter extends TestHarnessAdapter<TConfig>> = {
6
+ children: React.ReactNode;
7
+ adapter: TAdapter;
8
+ config: TConfig | null | undefined;
9
+ };
10
+
11
+ /**
12
+ * Component that optionally renders a given adapter with the given config.
13
+ *
14
+ * If the config is nullish, then the children are rendered in a fragment,
15
+ * otherwise the children are rendered within the given adapter with the
16
+ * given config.
17
+ */
18
+ export const Adapter = <TConfig, TAdapter extends TestHarnessAdapter<TConfig>>({
19
+ children,
20
+ adapter,
21
+ config,
22
+ }: Props<TConfig, TAdapter>): React.ReactElement => {
23
+ if (config == null) {
24
+ return <>{children}</>;
25
+ }
26
+ return adapter(children, config);
27
+ };
@@ -21,8 +21,12 @@ describe("WonderBlocksData.adapter", () => {
21
21
  const TestFixture = () => {
22
22
  const [result] = useCachedEffect("ID", jest.fn());
23
23
 
24
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'data' does not exist on type 'Result<ValidCacheData>'.
25
- return <div>CONTENT: {result?.data}</div>;
24
+ return (
25
+ <div>
26
+ CONTENT:{" "}
27
+ {result.status === "success" ? result.data : undefined}
28
+ </div>
29
+ );
26
30
  };
27
31
 
28
32
  // Act
@@ -43,8 +47,12 @@ describe("WonderBlocksData.adapter", () => {
43
47
  const TestFixture = () => {
44
48
  const [result] = useCachedEffect("ID", jest.fn());
45
49
 
46
- // @ts-expect-error [FEI-5019] - TS2339 - Property 'data' does not exist on type 'Result<ValidCacheData>'.
47
- return <div>CONTENT:{result?.data}</div>;
50
+ return (
51
+ <div>
52
+ CONTENT:
53
+ {result.status === "success" ? result.data : undefined}
54
+ </div>
55
+ );
48
56
  };
49
57
 
50
58
  // Act
@@ -0,0 +1,82 @@
1
+ import * as React from "react";
2
+ import {render, screen} from "@testing-library/react";
3
+ import * as WBCore from "@khanacademy/wonder-blocks-core";
4
+ import {makeTestHarness} from "../../make-test-harness";
5
+
6
+ import * as SSR from "../ssr";
7
+
8
+ jest.mock("@khanacademy/wonder-stuff-core", () => {
9
+ const actualCore = jest.requireActual("@khanacademy/wonder-stuff-core");
10
+ return {
11
+ ...actualCore,
12
+ RenderStateRoot: (props: any) => (
13
+ <actualCore.RenderStateRoot {...props} />
14
+ ),
15
+ };
16
+ });
17
+
18
+ describe("SSR.adapter", () => {
19
+ it("should render the RenderStateRoot", () => {
20
+ // Arrange
21
+ const children = <div>CHILDREN!</div>;
22
+ const renderStateRootSpy = jest.spyOn(WBCore, "RenderStateRoot");
23
+
24
+ // Act
25
+ render(SSR.adapter(children, true));
26
+
27
+ // Assert
28
+ expect(renderStateRootSpy).toHaveBeenCalledWith(
29
+ {
30
+ children,
31
+ },
32
+ {},
33
+ );
34
+ });
35
+
36
+ it("should render the children correctly", () => {
37
+ // Arrange
38
+ const children = <div>CHILDREN!</div>;
39
+
40
+ // Act
41
+ render(SSR.adapter(children, true));
42
+
43
+ // Assert
44
+ expect(screen.getByText("CHILDREN!")).toBeInTheDocument();
45
+ });
46
+
47
+ it("should enable harnessing of components that require RenderStateRoot", () => {
48
+ // Arrange
49
+ const ComponentNeedsSsr = (props: any) => {
50
+ const idf = WBCore.useUniqueIdWithoutMock();
51
+ return <div>{idf?.get("my-id")}</div>;
52
+ };
53
+ const testHarness = makeTestHarness(
54
+ {
55
+ ssr: SSR.adapter,
56
+ },
57
+ {
58
+ ssr: true,
59
+ },
60
+ );
61
+ const Harnessed = testHarness(ComponentNeedsSsr);
62
+
63
+ // Act
64
+ const underTest = () => render(<Harnessed />);
65
+
66
+ // Assert
67
+ expect(underTest).not.toThrowError();
68
+ });
69
+
70
+ it("should throw on bad configuration", () => {
71
+ // Arrange
72
+ const children = <div>CHILDREN!</div>;
73
+
74
+ // Act
75
+ const underTest = () => render(SSR.adapter(children, false as any));
76
+
77
+ // Assert
78
+ expect(underTest).toThrowErrorMatchingInlineSnapshot(
79
+ `"Unexpected configuration: set config to null to turn this adapter off"`,
80
+ );
81
+ });
82
+ });
@@ -2,6 +2,7 @@ import * as css from "./css";
2
2
  import * as data from "./data";
3
3
  import * as portal from "./portal";
4
4
  import * as router from "./router";
5
+ import * as ssr from "./ssr";
5
6
 
6
7
  import type {TestHarnessConfigs} from "../types";
7
8
 
@@ -19,6 +20,7 @@ export const DefaultAdapters = {
19
20
  data: data.adapter,
20
21
  portal: portal.adapter,
21
22
  router: router.adapter,
23
+ ssr: ssr.adapter,
22
24
  } as const;
23
25
 
24
26
  /**
@@ -29,4 +31,5 @@ export const DefaultConfigs: TestHarnessConfigs<typeof DefaultAdapters> = {
29
31
  data: data.defaultConfig,
30
32
  portal: portal.defaultConfig,
31
33
  router: router.defaultConfig,
34
+ ssr: ssr.defaultConfig,
32
35
  } as const;
@@ -40,9 +40,7 @@ const normalizeConfig = (
40
40
  return config;
41
41
  }
42
42
 
43
- // @ts-expect-error: at this point, `CSSProperties` is the only thing
44
- // that `config` can be.
45
- return {classes: [], style: config};
43
+ return {classes: [], style: config as CSSProperties};
46
44
  }
47
45
 
48
46
  throw new Error(`Invalid config: ${config}`);