vitest-plugin-vis 5.1.5 → 5.1.7
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/dist/client/external/vitest/vitest_suite_proxy.d.mts +1 -0
- package/dist/client/suite/auto_snapshot_matcher.d.mts.map +1 -1
- package/dist/client/suite/auto_snapshot_matcher.mjs +2 -4
- package/dist/client/suite/auto_snapshot_matcher.mjs.map +1 -1
- package/dist/client/suite/resolve_subject.mjs +34 -0
- package/dist/client/suite/resolve_subject.mjs.map +1 -0
- package/dist/client/task/task_id.mjs +9 -2
- package/dist/client/task/task_id.mjs.map +1 -1
- package/dist/server/snapshot_path.d.mts +3 -0
- package/dist/server/snapshot_path.d.mts.map +1 -1
- package/dist/server/vis_options.d.mts +1 -0
- package/dist/server/vis_options.d.mts.map +1 -1
- package/dist/server/vis_server_context.types.d.mts +1 -0
- package/dist/server/vis_server_context.types.d.mts.map +1 -1
- package/dist/shared/commands.types.d.mts +2 -2
- package/dist/shared/commands.types.d.mts.map +1 -1
- package/dist/shared/types.d.mts +2 -0
- package/dist/shared/types.d.mts.map +1 -1
- package/package.json +4 -4
- package/src/auto_snapshots/auto_snapshot_options.mdx +2 -0
- package/src/client/suite/auto_snapshot_matcher.ts +2 -5
- package/src/client/suite/resolve_subject.ts +32 -0
- package/src/client/task/task_id.ts +13 -2
- package/src/shared/commands.types.ts +2 -2
- package/src/shared/types.ts +2 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto_snapshot_matcher.d.mts","names":[],"sources":["../../../src/client/suite/auto_snapshot_matcher.ts"],"sourcesContent":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"auto_snapshot_matcher.d.mts","names":[],"sources":["../../../src/client/suite/auto_snapshot_matcher.ts"],"sourcesContent":[],"mappings":";;;;;iBAUgB,+BAA+B,mDACpC;;EADK,aAAA,CAAA,UAUU,gBAVS,EAAA,UAUmB,MAVnB,CAAA,MAAA,EAAA,GAAA,CAAA,GAAA,OAAA,GAAA,OAAA,CAAA,CAAA,MAAA,EAWxB,MAXwB,CAAA,MAAA,EAAA,OAAA,GAAA,CAAA,CAAA,OAAA,EAaV,YAbU,CAaG,CAbH,CAAA,GAaQ,CAbR,GAaY,EAbZ,EAAA,GAamB,OAbnB,CAAA,OAAA,CAAA,GAasC,OAbtC,CAAA,IAAA,CAAA,GAAA,OAAA,GAAA,IAAA,CAAA,CAAA,EAAA,aAAA,CAAA,EAehB,IAfgB,CAeX,eAfW,CAeK,EAfL,CAAA,EAAA,uBAAA,CAAA,GAAA,SAAA,CAAA,EAAA,GAAA,GAe8C,OAf9C,CAAA,IAAA,CAAA;CAAY"}
|
|
@@ -2,6 +2,7 @@ import { extractAutoSnapshotOptions } from "../../auto_snapshots/_extract_auto_s
|
|
|
2
2
|
import { shouldTakeSnapshot } from "../snapshot/should_take_snapshot.mjs";
|
|
3
3
|
import { toTaskId } from "../task/task_id.mjs";
|
|
4
4
|
import { ctx } from "./_ctx.mjs";
|
|
5
|
+
import { resolveSubject } from "./resolve_subject.mjs";
|
|
5
6
|
import dedent from "dedent";
|
|
6
7
|
|
|
7
8
|
//#region src/client/suite/auto_snapshot_matcher.ts
|
|
@@ -22,7 +23,7 @@ function autoSnapshotMatcher(commands, expect) {
|
|
|
22
23
|
const theme = themes[themeId];
|
|
23
24
|
if ((typeof theme === "function" ? await theme(meta) : theme) === false) continue;
|
|
24
25
|
const effectiveCreateMissingBaseline = meta != null && Object.hasOwn(meta, "createMissingBaseline") ? meta.createMissingBaseline : suiteDefaults?.createMissingBaseline;
|
|
25
|
-
await expect(
|
|
26
|
+
await expect(resolveSubject(meta?.subject ?? subject)).toMatchImageSnapshot({
|
|
26
27
|
...meta,
|
|
27
28
|
snapshotKey: meta?.snapshotKey ?? themeId,
|
|
28
29
|
...effectiveCreateMissingBaseline !== void 0 ? { createMissingBaseline: effectiveCreateMissingBaseline } : {}
|
|
@@ -43,9 +44,6 @@ function autoSnapshotMatcher(commands, expect) {
|
|
|
43
44
|
}
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
|
-
function getSubject(selectors) {
|
|
47
|
-
return selectors ? document.querySelector(selectors) ?? document.body : document.body;
|
|
48
|
-
}
|
|
49
47
|
|
|
50
48
|
//#endregion
|
|
51
49
|
export { autoSnapshotMatcher };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auto_snapshot_matcher.mjs","names":[],"sources":["../../../src/client/suite/auto_snapshot_matcher.ts"],"sourcesContent":["import dedent from 'dedent'\nimport { extractAutoSnapshotOptions } from '../../auto_snapshots/_extract_auto_snapshot_options.ts'\nimport type { SnapshotMeta } from '../../auto_snapshots/snapshot_meta.ts'\nimport type { SetupVisSuiteCommand } from '../../shared/commands.types.ts'\nimport type { ComparisonMethod, SetupVisOptions } from '../../shared/types.ts'\nimport { shouldTakeSnapshot } from '../snapshot/should_take_snapshot.ts'\nimport { toTaskId } from '../task/task_id.ts'\nimport { ctx } from './_ctx.ts'\n\nexport function autoSnapshotMatcher<GM extends Record<string, any> | unknown = unknown>(\n\tcommands: SetupVisSuiteCommand,\n\texpect: any,\n) {\n\tlet subject: string | undefined\n\n\treturn {\n\t\tasync setup() {\n\t\t\tsubject = (await commands.setupVisSuite()).subject\n\t\t},\n\t\tcreateMatcher<C extends ComparisonMethod, M extends Record<string, any> | unknown = unknown>(\n\t\t\tthemes: Record<\n\t\t\t\tstring,\n\t\t\t\tboolean | ((options: SnapshotMeta<C> & M & GM) => Promise<boolean> | Promise<void> | boolean | void)\n\t\t\t>,\n\t\t\tsuiteDefaults?: Pick<SetupVisOptions<GM>, 'createMissingBaseline'> | undefined,\n\t\t) {\n\t\t\treturn async function matchImageSnapshot() {\n\t\t\t\tconst test = ctx.getCurrentTest()\n\n\t\t\t\tconst meta = extractAutoSnapshotOptions(test)\n\t\t\t\tif (!shouldTakeSnapshot(meta)) return\n\t\t\t\tconst errors: Array<[string, Error]> = []\n\t\t\t\tfor (const themeId in themes) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait new Promise((a) => setTimeout(a, 10))\n\t\t\t\t\t\tconst theme = themes[themeId]\n\t\t\t\t\t\tconst r = typeof theme === 'function' ? await theme(meta! as any) : theme\n\t\t\t\t\t\tif (r === false) continue\n\t\t\t\t\t\tconst effectiveCreateMissingBaseline =\n\t\t\t\t\t\t\tmeta != null && Object.hasOwn(meta as object, 'createMissingBaseline')\n\t\t\t\t\t\t\t\t? (meta as { createMissingBaseline?: boolean }).createMissingBaseline\n\t\t\t\t\t\t\t\t: suiteDefaults?.createMissingBaseline\n\t\t\t\t\t\tawait expect(
|
|
1
|
+
{"version":3,"file":"auto_snapshot_matcher.mjs","names":[],"sources":["../../../src/client/suite/auto_snapshot_matcher.ts"],"sourcesContent":["import dedent from 'dedent'\nimport { extractAutoSnapshotOptions } from '../../auto_snapshots/_extract_auto_snapshot_options.ts'\nimport type { SnapshotMeta } from '../../auto_snapshots/snapshot_meta.ts'\nimport type { SetupVisSuiteCommand } from '../../shared/commands.types.ts'\nimport type { ComparisonMethod, SetupVisOptions } from '../../shared/types.ts'\nimport { shouldTakeSnapshot } from '../snapshot/should_take_snapshot.ts'\nimport { toTaskId } from '../task/task_id.ts'\nimport { ctx } from './_ctx.ts'\nimport { resolveSubject } from './resolve_subject.ts'\n\nexport function autoSnapshotMatcher<GM extends Record<string, any> | unknown = unknown>(\n\tcommands: SetupVisSuiteCommand,\n\texpect: any,\n) {\n\tlet subject: string | undefined\n\n\treturn {\n\t\tasync setup() {\n\t\t\tsubject = (await commands.setupVisSuite()).subject\n\t\t},\n\t\tcreateMatcher<C extends ComparisonMethod, M extends Record<string, any> | unknown = unknown>(\n\t\t\tthemes: Record<\n\t\t\t\tstring,\n\t\t\t\tboolean | ((options: SnapshotMeta<C> & M & GM) => Promise<boolean> | Promise<void> | boolean | void)\n\t\t\t>,\n\t\t\tsuiteDefaults?: Pick<SetupVisOptions<GM>, 'createMissingBaseline'> | undefined,\n\t\t) {\n\t\t\treturn async function matchImageSnapshot() {\n\t\t\t\tconst test = ctx.getCurrentTest()\n\n\t\t\t\tconst meta = extractAutoSnapshotOptions(test)\n\t\t\t\tif (!shouldTakeSnapshot(meta)) return\n\t\t\t\tconst errors: Array<[string, Error]> = []\n\t\t\t\tfor (const themeId in themes) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait new Promise((a) => setTimeout(a, 10))\n\t\t\t\t\t\tconst theme = themes[themeId]\n\t\t\t\t\t\tconst r = typeof theme === 'function' ? await theme(meta! as any) : theme\n\t\t\t\t\t\tif (r === false) continue\n\t\t\t\t\t\tconst effectiveCreateMissingBaseline =\n\t\t\t\t\t\t\tmeta != null && Object.hasOwn(meta as object, 'createMissingBaseline')\n\t\t\t\t\t\t\t\t? (meta as { createMissingBaseline?: boolean }).createMissingBaseline\n\t\t\t\t\t\t\t\t: suiteDefaults?.createMissingBaseline\n\t\t\t\t\t\tawait expect(resolveSubject(meta?.subject ?? subject)).toMatchImageSnapshot({\n\t\t\t\t\t\t\t...meta,\n\t\t\t\t\t\t\tsnapshotKey: meta?.snapshotKey ?? themeId,\n\t\t\t\t\t\t\t...(effectiveCreateMissingBaseline !== undefined\n\t\t\t\t\t\t\t\t? { createMissingBaseline: effectiveCreateMissingBaseline }\n\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t})\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\terrors.push([themeId, error as Error])\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (errors.length > 0) {\n\t\t\t\t\tif (errors.length === 1) throw errors[0]![1]\n\t\t\t\t\tconst taskId = toTaskId(test!)\n\t\t\t\t\tthrow new AggregateError(\n\t\t\t\t\t\terrors.map(([, e]) => e),\n\t\t\t\t\t\tdedent`Snapshot \\`${taskId}\\` mismatched\n\n\t\t\t\t\t${errors\n\t\t\t\t\t\t.map(([themeId, error]) => {\n\t\t\t\t\t\t\treturn `Theme \\`${themeId}\\` failed: ${error.message}`\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.join('\\n\\n')}`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t}\n}\n"],"mappings":";;;;;;;;AAUA,SAAgB,oBACf,UACA,QACC;CACD,IAAI;AAEJ,QAAO;EACN,MAAM,QAAQ;AACb,cAAW,MAAM,SAAS,eAAe,EAAE;;EAE5C,cACC,QAIA,eACC;AACD,UAAO,eAAe,qBAAqB;IAC1C,MAAM,OAAO,IAAI,gBAAgB;IAEjC,MAAM,OAAO,2BAA2B,KAAK;AAC7C,QAAI,CAAC,mBAAmB,KAAK,CAAE;IAC/B,MAAM,SAAiC,EAAE;AACzC,SAAK,MAAM,WAAW,OACrB,KAAI;AACH,WAAM,IAAI,SAAS,MAAM,WAAW,GAAG,GAAG,CAAC;KAC3C,MAAM,QAAQ,OAAO;AAErB,UADU,OAAO,UAAU,aAAa,MAAM,MAAM,KAAa,GAAG,WAC1D,MAAO;KACjB,MAAM,iCACL,QAAQ,QAAQ,OAAO,OAAO,MAAgB,wBAAwB,GAClE,KAA6C,wBAC9C,eAAe;AACnB,WAAM,OAAO,eAAe,MAAM,WAAW,QAAQ,CAAC,CAAC,qBAAqB;MAC3E,GAAG;MACH,aAAa,MAAM,eAAe;MAClC,GAAI,mCAAmC,SACpC,EAAE,uBAAuB,gCAAgC,GACzD,EAAE;MACL,CAAC;aACM,OAAO;AACf,YAAO,KAAK,CAAC,SAAS,MAAe,CAAC;;AAGxC,QAAI,OAAO,SAAS,GAAG;AACtB,SAAI,OAAO,WAAW,EAAG,OAAM,OAAO,GAAI;KAC1C,MAAM,SAAS,SAAS,KAAM;AAC9B,WAAM,IAAI,eACT,OAAO,KAAK,GAAG,OAAO,EAAE,EACxB,MAAM,cAAc,OAAO;;OAE1B,OACA,KAAK,CAAC,SAAS,WAAW;AAC1B,aAAO,WAAW,QAAQ,aAAa,MAAM;OAC5C,CACD,KAAK,OAAO,GACb;;;;EAIJ"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
//#region src/client/suite/resolve_subject.ts
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the subject element of an auto snapshot from a CSS selector.
|
|
4
|
+
*
|
|
5
|
+
* When the selector matches multiple elements,
|
|
6
|
+
* the first visible one is used,
|
|
7
|
+
* so hidden matches elsewhere in the document (e.g. Storybook's own UI) do not shadow the rendered subject.
|
|
8
|
+
*
|
|
9
|
+
* If the selector is not provided or matches nothing, `document.body` is used.
|
|
10
|
+
* If the selector only matches hidden elements, it throws right away instead of letting the screenshot wait until timeout.
|
|
11
|
+
*/
|
|
12
|
+
function resolveSubject(selector) {
|
|
13
|
+
if (!selector) return document.body;
|
|
14
|
+
const matches = Array.from(document.querySelectorAll(selector));
|
|
15
|
+
if (matches.length === 0) return document.body;
|
|
16
|
+
const visible = matches.find(isVisible);
|
|
17
|
+
if (visible) return visible;
|
|
18
|
+
throw new Error(`Snapshot subject \`${selector}\` is not visible: it matched ${matches.length} element(s), but none of them is visible.`);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Same definition of visibility as Playwright:
|
|
22
|
+
* the element has a non-empty bounding box and does not have `visibility: hidden`.
|
|
23
|
+
*
|
|
24
|
+
* @see https://playwright.dev/docs/actionability#visible
|
|
25
|
+
*/
|
|
26
|
+
function isVisible(element) {
|
|
27
|
+
const rect = element.getBoundingClientRect();
|
|
28
|
+
if (rect.width === 0 || rect.height === 0) return false;
|
|
29
|
+
return getComputedStyle(element).visibility !== "hidden";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
export { resolveSubject };
|
|
34
|
+
//# sourceMappingURL=resolve_subject.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve_subject.mjs","names":[],"sources":["../../../src/client/suite/resolve_subject.ts"],"sourcesContent":["/**\n * Resolves the subject element of an auto snapshot from a CSS selector.\n *\n * When the selector matches multiple elements,\n * the first visible one is used,\n * so hidden matches elsewhere in the document (e.g. Storybook's own UI) do not shadow the rendered subject.\n *\n * If the selector is not provided or matches nothing, `document.body` is used.\n * If the selector only matches hidden elements, it throws right away instead of letting the screenshot wait until timeout.\n */\nexport function resolveSubject(selector: string | undefined): Element {\n\tif (!selector) return document.body\n\tconst matches = Array.from(document.querySelectorAll(selector))\n\tif (matches.length === 0) return document.body\n\tconst visible = matches.find(isVisible)\n\tif (visible) return visible\n\tthrow new Error(\n\t\t`Snapshot subject \\`${selector}\\` is not visible: it matched ${matches.length} element(s), but none of them is visible.`,\n\t)\n}\n\n/**\n * Same definition of visibility as Playwright:\n * the element has a non-empty bounding box and does not have `visibility: hidden`.\n *\n * @see https://playwright.dev/docs/actionability#visible\n */\nfunction isVisible(element: Element) {\n\tconst rect = element.getBoundingClientRect()\n\tif (rect.width === 0 || rect.height === 0) return false\n\treturn getComputedStyle(element).visibility !== 'hidden'\n}\n"],"mappings":";;;;;;;;;;;AAUA,SAAgB,eAAe,UAAuC;AACrE,KAAI,CAAC,SAAU,QAAO,SAAS;CAC/B,MAAM,UAAU,MAAM,KAAK,SAAS,iBAAiB,SAAS,CAAC;AAC/D,KAAI,QAAQ,WAAW,EAAG,QAAO,SAAS;CAC1C,MAAM,UAAU,QAAQ,KAAK,UAAU;AACvC,KAAI,QAAS,QAAO;AACpB,OAAM,IAAI,MACT,sBAAsB,SAAS,gCAAgC,QAAQ,OAAO,2CAC9E;;;;;;;;AASF,SAAS,UAAU,SAAkB;CACpC,MAAM,OAAO,QAAQ,uBAAuB;AAC5C,KAAI,KAAK,UAAU,KAAK,KAAK,WAAW,EAAG,QAAO;AAClD,QAAO,iBAAiB,QAAQ,CAAC,eAAe"}
|
|
@@ -3,12 +3,19 @@ function toTaskId(task) {
|
|
|
3
3
|
const l = [];
|
|
4
4
|
let t = task;
|
|
5
5
|
while (t?.suite) {
|
|
6
|
-
l.unshift(t.suite.name
|
|
6
|
+
l.unshift(toTaskIdSegment(t.suite.name));
|
|
7
7
|
t = t.suite;
|
|
8
8
|
}
|
|
9
|
-
l.push(task.name
|
|
9
|
+
l.push(toTaskIdSegment(task.name));
|
|
10
10
|
return l.join("/");
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Leading and trailing whitespace is trimmed so it does not leak into the snapshot path.
|
|
14
|
+
* e.g. Storybook appends two spaces to the `describe` title it generates for CSF Next `story.test()`.
|
|
15
|
+
*/
|
|
16
|
+
function toTaskIdSegment(name) {
|
|
17
|
+
return name.trim().replace(/[^a-z0-9]/gi, "-").toLowerCase();
|
|
18
|
+
}
|
|
12
19
|
|
|
13
20
|
//#endregion
|
|
14
21
|
export { toTaskId };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task_id.mjs","names":[],"sources":["../../../src/client/task/task_id.ts"],"sourcesContent":["type Task = {\n\tname: string\n\tsuite?: Task | undefined\n}\n\nexport function toTaskId(task: Task) {\n\tconst l: any[] = []\n\tlet t = task\n\twhile (t?.suite) {\n\t\tl.unshift(t.suite.name
|
|
1
|
+
{"version":3,"file":"task_id.mjs","names":[],"sources":["../../../src/client/task/task_id.ts"],"sourcesContent":["type Task = {\n\tname: string\n\tsuite?: Task | undefined\n}\n\nexport function toTaskId(task: Task) {\n\tconst l: any[] = []\n\tlet t = task\n\twhile (t?.suite) {\n\t\tl.unshift(toTaskIdSegment(t.suite.name))\n\t\tt = t.suite\n\t}\n\n\tl.push(toTaskIdSegment(task.name))\n\treturn l.join('/')\n}\n\n/**\n * Leading and trailing whitespace is trimmed so it does not leak into the snapshot path.\n * e.g. Storybook appends two spaces to the `describe` title it generates for CSF Next `story.test()`.\n */\nfunction toTaskIdSegment(name: string) {\n\treturn name\n\t\t.trim()\n\t\t.replace(/[^a-z0-9]/gi, '-')\n\t\t.toLowerCase()\n}\n"],"mappings":";AAKA,SAAgB,SAAS,MAAY;CACpC,MAAM,IAAW,EAAE;CACnB,IAAI,IAAI;AACR,QAAO,GAAG,OAAO;AAChB,IAAE,QAAQ,gBAAgB,EAAE,MAAM,KAAK,CAAC;AACxC,MAAI,EAAE;;AAGP,GAAE,KAAK,gBAAgB,KAAK,KAAK,CAAC;AAClC,QAAO,EAAE,KAAK,IAAI;;;;;;AAOnB,SAAS,gBAAgB,MAAc;AACtC,QAAO,KACL,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,aAAa"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot_path.d.mts","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"snapshot_path.d.mts","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":[],"mappings":";;;;;;AAgCA;;;;;;;iBAAgB,kBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vis_options.d.mts","names":[],"sources":["../../src/server/vis_options.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vis_options.d.mts","names":[],"sources":["../../src/server/vis_options.ts"],"sourcesContent":[],"mappings":";;;;;;;;;AA6BmE,iBAAnD,YAAA,CAAmD,OAAA,EAA7B,6BAA6B,CAAA,EAAA;;wBAAA,QAAA,CAAA,OAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vis_server_context.types.d.mts","names":[],"sources":["../../src/server/vis_server_context.types.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"vis_server_context.types.d.mts","names":[],"sources":["../../src/server/vis_server_context.types.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;KA6CK,cAAc,KAAK,SAAS,mBACpB,IAAI,gBAAgB,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE;KAGzC,6BAAA;QACL;WACG;UACD;YACE,YAAY;YACZ;WAED,MACR;;;;;;;;;;YAWS"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ImageSnapshotComparisonInfo, MatchImageSnapshotOptions } from "./types.mjs";
|
|
2
2
|
import { VisOptions } from "../config/types.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { ObjectPlus } from "type-plus";
|
|
4
4
|
|
|
5
5
|
//#region src/shared/commands.types.d.ts
|
|
6
6
|
interface PrepareImageSnapshotComparisonCommand {
|
|
@@ -17,7 +17,7 @@ interface SetupVisSuiteCommand {
|
|
|
17
17
|
*
|
|
18
18
|
* In subsequent runs, it will only remove the results and diffs directory of the current suite.
|
|
19
19
|
*/
|
|
20
|
-
setupVisSuite: () => Promise<Pick<VisOptions, 'comparisonMethod' | 'diffOptions' | 'failureThreshold' | 'failureThresholdType' | 'snapshotKey' | 'subject' | 'timeout'>>;
|
|
20
|
+
setupVisSuite: () => Promise<ObjectPlus.Pick<VisOptions, 'comparisonMethod' | 'diffOptions' | 'failureThreshold' | 'failureThresholdType' | 'snapshotKey' | 'subject' | 'timeout'>>;
|
|
21
21
|
}
|
|
22
22
|
interface LoadImageSnapshotResultsCommand {
|
|
23
23
|
loadImageSnapshotResults(taskId: string): Promise<ImageSnapshotResult[]>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.types.d.mts","names":[],"sources":["../../src/shared/commands.types.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB,qCAAA;8EAIL,0CACN,QAAQ;AALd;AAIY,UAIK,yCAAA,CAJL;EACE,kCAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAMF,yBANE,GAAA,SAAA,EAAA,GAOR,OAPQ,CAOA,2BAPA,CAAA;;AAAD,UAUI,oBAAA,CAVJ;EAGI;;;;;AAOjB;;EASE,
|
|
1
|
+
{"version":3,"file":"commands.types.d.mts","names":[],"sources":["../../src/shared/commands.types.ts"],"sourcesContent":[],"mappings":";;;;;UAIiB,qCAAA;8EAIL,0CACN,QAAQ;AALd;AAIY,UAIK,yCAAA,CAJL;EACE,kCAAA,EAAA,CAAA,MAAA,EAAA,MAAA,EAAA,OAAA,CAAA,EAMF,yBANE,GAAA,SAAA,EAAA,GAOR,OAPQ,CAOA,2BAPA,CAAA;;AAAD,UAUI,oBAAA,CAVJ;EAGI;;;;;AAOjB;;EASE,aAAW,EAAA,GAAA,GADS,OACT,CAAX,UAAA,CAAW,IAAA,CACV,UADU,EAAA,kBAAA,GAAA,aAAA,GAAA,kBAAA,GAAA,sBAAA,GAAA,aAAA,GAAA,SAAA,GAAA,SAAA,CAAA,CAAA;;AADgB,UAcZ,+BAAA,CAdY;EAcZ,wBAAA,CAAA,MAAA,EAAA,MAA+B,CAAA,EACL,OAAQ,CAAA,mBAAR,EAAA,CAAA;AAG3C;AAgBiB,UAhBA,mBAAA,CAgBuB;EAIvB,QAAA,EAAA,MAAA;;;;;;;;;;;;;;UAJA,uBAAA;qEACmD;;UAGnD,6BAAA;;;;0CAIwB"}
|
package/dist/shared/types.d.mts
CHANGED
|
@@ -68,8 +68,10 @@ type ImageSnapshotSubjectOptions = {
|
|
|
68
68
|
/**
|
|
69
69
|
* Specify the query of the subject element.
|
|
70
70
|
*
|
|
71
|
+
* If the query matches multiple elements, the first visible one is used.
|
|
71
72
|
* If the test does not have an element with the specified query,
|
|
72
73
|
* the `body` element will be used.
|
|
74
|
+
* If the query only matches hidden elements, the snapshot fails right away.
|
|
73
75
|
*/
|
|
74
76
|
subject?: string | undefined;
|
|
75
77
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/shared/types.ts"],"sourcesContent":[],"mappings":";;;;;;KAKY,eAAe,IAAI,YAAY;UAE1B,2BAAA;EAFL;;;;;EAEK,OAAA,CAAA,EAAA,MAAA,GAAA,SAAA;AASjB;AAOY,UAPK,uBAAA,CAOW;EAEhB;AA6BZ;;EAKuB,WAAA,CAAA,EAAA,MAAA,GAAA,SAAA;;AAAD,KApCV,gBAAA,GAoCU,OAAA,GAAA,MAAA;AAKV,KAvCA,uBAAA,GAuCsB;EAQtB;AAEZ;;;;EACG,oBAAA,CAAA,EAAA,OAAA,GAAA,SAAA,GAAA,SAAA;EACuB;;;;AAG1B;
|
|
1
|
+
{"version":3,"file":"types.d.mts","names":[],"sources":["../../src/shared/types.ts"],"sourcesContent":[],"mappings":";;;;;;KAKY,eAAe,IAAI,YAAY;UAE1B,2BAAA;EAFL;;;;;EAEK,OAAA,CAAA,EAAA,MAAA,GAAA,SAAA;AASjB;AAOY,UAPK,uBAAA,CAOW;EAEhB;AA6BZ;;EAKuB,WAAA,CAAA,EAAA,MAAA,GAAA,SAAA;;AAAD,KApCV,gBAAA,GAoCU,OAAA,GAAA,MAAA;AAKV,KAvCA,uBAAA,GAuCsB;EAQtB;AAEZ;;;;EACG,oBAAA,CAAA,EAAA,OAAA,GAAA,SAAA,GAAA,SAAA;EACuB;;;;AAG1B;AAWA;;;;;;;AAmBA;AAIA;;;;;;EAGY,gBAAA,CAAA,EAAgB,MAAA,GAAA,SAAA;CAEJ;AAAb,KAhEC,qBAgED,CAAA,IAAA,MAAA,CAAA,GAAA;EAAR,gBAAA,EA/DgB,CA+DhB;EAAI;AAIP;;EA+BC,WAAA,CAAA,EA9Fc,OA8Fd,CA9FsB,eA8FtB,CAAA,GAAA,SAAA;CACC;AAA8C,KA1FpC,sBA0FoC,CAAA,IAAA,OAAA,CAAA,GAAA;EAEpC,gBAAA,CAAA,EA3FQ,CA2FR,GAAA,SAAyB;EAAG;;;EAEhB,WAAA,CAAA,EAzFT,gBAyFS,GAAA,SAAA;AAOxB,CAAA;AAAuC,KA7F3B,gBAAA,GAAmB,UA6FQ,CAAA,OA7FU,UA6FV,CAAA,CAAA,CAAA,CAAA;AAGvB,KA9FJ,2BA8FI,CAAA,UA9FkC,gBA8FlC,GAAA,OAAA,CAAA,GAAA,CA9FiE,CA8FjE,SAAA,MAAA,GA7Fb,qBA6Fa,CA7FS,CA6FT,CAAA,GA5Fb,sBA4Fa,CA5FU,CA4FV,CAAA,CAAA,GA3Ff,uBA2Fe;AAA4B,KAzFhC,2BAAA,GAyFgC;EAClB;;;;;;;;EAME,OAAA,CAAA,EAAA,MAAA,GAAA,SAAA;CAAb;AAAkB,KArFrB,2BAqFqB,CAAA,UArFiB,gBAqFjB,GAAA,OAAA,CAAA,GArF+C,2BAqF/C,GApFhC,uBAoFgC,GAnFhC,2BAmFgC,CAnFJ,CAmFI,CAAA,GAAA;EAAI;;;;EAJ3B,YAAA,CAAA,EAAA,OAAA,GAAA,SAAA;;;;;;;;;;;UA9DO,wBAAA;;;KAIL,0CAA0C,8BAA8B,4BAA4B,KAC/G;KAEW,gBAAA;;KAET,IAAA,IAAQ,aAAa;;;KAIZ,2BAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8BR,8BACH,2BACC,wBAAwB;KAEd,yBAAA,GAA4B,8BACvC,mCACA;;;;;;KAOW,2BAA2B;+BAGvB,4BAA4B,kDAC/B,aAAa,KAAK,IAAI,OAC1B,mBAAmB,kCACxB,qCAGc,4BAA4B,kDAC/B,aAAa,KAAK,IAAI,OAC1B,mBAAmB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vitest-plugin-vis",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.7",
|
|
4
4
|
"description": "Vitest visual testing plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image-snapshot",
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"pngjs": "^7.0.0",
|
|
58
58
|
"rimraf": "^6.1.3",
|
|
59
59
|
"ssim.js": "^3.5.0",
|
|
60
|
-
"type-plus": "8.0.0-beta.
|
|
60
|
+
"type-plus": "8.0.0-beta.12"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@repobuddy/storybook": "^2.
|
|
64
|
-
"@repobuddy/typescript": "^2.
|
|
63
|
+
"@repobuddy/storybook": "^2.33.1",
|
|
64
|
+
"@repobuddy/typescript": "^2.2.0",
|
|
65
65
|
"@storybook-community/storybook-dark-mode": "^7.1.3",
|
|
66
66
|
"@storybook/addon-docs": "^10.5.10",
|
|
67
67
|
"@storybook/addon-vitest": "^10.5.10",
|
|
@@ -54,6 +54,8 @@ setAutoSnapshotOptions(false)
|
|
|
54
54
|
|
|
55
55
|
#### `subject` (string)
|
|
56
56
|
Specifies the CSS selector for the element to snapshot. If not provided or element not found, defaults to `document.body`.
|
|
57
|
+
If the selector matches multiple elements, the first visible one is used.
|
|
58
|
+
If it only matches hidden elements, the snapshot fails right away with a "not visible" error instead of waiting until the timeout.
|
|
57
59
|
|
|
58
60
|
```ts
|
|
59
61
|
// Snapshot a specific element
|
|
@@ -6,6 +6,7 @@ import type { ComparisonMethod, SetupVisOptions } from '../../shared/types.ts'
|
|
|
6
6
|
import { shouldTakeSnapshot } from '../snapshot/should_take_snapshot.ts'
|
|
7
7
|
import { toTaskId } from '../task/task_id.ts'
|
|
8
8
|
import { ctx } from './_ctx.ts'
|
|
9
|
+
import { resolveSubject } from './resolve_subject.ts'
|
|
9
10
|
|
|
10
11
|
export function autoSnapshotMatcher<GM extends Record<string, any> | unknown = unknown>(
|
|
11
12
|
commands: SetupVisSuiteCommand,
|
|
@@ -40,7 +41,7 @@ export function autoSnapshotMatcher<GM extends Record<string, any> | unknown = u
|
|
|
40
41
|
meta != null && Object.hasOwn(meta as object, 'createMissingBaseline')
|
|
41
42
|
? (meta as { createMissingBaseline?: boolean }).createMissingBaseline
|
|
42
43
|
: suiteDefaults?.createMissingBaseline
|
|
43
|
-
await expect(
|
|
44
|
+
await expect(resolveSubject(meta?.subject ?? subject)).toMatchImageSnapshot({
|
|
44
45
|
...meta,
|
|
45
46
|
snapshotKey: meta?.snapshotKey ?? themeId,
|
|
46
47
|
...(effectiveCreateMissingBaseline !== undefined
|
|
@@ -69,7 +70,3 @@ export function autoSnapshotMatcher<GM extends Record<string, any> | unknown = u
|
|
|
69
70
|
},
|
|
70
71
|
}
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
-
function getSubject(selectors: string | undefined) {
|
|
74
|
-
return selectors ? (document.querySelector(selectors) ?? document.body) : document.body
|
|
75
|
-
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the subject element of an auto snapshot from a CSS selector.
|
|
3
|
+
*
|
|
4
|
+
* When the selector matches multiple elements,
|
|
5
|
+
* the first visible one is used,
|
|
6
|
+
* so hidden matches elsewhere in the document (e.g. Storybook's own UI) do not shadow the rendered subject.
|
|
7
|
+
*
|
|
8
|
+
* If the selector is not provided or matches nothing, `document.body` is used.
|
|
9
|
+
* If the selector only matches hidden elements, it throws right away instead of letting the screenshot wait until timeout.
|
|
10
|
+
*/
|
|
11
|
+
export function resolveSubject(selector: string | undefined): Element {
|
|
12
|
+
if (!selector) return document.body
|
|
13
|
+
const matches = Array.from(document.querySelectorAll(selector))
|
|
14
|
+
if (matches.length === 0) return document.body
|
|
15
|
+
const visible = matches.find(isVisible)
|
|
16
|
+
if (visible) return visible
|
|
17
|
+
throw new Error(
|
|
18
|
+
`Snapshot subject \`${selector}\` is not visible: it matched ${matches.length} element(s), but none of them is visible.`,
|
|
19
|
+
)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Same definition of visibility as Playwright:
|
|
24
|
+
* the element has a non-empty bounding box and does not have `visibility: hidden`.
|
|
25
|
+
*
|
|
26
|
+
* @see https://playwright.dev/docs/actionability#visible
|
|
27
|
+
*/
|
|
28
|
+
function isVisible(element: Element) {
|
|
29
|
+
const rect = element.getBoundingClientRect()
|
|
30
|
+
if (rect.width === 0 || rect.height === 0) return false
|
|
31
|
+
return getComputedStyle(element).visibility !== 'hidden'
|
|
32
|
+
}
|
|
@@ -7,10 +7,21 @@ export function toTaskId(task: Task) {
|
|
|
7
7
|
const l: any[] = []
|
|
8
8
|
let t = task
|
|
9
9
|
while (t?.suite) {
|
|
10
|
-
l.unshift(t.suite.name
|
|
10
|
+
l.unshift(toTaskIdSegment(t.suite.name))
|
|
11
11
|
t = t.suite
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
l.push(task.name
|
|
14
|
+
l.push(toTaskIdSegment(task.name))
|
|
15
15
|
return l.join('/')
|
|
16
16
|
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Leading and trailing whitespace is trimmed so it does not leak into the snapshot path.
|
|
20
|
+
* e.g. Storybook appends two spaces to the `describe` title it generates for CSF Next `story.test()`.
|
|
21
|
+
*/
|
|
22
|
+
function toTaskIdSegment(name: string) {
|
|
23
|
+
return name
|
|
24
|
+
.trim()
|
|
25
|
+
.replace(/[^a-z0-9]/gi, '-')
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ObjectPlus } from 'type-plus'
|
|
2
2
|
import type { VisOptions } from '../config/types.ts'
|
|
3
3
|
import type { ImageSnapshotComparisonInfo, MatchImageSnapshotOptions } from './types.ts'
|
|
4
4
|
|
|
@@ -26,7 +26,7 @@ export interface SetupVisSuiteCommand {
|
|
|
26
26
|
* In subsequent runs, it will only remove the results and diffs directory of the current suite.
|
|
27
27
|
*/
|
|
28
28
|
setupVisSuite: () => Promise<
|
|
29
|
-
Pick<
|
|
29
|
+
ObjectPlus.Pick<
|
|
30
30
|
VisOptions,
|
|
31
31
|
| 'comparisonMethod'
|
|
32
32
|
| 'diffOptions'
|
package/src/shared/types.ts
CHANGED
|
@@ -81,8 +81,10 @@ export type ImageSnapshotSubjectOptions = {
|
|
|
81
81
|
/**
|
|
82
82
|
* Specify the query of the subject element.
|
|
83
83
|
*
|
|
84
|
+
* If the query matches multiple elements, the first visible one is used.
|
|
84
85
|
* If the test does not have an element with the specified query,
|
|
85
86
|
* the `body` element will be used.
|
|
87
|
+
* If the query only matches hidden elements, the snapshot fails right away.
|
|
86
88
|
*/
|
|
87
89
|
subject?: string | undefined
|
|
88
90
|
}
|