Skip to content

Commit 1ba50d6

Browse files
feat: add ability to use functions as autoScreenshotStorybookGlobals
1 parent f01ca4d commit 1ba50d6

File tree

9 files changed

+1767
-2652
lines changed

9 files changed

+1767
-2652
lines changed

package-lock.json

Lines changed: 1604 additions & 2627 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@
4444
"devDependencies": {
4545
"@gemini-testing/commander": "^2.15.3",
4646
"@types/fs-extra": "^11.0.4",
47-
"@types/jest": "^27.5.2",
47+
"@types/jest": "^29.5.14",
4848
"@types/lodash": "^4.14.172",
4949
"@types/node": "^12.20.19",
5050
"@types/npm-which": "^3.0.3",
5151
"@typescript-eslint/eslint-plugin": "^4.29.3",
5252
"@typescript-eslint/parser": "^4.29.3",
5353
"eslint": "^7.32.0",
5454
"eslint-config-gemini-testing": "^3.0.0",
55-
"jest": "^27.5.1",
55+
"jest": "^29.7.0",
5656
"jest-extended": "^0.11.5",
5757
"prettier": "^3.4.2",
5858
"rimraf": "^3.0.2",
5959
"testplane": "^0.1.0-rc.0",
60-
"ts-jest": "^27.1.5",
60+
"ts-jest": "^29.2.5",
6161
"typescript": "^4.3.5"
6262
},
6363
"dependencies": {

src/storybook/story-test-runner/extend-stories.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ describe("storybook/story-test-runner/extend-stories", () => {
5959

6060
const extendedStories = extendStoriesFromStoryFile(stories, { requireFn });
6161

62-
expect(extendedStories).toStrictEqual([
62+
expect(extendedStories).toEqual([
6363
{
6464
name: "foo",
6565
absolutePath: "not/existing.js",
66-
extraTests: customTests,
66+
extraTests: {},
6767
skip: false,
6868
browserIds: ["chrome"],
6969
assertViewOpts: {

src/storybook/story-test-runner/extend-stories.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import TypedModule from "module";
2+
import { extractInheritedValue, inheritValue } from "./inheritable-values";
23
import type { TestplaneMetaConfig, TestplaneStoryConfig } from "../../types";
34
import type { StorybookStory, StorybookStoryExtraProperties, StorybookStoryExtended } from "./types";
45

@@ -55,19 +56,17 @@ export function extendStoriesFromStoryFile(
5556
extraTests: storyFile[storyName].testplane || null,
5657
});
5758

58-
story.assertViewOpts = Object.assign(
59-
{},
59+
story.assertViewOpts = extractInheritedValue(
60+
inheritValue(storyTestplaneDefaultConfigs.assertViewOpts, storyTestlaneConfigs.assertViewOpts),
6061
storyExtendedBaseConfig.assertViewOpts,
61-
storyTestplaneDefaultConfigs.assertViewOpts,
62-
storyTestlaneConfigs.assertViewOpts,
6362
);
6463

65-
story.autoScreenshotStorybookGlobals = Object.assign(
66-
{},
67-
storyExtendedBaseConfig.autoScreenshotStorybookGlobals,
68-
storyTestplaneDefaultConfigs.autoScreenshotStorybookGlobals,
69-
storyTestlaneConfigs.autoScreenshotStorybookGlobals,
70-
);
64+
story.autoScreenshotStorybookGlobals =
65+
inheritValue(
66+
storyExtendedBaseConfig.autoScreenshotStorybookGlobals,
67+
storyTestplaneDefaultConfigs.autoScreenshotStorybookGlobals,
68+
storyTestlaneConfigs.autoScreenshotStorybookGlobals,
69+
) || {};
7170
}
7271

7372
return withStoryFileExtendedStories;

src/storybook/story-test-runner/index.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { StoryLoadResult } from "./open-story/testplane-open-story";
55
import type { TestplaneOpts } from "../story-to-test";
66
import type { TestFunctionExtendedCtx } from "../../types";
77
import type { StorybookStoryExtended, StorybookStory } from "./types";
8+
import { extractInheritedValue } from "./inheritable-values";
89

910
export function getAbsoluteFilePath(): string {
1011
return __filename;
@@ -22,11 +23,10 @@ function createTestplaneTests(
2223
{ autoScreenshots, autoscreenshotSelector, autoScreenshotStorybookGlobals }: TestplaneOpts,
2324
): void {
2425
nestedDescribe(story, () => {
25-
const rawAutoScreenshotGlobalSets = {
26-
...autoScreenshotStorybookGlobals,
27-
...story.autoScreenshotStorybookGlobals,
28-
};
29-
26+
const rawAutoScreenshotGlobalSets = extractInheritedValue(
27+
story.autoScreenshotStorybookGlobals,
28+
autoScreenshotStorybookGlobals,
29+
);
3030
const screenshotGlobalSetNames = Object.keys(rawAutoScreenshotGlobalSets);
3131

3232
const autoScreenshotGlobalSets = screenshotGlobalSetNames.length
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { extractInheritedValue, inheritValue } from "./inheritable-values";
2+
3+
type TestingObject = { foo: number; bar: number; baz: number };
4+
type DeepObject = { foo: number; bar: { baz: number[] } };
5+
type InheritedFn<T> = (base: T) => T;
6+
7+
describe("storybook/story-test-runner/inheritable-values", () => {
8+
describe("inheritValue", () => {
9+
it("should return overlayed value with objects", () => {
10+
const firstObject = { foo: 4 } as TestingObject;
11+
const secondObject = { bar: 6 } as TestingObject;
12+
const thirdObject = { baz: 8 } as TestingObject;
13+
const fourthObject = { bar: 10 } as TestingObject;
14+
15+
const inheritedValue = inheritValue(firstObject, secondObject, thirdObject, fourthObject);
16+
17+
expect(inheritedValue).toEqual({ foo: 4, bar: 10, baz: 8 });
18+
});
19+
20+
it("should return overlayed value with mixed objects and functions", () => {
21+
const baseObject = { foo: 4 } as TestingObject;
22+
const extendObjectFn: InheritedFn<TestingObject> = baseObj => ({ ...baseObj, bar: 8 });
23+
24+
const inheritedValueFn = inheritValue(baseObject, extendObjectFn);
25+
const inheritedValue = (inheritedValueFn as InheritedFn<TestingObject>)({} as TestingObject);
26+
27+
expect(inheritedValue).toEqual({ foo: 4, bar: 8 });
28+
});
29+
30+
it("should not mutate objects", () => {
31+
const firstObject = { foo: 4 } as TestingObject;
32+
const secondObject = { bar: 6 } as TestingObject;
33+
const thirdObject = { bar: 8 } as TestingObject;
34+
35+
inheritValue(firstObject, secondObject, thirdObject);
36+
37+
expect(firstObject).toEqual({ foo: 4 });
38+
expect(secondObject).toEqual({ bar: 6 });
39+
expect(thirdObject).toEqual({ bar: 8 });
40+
});
41+
42+
it("should not mutate objects deeply even if they are mutated directly by functions", () => {
43+
const baseObject = { bar: { baz: [1, 2, 3] } } as DeepObject;
44+
const mutateFn: InheritedFn<DeepObject> = baseObj => {
45+
baseObj.foo = 4;
46+
baseObj.bar.baz[1] = 100500;
47+
48+
return baseObj;
49+
};
50+
51+
const inheritedValueFn = inheritValue(baseObject, mutateFn);
52+
const inheritedValue = (inheritedValueFn as InheritedFn<DeepObject>)({} as DeepObject);
53+
54+
expect(inheritedValue).toEqual({ foo: 4, bar: { baz: [1, 100500, 3] } });
55+
expect(baseObject).toEqual({ bar: { baz: [1, 2, 3] } });
56+
});
57+
});
58+
59+
describe("extractInheritedValue", () => {
60+
it("should extract inherited value", () => {
61+
const baseValue = { foo: 4 } as TestingObject;
62+
const overlayedValue = { bar: 6 } as TestingObject;
63+
64+
const inheritedValue = inheritValue(baseValue, overlayedValue);
65+
const resultValue = extractInheritedValue(inheritedValue, { foo: 8, baz: 10 } as TestingObject);
66+
67+
expect(resultValue).toEqual({ foo: 4, bar: 6, baz: 10 });
68+
});
69+
70+
it("should extract inherited function", () => {
71+
const initialValue = { foo: 100, baz: 10 } as TestingObject;
72+
const baseValue = { foo: 4 } as TestingObject;
73+
const mutateFn: InheritedFn<TestingObject> = baseObj => {
74+
baseObj.foo *= 2;
75+
baseObj.baz *= 2;
76+
77+
return baseObj;
78+
};
79+
80+
const inheritedValue = inheritValue(baseValue, mutateFn);
81+
const resultValue = extractInheritedValue(inheritedValue, initialValue);
82+
83+
expect(resultValue).toEqual({ foo: 8, baz: 20 });
84+
});
85+
});
86+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2+
export type Inheritable<T extends Record<any, any>> = T | ((baseValue: T) => T);
3+
4+
// Acts like "Object.assign", but:
5+
// - Does not mutate arguments
6+
// - Supports functions (previousValue) => nextValue
7+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
8+
export function inheritValue<T extends Record<any, any>>(
9+
...values: Array<undefined | Inheritable<T>>
10+
): undefined | Inheritable<T> {
11+
let resultValue = values[0];
12+
13+
for (let i = 1; i < values.length; i++) {
14+
const prevValue = resultValue;
15+
const curValue = values[i];
16+
17+
if (!curValue) {
18+
continue;
19+
}
20+
21+
if (!resultValue) {
22+
resultValue = curValue;
23+
}
24+
25+
if (typeof prevValue !== "function" && typeof curValue !== "function") {
26+
resultValue = Object.assign({}, prevValue, curValue);
27+
} else if (typeof prevValue === "function" && typeof curValue === "function") {
28+
resultValue = (baseValue: T) => curValue(prevValue(structuredClone(baseValue)));
29+
} else if (typeof prevValue === "function" && typeof curValue !== "function") {
30+
resultValue = (baseValue: T) => Object.assign({}, prevValue(structuredClone(baseValue)), curValue);
31+
} else if (typeof prevValue !== "function" && typeof curValue === "function") {
32+
resultValue = (baseValue: T) => curValue(structuredClone(Object.assign({}, baseValue, prevValue)));
33+
}
34+
}
35+
36+
return resultValue;
37+
}
38+
39+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
40+
export function extractInheritedValue<T extends Record<any, any>>(value: undefined | Inheritable<T>, baseValue: T): T {
41+
if (!value) {
42+
return baseValue;
43+
}
44+
45+
return typeof value === "function" ? value(baseValue) : structuredClone(Object.assign({}, baseValue, value));
46+
}

src/storybook/story-test-runner/types.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
import type { AssertViewOpts } from "testplane";
22
import type { TestplaneTestFunction } from "../../types";
33
import type { StorybookStoryExtended as StorybookStory } from "../get-stories";
4+
import type { Inheritable } from "./inheritable-values";
5+
6+
export type AutoScreenshotStorybookGlobals = Record<string, Record<string, unknown>>;
47

58
export interface StorybookStoryExtraProperties {
69
skip: boolean;
7-
assertViewOpts: AssertViewOpts;
10+
assertViewOpts: Inheritable<AssertViewOpts>;
811
browserIds: Array<string | RegExp> | null;
912
extraTests: Record<string, TestplaneTestFunction> | null;
1013
autoScreenshots: boolean | null;
1114
autoscreenshotSelector: string | null;
12-
autoScreenshotStorybookGlobals: Record<string, Record<string, unknown>>;
15+
autoScreenshotStorybookGlobals: Inheritable<AutoScreenshotStorybookGlobals>;
1316
}
1417

15-
export interface StorybookStoryExtended extends StorybookStory, StorybookStoryExtraProperties {}
18+
export interface StorybookStoryExtended extends StorybookStory, Omit<StorybookStoryExtraProperties, "assertViewOpts"> {
19+
assertViewOpts: AssertViewOpts;
20+
}
1621

1722
export type ExecutionContextExtended = WebdriverIO.Browser["executionContext"] & {
1823
"@testplane/storybook-assertView-opts": AssertViewOpts;

src/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import type { AssertViewOpts } from "testplane";
2+
import type { AutoScreenshotStorybookGlobals } from "./storybook/story-test-runner/types";
3+
import type { Inheritable } from "./storybook/story-test-runner/inheritable-values";
24

35
export type TestFunctionExtendedCtx = TestFunctionCtx & { expect: ExpectWebdriverIO.Expect };
46

@@ -15,11 +17,11 @@ type Combined<N, B = void> = B extends void ? N : N & B;
1517

1618
type TestplaneStoryFileConfig = {
1719
skip?: boolean;
18-
assertViewOpts?: AssertViewOpts;
20+
assertViewOpts?: Inheritable<AssertViewOpts>;
1921
browserIds?: Array<string | RegExp>;
2022
autoScreenshots?: boolean;
2123
autoscreenshotSelector?: string;
22-
autoScreenshotStorybookGlobals?: Record<string, Record<string, unknown>>;
24+
autoScreenshotStorybookGlobals?: Inheritable<AutoScreenshotStorybookGlobals>;
2325
};
2426

2527
export type TestplaneMetaConfig<T = void> = Combined<

0 commit comments

Comments
 (0)