vitest-plugin-vis 5.1.0 → 5.1.2

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 (29) hide show
  1. package/dist/client/external/vitest/vitest_suite_proxy.d.mts +1 -0
  2. package/dist/client/page/_ctx.mjs.map +1 -1
  3. package/dist/client/snapshot/compare_image_snapshot.logic.mjs +16 -1
  4. package/dist/client/snapshot/compare_image_snapshot.logic.mjs.map +1 -1
  5. package/dist/client/snapshot/compare_image_snapshot.mjs +7 -7
  6. package/dist/client/snapshot/compare_image_snapshot.mjs.map +1 -1
  7. package/dist/client/suite/auto_snapshot_matcher.mjs +1 -1
  8. package/dist/client/suite/auto_snapshot_matcher.mjs.map +1 -1
  9. package/dist/server/commands/_assertions.mjs +1 -1
  10. package/dist/server/externals/file.mjs +1 -1
  11. package/dist/server/externals/file.mjs.map +1 -1
  12. package/dist/server/snapshot_path.d.mts +1 -0
  13. package/dist/server/snapshot_path.d.mts.map +1 -1
  14. package/dist/server/snapshot_path.mjs +1 -1
  15. package/dist/server/snapshot_path.mjs.map +1 -1
  16. package/dist/server/testing/stubSuite.mjs.map +1 -1
  17. package/dist/server/vis_options.d.mts +3 -0
  18. package/dist/server/vis_options.d.mts.map +1 -1
  19. package/dist/server/vis_server_context.types.d.mts +9 -1
  20. package/dist/server/vis_server_context.types.d.mts.map +1 -1
  21. package/package.json +32 -32
  22. package/src/client/page/_ctx.ts +1 -1
  23. package/src/client/snapshot/compare_image_snapshot.logic.ts +17 -0
  24. package/src/client/snapshot/compare_image_snapshot.ts +7 -7
  25. package/src/client/suite/auto_snapshot_matcher.ts +1 -1
  26. package/src/server/externals/file.ts +2 -2
  27. package/src/server/snapshot_path.ts +1 -1
  28. package/src/server/testing/stubSuite.ts +1 -1
  29. package/src/server/vis_server_context.types.ts +11 -1
@@ -1 +1,2 @@
1
+ import "../../../shared/types.mjs";
1
2
  import "vitest";
@@ -1 +1 @@
1
- {"version":3,"file":"_ctx.mjs","names":[],"sources":["../../../src/client/page/_ctx.ts"],"sourcesContent":["import { commands, type BrowserCommands, type BrowserPage } from 'vitest/browser'\nimport { getCurrentTest } from '../external/vitest/vitest_suite_proxy.ts'\n\nexport type { BrowserPage }\n\nexport const ctx = {\n\tcommands: commands as BrowserCommands,\n\tgetCurrentTest,\n\t__test__reset() {\n\t\tctx.getCurrentTest = getCurrentTest\n\t},\n}\n"],"mappings":";;;;AAKA,MAAa,MAAM;CACR;CACV;CACA,gBAAgB;AACf,MAAI,iBAAiB;;CAEtB"}
1
+ {"version":3,"file":"_ctx.mjs","names":[],"sources":["../../../src/client/page/_ctx.ts"],"sourcesContent":["import { type BrowserCommands, type BrowserPage, commands } from 'vitest/browser'\nimport { getCurrentTest } from '../external/vitest/vitest_suite_proxy.ts'\n\nexport type { BrowserPage }\n\nexport const ctx = {\n\tcommands: commands as BrowserCommands,\n\tgetCurrentTest,\n\t__test__reset() {\n\t\tctx.getCurrentTest = getCurrentTest\n\t},\n}\n"],"mappings":";;;;AAKA,MAAa,MAAM;CACR;CACV;CACA,gBAAgB;AACf,MAAI,iBAAiB;;CAEtB"}
@@ -1,3 +1,5 @@
1
+ import { isAbsolute, relative, resolve } from "pathe";
2
+
1
3
  //#region src/client/snapshot/compare_image_snapshot.logic.ts
2
4
  function prettifyOptions(options) {
3
5
  if (!options) return "none";
@@ -9,7 +11,20 @@ function prettifyOptions(options) {
9
11
  options.createMissingBaseline ? "createMissingBaseline: true" : ""
10
12
  ].filter(Boolean).join("\n ");
11
13
  }
14
+ /**
15
+ * Formats a snapshot path for display in a failure message.
16
+ *
17
+ * Paths within the project root are shown relative to it so that the failure output can be shared
18
+ * verbatim without leaking the local absolute path, while remaining clickable in editors and terminals.
19
+ * Paths outside the project root stay absolute, as a `../../..` chain is neither shorter nor clearer.
20
+ */
21
+ function formatSnapshotPath(projectRoot, path) {
22
+ const absolutePath = resolve(projectRoot, path);
23
+ const relativePath = relative(projectRoot, absolutePath);
24
+ if (!relativePath || relativePath === ".." || relativePath.startsWith("../") || isAbsolute(relativePath)) return absolutePath;
25
+ return `./${relativePath}`;
26
+ }
12
27
 
13
28
  //#endregion
14
- export { prettifyOptions };
29
+ export { formatSnapshotPath, prettifyOptions };
15
30
  //# sourceMappingURL=compare_image_snapshot.logic.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"compare_image_snapshot.logic.mjs","names":[],"sources":["../../../src/client/snapshot/compare_image_snapshot.logic.ts"],"sourcesContent":["import type { ToMatchImageSnapshotOptions } from '../../shared/types.ts'\n\nexport function prettifyOptions(options: ToMatchImageSnapshotOptions<any> | undefined) {\n\tif (!options) return 'none'\n\n\treturn [\n\t\t`failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? 'pixels'}`,\n\t\toptions.timeout ? `timeout: ${options.timeout} ms` : '',\n\t\t`comparisonMethod: ${options.comparisonMethod ?? 'pixel'}`,\n\t\toptions.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',\n\t\toptions.createMissingBaseline ? 'createMissingBaseline: true' : '',\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n ')\n}\n"],"mappings":";AAEA,SAAgB,gBAAgB,SAAuD;AACtF,KAAI,CAAC,QAAS,QAAO;AAErB,QAAO;EACN,qBAAqB,QAAQ,oBAAoB,EAAE,GAAG,QAAQ,wBAAwB;EACtF,QAAQ,UAAU,YAAY,QAAQ,QAAQ,OAAO;EACrD,qBAAqB,QAAQ,oBAAoB;EACjD,QAAQ,cAAc,gBAAgB,KAAK,UAAU,QAAQ,YAAY,KAAK;EAC9E,QAAQ,wBAAwB,gCAAgC;EAChE,CACC,OAAO,QAAQ,CACf,KAAK,qBAAqB"}
