@khanacademy/wonder-blocks-testing 10.1.1 → 11.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 +28 -0
- package/dist/es/index.js +15 -390
- package/dist/gql/types.d.ts +1 -2
- package/dist/harness/adapters/data.d.ts +1 -1
- package/dist/harness/adapters/index.d.ts +41 -0
- package/dist/harness/adapters/ssr.d.ts +1 -1
- package/dist/index.d.ts +8 -13
- package/dist/index.js +54 -399
- package/package.json +9 -7
- package/src/gql/__tests__/mock-gql-fetch.test.tsx +1 -1
- package/src/gql/__tests__/types.typestest.ts +1 -1
- package/src/gql/__tests__/wb-data-integration.test.tsx +1 -1
- package/src/gql/mock-gql-fetch.ts +1 -1
- package/src/gql/types.ts +4 -2
- package/src/harness/adapters/__tests__/ssr.test.tsx +1 -1
- package/src/harness/adapters/data.tsx +1 -1
- package/src/harness/adapters/{adapters.ts → index.ts} +10 -11
- package/src/harness/adapters/ssr.tsx +1 -1
- package/src/index.ts +32 -13
- package/tsconfig-build.json +1 -0
- package/tsconfig-build.tsbuildinfo +1 -1
- package/dist/fetch/fetch-request-matches-mock.d.ts +0 -5
- package/dist/fetch/mock-fetch.d.ts +0 -5
- package/dist/fetch/types.d.ts +0 -9
- package/dist/fixtures/fixtures.basic.stories.d.ts +0 -13
- package/dist/fixtures/fixtures.d.ts +0 -13
- package/dist/fixtures/fixtures.defaultwrapper.stories.d.ts +0 -9
- package/dist/fixtures/types.d.ts +0 -36
- package/dist/harness/adapt.d.ts +0 -17
- package/dist/harness/adapters/adapters.d.ts +0 -36
- package/dist/harness/adapters/css.d.ts +0 -12
- package/dist/harness/adapters/portal.d.ts +0 -12
- package/dist/harness/adapters/router.d.ts +0 -94
- package/dist/harness/get-named-adapter-component.d.ts +0 -16
- package/dist/harness/hook-harness.d.ts +0 -13
- package/dist/harness/make-hook-harness.d.ts +0 -17
- package/dist/harness/make-test-harness.d.ts +0 -15
- package/dist/harness/test-harness.d.ts +0 -33
- package/dist/harness/types.d.ts +0 -36
- package/dist/mock-requester.d.ts +0 -5
- package/dist/respond-with.d.ts +0 -75
- package/dist/response-impl.d.ts +0 -1
- package/dist/settle-controller.d.ts +0 -19
- package/dist/settle-signal.d.ts +0 -18
- package/dist/types.d.ts +0 -25
- package/src/__tests__/mock-requester.test.ts +0 -212
- package/src/__tests__/respond-with.test.ts +0 -524
- package/src/__tests__/response-impl.test.js +0 -47
- package/src/__tests__/settle-controller.test.ts +0 -28
- package/src/__tests__/settle-signal.test.ts +0 -104
- package/src/fetch/__tests__/__snapshots__/mock-fetch.test.ts.snap +0 -29
- package/src/fetch/__tests__/fetch-request-matches-mock.test.ts +0 -98
- package/src/fetch/__tests__/mock-fetch.test.ts +0 -83
- package/src/fetch/fetch-request-matches-mock.ts +0 -42
- package/src/fetch/mock-fetch.ts +0 -20
- package/src/fetch/types.ts +0 -14
- package/src/fixtures/__tests__/fixtures.test.tsx +0 -147
- package/src/fixtures/fixtures.basic.stories.tsx +0 -62
- package/src/fixtures/fixtures.defaultwrapper.stories.tsx +0 -49
- package/src/fixtures/fixtures.tsx +0 -72
- package/src/fixtures/types.ts +0 -42
- package/src/harness/__tests__/adapt.test.tsx +0 -248
- package/src/harness/__tests__/hook-harness.test.ts +0 -73
- package/src/harness/__tests__/make-hook-harness.test.tsx +0 -93
- package/src/harness/__tests__/make-test-harness.test.tsx +0 -195
- package/src/harness/__tests__/test-harness.test.ts +0 -75
- package/src/harness/__tests__/types.typestest.tsx +0 -103
- package/src/harness/adapt.tsx +0 -41
- package/src/harness/adapters/__tests__/__snapshots__/router.test.tsx.snap +0 -5
- package/src/harness/adapters/__tests__/css.test.tsx +0 -95
- package/src/harness/adapters/__tests__/portal.test.tsx +0 -30
- package/src/harness/adapters/__tests__/router.test.tsx +0 -252
- package/src/harness/adapters/css.tsx +0 -66
- package/src/harness/adapters/portal.tsx +0 -25
- package/src/harness/adapters/router.tsx +0 -205
- package/src/harness/get-named-adapter-component.tsx +0 -36
- package/src/harness/hook-harness.ts +0 -22
- package/src/harness/make-hook-harness.tsx +0 -40
- package/src/harness/make-test-harness.tsx +0 -60
- package/src/harness/test-harness.ts +0 -13
- package/src/harness/types.ts +0 -47
- package/src/mock-requester.ts +0 -68
- package/src/respond-with.ts +0 -263
- package/src/response-impl.ts +0 -8
- package/src/settle-controller.ts +0 -34
- package/src/settle-signal.ts +0 -42
- package/src/types.ts +0 -40
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
2
|
-
|
|
3
|
-
exports[`#mockFetch should reject with a useful error when there are no matching mocks for %s 1`] = `
|
|
4
|
-
"No matching mock response found for request:
|
|
5
|
-
Input: http://example.com/foo
|
|
6
|
-
Options: None"
|
|
7
|
-
`;
|
|
8
|
-
|
|
9
|
-
exports[`#mockFetch should reject with a useful error when there are no matching mocks for %s 2`] = `
|
|
10
|
-
"No matching mock response found for request:
|
|
11
|
-
Input: "http://example.com/foo"
|
|
12
|
-
Options: {
|
|
13
|
-
"method": "GET"
|
|
14
|
-
}"
|
|
15
|
-
`;
|
|
16
|
-
|
|
17
|
-
exports[`#mockFetch should reject with a useful error when there are no matching mocks for %s 3`] = `
|
|
18
|
-
"No matching mock response found for request:
|
|
19
|
-
Input: {
|
|
20
|
-
"size": 0,
|
|
21
|
-
"timeout": 0,
|
|
22
|
-
"follow": 20,
|
|
23
|
-
"compress": true,
|
|
24
|
-
"counter": 0
|
|
25
|
-
}
|
|
26
|
-
Options: {
|
|
27
|
-
"method": "POST"
|
|
28
|
-
}"
|
|
29
|
-
`;
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import {Request} from "node-fetch";
|
|
2
|
-
import {fetchRequestMatchesMock} from "../fetch-request-matches-mock";
|
|
3
|
-
|
|
4
|
-
const TEST_URL = "http://example.com/foo?querya=1&queryb=elephants#fragment";
|
|
5
|
-
|
|
6
|
-
describe("#fetchRequestMatchesMock", () => {
|
|
7
|
-
it("should throw if mock is not valid", () => {
|
|
8
|
-
// Arrange
|
|
9
|
-
const mock = {
|
|
10
|
-
operation: {
|
|
11
|
-
id: "foo",
|
|
12
|
-
type: "query",
|
|
13
|
-
},
|
|
14
|
-
} as const;
|
|
15
|
-
|
|
16
|
-
// Act
|
|
17
|
-
const underTest = () =>
|
|
18
|
-
fetchRequestMatchesMock(mock as any, TEST_URL, null);
|
|
19
|
-
|
|
20
|
-
// Assert
|
|
21
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
22
|
-
`"Unsupported mock operation: {"operation":{"id":"foo","type":"query"}}"`,
|
|
23
|
-
);
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("should throw if input is not valid", () => {
|
|
27
|
-
// Arrange
|
|
28
|
-
const mock = "http://example.com/foo";
|
|
29
|
-
|
|
30
|
-
// Act
|
|
31
|
-
const underTest = () =>
|
|
32
|
-
fetchRequestMatchesMock(
|
|
33
|
-
mock,
|
|
34
|
-
{not: "a valid request"} as any,
|
|
35
|
-
null,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
// Assert
|
|
39
|
-
expect(underTest).toThrowErrorMatchingInlineSnapshot(
|
|
40
|
-
`"Unsupported input type"`,
|
|
41
|
-
);
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
describe.each([TEST_URL, new URL(TEST_URL), new Request(TEST_URL)])(
|
|
45
|
-
"for valid inputs",
|
|
46
|
-
(input: any) => {
|
|
47
|
-
it("should return false if mock is a string and it does not match the fetched URL", () => {
|
|
48
|
-
// Arrange
|
|
49
|
-
const mock = "http://example.com/bar";
|
|
50
|
-
|
|
51
|
-
// Act
|
|
52
|
-
const result = fetchRequestMatchesMock(mock, input, null);
|
|
53
|
-
|
|
54
|
-
// Assert
|
|
55
|
-
expect(result).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("should return false if the mock is a regular expression and it doesn't match the fetched URL", () => {
|
|
59
|
-
// Arrange
|
|
60
|
-
const mock = /\/bar/;
|
|
61
|
-
|
|
62
|
-
// Act
|
|
63
|
-
const result = fetchRequestMatchesMock(mock, input, null);
|
|
64
|
-
|
|
65
|
-
// Assert
|
|
66
|
-
expect(result).toBe(false);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should return true if the mock is a string and matches the fetched URL", () => {
|
|
70
|
-
// Arrange
|
|
71
|
-
const mock = TEST_URL;
|
|
72
|
-
|
|
73
|
-
// Act
|
|
74
|
-
const result = fetchRequestMatchesMock(mock, input, null);
|
|
75
|
-
|
|
76
|
-
// Assert
|
|
77
|
-
expect(result).toBe(true);
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it.each([
|
|
81
|
-
/http:\/\/example.com\/foo/,
|
|
82
|
-
/queryb=elephants/,
|
|
83
|
-
/^.*#fragment$/,
|
|
84
|
-
])(
|
|
85
|
-
"should return true if the mock is a %s and matches the fetched URL",
|
|
86
|
-
(regex: any) => {
|
|
87
|
-
// Arrange
|
|
88
|
-
|
|
89
|
-
// Act
|
|
90
|
-
const result = fetchRequestMatchesMock(regex, input, null);
|
|
91
|
-
|
|
92
|
-
// Assert
|
|
93
|
-
expect(result).toBe(true);
|
|
94
|
-
},
|
|
95
|
-
);
|
|
96
|
-
},
|
|
97
|
-
);
|
|
98
|
-
});
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import {Request} from "node-fetch";
|
|
2
|
-
import {RespondWith} from "../../respond-with";
|
|
3
|
-
import {mockFetch} from "../mock-fetch";
|
|
4
|
-
|
|
5
|
-
describe("#mockFetch", () => {
|
|
6
|
-
it.each`
|
|
7
|
-
input | init
|
|
8
|
-
${"http://example.com/foo"} | ${undefined}
|
|
9
|
-
${new URL("http://example.com/foo")} | ${{method: "GET"}}
|
|
10
|
-
${new Request("http://example.com/foo")} | ${{method: "POST"}}
|
|
11
|
-
`(
|
|
12
|
-
"should reject with a useful error when there are no matching mocks for %s",
|
|
13
|
-
async ({input, init}: any) => {
|
|
14
|
-
// Arrange
|
|
15
|
-
const mockFn = mockFetch();
|
|
16
|
-
|
|
17
|
-
// Act
|
|
18
|
-
const result = mockFn(input, init);
|
|
19
|
-
|
|
20
|
-
// Assert
|
|
21
|
-
await expect(result).rejects.toThrowErrorMatchingSnapshot();
|
|
22
|
-
},
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
describe("mockOperation", () => {
|
|
26
|
-
it("should match a similar operation", async () => {
|
|
27
|
-
// Arrange
|
|
28
|
-
const mockFn = mockFetch();
|
|
29
|
-
const operation = "http://example.com/foo";
|
|
30
|
-
|
|
31
|
-
// Act
|
|
32
|
-
mockFn.mockOperation(operation, RespondWith.text("TADA!"));
|
|
33
|
-
const result = mockFn(operation, {method: "GET"});
|
|
34
|
-
|
|
35
|
-
// Assert
|
|
36
|
-
await expect(result).resolves.toBeDefined();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it("should not match a different operation", async () => {
|
|
40
|
-
// Arrange
|
|
41
|
-
const mockFn = mockFetch();
|
|
42
|
-
const operation = "http://example.com/foo";
|
|
43
|
-
|
|
44
|
-
// Act
|
|
45
|
-
mockFn.mockOperation(operation, RespondWith.text("TADA!"));
|
|
46
|
-
const result = mockFn("http://example.com/bar", {method: "GET"});
|
|
47
|
-
|
|
48
|
-
// Assert
|
|
49
|
-
await expect(result).rejects.toThrowError();
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe("mockOperationOnce", () => {
|
|
54
|
-
it("should match once", async () => {
|
|
55
|
-
// Arrange
|
|
56
|
-
const mockFn = mockFetch();
|
|
57
|
-
const operation = "http://example.com/foo";
|
|
58
|
-
|
|
59
|
-
// Act
|
|
60
|
-
mockFn.mockOperationOnce(operation, RespondWith.text("TADA!"));
|
|
61
|
-
const result = mockFn(operation, {method: "GET"});
|
|
62
|
-
|
|
63
|
-
// Assert
|
|
64
|
-
await expect(result).resolves.toBeDefined();
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("should only match once", async () => {
|
|
68
|
-
// Arrange
|
|
69
|
-
const mockFn = mockFetch();
|
|
70
|
-
const operation = "http://example.com/foo";
|
|
71
|
-
|
|
72
|
-
// Act
|
|
73
|
-
mockFn.mockOperationOnce(operation, RespondWith.text("TADA!"));
|
|
74
|
-
const result = Promise.all([
|
|
75
|
-
mockFn(operation, {method: "GET"}),
|
|
76
|
-
mockFn(operation, {method: "POST"}),
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
// Assert
|
|
80
|
-
await expect(result).rejects.toThrowError();
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type {FetchMockOperation} from "./types";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Get the URL from the given RequestInfo.
|
|
5
|
-
*
|
|
6
|
-
* Since we could be running in Node or in JSDOM, we don't check instance
|
|
7
|
-
* types, but just use a heuristic so that this works without knowing what
|
|
8
|
-
* was polyfilling things.
|
|
9
|
-
*/
|
|
10
|
-
const getHref = (input: RequestInfo): string => {
|
|
11
|
-
if (typeof input === "string") {
|
|
12
|
-
return input;
|
|
13
|
-
} else if (typeof input.url === "string") {
|
|
14
|
-
return input.url;
|
|
15
|
-
} else if (typeof (input as any).href === "string") {
|
|
16
|
-
return (input as any).href;
|
|
17
|
-
} else {
|
|
18
|
-
throw new Error(`Unsupported input type`);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Determines if a given fetch invocation matches the given mock.
|
|
24
|
-
*/
|
|
25
|
-
export const fetchRequestMatchesMock = (
|
|
26
|
-
mock: FetchMockOperation,
|
|
27
|
-
input: RequestInfo,
|
|
28
|
-
init?: RequestInit | null,
|
|
29
|
-
): boolean => {
|
|
30
|
-
// Currently, we only match on the input portion.
|
|
31
|
-
// This can be a Request, a URL, or a string.
|
|
32
|
-
const href = getHref(input);
|
|
33
|
-
|
|
34
|
-
// Our mock operation is either a string for an exact match, or a regex.
|
|
35
|
-
if (typeof mock === "string") {
|
|
36
|
-
return href === mock;
|
|
37
|
-
} else if (mock instanceof RegExp) {
|
|
38
|
-
return mock.test(href);
|
|
39
|
-
} else {
|
|
40
|
-
throw new Error(`Unsupported mock operation: ${JSON.stringify(mock)}`);
|
|
41
|
-
}
|
|
42
|
-
};
|
package/src/fetch/mock-fetch.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import {fetchRequestMatchesMock} from "./fetch-request-matches-mock";
|
|
2
|
-
import {mockRequester} from "../mock-requester";
|
|
3
|
-
import type {FetchMockFn, FetchMockOperation} from "./types";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A mock for the fetch function passed to GqlRouter.
|
|
7
|
-
*/
|
|
8
|
-
export const mockFetch = (): FetchMockFn =>
|
|
9
|
-
mockRequester<FetchMockOperation>(
|
|
10
|
-
fetchRequestMatchesMock,
|
|
11
|
-
// NOTE(somewhatabstract): The indentation is expected on the lines
|
|
12
|
-
// here.
|
|
13
|
-
(input, init) =>
|
|
14
|
-
`Input: ${
|
|
15
|
-
typeof input === "string"
|
|
16
|
-
? input
|
|
17
|
-
: JSON.stringify(input, null, 2)
|
|
18
|
-
}
|
|
19
|
-
Options: ${init == null ? "None" : JSON.stringify(init, null, 2)}`,
|
|
20
|
-
);
|
package/src/fetch/types.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type {MockResponse} from "../respond-with";
|
|
2
|
-
|
|
3
|
-
export type FetchMockOperation = RegExp | string;
|
|
4
|
-
|
|
5
|
-
type FetchMockOperationFn = (
|
|
6
|
-
operation: FetchMockOperation,
|
|
7
|
-
response: MockResponse<any>,
|
|
8
|
-
) => FetchMockFn;
|
|
9
|
-
|
|
10
|
-
export type FetchMockFn = {
|
|
11
|
-
(input: RequestInfo, init?: RequestInit): Promise<Response>;
|
|
12
|
-
mockOperation: FetchMockOperationFn;
|
|
13
|
-
mockOperationOnce: FetchMockOperationFn;
|
|
14
|
-
};
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {render, screen} from "@testing-library/react";
|
|
3
|
-
import * as AddonActionsModule from "@storybook/addon-actions";
|
|
4
|
-
import {fixtures} from "../fixtures";
|
|
5
|
-
|
|
6
|
-
jest.mock("@storybook/addon-actions");
|
|
7
|
-
|
|
8
|
-
describe("fixtures", () => {
|
|
9
|
-
beforeEach(() => {
|
|
10
|
-
jest.resetAllMocks();
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
it("should create return a function", () => {
|
|
14
|
-
// Arrange
|
|
15
|
-
|
|
16
|
-
// Act
|
|
17
|
-
const result = fixtures(() => <div />);
|
|
18
|
-
|
|
19
|
-
// Assert
|
|
20
|
-
expect(result).toBeFunction();
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
describe("returned function", () => {
|
|
24
|
-
it("should return a story component function", () => {
|
|
25
|
-
// Arrange
|
|
26
|
-
const fixture = fixtures(() => <div />);
|
|
27
|
-
|
|
28
|
-
// Act
|
|
29
|
-
const result = fixture("My fixture", {});
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(result).toBeFunction();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("should inject API into getProps generator", () => {
|
|
36
|
-
// Arrange
|
|
37
|
-
const fixture = fixtures(() => <div />);
|
|
38
|
-
const getPropsFn = jest.fn();
|
|
39
|
-
|
|
40
|
-
// Act
|
|
41
|
-
fixture("My fixture", getPropsFn);
|
|
42
|
-
|
|
43
|
-
// Assert
|
|
44
|
-
expect(getPropsFn).toHaveBeenCalledWith({
|
|
45
|
-
log: expect.any(Function),
|
|
46
|
-
logHandler: expect.any(Function),
|
|
47
|
-
});
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it("should inject log function that logs to storybook actions", () => {
|
|
51
|
-
// Arrange
|
|
52
|
-
const fixture = fixtures(() => <div />);
|
|
53
|
-
const getPropsFn = jest.fn();
|
|
54
|
-
const actionReturnFn = jest.fn();
|
|
55
|
-
const actionSpy = jest
|
|
56
|
-
.spyOn(AddonActionsModule, "action")
|
|
57
|
-
.mockReturnValue(actionReturnFn);
|
|
58
|
-
fixture("My fixture", getPropsFn);
|
|
59
|
-
const {log: logFn} = getPropsFn.mock.calls[0][0];
|
|
60
|
-
|
|
61
|
-
// Act
|
|
62
|
-
logFn("MESSAGE", "ARG1", "ARG2");
|
|
63
|
-
|
|
64
|
-
// Assert
|
|
65
|
-
expect(actionSpy).toHaveBeenCalledWith("MESSAGE");
|
|
66
|
-
expect(actionReturnFn).toHaveBeenCalledWith("ARG1", "ARG2");
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it("should inject logHandler function that logs to storybook actions", () => {
|
|
70
|
-
// Arrange
|
|
71
|
-
const fixture = fixtures(() => <div />);
|
|
72
|
-
const getPropsFn = jest.fn();
|
|
73
|
-
const actionReturnFn = jest.fn();
|
|
74
|
-
const actionSpy = jest
|
|
75
|
-
.spyOn(AddonActionsModule, "action")
|
|
76
|
-
.mockReturnValue(actionReturnFn);
|
|
77
|
-
fixture("My fixture", getPropsFn);
|
|
78
|
-
const {logHandler: logHandlerFn} = getPropsFn.mock.calls[0][0];
|
|
79
|
-
|
|
80
|
-
// Act
|
|
81
|
-
const logFn = logHandlerFn("MESSAGE");
|
|
82
|
-
logFn("ARG1", "ARG2");
|
|
83
|
-
|
|
84
|
-
// Assert
|
|
85
|
-
expect(actionSpy).toHaveBeenCalledWith("MESSAGE");
|
|
86
|
-
expect(actionReturnFn).toHaveBeenCalledWith("ARG1", "ARG2");
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("should have args attached", () => {
|
|
90
|
-
// Arrange
|
|
91
|
-
const fixture = fixtures(() => <div />);
|
|
92
|
-
const props = {
|
|
93
|
-
this: "isAProp",
|
|
94
|
-
andSo: "isThis",
|
|
95
|
-
} as const;
|
|
96
|
-
|
|
97
|
-
// Act
|
|
98
|
-
const result = fixture("A simple story", props);
|
|
99
|
-
|
|
100
|
-
// Assert
|
|
101
|
-
expect(result).toHaveProperty("args", props);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("should have story name attached", () => {
|
|
105
|
-
// Arrange
|
|
106
|
-
const fixture = fixtures(() => <div />);
|
|
107
|
-
|
|
108
|
-
// Act
|
|
109
|
-
const result = fixture("A simple story", {});
|
|
110
|
-
|
|
111
|
-
// Assert
|
|
112
|
-
expect(result).toHaveProperty("storyName", "1 A simple story");
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it("should render the component", () => {
|
|
116
|
-
// Arrange
|
|
117
|
-
const fixture = fixtures((props: any) => (
|
|
118
|
-
<>{`I rendered ${JSON.stringify(props)}`}</>
|
|
119
|
-
));
|
|
120
|
-
const Fixture: any = fixture("A simple story", {});
|
|
121
|
-
|
|
122
|
-
// Act
|
|
123
|
-
render(<Fixture aProp="aValue" />);
|
|
124
|
-
|
|
125
|
-
// Assert
|
|
126
|
-
expect(
|
|
127
|
-
screen.getByText('I rendered {"aProp":"aValue"}'),
|
|
128
|
-
).toBeInTheDocument();
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("should render the wrapper", () => {
|
|
132
|
-
// Arrange
|
|
133
|
-
const fixture = fixtures((props: any) => (
|
|
134
|
-
<>{`I rendered ${JSON.stringify(props)}`}</>
|
|
135
|
-
));
|
|
136
|
-
const Fixture: any = fixture("A simple story", {}, () => (
|
|
137
|
-
<>I am a wrapper</>
|
|
138
|
-
));
|
|
139
|
-
|
|
140
|
-
// Act
|
|
141
|
-
render(<Fixture aProp="aValue" />);
|
|
142
|
-
|
|
143
|
-
// Assert
|
|
144
|
-
expect(screen.getByText("I am a wrapper")).toBeInTheDocument();
|
|
145
|
-
});
|
|
146
|
-
});
|
|
147
|
-
});
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import {fixtures} from "../index";
|
|
4
|
-
|
|
5
|
-
type Props = {
|
|
6
|
-
propA: string;
|
|
7
|
-
propB?: string;
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const MyComponent = (props: Props): React.ReactElement => (
|
|
11
|
-
<>
|
|
12
|
-
{`I am a component. Here are my props: ${JSON.stringify(
|
|
13
|
-
props,
|
|
14
|
-
null,
|
|
15
|
-
2,
|
|
16
|
-
)}`}
|
|
17
|
-
</>
|
|
18
|
-
);
|
|
19
|
-
|
|
20
|
-
const Wrapper = (props: any) => (
|
|
21
|
-
<>
|
|
22
|
-
Wrapper >>>
|
|
23
|
-
<MyComponent {...props} />
|
|
24
|
-
<<< Wrapper
|
|
25
|
-
</>
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const fixture = fixtures(MyComponent);
|
|
29
|
-
|
|
30
|
-
export const F1: unknown = fixture(
|
|
31
|
-
"This is a fixture with some regular props",
|
|
32
|
-
{
|
|
33
|
-
propA: "this is a prop",
|
|
34
|
-
propB: "this is another",
|
|
35
|
-
},
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
export const F2: unknown = fixture(
|
|
39
|
-
"This is a fixture with props from functions, and a bit of logging",
|
|
40
|
-
({log}) => {
|
|
41
|
-
log("This is a log from a fixture during props generation", {
|
|
42
|
-
and: "some data",
|
|
43
|
-
});
|
|
44
|
-
return {
|
|
45
|
-
propA: "prop was made from a function",
|
|
46
|
-
};
|
|
47
|
-
},
|
|
48
|
-
);
|
|
49
|
-
|
|
50
|
-
export const F3: unknown = fixture(
|
|
51
|
-
"This fixture uses a custom wrapper",
|
|
52
|
-
{
|
|
53
|
-
propA: "some props again",
|
|
54
|
-
propB: "this one",
|
|
55
|
-
},
|
|
56
|
-
Wrapper,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
export default {
|
|
60
|
-
title: "Testing / Fixtures / Basic",
|
|
61
|
-
component: MyComponent,
|
|
62
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
import {fixtures} from "../index";
|
|
4
|
-
|
|
5
|
-
type Props = Record<any, any>;
|
|
6
|
-
|
|
7
|
-
const MyComponent = (props: Props): React.ReactElement => (
|
|
8
|
-
<>{`My props: ${JSON.stringify(props, null, 2)}`}</>
|
|
9
|
-
);
|
|
10
|
-
|
|
11
|
-
const Wrapper = (props: any) => (
|
|
12
|
-
<>
|
|
13
|
-
Wrapper >>>
|
|
14
|
-
<MyComponent {...props} />
|
|
15
|
-
<<< Wrapper
|
|
16
|
-
</>
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
const DefaultWrapper = (props: Props): React.ReactElement => (
|
|
20
|
-
<>
|
|
21
|
-
DefaultWrapper >>>
|
|
22
|
-
<MyComponent {...props} />
|
|
23
|
-
<<< DefaultWrapper
|
|
24
|
-
</>
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const fixture = fixtures(DefaultWrapper);
|
|
28
|
-
|
|
29
|
-
export const F1: unknown = fixture(
|
|
30
|
-
"This is a fixture with some regular props and the default wrapper",
|
|
31
|
-
{
|
|
32
|
-
see: "this is a prop",
|
|
33
|
-
and: "this is another",
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
export const F2: unknown = fixture(
|
|
38
|
-
"This fixture uses a custom wrapper",
|
|
39
|
-
{
|
|
40
|
-
just: "some props again",
|
|
41
|
-
like: "this one",
|
|
42
|
-
},
|
|
43
|
-
Wrapper,
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
export default {
|
|
47
|
-
title: "Testing / Fixtures / DefaultWrapper",
|
|
48
|
-
component: DefaultWrapper,
|
|
49
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
import {action} from "@storybook/addon-actions";
|
|
3
|
-
|
|
4
|
-
import type {FixtureFn, FixtureProps} from "./types";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Describe a group of fixtures for a given component.
|
|
8
|
-
*
|
|
9
|
-
* Only one `fixtures` call should be used per fixture file as it returns
|
|
10
|
-
* the exports for that file.
|
|
11
|
-
*
|
|
12
|
-
* @param {React.ComponentType<any>} Component The component we want to create
|
|
13
|
-
* stories for.
|
|
14
|
-
* @returns {FixtureFn<TProps>} A function to create a CSF compatible story.
|
|
15
|
-
*/
|
|
16
|
-
export const fixtures = <
|
|
17
|
-
TComponent extends React.ComponentType<any>,
|
|
18
|
-
TProps extends JSX.LibraryManagedAttributes<
|
|
19
|
-
TComponent,
|
|
20
|
-
React.ComponentProps<TComponent>
|
|
21
|
-
>,
|
|
22
|
-
>(
|
|
23
|
-
Component: TComponent,
|
|
24
|
-
): FixtureFn<TProps> => {
|
|
25
|
-
const templateMap = new WeakMap();
|
|
26
|
-
// We use this to make sure each story gets a unique name.
|
|
27
|
-
let storyNumber = 1;
|
|
28
|
-
|
|
29
|
-
const getPropsOptions = {
|
|
30
|
-
log: (message: string, ...args: Array<any>) => action(message)(...args),
|
|
31
|
-
logHandler: action,
|
|
32
|
-
} as const;
|
|
33
|
-
|
|
34
|
-
const makeStory = (
|
|
35
|
-
description: string,
|
|
36
|
-
props: FixtureProps<TProps>,
|
|
37
|
-
wrapper: React.ComponentType<TProps> | null = null,
|
|
38
|
-
): unknown => {
|
|
39
|
-
const storyName = `${storyNumber++} ${description}`;
|
|
40
|
-
const getProps = (options: {
|
|
41
|
-
log: (message: string, ...args: Array<any>) => any;
|
|
42
|
-
logHandler: any;
|
|
43
|
-
}) => (typeof props === "function" ? props(options) : props);
|
|
44
|
-
|
|
45
|
-
const RealComponent = wrapper || Component;
|
|
46
|
-
|
|
47
|
-
// We create a “template” of how args map to rendering
|
|
48
|
-
// for each type of component as the component here could
|
|
49
|
-
// be the component under test, or wrapped in a wrapper
|
|
50
|
-
// component. We don't use decorators for the wrapper
|
|
51
|
-
// because we may not be in a storybook context and it
|
|
52
|
-
// keeps the framework API simpler this way.
|
|
53
|
-
let Template = templateMap.get(RealComponent as any);
|
|
54
|
-
if (Template == null) {
|
|
55
|
-
Template = (args: any) => <RealComponent {...args} />;
|
|
56
|
-
templateMap.set(RealComponent as any, Template);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Each story that shares that component then reuses that
|
|
60
|
-
// template.
|
|
61
|
-
const story = Template.bind({});
|
|
62
|
-
story.args = getProps(getPropsOptions);
|
|
63
|
-
// Adding a story name here means that we don't have to
|
|
64
|
-
// care about naming the exports correctly, if we don't
|
|
65
|
-
// want (useful if we need to autogenerate or manually
|
|
66
|
-
// expose ESM exports).
|
|
67
|
-
story.storyName = storyName;
|
|
68
|
-
|
|
69
|
-
return story;
|
|
70
|
-
};
|
|
71
|
-
return makeStory;
|
|
72
|
-
};
|
package/src/fixtures/types.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import * as React from "react";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Options injected to the function that returns the fixture props.
|
|
5
|
-
*/
|
|
6
|
-
export type GetPropsOptions = {
|
|
7
|
-
/**
|
|
8
|
-
* A function to call that will log output.
|
|
9
|
-
*/
|
|
10
|
-
log: (message: string, ...args: Array<any>) => void;
|
|
11
|
-
/**
|
|
12
|
-
* A function to make a handler that will log all arguments with the given
|
|
13
|
-
* name or message. Useful for logging events as it avoids the boilerplate
|
|
14
|
-
* of the `log` function.
|
|
15
|
-
*/
|
|
16
|
-
logHandler: (name: string) => (...args: Array<any>) => void;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export type FixtureProps<TProps extends object> =
|
|
20
|
-
| Readonly<TProps>
|
|
21
|
-
| ((options: Readonly<GetPropsOptions>) => Readonly<TProps>);
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* A function for defining a fixture.
|
|
25
|
-
*/
|
|
26
|
-
export type FixtureFn<TProps extends object> = (
|
|
27
|
-
/**
|
|
28
|
-
* The name of the fixture.
|
|
29
|
-
*/
|
|
30
|
-
description: string,
|
|
31
|
-
/**
|
|
32
|
-
* The props for the fixture or a function that returns the props.
|
|
33
|
-
* The function is injected with an API to facilitate logging.
|
|
34
|
-
*/
|
|
35
|
-
props: FixtureProps<TProps>,
|
|
36
|
-
/**
|
|
37
|
-
* An alternative component to render for the fixture.
|
|
38
|
-
* Useful if the fixture requires some additional setup for testing the
|
|
39
|
-
* component.
|
|
40
|
-
*/
|
|
41
|
-
wrapper?: React.ComponentType<TProps>,
|
|
42
|
-
) => unknown;
|