1
+ {"version":3,"file":"compare_image_snapshot.logic.mjs","names":[],"sources":["../../../src/client/snapshot/compare_image_snapshot.logic.ts"],"sourcesContent":["import { isAbsolute, relative, resolve } from 'pathe'\nimport type { ToMatchImageSnapshotOptions } from '../../shared/types.ts'\n\nexport function prettifyOptions(options: ToMatchImageSnapshotOptions<any> | undefined) {\n\tif (!options) return 'none'\n\n\treturn [\n\t\t`failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? 'pixels'}`,\n\t\toptions.timeout ? `timeout: ${options.timeout} ms` : '',\n\t\t`comparisonMethod: ${options.comparisonMethod ?? 'pixel'}`,\n\t\toptions.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',\n\t\toptions.createMissingBaseline ? 'createMissingBaseline: true' : '',\n\t]\n\t\t.filter(Boolean)\n\t\t.join('\\n ')\n}\n\n/**\n * Formats a snapshot path for display in a failure message.\n *\n * Paths within the project root are shown relative to it so that the failure output can be shared\n * verbatim without leaking the local absolute path, while remaining clickable in editors and terminals.\n * Paths outside the project root stay absolute, as a `../../..` chain is neither shorter nor clearer.\n */\nexport function formatSnapshotPath(projectRoot: string, path: string) {\n\tconst absolutePath = resolve(projectRoot, path)\n\tconst relativePath = relative(projectRoot, absolutePath)\n\tif (!relativePath || relativePath === '..' || relativePath.startsWith('../') || isAbsolute(relativePath)) {\n\t\treturn absolutePath\n\t}\n\treturn `./${relativePath}`\n}\n"],"mappings":";;;AAGA,SAAgB,gBAAgB,SAAuD;AACtF,KAAI,CAAC,QAAS,QAAO;AAErB,QAAO;EACN,qBAAqB,QAAQ,oBAAoB,EAAE,GAAG,QAAQ,wBAAwB;EACtF,QAAQ,UAAU,YAAY,QAAQ,QAAQ,OAAO;EACrD,qBAAqB,QAAQ,oBAAoB;EACjD,QAAQ,cAAc,gBAAgB,KAAK,UAAU,QAAQ,YAAY,KAAK;EAC9E,QAAQ,wBAAwB,gCAAgC;EAChE,CACC,OAAO,QAAQ,CACf,KAAK,qBAAqB;;;;;;;;;AAU7B,SAAgB,mBAAmB,aAAqB,MAAc;CACrE,MAAM,eAAe,QAAQ,aAAa,KAAK;CAC/C,MAAM,eAAe,SAAS,aAAa,aAAa;AACxD,KAAI,CAAC,gBAAgB,iBAAiB,QAAQ,aAAa,WAAW,MAAM,IAAI,WAAW,aAAa,CACvG,QAAO;AAER,QAAO,KAAK"}
@@ -2,7 +2,7 @@ import { compareImage } from "../../shared/compare_image.mjs";
2
2
  import { toDataURL, toImageData } from "../external/browser/image_data.mjs";
3
3
  import { server } from "../external/vitest/vitest_browser_context_proxy.mjs";
4
4
  import { alignImagesToSameSize } from "../image/align_images.mjs";
5
- import { prettifyOptions } from "./compare_image_snapshot.logic.mjs";
5
+ import { formatSnapshotPath, prettifyOptions } from "./compare_image_snapshot.logic.mjs";
6
6
  import { resolve } from "pathe";
7
7
  import dedent from "dedent";
8
8
 
@@ -21,7 +21,7 @@ async function compareImageSnapshot(commands, taskId, info, options) {
21
21
  throw new Error(dedent`Snapshot \`${taskId}\` has no baseline image. Please review the new snapshot and update the baseline image.
22
22
 
23
23
  Options: ${prettifyOptions(options)}
24
- Actual: ${resolve(info.projectRoot, info.resultPath)}`);
24
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`);
25
25
  }
26
26
  const baselineImage = await toImageData(info.baseline);
27
27
  const [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage);
@@ -34,8 +34,8 @@ async function compareImageSnapshot(commands, taskId, info, options) {
34
34
  Options: ${prettifyOptions(options)}
35
35
  Diff: ${options.failureThresholdType === "percent" ? `${diffAmount}%` : `${diffAmount} pixels`}
36
36
 
37
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
38
- Actual: ${resolve(info.projectRoot, info.resultPath)}`);
37
+ Expected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}
38
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`);
39
39
  return;
40
40
  }
41
41
  if (server.config.snapshotOptions.updateSnapshot === "all" && !options?.expectToFail) {
@@ -49,9 +49,9 @@ async function compareImageSnapshot(commands, taskId, info, options) {
49
49
 
50
50
  Options: ${prettifyOptions(options)}
51
51
 
52
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
53
- Actual: ${resolve(info.projectRoot, info.resultPath)}
54
- Difference: ${resolve(info.projectRoot, info.diffPath)}`);
52
+ Expected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}
53
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}
54
+ Difference: ${formatSnapshotPath(info.projectRoot, info.diffPath)}`);
55
55
  }
56
56
  async function writeSnapshot(commands, path, image) {
57
57
  const content = (await toDataURL(image)).split(",")[1];
@@ -1 +1 @@
1
- {"version":3,"file":"compare_image_snapshot.mjs","names":[],"sources":["../../../src/client/snapshot/compare_image_snapshot.ts"],"sourcesContent":["import dedent from 'dedent'\nimport { resolve } from 'pathe'\nimport type { BrowserCommands } from 'vitest/browser'\nimport type {\n\tImageSnapshotNextIndexCommand,\n\tPrepareImageSnapshotComparisonCommand,\n} from '../../shared/commands.types.ts'\nimport { compareImage } from '../../shared/compare_image.ts'\nimport type { ImageSnapshotComparisonInfo, ToMatchImageSnapshotOptions } from '../../shared/types.ts'\nimport { toDataURL, toImageData } from '../external/browser/image_data.ts'\nimport { server } from '../external/vitest/vitest_browser_context_proxy.ts'\nimport { alignImagesToSameSize } from '../image/align_images.ts'\nimport { prettifyOptions } from './compare_image_snapshot.logic.ts'\n\nexport async function compareImageSnapshot(\n\tcommands: BrowserCommands & PrepareImageSnapshotComparisonCommand & ImageSnapshotNextIndexCommand,\n\ttaskId: string,\n\tinfo: ImageSnapshotComparisonInfo,\n\toptions?: ToMatchImageSnapshotOptions<any>,\n) {\n\toptions = { ...info, ...options } as any\n\n\tconst resultImage = await toImageData(info.result)\n\n\tif (!info.baseline) {\n\t\tif (server.config.snapshotOptions.updateSnapshot === 'all' || options?.createMissingBaseline === true) {\n\t\t\tawait writeSnapshot(commands, resolve(info.projectRoot, info.baselinePath), resultImage)\n\t\t\treturn\n\t\t}\n\t\tthrow new Error(\n\t\t\tdedent`Snapshot \\`${taskId}\\` has no baseline image. Please review the new snapshot and update the baseline image.\n\n\t\t\t\tOptions: ${prettifyOptions(options)}\n\t\t\t\tActual: ${resolve(info.projectRoot, info.resultPath)}`,\n\t\t)\n\t}\n\n\tconst baselineImage = await toImageData(info.baseline)\n\tconst [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage)\n\tconst { width, height } = baselineAlignedImage\n\tconst diffImage = new ImageData(width, height)\n\tconst { pass, diffAmount } = compareImage(\n\t\tbaselineAlignedImage.data,\n\t\tresultAlignedImage.data,\n\t\tdiffImage.data,\n\t\twidth,\n\t\theight,\n\t\toptions as ToMatchImageSnapshotOptions<any>,\n\t)\n\tif (pass) {\n\t\tif (options?.expectToFail) {\n\t\t\tthrow new Error(\n\t\t\t\tdedent`Snapshot \\`${taskId}\\` matched but expected to fail.\n\n\t\t\t\t\t\t\tOptions: ${prettifyOptions(options)}\n\t\t\t\t\t\t\tDiff: ${options.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}\n\n\t\t\t\t\t\t\tExpected: ${resolve(info.projectRoot, info.baselinePath)}\n\t\t\t\t\t\t\tActual: ${resolve(info.projectRoot, info.resultPath)}`,\n\t\t\t)\n\t\t}\n\t\treturn\n\t}\n\tif (server.config.snapshotOptions.updateSnapshot === 'all' && !options?.expectToFail) {\n\t\tawait writeSnapshot(commands, resolve(info.projectRoot, info.baselinePath), resultImage)\n\t\treturn\n\t}\n\n\tawait writeSnapshot(commands, resolve(info.projectRoot, info.diffPath), diffImage)\n\n\tthrow new Error(\n\t\tdedent`Snapshot \\`${taskId}\\` mismatched\n\n\t\t\t\t\t${\n\t\t\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\t\t\t\t\t\t\t\t? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`\n\t\t\t\t\t\t\t\t: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t\t\t: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t\t\t}${\n\t\t\t\t\t\tbaselineImage.width !== resultImage.width || baselineImage.height !== resultImage.height\n\t\t\t\t\t\t\t? `\\nThe image size changed from ${baselineImage.width}x${baselineImage.height} to ${resultImage.width}x${resultImage.height}.`\n\t\t\t\t\t\t\t: ''\n\t\t\t\t\t}\n\n\t\t\t\t\tOptions: ${prettifyOptions(options)}\n\n\t\t\t\t\tExpected: ${resolve(info.projectRoot, info.baselinePath)}\n\t\t\t\t\tActual: ${resolve(info.projectRoot, info.resultPath)}\n\t\t\t\t\tDifference: ${resolve(info.projectRoot, info.diffPath)}`,\n\t)\n}\n\nasync function writeSnapshot(commands: BrowserCommands, path: string, image: ImageData) {\n\tconst content = (await toDataURL(image)).split(',')[1]!\n\treturn commands.writeFile(path, content, { encoding: 'base64' })\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,qBACrB,UACA,QACA,MACA,SACC;AACD,WAAU;EAAE,GAAG;EAAM,GAAG;EAAS;CAEjC,MAAM,cAAc,MAAM,YAAY,KAAK,OAAO;AAElD,KAAI,CAAC,KAAK,UAAU;AACnB,MAAI,OAAO,OAAO,gBAAgB,mBAAmB,SAAS,SAAS,0BAA0B,MAAM;AACtG,SAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,aAAa,EAAE,YAAY;AACxF;;AAED,QAAM,IAAI,MACT,MAAM,cAAc,OAAO;;kBAEZ,gBAAgB,QAAQ,CAAC;kBACzB,QAAQ,KAAK,aAAa,KAAK,WAAW,GACzD;;CAGF,MAAM,gBAAgB,MAAM,YAAY,KAAK,SAAS;CACtD,MAAM,CAAC,sBAAsB,sBAAsB,sBAAsB,eAAe,YAAY;CACpG,MAAM,EAAE,OAAO,WAAW;CAC1B,MAAM,YAAY,IAAI,UAAU,OAAO,OAAO;CAC9C,MAAM,EAAE,MAAM,eAAe,aAC5B,qBAAqB,MACrB,mBAAmB,MACnB,UAAU,MACV,OACA,QACA,QACA;AACD,KAAI,MAAM;AACT,MAAI,SAAS,aACZ,OAAM,IAAI,MACT,MAAM,cAAc,OAAO;;qBAEV,gBAAgB,QAAQ,CAAC;qBACzB,QAAQ,yBAAyB,YAAY,GAAG,WAAW,KAAK,GAAG,WAAW,SAAS;;qBAEvF,QAAQ,KAAK,aAAa,KAAK,aAAa,CAAC;qBAC7C,QAAQ,KAAK,aAAa,KAAK,WAAW,GAC3D;AAEF;;AAED,KAAI,OAAO,OAAO,gBAAgB,mBAAmB,SAAS,CAAC,SAAS,cAAc;AACrF,QAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,aAAa,EAAE,YAAY;AACxF;;AAGD,OAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,SAAS,EAAE,UAAU;AAElF,OAAM,IAAI,MACT,MAAM,cAAc,OAAO;;OAGvB,SAAS,mBACN,SAAS,yBAAyB,YACjC,kCAAkC,QAAQ,iBAAiB,sBAAsB,WAAW,MAC5F,kCAAkC,QAAQ,iBAAiB,4BAA4B,WAAW,YACnG,6CAA6C,SAAS,yBAAyB,YAAY,GAAG,WAAW,KAAK,GAAG,WAAW,SAAS,KAExI,cAAc,UAAU,YAAY,SAAS,cAAc,WAAW,YAAY,SAC/E,iCAAiC,cAAc,MAAM,GAAG,cAAc,OAAO,MAAM,YAAY,MAAM,GAAG,YAAY,OAAO,KAC3H,GACH;;mBAEa,gBAAgB,QAAQ,CAAC;;mBAEzB,QAAQ,KAAK,aAAa,KAAK,aAAa,CAAC;mBAC7C,QAAQ,KAAK,aAAa,KAAK,WAAW,CAAC;mBAC3C,QAAQ,KAAK,aAAa,KAAK,SAAS,GACzD;;AAGF,eAAe,cAAc,UAA2B,MAAc,OAAkB;CACvF,MAAM,WAAW,MAAM,UAAU,MAAM,EAAE,MAAM,IAAI,CAAC;AACpD,QAAO,SAAS,UAAU,MAAM,SAAS,EAAE,UAAU,UAAU,CAAC"}
1
+ {"version":3,"file":"compare_image_snapshot.mjs","names":[],"sources":["../../../src/client/snapshot/compare_image_snapshot.ts"],"sourcesContent":["import dedent from 'dedent'\nimport { resolve } from 'pathe'\nimport type { BrowserCommands } from 'vitest/browser'\nimport type {\n\tImageSnapshotNextIndexCommand,\n\tPrepareImageSnapshotComparisonCommand,\n} from '../../shared/commands.types.ts'\nimport { compareImage } from '../../shared/compare_image.ts'\nimport type { ImageSnapshotComparisonInfo, ToMatchImageSnapshotOptions } from '../../shared/types.ts'\nimport { toDataURL, toImageData } from '../external/browser/image_data.ts'\nimport { server } from '../external/vitest/vitest_browser_context_proxy.ts'\nimport { alignImagesToSameSize } from '../image/align_images.ts'\nimport { formatSnapshotPath, prettifyOptions } from './compare_image_snapshot.logic.ts'\n\nexport async function compareImageSnapshot(\n\tcommands: BrowserCommands & PrepareImageSnapshotComparisonCommand & ImageSnapshotNextIndexCommand,\n\ttaskId: string,\n\tinfo: ImageSnapshotComparisonInfo,\n\toptions?: ToMatchImageSnapshotOptions<any>,\n) {\n\toptions = { ...info, ...options } as any\n\n\tconst resultImage = await toImageData(info.result)\n\n\tif (!info.baseline) {\n\t\tif (server.config.snapshotOptions.updateSnapshot === 'all' || options?.createMissingBaseline === true) {\n\t\t\tawait writeSnapshot(commands, resolve(info.projectRoot, info.baselinePath), resultImage)\n\t\t\treturn\n\t\t}\n\t\tthrow new Error(\n\t\t\tdedent`Snapshot \\`${taskId}\\` has no baseline image. Please review the new snapshot and update the baseline image.\n\n\t\t\t\tOptions: ${prettifyOptions(options)}\n\t\t\t\tActual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`,\n\t\t)\n\t}\n\n\tconst baselineImage = await toImageData(info.baseline)\n\tconst [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage)\n\tconst { width, height } = baselineAlignedImage\n\tconst diffImage = new ImageData(width, height)\n\tconst { pass, diffAmount } = compareImage(\n\t\tbaselineAlignedImage.data,\n\t\tresultAlignedImage.data,\n\t\tdiffImage.data,\n\t\twidth,\n\t\theight,\n\t\toptions as ToMatchImageSnapshotOptions<any>,\n\t)\n\tif (pass) {\n\t\tif (options?.expectToFail) {\n\t\t\tthrow new Error(\n\t\t\t\tdedent`Snapshot \\`${taskId}\\` matched but expected to fail.\n\n\t\t\t\t\t\t\tOptions: ${prettifyOptions(options)}\n\t\t\t\t\t\t\tDiff: ${options.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}\n\n\t\t\t\t\t\t\tExpected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}\n\t\t\t\t\t\t\tActual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`,\n\t\t\t)\n\t\t}\n\t\treturn\n\t}\n\tif (server.config.snapshotOptions.updateSnapshot === 'all' && !options?.expectToFail) {\n\t\tawait writeSnapshot(commands, resolve(info.projectRoot, info.baselinePath), resultImage)\n\t\treturn\n\t}\n\n\tawait writeSnapshot(commands, resolve(info.projectRoot, info.diffPath), diffImage)\n\n\tthrow new Error(\n\t\tdedent`Snapshot \\`${taskId}\\` mismatched\n\n\t\t\t\t\t${\n\t\t\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\t\t\t\t\t\t\t\t? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`\n\t\t\t\t\t\t\t\t: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t\t\t: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t\t\t}${\n\t\t\t\t\t\tbaselineImage.width !== resultImage.width || baselineImage.height !== resultImage.height\n\t\t\t\t\t\t\t? `\\nThe image size changed from ${baselineImage.width}x${baselineImage.height} to ${resultImage.width}x${resultImage.height}.`\n\t\t\t\t\t\t\t: ''\n\t\t\t\t\t}\n\n\t\t\t\t\tOptions: ${prettifyOptions(options)}\n\n\t\t\t\t\tExpected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}\n\t\t\t\t\tActual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}\n\t\t\t\t\tDifference: ${formatSnapshotPath(info.projectRoot, info.diffPath)}`,\n\t)\n}\n\nasync function writeSnapshot(commands: BrowserCommands, path: string, image: ImageData) {\n\tconst content = (await toDataURL(image)).split(',')[1]!\n\treturn commands.writeFile(path, content, { encoding: 'base64' })\n}\n"],"mappings":";;;;;;;;;AAcA,eAAsB,qBACrB,UACA,QACA,MACA,SACC;AACD,WAAU;EAAE,GAAG;EAAM,GAAG;EAAS;CAEjC,MAAM,cAAc,MAAM,YAAY,KAAK,OAAO;AAElD,KAAI,CAAC,KAAK,UAAU;AACnB,MAAI,OAAO,OAAO,gBAAgB,mBAAmB,SAAS,SAAS,0BAA0B,MAAM;AACtG,SAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,aAAa,EAAE,YAAY;AACxF;;AAED,QAAM,IAAI,MACT,MAAM,cAAc,OAAO;;kBAEZ,gBAAgB,QAAQ,CAAC;kBACzB,mBAAmB,KAAK,aAAa,KAAK,WAAW,GACpE;;CAGF,MAAM,gBAAgB,MAAM,YAAY,KAAK,SAAS;CACtD,MAAM,CAAC,sBAAsB,sBAAsB,sBAAsB,eAAe,YAAY;CACpG,MAAM,EAAE,OAAO,WAAW;CAC1B,MAAM,YAAY,IAAI,UAAU,OAAO,OAAO;CAC9C,MAAM,EAAE,MAAM,eAAe,aAC5B,qBAAqB,MACrB,mBAAmB,MACnB,UAAU,MACV,OACA,QACA,QACA;AACD,KAAI,MAAM;AACT,MAAI,SAAS,aACZ,OAAM,IAAI,MACT,MAAM,cAAc,OAAO;;qBAEV,gBAAgB,QAAQ,CAAC;qBACzB,QAAQ,yBAAyB,YAAY,GAAG,WAAW,KAAK,GAAG,WAAW,SAAS;;qBAEvF,mBAAmB,KAAK,aAAa,KAAK,aAAa,CAAC;qBACxD,mBAAmB,KAAK,aAAa,KAAK,WAAW,GACtE;AAEF;;AAED,KAAI,OAAO,OAAO,gBAAgB,mBAAmB,SAAS,CAAC,SAAS,cAAc;AACrF,QAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,aAAa,EAAE,YAAY;AACxF;;AAGD,OAAM,cAAc,UAAU,QAAQ,KAAK,aAAa,KAAK,SAAS,EAAE,UAAU;AAElF,OAAM,IAAI,MACT,MAAM,cAAc,OAAO;;OAGvB,SAAS,mBACN,SAAS,yBAAyB,YACjC,kCAAkC,QAAQ,iBAAiB,sBAAsB,WAAW,MAC5F,kCAAkC,QAAQ,iBAAiB,4BAA4B,WAAW,YACnG,6CAA6C,SAAS,yBAAyB,YAAY,GAAG,WAAW,KAAK,GAAG,WAAW,SAAS,KAExI,cAAc,UAAU,YAAY,SAAS,cAAc,WAAW,YAAY,SAC/E,iCAAiC,cAAc,MAAM,GAAG,cAAc,OAAO,MAAM,YAAY,MAAM,GAAG,YAAY,OAAO,KAC3H,GACH;;mBAEa,gBAAgB,QAAQ,CAAC;;mBAEzB,mBAAmB,KAAK,aAAa,KAAK,aAAa,CAAC;mBACxD,mBAAmB,KAAK,aAAa,KAAK,WAAW,CAAC;mBACtD,mBAAmB,KAAK,aAAa,KAAK,SAAS,GACpE;;AAGF,eAAe,cAAc,UAA2B,MAAc,OAAkB;CACvF,MAAM,WAAW,MAAM,UAAU,MAAM,EAAE,MAAM,IAAI,CAAC;AACpD,QAAO,SAAS,UAAU,MAAM,SAAS,EAAE,UAAU,UAAU,CAAC"}
@@ -33,7 +33,7 @@ function autoSnapshotMatcher(commands, expect) {
33
33
  if (errors.length > 0) {
34
34
  if (errors.length === 1) throw errors[0][1];
35
35
  const taskId = toTaskId(test);
36
- throw new AggregateError(errors, dedent`Snapshot \`${taskId}\` mismatched
36
+ throw new AggregateError(errors.map(([, e]) => e), dedent`Snapshot \`${taskId}\` mismatched
37
37
 
38
38
  ${errors.map(([themeId, error]) => {
39
39
  return `Theme \`${themeId}\` failed: ${error.message}`;
@@ -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(getSubject(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,\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\nfunction getSubject(selectors: string | undefined) {\n\treturn selectors ? (document.querySelector(selectors) ?? document.body) : document.body\n}\n"],"mappings":";;;;;;;AASA,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,WAAW,MAAM,WAAW,QAAQ,CAAC,CAAC,qBAAqB;MACvE,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,QACA,MAAM,cAAc,OAAO;;OAE1B,OACA,KAAK,CAAC,SAAS,WAAW;AAC1B,aAAO,WAAW,QAAQ,aAAa,MAAM;OAC5C,CACD,KAAK,OAAO,GACb;;;;EAIJ;;AAGF,SAAS,WAAW,WAA+B;AAClD,QAAO,YAAa,SAAS,cAAc,UAAU,IAAI,SAAS,OAAQ,SAAS"}
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(getSubject(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\nfunction getSubject(selectors: string | undefined) {\n\treturn selectors ? (document.querySelector(selectors) ?? document.body) : document.body\n}\n"],"mappings":";;;;;;;AASA,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,WAAW,MAAM,WAAW,QAAQ,CAAC,CAAC,qBAAqB;MACvE,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;;AAGF,SAAS,WAAW,WAA+B;AAClD,QAAO,YAAa,SAAS,cAAc,UAAU,IAAI,SAAS,OAAQ,SAAS"}
@@ -1,4 +1,4 @@
1
- import "pathe";
1
+ import { isAbsolute } from "pathe";
2
2
 
3
3
  //#region src/server/commands/_assertions.ts
4
4
  function assertTestPathDefined(context, commandName) {
@@ -1,6 +1,6 @@
1
+ import { readFile, stat, writeFile } from "node:fs/promises";
1
2
  import { glob } from "glob";
2
3
  import { mkdirp } from "mkdirp";
3
- import { readFile, stat, writeFile } from "node:fs/promises";
4
4
 
5
5
  //#region src/server/externals/file.ts
6
6
  const file = {
@@ -1 +1 @@
1
- {"version":3,"file":"file.mjs","names":[],"sources":["../../../src/server/externals/file.ts"],"sourcesContent":["import { glob, type GlobOptionsWithFileTypesUnset } from 'glob'\nimport { mkdirp } from 'mkdirp'\nimport { readFile, stat, writeFile } from 'node:fs/promises'\n\nexport const file = {\n\texistFile(filePath: string) {\n\t\treturn stat(filePath)\n\t\t\t.then((s) => s.isFile())\n\t\t\t.catch(() => false)\n\t},\n\tglob(pattern: string | string[], options?: GlobOptionsWithFileTypesUnset | undefined) {\n\t\treturn glob(pattern, options)\n\t},\n\tmkdirp: mkdirp as (path: string) => Promise<string | undefined>,\n\tasync tryReadFile(filePath: string | undefined): Promise<Buffer | undefined> {\n\t\tif (!filePath) return undefined\n\n\t\treturn readFile(filePath).catch(() => undefined)\n\t},\n\twriteFile,\n}\n"],"mappings":";;;;;AAIA,MAAa,OAAO;CACnB,UAAU,UAAkB;AAC3B,SAAO,KAAK,SAAS,CACnB,MAAM,MAAM,EAAE,QAAQ,CAAC,CACvB,YAAY,MAAM;;CAErB,KAAK,SAA4B,SAAqD;AACrF,SAAO,KAAK,SAAS,QAAQ;;CAEtB;CACR,MAAM,YAAY,UAA2D;AAC5E,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO,SAAS,SAAS,CAAC,YAAY,OAAU;;CAEjD;CACA"}
1
+ {"version":3,"file":"file.mjs","names":[],"sources":["../../../src/server/externals/file.ts"],"sourcesContent":["import { readFile, stat, writeFile } from 'node:fs/promises'\nimport { type GlobOptionsWithFileTypesUnset, glob } from 'glob'\nimport { mkdirp } from 'mkdirp'\n\nexport const file = {\n\texistFile(filePath: string) {\n\t\treturn stat(filePath)\n\t\t\t.then((s) => s.isFile())\n\t\t\t.catch(() => false)\n\t},\n\tglob(pattern: string | string[], options?: GlobOptionsWithFileTypesUnset | undefined) {\n\t\treturn glob(pattern, options)\n\t},\n\tmkdirp: mkdirp as (path: string) => Promise<string | undefined>,\n\tasync tryReadFile(filePath: string | undefined): Promise<Buffer | undefined> {\n\t\tif (!filePath) return undefined\n\n\t\treturn readFile(filePath).catch(() => undefined)\n\t},\n\twriteFile,\n}\n"],"mappings":";;;;;AAIA,MAAa,OAAO;CACnB,UAAU,UAAkB;AAC3B,SAAO,KAAK,SAAS,CACnB,MAAM,MAAM,EAAE,QAAQ,CAAC,CACvB,YAAY,MAAM;;CAErB,KAAK,SAA4B,SAAqD;AACrF,SAAO,KAAK,SAAS,QAAQ;;CAEtB;CACR,MAAM,YAAY,UAA2D;AAC5E,MAAI,CAAC,SAAU,QAAO;AAEtB,SAAO,SAAS,SAAS,CAAC,YAAY,OAAU;;CAEjD;CACA"}
@@ -1,3 +1,4 @@
1
+ import "../config/types.mjs";
1
2
  import "./vis_server_context.types.mjs";
2
3
 
3
4
  //#region src/server/snapshot_path.d.ts
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot_path.d.mts","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":[],"mappings":";;;;AAgCA;;;;;;;;iBAAgB,kBAAA"}
1
+ {"version":3,"file":"snapshot_path.d.mts","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":[],"mappings":";;;;;;AAgCA;;;;;;;iBAAgB,kBAAA"}
@@ -1,7 +1,7 @@
1
1
  import { SNAPSHOT_ROOT_DIR } from "../shared/constants.mjs";
2
2
  import { trimCommonFolder } from "../shared/trim_common_folder.mjs";
3
- import ci from "is-ci";
4
3
  import { platform } from "node:process";
4
+ import ci from "is-ci";
5
5
 
6
6
  //#region src/server/snapshot_path.ts
7
7
  function resolveSnapshotRootDir(browserCommandContext, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot_path.mjs","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":["import ci from 'is-ci'\nimport { platform } from 'node:process'\nimport type { VisOptions } from '../config/types.ts'\nimport { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'\nimport { trimCommonFolder } from '../shared/trim_common_folder.ts'\nimport type { ExtendedBrowserCommandContext } from './vis_server_context.types.ts'\n\nexport function resolveSnapshotRootDir(browserCommandContext: ExtendedBrowserCommandContext, options: VisOptions) {\n\tif (!options.snapshotRootDir) return getSnapshotRootDir()\n\tconst snapshotRootDir = options.snapshotRootDir\n\tif (typeof snapshotRootDir === 'string') return getSnapshotRootDir(snapshotRootDir)\n\tconst { config } = browserCommandContext.project.browser\n\treturn snapshotRootDir({\n\t\tci,\n\t\trootDir: SNAPSHOT_ROOT_DIR,\n\t\tprojectName: browserCommandContext.project.name,\n\t\tbrowserName: config.browser.name,\n\t\tproviderName: browserCommandContext.provider.name,\n\t\tplatform,\n\t\tscreenshotFailures: config.browser.screenshotFailures,\n\t\tscreenshotDirectory: config.browser.screenshotDirectory,\n\t})\n}\n\n/**\n * Gets the root directory for storing snapshots.\n *\n * @param snapshotRootDir - Base directory for snapshots. Defaults to `__vis__`.\n * @returns The full snapshot root directory path, which includes a platform-specific subdirectory:\n * - When running in CI: `<snapshotRootDir>/<platform>` (e.g. `__vis__/linux`)\n * - When running locally: `<snapshotRootDir>/local`\n */\nexport function getSnapshotRootDir(snapshotRootDir = SNAPSHOT_ROOT_DIR) {\n\treturn `${snapshotRootDir}/${ci ? platform : 'local'}`\n}\n\nexport function getSnapshotSubpath(suiteName: string, options: Pick<VisOptions, 'snapshotSubpath'>) {\n\tconst snapshotSubpath = options.snapshotSubpath ?? (({ subpath }) => trimCommonFolder(subpath))\n\treturn snapshotSubpath({ subpath: suiteName })\n}\n"],"mappings":";;;;;;AAOA,SAAgB,uBAAuB,uBAAsD,SAAqB;AACjH,KAAI,CAAC,QAAQ,gBAAiB,QAAO,oBAAoB;CACzD,MAAM,kBAAkB,QAAQ;AAChC,KAAI,OAAO,oBAAoB,SAAU,QAAO,mBAAmB,gBAAgB;CACnF,MAAM,EAAE,WAAW,sBAAsB,QAAQ;AACjD,QAAO,gBAAgB;EACtB;EACA,SAAS;EACT,aAAa,sBAAsB,QAAQ;EAC3C,aAAa,OAAO,QAAQ;EAC5B,cAAc,sBAAsB,SAAS;EAC7C;EACA,oBAAoB,OAAO,QAAQ;EACnC,qBAAqB,OAAO,QAAQ;EACpC,CAAC;;;;;;;;;;AAWH,SAAgB,mBAAmB,kBAAkB,mBAAmB;AACvE,QAAO,GAAG,gBAAgB,GAAG,KAAK,WAAW;;AAG9C,SAAgB,mBAAmB,WAAmB,SAA8C;AAEnG,SADwB,QAAQ,qBAAqB,EAAE,cAAc,iBAAiB,QAAQ,GACvE,EAAE,SAAS,WAAW,CAAC"}
1
+ {"version":3,"file":"snapshot_path.mjs","names":[],"sources":["../../src/server/snapshot_path.ts"],"sourcesContent":["import { platform } from 'node:process'\nimport ci from 'is-ci'\nimport type { VisOptions } from '../config/types.ts'\nimport { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'\nimport { trimCommonFolder } from '../shared/trim_common_folder.ts'\nimport type { ExtendedBrowserCommandContext } from './vis_server_context.types.ts'\n\nexport function resolveSnapshotRootDir(browserCommandContext: ExtendedBrowserCommandContext, options: VisOptions) {\n\tif (!options.snapshotRootDir) return getSnapshotRootDir()\n\tconst snapshotRootDir = options.snapshotRootDir\n\tif (typeof snapshotRootDir === 'string') return getSnapshotRootDir(snapshotRootDir)\n\tconst { config } = browserCommandContext.project.browser\n\treturn snapshotRootDir({\n\t\tci,\n\t\trootDir: SNAPSHOT_ROOT_DIR,\n\t\tprojectName: browserCommandContext.project.name,\n\t\tbrowserName: config.browser.name,\n\t\tproviderName: browserCommandContext.provider.name,\n\t\tplatform,\n\t\tscreenshotFailures: config.browser.screenshotFailures,\n\t\tscreenshotDirectory: config.browser.screenshotDirectory,\n\t})\n}\n\n/**\n * Gets the root directory for storing snapshots.\n *\n * @param snapshotRootDir - Base directory for snapshots. Defaults to `__vis__`.\n * @returns The full snapshot root directory path, which includes a platform-specific subdirectory:\n * - When running in CI: `<snapshotRootDir>/<platform>` (e.g. `__vis__/linux`)\n * - When running locally: `<snapshotRootDir>/local`\n */\nexport function getSnapshotRootDir(snapshotRootDir = SNAPSHOT_ROOT_DIR) {\n\treturn `${snapshotRootDir}/${ci ? platform : 'local'}`\n}\n\nexport function getSnapshotSubpath(suiteName: string, options: Pick<VisOptions, 'snapshotSubpath'>) {\n\tconst snapshotSubpath = options.snapshotSubpath ?? (({ subpath }) => trimCommonFolder(subpath))\n\treturn snapshotSubpath({ subpath: suiteName })\n}\n"],"mappings":";;;;;;AAOA,SAAgB,uBAAuB,uBAAsD,SAAqB;AACjH,KAAI,CAAC,QAAQ,gBAAiB,QAAO,oBAAoB;CACzD,MAAM,kBAAkB,QAAQ;AAChC,KAAI,OAAO,oBAAoB,SAAU,QAAO,mBAAmB,gBAAgB;CACnF,MAAM,EAAE,WAAW,sBAAsB,QAAQ;AACjD,QAAO,gBAAgB;EACtB;EACA,SAAS;EACT,aAAa,sBAAsB,QAAQ;EAC3C,aAAa,OAAO,QAAQ;EAC5B,cAAc,sBAAsB,SAAS;EAC7C;EACA,oBAAoB,OAAO,QAAQ;EACnC,qBAAqB,OAAO,QAAQ;EACpC,CAAC;;;;;;;;;;AAWH,SAAgB,mBAAmB,kBAAkB,mBAAmB;AACvE,QAAO,GAAG,gBAAgB,GAAG,KAAK,WAAW;;AAG9C,SAAgB,mBAAmB,WAAmB,SAA8C;AAEnG,SADwB,QAAQ,qBAAqB,EAAE,cAAc,iBAAiB,QAAQ,GACvE,EAAE,SAAS,WAAW,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"stubSuite.mjs","names":[],"sources":["../../../src/server/testing/stubSuite.ts"],"sourcesContent":["import { resolve } from 'pathe'\nimport { requiredDeep, type RecursivePartial } from 'type-plus'\nimport type { TestUserConfig } from 'vitest/node'\nimport type { ExtendedBrowserCommandContext } from '../vis_server_context.types.ts'\nimport { stubBrowserCommandContext } from './stubBrowserCommandContext.ts'\nimport { stubUserConfig } from './stubUserConfig.ts'\n\nexport function stubSuite(\n\tpartialUserConfig: RecursivePartial<{ test?: { name?: TestUserConfig['name'] } }> = {},\n\tpartialBrowserCommandContext: RecursivePartial<ExtendedBrowserCommandContext> = {},\n) {\n\tconst root = resolve(import.meta.dirname, '../..')\n\tconst userConfig = stubUserConfig({\n\t\troot,\n\t\t...partialUserConfig,\n\t})\n\tconst browserCommandContext = stubBrowserCommandContext(\n\t\trequiredDeep<RecursivePartial<ExtendedBrowserCommandContext>>(\n\t\t\t{\n\t\t\t\tproject: {\n\t\t\t\t\tconfig: { root },\n\t\t\t\t\trunner: {\n\t\t\t\t\t\tconfig: {\n\t\t\t\t\t\t\tname: partialUserConfig.test?.name as string | undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpartialBrowserCommandContext,\n\t\t),\n\t)\n\treturn {\n\t\tuserConfig,\n\t\tbrowserCommandContext,\n\t}\n}\n"],"mappings":";;;;;;AAOA,SAAgB,UACf,oBAAoF,EAAE,EACtF,+BAAgF,EAAE,EACjF;CACD,MAAM,OAAO,QAAQ,OAAO,KAAK,SAAS,QAAQ;AAoBlD,QAAO;EACN,YApBkB,eAAe;GACjC;GACA,GAAG;GACH,CAAC;EAkBD,uBAjB6B,0BAC7B,aACC,EACC,SAAS;GACR,QAAQ,EAAE,MAAM;GAChB,QAAQ,EACP,QAAQ,EACP,MAAM,kBAAkB,MAAM,MAC9B,EACD;GACD,EACD,EACD,6BACA,CACD;EAIA"}
1
+ {"version":3,"file":"stubSuite.mjs","names":[],"sources":["../../../src/server/testing/stubSuite.ts"],"sourcesContent":["import { resolve } from 'pathe'\nimport { type RecursivePartial, requiredDeep } from 'type-plus'\nimport type { TestUserConfig } from 'vitest/node'\nimport type { ExtendedBrowserCommandContext } from '../vis_server_context.types.ts'\nimport { stubBrowserCommandContext } from './stubBrowserCommandContext.ts'\nimport { stubUserConfig } from './stubUserConfig.ts'\n\nexport function stubSuite(\n\tpartialUserConfig: RecursivePartial<{ test?: { name?: TestUserConfig['name'] } }> = {},\n\tpartialBrowserCommandContext: RecursivePartial<ExtendedBrowserCommandContext> = {},\n) {\n\tconst root = resolve(import.meta.dirname, '../..')\n\tconst userConfig = stubUserConfig({\n\t\troot,\n\t\t...partialUserConfig,\n\t})\n\tconst browserCommandContext = stubBrowserCommandContext(\n\t\trequiredDeep<RecursivePartial<ExtendedBrowserCommandContext>>(\n\t\t\t{\n\t\t\t\tproject: {\n\t\t\t\t\tconfig: { root },\n\t\t\t\t\trunner: {\n\t\t\t\t\t\tconfig: {\n\t\t\t\t\t\t\tname: partialUserConfig.test?.name as string | undefined,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\tpartialBrowserCommandContext,\n\t\t),\n\t)\n\treturn {\n\t\tuserConfig,\n\t\tbrowserCommandContext,\n\t}\n}\n"],"mappings":";;;;;;AAOA,SAAgB,UACf,oBAAoF,EAAE,EACtF,+BAAgF,EAAE,EACjF;CACD,MAAM,OAAO,QAAQ,OAAO,KAAK,SAAS,QAAQ;AAoBlD,QAAO;EACN,YApBkB,eAAe;GACjC;GACA,GAAG;GACH,CAAC;EAkBD,uBAjB6B,0BAC7B,aACC,EACC,SAAS;GACR,QAAQ,EAAE,MAAM;GAChB,QAAQ,EACP,QAAQ,EACP,MAAM,kBAAkB,MAAM,MAC9B,EACD;GACD,EACD,EACD,6BACA,CACD;EAIA"}
@@ -1,4 +1,5 @@
1
1
  import { PixelDiffOptions } from "../shared/types.mjs";
2
+ import "../config/types.mjs";
2
3
  import { ExtendedBrowserCommandContext } from "./vis_server_context.types.mjs";
3
4
  import "../index.mjs";
4
5
  import * as ssim_js0 from "ssim.js";
@@ -39,6 +40,7 @@ declare function getVisOption(context: ExtendedBrowserCommandContext): {
39
40
  diffColor?: [number, number, number] | undefined;
40
41
  diffColorAlt?: [number, number, number] | undefined;
41
42
  diffMask?: boolean | undefined;
43
+ checkerboard?: boolean | undefined;
42
44
  } | Partial<ssim_js0.Options> | undefined;
43
45
  failureThresholdType?: "pixel" | "percent" | undefined;
44
46
  failureThreshold?: number | undefined;
@@ -70,6 +72,7 @@ declare function getVisOption(context: ExtendedBrowserCommandContext): {
70
72
  diffColor?: [number, number, number] | undefined;
71
73
  diffColorAlt?: [number, number, number] | undefined;
72
74
  diffMask?: boolean | undefined;
75
+ checkerboard?: boolean | undefined;
73
76
  } | Partial<ssim_js0.Options> | undefined;
74
77
  failureThresholdType?: "pixel" | "percent" | undefined;
75
78
  failureThreshold?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"vis_options.d.mts","names":[],"sources":["../../src/server/vis_options.ts"],"sourcesContent":[],"mappings":";;;;;;;;iBA6BgB,YAAA,UAAsB;;wBAA6B,QAAA,CAAA,OAAA"}
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,8 +1,16 @@
1
- import { Merge } from "type-plus";
1
+ import "../shared/types.mjs";
2
2
  import { BrowserCommandContext } from "vitest/node";
3
3
 
4
4
  //#region src/server/vis_server_context.types.d.ts
5
5
 
6
+ /**
7
+ * Left-join `A` with `B`: keys only in `A` are kept as-is, keys in both are
8
+ * intersected, keys only in `B` are added.
9
+ *
10
+ * Defined locally instead of imported so this package does not depend on which
11
+ * major of `type-plus` exposes a `Merge` alias.
12
+ */
13
+ type Merge<A, B> = Omit<A, keyof B> & { [K in keyof B]: K extends keyof A ? A[K] & B[K] : B[K] };
6
14
  type ExtendedBrowserCommandContext = {
7
15
  page: BrowserCommandContext['page'];
8
16
  browser: BrowserCommandContext['browser'];
@@ -1 +1 @@
1
- {"version":3,"file":"vis_server_context.types.d.mts","names":[],"sources":["../../src/server/vis_server_context.types.ts"],"sourcesContent":[],"mappings":";;;;;KAuCY,6BAAA;QACL;WACG;UACD;YACE,YAAY;YACZ;WAED,MACR;;;;;;;;;;YAWS"}
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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vitest-plugin-vis",
3
- "version": "5.1.0",
3
+ "version": "5.1.2",
4
4
  "description": "Vitest visual testing plugin",
5
5
  "keywords": [
6
6
  "image-snapshot",
@@ -48,48 +48,48 @@
48
48
  "!**/*.{spec,test,unit,accept,integrate,system,perf,stress}.*"
49
49
  ],
50
50
  "dependencies": {
51
- "dedent": "^1.5.1",
52
- "glob": "^13.0.0",
53
- "is-ci": "^4.0.0",
51
+ "dedent": "^1.7.2",
52
+ "glob": "^13.0.6",
53
+ "is-ci": "^4.1.0",
54
54
  "mkdirp": "^3.0.1",
55
- "pathe": "^2.0.0",
56
- "pixelmatch": "^7.1.0",
55
+ "pathe": "^2.0.3",
56
+ "pixelmatch": "^7.2.0",
57
57
  "pngjs": "^7.0.0",
58
- "rimraf": "^6.0.1",
58
+ "rimraf": "^6.1.3",
59
59
  "ssim.js": "^3.5.0",
60
- "type-plus": "8.0.0-beta.8"
60
+ "type-plus": "^7.6.2"
61
61
  },
62
62
  "devDependencies": {
63
- "@repobuddy/storybook": "^2.28.0",
63
+ "@repobuddy/storybook": "^2.32.0",
64
64
  "@repobuddy/typescript": "^2.1.0",
65
- "@storybook-community/storybook-dark-mode": "^7.1.1",
66
- "@storybook/addon-docs": "^10.3.5",
67
- "@storybook/addon-vitest": "^10.3.5",
68
- "@storybook/react-vite": "^10.3.5",
69
- "@tailwindcss/vite": "^4.1.14",
65
+ "@storybook-community/storybook-dark-mode": "^7.1.3",
66
+ "@storybook/addon-docs": "^10.5.10",
67
+ "@storybook/addon-vitest": "^10.5.10",
68
+ "@storybook/react-vite": "^10.5.10",
69
+ "@tailwindcss/vite": "^4.3.3",
70
70
  "@types/is-ci": "^3.0.4",
71
71
  "@types/pngjs": "^6.0.5",
72
- "@types/react": "^19.2.2",
73
- "@vitest/browser": "^4.1.4",
74
- "@vitest/browser-playwright": "^4.1.4",
75
- "@vitest/browser-webdriverio": "^4.1.4",
76
- "@vitest/coverage-istanbul": "^4.1.4",
77
- "@vitest/coverage-v8": "^4.1.4",
78
- "@vitest/expect": "^4.1.4",
79
- "@vitest/ui": "^4.1.4",
80
- "cross-env": "^10.0.0",
72
+ "@types/react": "^19.2.18",
73
+ "@vitest/browser": "^4.1.11",
74
+ "@vitest/browser-playwright": "^4.1.11",
75
+ "@vitest/browser-webdriverio": "^4.1.11",
76
+ "@vitest/coverage-istanbul": "^4.1.11",
77
+ "@vitest/coverage-v8": "^4.1.11",
78
+ "@vitest/expect": "^4.1.11",
79
+ "@vitest/ui": "^4.1.11",
80
+ "cross-env": "^10.1.0",
81
81
  "never-fail": "^1.1.4",
82
- "npm-run-all2": "^8.0.0",
83
- "react": "^19.0.0",
84
- "react-dom": "^19.0.0",
85
- "storybook": "^10.3.5",
82
+ "npm-run-all2": "^9.0.3",
83
+ "react": "^19.2.8",
84
+ "react-dom": "^19.2.8",
85
+ "storybook": "^10.5.10",
86
86
  "storybook-addon-tag-badges": "^3.1.0",
87
- "tailwindcss": "^4.1.14",
87
+ "tailwindcss": "^4.3.3",
88
88
  "tsdown": "^0.19.0",
89
- "typescript": "^6.0.0",
90
- "vite": "^8.0.0",
91
- "vitest": "^4.1.4",
92
- "vitest-browser-react": "^2.0.2"
89
+ "typescript": "^6.0.3",
90
+ "vite": "^8.2.2",
91
+ "vitest": "^4.1.11",
92
+ "vitest-browser-react": "^2.2.0"
93
93
  },
94
94
  "peerDependencies": {
95
95
  "@vitest/browser": "^4",
@@ -1,4 +1,4 @@
1
- import { commands, type BrowserCommands, type BrowserPage } from 'vitest/browser'
1
+ import { type BrowserCommands, type BrowserPage, commands } from 'vitest/browser'
2
2
  import { getCurrentTest } from '../external/vitest/vitest_suite_proxy.ts'
3
3
 
4
4
  export type { BrowserPage }
@@ -1,3 +1,4 @@
1
+ import { isAbsolute, relative, resolve } from 'pathe'
1
2
  import type { ToMatchImageSnapshotOptions } from '../../shared/types.ts'
2
3
 
3
4
  export function prettifyOptions(options: ToMatchImageSnapshotOptions<any> | undefined) {
@@ -13,3 +14,19 @@ export function prettifyOptions(options: ToMatchImageSnapshotOptions<any> | unde
13
14
  .filter(Boolean)
14
15
  .join('\n ')
15
16
  }
17
+
18
+ /**
19
+ * Formats a snapshot path for display in a failure message.
20
+ *
21
+ * Paths within the project root are shown relative to it so that the failure output can be shared
22
+ * verbatim without leaking the local absolute path, while remaining clickable in editors and terminals.
23
+ * Paths outside the project root stay absolute, as a `../../..` chain is neither shorter nor clearer.
24
+ */
25
+ export function formatSnapshotPath(projectRoot: string, path: string) {
26
+ const absolutePath = resolve(projectRoot, path)
27
+ const relativePath = relative(projectRoot, absolutePath)
28
+ if (!relativePath || relativePath === '..' || relativePath.startsWith('../') || isAbsolute(relativePath)) {
29
+ return absolutePath
30
+ }
31
+ return `./${relativePath}`
32
+ }
@@ -10,7 +10,7 @@ import type { ImageSnapshotComparisonInfo, ToMatchImageSnapshotOptions } from '.
10
10
  import { toDataURL, toImageData } from '../external/browser/image_data.ts'
11
11
  import { server } from '../external/vitest/vitest_browser_context_proxy.ts'
12
12
  import { alignImagesToSameSize } from '../image/align_images.ts'
13
- import { prettifyOptions } from './compare_image_snapshot.logic.ts'
13
+ import { formatSnapshotPath, prettifyOptions } from './compare_image_snapshot.logic.ts'
14
14
 
15
15
  export async function compareImageSnapshot(
16
16
  commands: BrowserCommands & PrepareImageSnapshotComparisonCommand & ImageSnapshotNextIndexCommand,
@@ -31,7 +31,7 @@ export async function compareImageSnapshot(
31
31
  dedent`Snapshot \`${taskId}\` has no baseline image. Please review the new snapshot and update the baseline image.
32
32
 
33
33
  Options: ${prettifyOptions(options)}
34
- Actual: ${resolve(info.projectRoot, info.resultPath)}`,
34
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`,
35
35
  )
36
36
  }
37
37
 
@@ -55,8 +55,8 @@ export async function compareImageSnapshot(
55
55
  Options: ${prettifyOptions(options)}
56
56
  Diff: ${options.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}
57
57
 
58
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
59
- Actual: ${resolve(info.projectRoot, info.resultPath)}`,
58
+ Expected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}
59
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}`,
60
60
  )
61
61
  }
62
62
  return
@@ -85,9 +85,9 @@ export async function compareImageSnapshot(
85
85
 
86
86
  Options: ${prettifyOptions(options)}
87
87
 
88
- Expected: ${resolve(info.projectRoot, info.baselinePath)}
89
- Actual: ${resolve(info.projectRoot, info.resultPath)}
90
- Difference: ${resolve(info.projectRoot, info.diffPath)}`,
88
+ Expected: ${formatSnapshotPath(info.projectRoot, info.baselinePath)}
89
+ Actual: ${formatSnapshotPath(info.projectRoot, info.resultPath)}
90
+ Difference: ${formatSnapshotPath(info.projectRoot, info.diffPath)}`,
91
91
  )
92
92
  }
93
93
 
@@ -55,7 +55,7 @@ export function autoSnapshotMatcher<GM extends Record<string, any> | unknown = u
55
55
  if (errors.length === 1) throw errors[0]![1]
56
56
  const taskId = toTaskId(test!)
57
57
  throw new AggregateError(
58
- errors,
58
+ errors.map(([, e]) => e),
59
59
  dedent`Snapshot \`${taskId}\` mismatched
60
60
 
61
61
  ${errors
@@ -1,6 +1,6 @@
1
- import { glob, type GlobOptionsWithFileTypesUnset } from 'glob'
2
- import { mkdirp } from 'mkdirp'
3
1
  import { readFile, stat, writeFile } from 'node:fs/promises'
2
+ import { type GlobOptionsWithFileTypesUnset, glob } from 'glob'
3
+ import { mkdirp } from 'mkdirp'
4
4
 
5
5
  export const file = {
6
6
  existFile(filePath: string) {
@@ -1,5 +1,5 @@
1
- import ci from 'is-ci'
2
1
  import { platform } from 'node:process'
2
+ import ci from 'is-ci'
3
3
  import type { VisOptions } from '../config/types.ts'
4
4
  import { SNAPSHOT_ROOT_DIR } from '../shared/constants.ts'
5
5
  import { trimCommonFolder } from '../shared/trim_common_folder.ts'
@@ -1,5 +1,5 @@
1
1
  import { resolve } from 'pathe'
2
- import { requiredDeep, type RecursivePartial } from 'type-plus'
2
+ import { type RecursivePartial, requiredDeep } from 'type-plus'
3
3
  import type { TestUserConfig } from 'vitest/node'
4
4
  import type { ExtendedBrowserCommandContext } from '../vis_server_context.types.ts'
5
5
  import { stubBrowserCommandContext } from './stubBrowserCommandContext.ts'
@@ -1,4 +1,3 @@
1
- import type { Merge } from 'type-plus'
2
1
  import type { BrowserCommandContext } from 'vitest/node'
3
2
  import type { Awaitable } from '../shared/types.ts'
4
3
 
@@ -37,6 +36,17 @@ export type ExtendedBrowserCommand<Payload extends unknown[] = [], ReturnValue =
37
36
  ...payload: Payload
38
37
  ) => Awaitable<ReturnValue>
39
38
 
39
+ /**
40
+ * Left-join `A` with `B`: keys only in `A` are kept as-is, keys in both are
41
+ * intersected, keys only in `B` are added.
42
+ *
43
+ * Defined locally instead of imported so this package does not depend on which
44
+ * major of `type-plus` exposes a `Merge` alias.
45
+ */
46
+ type Merge<A, B> = Omit<A, keyof B> & {
47
+ [K in keyof B]: K extends keyof A ? A[K] & B[K] : B[K]
48
+ }
49
+
40
50
  export type ExtendedBrowserCommandContext = {
41
51
  page: BrowserCommandContext['page']
42
52
  browser: BrowserCommandContext['browser']