storybook-addon-vis 0.11.2 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { getByAltText, getByRole, getByTestId, getByText, getByTitle, getByPlaceholderText, getByLabelText } from '@storybook/test';
2
- import { basename, join } from 'pathe';
2
+ import { resolve, join, dirname, relative } from 'pathe';
3
3
  import { required, omit } from 'type-plus';
4
4
  import { getCurrentTest } from 'vitest/suite';
5
5
  import dedent from 'dedent';
@@ -27,12 +27,12 @@ function toSnapshotId(taskName) {
27
27
  // src/state.ts
28
28
  function createStore() {
29
29
  let testFilepath;
30
- let testFilename;
30
+ let currentDir;
31
31
  let baselineDir;
32
32
  let resultDir;
33
33
  let diffDir;
34
34
  let snapshot;
35
- let suiteOptions = {};
35
+ let suiteOptions = /* @__PURE__ */ Object.create(null);
36
36
  let tags;
37
37
  let parameters;
38
38
  let taskName;
@@ -42,17 +42,20 @@ function createStore() {
42
42
  resultDir,
43
43
  diffDir,
44
44
  snapshot: {},
45
- async setupSuite(suite, options) {
46
- const snapshotPath = options?.snapshotDir ?? `__vis__/${await commands.getSnapshotPlatform()}`;
45
+ async setupSuite(suite, options = {}) {
47
46
  testFilepath = suite.file.filepath;
48
- testFilename = basename(testFilepath);
49
- baselineDir = join(snapshotPath, testFilename);
50
- resultDir = join(snapshotPath, "__results__", testFilename);
51
- diffDir = join(snapshotPath, "__diff_output__", testFilename);
52
- suiteOptions = options ?? {};
47
+ const projectDir = testFilepath.slice(0, -suite.name.length);
48
+ const snapshotPath = resolveSnapshotPath(await getPlatform(), options);
49
+ const snapshotFullPath = join(projectDir, snapshotPath);
50
+ currentDir = dirname(testFilepath);
51
+ const suiteDir = trimSuiteDir(suite.name, options);
52
+ baselineDir = relative(currentDir, join(snapshotFullPath, suiteDir));
53
+ resultDir = relative(currentDir, join(snapshotFullPath, "__results__", suiteDir));
54
+ diffDir = relative(currentDir, join(snapshotFullPath, "__diff_output__", suiteDir));
55
+ suiteOptions = options;
53
56
  snapshot = snapshot ?? {};
54
57
  if (!snapshot[testFilepath]) {
55
- snapshot[testFilepath] = {};
58
+ snapshot[testFilepath] = /* @__PURE__ */ Object.create(null);
56
59
  await commands.rmDir(resultDir);
57
60
  await commands.rmDir(diffDir);
58
61
  }
@@ -68,11 +71,14 @@ function createStore() {
68
71
  getName() {
69
72
  return taskName;
70
73
  },
74
+ getCurrentDir() {
75
+ return currentDir;
76
+ },
71
77
  getTimeout(timeout) {
72
78
  return timeout ?? suiteOptions.timeout ?? 3e4;
73
79
  },
74
80
  mergeMatchImageSnapshotOptions(options) {
75
- return required(omit(suiteOptions, "snapshotDir"), parameters?.snapshot, options);
81
+ return required(omit(suiteOptions, "snapshotRootDir", "customizeSnapshotSubpath"), parameters?.snapshot, options);
76
82
  },
77
83
  getSnapshotFilePaths(options) {
78
84
  const test = getCurrentTest();
@@ -94,6 +100,24 @@ function createStore() {
94
100
  return state2;
95
101
  }
96
102
  var state = createStore();
103
+ function resolveSnapshotPath(platform, options) {
104
+ return options.snapshotRootDir ?? `__vis__/${platform}`;
105
+ }
106
+ function trimSuiteDir(suiteName, options) {
107
+ const customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? defaultCustomizeSnapshotSubpath;
108
+ return customizeSnapshotSubpath(suiteName);
109
+ }
110
+ function defaultCustomizeSnapshotSubpath(suiteName) {
111
+ const [suiteDir] = suiteName.split("/", 1);
112
+ if (["tests", "test", "src", "source", "js", "ts", "lib"].includes(suiteDir))
113
+ return suiteName.slice(suiteDir.length + 1);
114
+ return suiteName;
115
+ }
116
+ var platformP;
117
+ function getPlatform() {
118
+ if (platformP) return platformP;
119
+ return platformP = commands.getSnapshotPlatform();
120
+ }
97
121
 
98
122
  // src/@vitest/browser/page.has_image_snapshot.ts
99
123
  async function hasImageSnapshot(options) {
@@ -103,7 +127,7 @@ async function hasImageSnapshot(options) {
103
127
 
104
128
  // src/image_data.ts
105
129
  function toImageData(base64) {
106
- return new Promise((resolve, reject) => {
130
+ return new Promise((resolve2, reject) => {
107
131
  const img = new Image();
108
132
  img.src = `data:image/png;base64,${base64}`;
109
133
  img.onload = () => {
@@ -112,7 +136,7 @@ function toImageData(base64) {
112
136
  canvas.height = img.height;
113
137
  const ctx2 = canvas.getContext("2d");
114
138
  ctx2.drawImage(img, 0, 0);
115
- resolve(ctx2.getImageData(0, 0, canvas.width, canvas.height));
139
+ resolve2(ctx2.getImageData(0, 0, canvas.width, canvas.height));
116
140
  };
117
141
  img.onerror = () => {
118
142
  reject(new Error("Failed to load image"));
@@ -125,11 +149,11 @@ async function toDataURL(imageData) {
125
149
  canvas.height = imageData.height;
126
150
  const ctx2 = canvas.getContext("2d");
127
151
  ctx2.putImageData(imageData, 0, 0);
128
- return new Promise((resolve) => {
152
+ return new Promise((resolve2) => {
129
153
  canvas.toBlob((blob) => {
130
154
  const reader = new FileReader();
131
155
  reader.onload = () => {
132
- resolve(reader.result);
156
+ resolve2(reader.result);
133
157
  };
134
158
  reader.readAsDataURL(blob);
135
159
  });
@@ -285,11 +309,11 @@ async function toMatchImageSnapshotInternal(actual, options) {
285
309
  pass: false,
286
310
  message: () => dedent`Snapshot \`${state.getName()}\` mismatched
287
311
 
288
- ${options?.failureThreshold ? options?.failureThresholdType === "percent" ? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.` : `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.` : `Expected image to match but was differ by ${options?.failureThresholdType === "percent" ? `${diffAmount}%` : `${diffAmount} pixels`}.`}
312
+ ${options?.failureThreshold ? options?.failureThresholdType === "percent" ? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.` : `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.` : `Expected simage to match but was differ by ${options?.failureThresholdType === "percent" ? `${diffAmount}%` : `${diffAmount} pixels`}.`}
289
313
 
290
- Expected: '${subject.baselinePath}'
291
- Actual: '${subject.resultPath}'
292
- Difference: '${subject.diffPath}'`
314
+ Expected: '${resolve(state.getCurrentDir(), subject.baselinePath)}'
315
+ Actual: '${resolve(state.getCurrentDir(), subject.resultPath)}'
316
+ Difference: '${resolve(state.getCurrentDir(), subject.diffPath)}'`
293
317
  };
294
318
  }
295
319
  return success;
@@ -344,5 +368,5 @@ function toThresholdUnit({ failureThresholdType, width, height }, pixelDiff) {
344
368
  }
345
369
 
346
370
  export { cdp, commands, page, server, state, toMatchImageSnapshot };
347
- //# sourceMappingURL=chunk-UIFACFHT.js.map
348
- //# sourceMappingURL=chunk-UIFACFHT.js.map
371
+ //# sourceMappingURL=chunk-MBASHGUW.js.map
372
+ //# sourceMappingURL=chunk-MBASHGUW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/state.ts","../src/@vitest/browser/page.has_image_snapshot.ts","../src/image_data.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/context.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts"],"names":["state","ctx","id","index","resolve","getCurrentTest"],"mappings":"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","file":"chunk-MBASHGUW.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\n","import { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot } from './types.js'\n\nexport function isImageSnapshot(subject: any): subject is ImageSnapshot {\n\treturn !!subject && subject.type === imageSnapshotSymbol\n}\n\nexport function assertImageSnapshot(subject: any): asserts subject is ImageSnapshot {\n\tif (!isImageSnapshot(subject)) {\n\t\tthrow new Error('Expected subject to be an image snapshot')\n\t}\n}\n\nexport function toSnapshotId(taskName: string) {\n\treturn `${taskName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`\n}\n","import { dirname, join, relative } from 'pathe'\nimport type { StoryContext } from 'storybook/internal/types'\nimport { omit, required } from 'type-plus'\nimport { getCurrentTest } from 'vitest/suite'\nimport { commands } from './@vitest/browser/context.js'\nimport { toSnapshotId } from './@vitest/browser/image_snapshot.logic'\nimport type { ImageSnapshotOptions } from './@vitest/browser/types'\nimport type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\nimport type { VisOptions } from './types.js'\n\nfunction createStore() {\n\t// test suite (runner.beforeAll) states\n\tlet testFilepath: string\n\tlet currentDir: string\n\tlet baselineDir: string\n\tlet resultDir: string\n\tlet diffDir: string\n\tlet snapshot: Record<string, Record<string, { index: number }>>\n\tlet suiteOptions: VisOptions = Object.create(null)\n\n\t// story states (story.beforeEach)\n\tlet tags: string[] | undefined\n\tlet parameters: Record<string, any> | undefined\n\tlet taskName: string\n\tlet id: string\n\n\tconst state = {\n\t\tbaselineDir,\n\t\tresultDir,\n\t\tdiffDir,\n\t\tsnapshot: {},\n\t\tasync setupSuite(suite: { file: { filepath: string }; name: string }, options: VisOptions = {}) {\n\t\t\t// console.debug('setupSuite', suite.name)\n\t\t\ttestFilepath = suite.file.filepath\n\t\t\tconst projectDir = testFilepath.slice(0, -suite.name.length)\n\n\t\t\tconst snapshotPath = resolveSnapshotPath(await getPlatform(), options)\n\t\t\tconst snapshotFullPath = join(projectDir, snapshotPath)\n\t\t\tcurrentDir = dirname(testFilepath)\n\t\t\tconst suiteDir = trimSuiteDir(suite.name, options)\n\t\t\tbaselineDir = relative(currentDir, join(snapshotFullPath, suiteDir))\n\t\t\tresultDir = relative(currentDir, join(snapshotFullPath, '__results__', suiteDir))\n\t\t\tdiffDir = relative(currentDir, join(snapshotFullPath, '__diff_output__', suiteDir))\n\n\t\t\tsuiteOptions = options\n\t\t\tsnapshot = snapshot ?? {}\n\t\t\tif (!snapshot[testFilepath]) {\n\t\t\t\tsnapshot[testFilepath] = Object.create(null)\n\t\t\t\tawait commands.rmDir(resultDir)\n\t\t\t\tawait commands.rmDir(diffDir)\n\t\t\t}\n\t\t},\n\t\tsetupStory(ctx: StoryContext) {\n\t\t\t// console.debug('setupStory', ctx.name)\n\t\t\ttags = ctx.tags\n\t\t\tparameters = ctx.parameters\n\t\t},\n\t\tshouldTakeSnapshot() {\n\t\t\t// console.debug('shouldTakeSnapshot', taskName, tags)\n\t\t\tif (!tags) return false\n\t\t\treturn tags.lastIndexOf('!snapshot') < tags.lastIndexOf('snapshot')\n\t\t},\n\t\tgetName() {\n\t\t\treturn taskName\n\t\t},\n\t\tgetCurrentDir() {\n\t\t\treturn currentDir\n\t\t},\n\t\tgetTimeout(timeout?: number | undefined) {\n\t\t\treturn timeout ?? suiteOptions.timeout ?? 30000\n\t\t},\n\t\tmergeMatchImageSnapshotOptions(options?: MatchImageSnapshotOptions) {\n\t\t\treturn required(omit(suiteOptions, 'snapshotRootDir', 'customizeSnapshotSubpath'), parameters?.snapshot, options)\n\t\t},\n\t\tgetSnapshotFilePaths(options?: ImageSnapshotOptions | undefined) {\n\t\t\tconst test = getCurrentTest()\n\t\t\ttaskName = test.name\n\t\t\tid = toSnapshotId(taskName)\n\t\t\tsnapshot[testFilepath][id] = snapshot[testFilepath][id] ?? { index: 1 }\n\n\t\t\tconst index = snapshot[testFilepath][id]!.index\n\t\t\tconst customizeSnapshotId =\n\t\t\t\tsuiteOptions.customizeSnapshotId ?? options?.customizeSnapshotId ?? ((id, index) => `${id}-${index}`)\n\t\t\tconst snapshotFilename = `${customizeSnapshotId(id, index)}.png`\n\t\t\tconst baselinePath = join(baselineDir, snapshotFilename)\n\t\t\tconst resultPath = join(resultDir, snapshotFilename)\n\t\t\tconst diffPath = join(diffDir, snapshotFilename)\n\t\t\treturn { snapshotFilename, baselinePath, resultPath, diffPath }\n\t\t},\n\t\tincrementSnapshotIndex() {\n\t\t\tsnapshot[testFilepath][id]!.index++\n\t\t},\n\t}\n\treturn state\n}\n\nexport const state = createStore()\n\nfunction resolveSnapshotPath(platform: string, options: VisOptions) {\n\treturn options.snapshotRootDir ?? `__vis__/${platform}`\n}\n\nfunction trimSuiteDir(suiteName: string, options: VisOptions) {\n\tconst customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? defaultCustomizeSnapshotSubpath\n\treturn customizeSnapshotSubpath(suiteName)\n}\n\nfunction defaultCustomizeSnapshotSubpath(suiteName: string) {\n\tconst [suiteDir] = suiteName.split('/', 1)\n\tif (['tests', 'test', 'src', 'source', 'js', 'ts', 'lib'].includes(suiteDir))\n\t\treturn suiteName.slice(suiteDir.length + 1)\n\treturn suiteName\n}\n\nlet platformP: Promise<string>\nfunction getPlatform() {\n\tif (platformP) return platformP\n\n\treturn (platformP = commands.getSnapshotPlatform())\n}\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { join } from 'pathe'\nimport { state } from '../../state'\nimport { commands } from './context'\nimport type { CustomizeSnapshotIdOptions } from './types'\n\nexport interface HasImageSnapshotAction {\n\thasImageSnapshot(this: BrowserPage, options?: CustomizeSnapshotIdOptions | undefined): Promise<boolean>\n}\n\n/**\n * Check if the snapshot image exists.\n */\nexport async function hasImageSnapshot(this: BrowserPage, options?: CustomizeSnapshotIdOptions | undefined) {\n\tconst { baselinePath } = state.getSnapshotFilePaths(options)\n\treturn commands.existFile(baselinePath)\n}\n","export function toImageData(base64: string) {\n\treturn new Promise<ImageData>((resolve, reject) => {\n\t\tconst img = new Image()\n\t\timg.src = `data:image/png;base64,${base64}`\n\n\t\timg.onload = () => {\n\t\t\tconst canvas = document.createElement('canvas')\n\t\t\tcanvas.width = img.width\n\t\t\tcanvas.height = img.height\n\t\t\tconst ctx = canvas.getContext('2d')!\n\t\t\tctx.drawImage(img, 0, 0)\n\t\t\tresolve(ctx.getImageData(0, 0, canvas.width, canvas.height))\n\t\t}\n\n\t\timg.onerror = () => {\n\t\t\treject(new Error('Failed to load image'))\n\t\t}\n\t})\n}\n\nexport async function toDataURL(imageData: ImageData) {\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = imageData.width\n\tcanvas.height = imageData.height\n\tconst ctx = canvas.getContext('2d')!\n\tctx.putImageData(imageData, 0, 0)\n\treturn new Promise<string>((resolve) => {\n\t\tcanvas.toBlob((blob) => {\n\t\t\tconst reader = new FileReader()\n\t\t\treader.onload = () => {\n\t\t\t\tresolve(reader.result as string)\n\t\t\t}\n\t\t\treader.readAsDataURL(blob!)\n\t\t})\n\t})\n}\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { toImageData } from '../../image_data.js'\nimport { state } from '../../state.js'\nimport { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot, ImageSnapshotOptions } from './types.js'\n\nexport interface ImageSnapshotAction {\n\timageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions | undefined): Promise<ImageSnapshot>\n}\n\nexport async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst { snapshotFilename, baselinePath, resultPath, diffPath } = state.getSnapshotFilePaths(options)\n\t// console.debug('taking snapshot', state.getName(), snapshotFilename)\n\tconst screenshot = await this.screenshot({\n\t\tbase64: true,\n\t\tpath: resultPath,\n\t\telement: options?.element,\n\t\ttimeout: state.getTimeout(options?.timeout),\n\t})\n\tstate.incrementSnapshotIndex()\n\tconst image = await toImageData(screenshot.base64)\n\n\treturn {\n\t\ttype: imageSnapshotSymbol,\n\t\tsnapshotFilename,\n\t\tbaselinePath,\n\t\tresultPath,\n\t\tdiffPath,\n\t\tbase64: screenshot.base64,\n\t\timage,\n\t}\n}\n","import {\n\tgetByAltText,\n\tgetByLabelText,\n\tgetByPlaceholderText,\n\tgetByRole,\n\tgetByTestId,\n\tgetByText,\n\tgetByTitle,\n} from '@storybook/test'\nimport type { BrowserCommands, BrowserPage, CDPSession, Platform } from '@vitest/browser/context'\nimport type { SerializedConfig } from 'vitest'\nimport { imageSnapshotStubSymbol } from './constants'\nimport { hasImageSnapshot } from './page.has_image_snapshot'\nimport { imageSnapshot } from './page.image_snapshot'\n\nlet ctx: Awaited<typeof import('@vitest/browser/context')>\n\nif ((globalThis as any).__vitest_browser__) {\n\timport('@vitest/browser/context').then((m) => {\n\t\tctx = m\n\t\tpage.extend({ imageSnapshot, hasImageSnapshot })\n\t})\n}\n\n// TODO: stub the functions within the `CDPSession`\n// when we better understands the use cases\nexport function cdp(): CDPSession {\n\treturn ctx?.cdp() ?? ({} as any)\n}\n\nexport const commands = new Proxy<BrowserCommands>({} as any, {\n\tget(target, prop) {\n\t\treturn (target as any)[prop] ?? (ctx?.commands as any)[prop]\n\t},\n})\n\nexport const page = new Proxy<BrowserPage>(\n\t{\n\t\tgetByAltText,\n\t\tgetByRole,\n\t\tgetByTestId,\n\t\tgetByText,\n\t\tgetByTitle,\n\t\tgetByPlaceholder: getByPlaceholderText,\n\t\tgetByLabelText,\n\t} as any,\n\t{\n\t\tget(target, prop) {\n\t\t\treturn ((target as any)[prop] ?? ctx?.page)\n\t\t\t\t? (ctx?.page as any)[prop]\n\t\t\t\t: () => {\n\t\t\t\t\t\tif (prop === 'imageSnapshot') {\n\t\t\t\t\t\t\treturn { type: imageSnapshotStubSymbol }\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prop === 'hasImageSnapshot') {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconsole.info(`\\`page.${prop.toString()}\\` does not exist when running in browser`)\n\t\t\t\t\t}\n\t\t},\n\t},\n)\n\nexport const server = new Proxy<{\n\t/**\n\t * Platform the Vitest server is running on.\n\t * The same as calling `process.platform` on the server.\n\t */\n\tplatform: Platform\n\t/**\n\t * Runtime version of the Vitest server.\n\t * The same as calling `process.version` on the server.\n\t */\n\tversion: string\n\t/**\n\t * Name of the browser provider.\n\t */\n\tprovider: string\n\t/**\n\t * Name of the current browser.\n\t */\n\tbrowser: string\n\t/**\n\t * Available commands for the browser.\n\t * @see {@link https://vitest.dev/guide/browser/commands}\n\t */\n\tcommands: BrowserCommands\n\t/**\n\t * Serialized test config.\n\t */\n\tconfig: SerializedConfig\n}>({} as any, {\n\tget(target, prop) {\n\t\treturn (target as any)[prop] ?? (ctx?.server as any)[prop]\n\t},\n})\n","export const createImageResizer =\n\t({ width, height }: { width: number; height: number }) =>\n\t(image: ImageData) => {\n\t\tif (image.width === width && image.height === height) {\n\t\t\treturn image\n\t\t}\n\t\tconst inArea = (x: number, y: number) => y <= image.height && x <= image.width\n\t\tconst result = new ImageData(width, height, { colorSpace: image.colorSpace })\n\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tfor (let x = 0; x < width; x++) {\n\t\t\t\tconst idx = (width * y + x) << 2\n\t\t\t\tif (inArea(x, y)) {\n\t\t\t\t\tconst old = (image.width * y + x) << 2\n\t\t\t\t\tresult.data[idx] = image.data[old]\n\t\t\t\t\tresult.data[idx + 1] = image.data[old + 1]\n\t\t\t\t\tresult.data[idx + 2] = image.data[old + 2]\n\t\t\t\t\tresult.data[idx + 3] = image.data[old + 3]\n\t\t\t\t} else {\n\t\t\t\t\tresult.data[idx] = 0\n\t\t\t\t\tresult.data[idx + 1] = 0\n\t\t\t\t\tresult.data[idx + 2] = 0\n\t\t\t\t\tresult.data[idx + 3] = 64\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n// slower\nexport const createImageResizer2 =\n\t({ width, height }: { width: number; height: number }) =>\n\t(image: ImageData) => {\n\t\tif (image.width === width && image.height === height) {\n\t\t\treturn image\n\t\t}\n\t\tconst inArea = (x: number, y: number) => y <= image.height && x <= image.width\n\t\tconst result = new ImageData(width, height, { colorSpace: image.colorSpace })\n\n\t\tconst copyBytes = image.width * 4\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tresult.data.set(image.data.slice(y * image.width, copyBytes), y * width * 4)\n\t\t\tfor (let x = image.width; x < width; x++) {\n\t\t\t\tconst idx = (width * y + x) << 2\n\t\t\t\tif (!inArea(x, y)) {\n\t\t\t\t\tresult.data[idx] = 0\n\t\t\t\t\tresult.data[idx + 1] = 0\n\t\t\t\t\tresult.data[idx + 2] = 0\n\t\t\t\t\tresult.data[idx + 3] = 64\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n","import type { AsyncExpectationResult } from '@vitest/expect'\nimport dedent from 'dedent'\nimport { resolve } from 'pathe'\nimport pixelmatch, { type PixelmatchOptions } from 'pixelmatch'\nimport { getCurrentTest } from 'vitest/suite'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands, page, server } from './@vitest/browser/context.js'\nimport { assertImageSnapshot, isImageSnapshot } from './@vitest/browser/image_snapshot.logic.js'\nimport { toDataURL, toImageData } from './image_data.js'\nimport { createImageResizer } from './image_resizer.js'\nimport { state } from './state.js'\n\nexport interface ImageSnapshotMatcher {\n\ttoMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>\n}\n\nexport type MatchImageSnapshotOptions = {\n\t/**\n\t * Custom options passed to 'pixelmatch'\n\t */\n\tdiffOptions?: PixelmatchOptions | undefined\n\t/**\n\t * Failure threshold should measure in `pixel` or `percent`.\n\t *\n\t * Default is `pixel`.\n\t */\n\tfailureThresholdType?: 'pixel' | 'percent' | undefined\n\t/**\n\t * Failure tolerance threshold.\n\t *\n\t * Default is `0`.\n\t */\n\tfailureThreshold?: number | undefined\n}\n\nexport async function toMatchImageSnapshot(\n\tactual: any,\n\toptions?: MatchImageSnapshotOptions | undefined,\n): AsyncExpectationResult {\n\tconst promise = toMatchImageSnapshotInternal(actual, options)\n\tconst test = getCurrentTest()\n\tif (!test) return promise\n\ttest.promises ??= []\n\ttest.promises.push(promise)\n\treturn promise\n}\n\nasync function toMatchImageSnapshotInternal(\n\tactual: any,\n\toptions?: MatchImageSnapshotOptions | undefined,\n): AsyncExpectationResult {\n\tlet subject = await actual\n\tif (subject?.type === imageSnapshotStubSymbol) {\n\t\treturn success\n\t}\n\tif (!isImageSnapshot(subject)) {\n\t\tif (subject?.path && subject?.base64) {\n\t\t\treturn {\n\t\t\t\tpass: false,\n\t\t\t\tactual: subject,\n\t\t\t\tmessage: () =>\n\t\t\t\t\t'`toMatchImageSnapshot()` expects the subject to be the result of `page.imageSnapshot()`, but seems like you are using `page.screenshot()`?',\n\t\t\t}\n\t\t}\n\n\t\tif (typeof subject !== 'object') {\n\t\t\treturn {\n\t\t\t\tpass: false,\n\t\t\t\tactual: subject,\n\t\t\t\tmessage: () =>\n\t\t\t\t\t`\\`toMatchImageSnapshot()\\` expects the subject to be an element, locator, or result of \\`page.imageSnapshot()\\`, but got: \\`${subject}\\``,\n\t\t\t}\n\t\t}\n\t\tsubject = await page.imageSnapshot({ element: subject })\n\t}\n\n\tassertImageSnapshot(subject)\n\n\tconst baseline = await tryReadSnapshot(subject.baselinePath)\n\tif (!baseline) {\n\t\tawait commands.copyFile(subject.resultPath, subject.baselinePath)\n\t\treturn success\n\t}\n\n\tconst originalImage = await toImageData(baseline)\n\tconst [resultImage, baselineImage] = alignImagesToSameSize(subject.image, originalImage)\n\n\t// const test = getCurrentTest()\n\t// console.log('test', test)\n\tconst { pass, diffAmount, diffImage } = compareImage(\n\t\tbaselineImage,\n\t\tresultImage,\n\t\tstate.mergeMatchImageSnapshotOptions(options),\n\t)\n\n\tif (!pass) {\n\t\tif (server.config.snapshotOptions.updateSnapshot === 'all') {\n\t\t\tawait writeSnapshot(`${subject.baselinePath}`, resultImage)\n\t\t\treturn success\n\t\t}\n\t\tawait writeSnapshot(`${subject.diffPath}`, diffImage)\n\t\treturn {\n\t\t\tpass: false,\n\t\t\tmessage: () =>\n\t\t\t\tdedent`Snapshot \\`${state.getName()}\\` mismatched\n\n\t\t\t${\n\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\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: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t: `Expected simage to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t}\n\n\t\t\tExpected: '${resolve(state.getCurrentDir(), subject.baselinePath)}'\n\t\t\tActual: '${resolve(state.getCurrentDir(), subject.resultPath)}'\n\t\t\tDifference: '${resolve(state.getCurrentDir(), subject.diffPath)}'`,\n\t\t}\n\t}\n\treturn success\n}\n\nconst success = {\n\tpass: true,\n\tmessage: () => '',\n}\n\nfunction tryReadSnapshot(path: string): Promise<string | undefined> {\n\treturn commands.readFile(path, { encoding: 'base64' }).catch(() => undefined)\n}\nasync function writeSnapshot(path: string, image: ImageData) {\n\tconst content = (await toDataURL(image)).split(',')[1]!\n\treturn commands.writeFile(path, content, 'base64url')\n}\n\nfunction alignImagesToSameSize(image1: ImageData, image2: ImageData): [image1: ImageData, image2: ImageData] {\n\tif (isSameSize(image1, image2)) {\n\t\treturn [image1, image2]\n\t}\n\tconst size = getMaxSize(image1, image2)\n\tconst resize = createImageResizer(size)\n\treturn [resize(image1), resize(image2)]\n}\n\nfunction getMaxSize(image1: ImageData, image2: ImageData) {\n\tconst width = Math.max(image1.width, image2.width)\n\tconst height = Math.max(image1.height, image2.height)\n\treturn { width, height }\n}\n\nfunction isSameSize(image1: ImageData, image2: ImageData) {\n\treturn image1.width === image2.width && image1.height === image2.height\n}\n\nfunction compareImage(\n\tbaselineImage: ImageData,\n\tresultImage: ImageData,\n\t{ failureThreshold = 0, failureThresholdType = 'pixel', diffOptions }: MatchImageSnapshotOptions = {},\n) {\n\tconst { width, height } = resultImage\n\tconst diffImage = new ImageData(width, height)\n\n\tconst pixelDiff = pixelmatch(resultImage.data, baselineImage.data, diffImage.data, width, height, diffOptions)\n\tconst diffAmount = toThresholdUnit({ failureThresholdType, width, height }, pixelDiff)\n\n\treturn {\n\t\tpass: diffAmount <= failureThreshold,\n\t\tdiffAmount,\n\t\tdiffImage,\n\t}\n}\n\nfunction toThresholdUnit(\n\t{ failureThresholdType, width, height }: { failureThresholdType: 'pixel' | 'percent'; width: number; height: number },\n\tpixelDiff: number,\n): number {\n\tswitch (failureThresholdType) {\n\t\tcase 'pixel':\n\t\t\treturn pixelDiff\n\t\tcase 'percent':\n\t\t\treturn (pixelDiff / (width * height)) * 100\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid failureThresholdType: ${failureThresholdType}`)\n\t}\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { toMatchImageSnapshot, state } from './chunk-UIFACFHT.js';
1
+ import { toMatchImageSnapshot, state } from './chunk-MBASHGUW.js';
2
2
  import { expect } from '@storybook/test';
3
3
 
4
4
  expect.extend({ toMatchImageSnapshot });
@@ -12,5 +12,5 @@ function defineVisPreview() {
12
12
  }
13
13
 
14
14
  export { defineVisPreview, storybookPreviewVis };
15
- //# sourceMappingURL=chunk-NRJIBFW7.js.map
16
- //# sourceMappingURL=chunk-NRJIBFW7.js.map
15
+ //# sourceMappingURL=chunk-NZOXGKP4.js.map
16
+ //# sourceMappingURL=chunk-NZOXGKP4.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/storybook/preview.ts"],"names":[],"mappings":";;;AAKA,MAAO,CAAA,MAAA,CAAO,EAAE,oBAAA,EAAsB,CAAA;AAE/B,IAAM,sBAAsB,gBAAiB;AAE7C,SAAS,gBAAuC,GAAA;AACtD,EAAO,OAAA;AAAA,IACN,WAAW,GAAmB,EAAA;AAC7B,MAAO,OAAA,KAAA,CAAM,WAAW,GAAG,CAAA;AAAA;AAC5B,GACD;AACD","file":"chunk-NRJIBFW7.js","sourcesContent":["import { expect } from '@storybook/test'\nimport type { ProjectAnnotations, Renderer, StoryContext } from 'storybook/internal/types'\nimport { toMatchImageSnapshot } from '../expect.to_match_image_snapshot'\nimport { state } from '../state'\n\nexpect.extend({ toMatchImageSnapshot })\n\nexport const storybookPreviewVis = defineVisPreview()\n\nexport function defineVisPreview<R extends Renderer>() {\n\treturn {\n\t\tbeforeEach(ctx: StoryContext) {\n\t\t\treturn state.setupStory(ctx)\n\t\t},\n\t} satisfies ProjectAnnotations<R>\n}\n"]}
1
+ {"version":3,"sources":["../src/storybook/preview.ts"],"names":[],"mappings":";;;AAKA,MAAO,CAAA,MAAA,CAAO,EAAE,oBAAA,EAAsB,CAAA;AAE/B,IAAM,sBAAsB,gBAAiB;AAE7C,SAAS,gBAAuC,GAAA;AACtD,EAAO,OAAA;AAAA,IACN,WAAW,GAAmB,EAAA;AAC7B,MAAO,OAAA,KAAA,CAAM,WAAW,GAAG,CAAA;AAAA;AAC5B,GACD;AACD","file":"chunk-NZOXGKP4.js","sourcesContent":["import { expect } from '@storybook/test'\nimport type { ProjectAnnotations, Renderer, StoryContext } from 'storybook/internal/types'\nimport { toMatchImageSnapshot } from '../expect.to_match_image_snapshot'\nimport { state } from '../state'\n\nexpect.extend({ toMatchImageSnapshot })\n\nexport const storybookPreviewVis = defineVisPreview()\n\nexport function defineVisPreview<R extends Renderer>() {\n\treturn {\n\t\tbeforeEach(ctx: StoryContext) {\n\t\t\treturn state.setupStory(ctx)\n\t\t},\n\t} satisfies ProjectAnnotations<R>\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { M as MatchImageSnapshotOptions } from './types-BNaVF9pG.js';
2
- export { C as CustomizeSnapshotIdOptions, d as ImageSnapshot, I as ImageSnapshotMatcher, b as ImageSnapshotOptions, V as VisOptions, c as cdp, a as commands, p as page, s as server, t as toMatchImageSnapshot } from './types-BNaVF9pG.js';
1
+ import { M as MatchImageSnapshotOptions } from './types-DZLg7FIU.js';
2
+ export { C as CustomizeSnapshotIdOptions, d as ImageSnapshot, I as ImageSnapshotMatcher, b as ImageSnapshotOptions, V as VisOptions, c as cdp, a as commands, p as page, s as server, t as toMatchImageSnapshot } from './types-DZLg7FIU.js';
3
3
  export { d as defineVisPreview, s as storybookPreviewVis } from './preview-CL-IBwxx.js';
4
4
  import '@vitest/browser/context';
5
5
  import 'vitest';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- export { defineVisPreview, storybookPreviewVis } from './chunk-NRJIBFW7.js';
2
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-UIFACFHT.js';
1
+ export { defineVisPreview, storybookPreviewVis } from './chunk-NZOXGKP4.js';
2
+ export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-MBASHGUW.js';
3
3
 
4
4
  // src/param.ts
5
5
  function defineSnapshotParam(snapshot) {
package/dist/preview.js CHANGED
@@ -1,5 +1,5 @@
1
- import { storybookPreviewVis } from './chunk-NRJIBFW7.js';
2
- import './chunk-UIFACFHT.js';
1
+ import { storybookPreviewVis } from './chunk-NZOXGKP4.js';
2
+ import './chunk-MBASHGUW.js';
3
3
 
4
4
  // src/preview.ts
5
5
  var preview_default = storybookPreviewVis;
@@ -146,15 +146,31 @@ declare const server: {
146
146
  */
147
147
  interface VisOptions extends MatchImageSnapshotOptions, CustomizeSnapshotIdOptions {
148
148
  /**
149
- * The snapshot folder relative to the test file.
149
+ * The snapshot folder relative to the root of the project.
150
150
  *
151
- * Default: `__snapshots__/<local | CI platform>`
151
+ * Default: `__vis__/<local | CI platform>`
152
152
  */
153
- snapshotDir?: string | undefined;
153
+ snapshotRootDir?: string | undefined;
154
+ /**
155
+ * Customize the snapshot subpath.
156
+ *
157
+ * The snapshot subpath is used along with `snapshotRootDir` to determine the folder of the snapshots:
158
+ *
159
+ * - baseline: `<snapshotRootDir>/<snapshotSubpath>/<snapshotId>.png`
160
+ * - result: `<snapshotRootDir>/<snapshotSubpath>/__results__/<snapshotId>.png`
161
+ * - diff: `<snapshotRootDir>/<snapshotSubpath>/__diff_output__/<snapshotId>.png`
162
+ *
163
+ * Typically, you place your test files in a `tests` folder or `src` folder along with your source code.
164
+ * By default, the snapshot subpath is the test file path with that folder removed.
165
+ * This reduces the nesting of the snapshot folders.
166
+ *
167
+ * If you have a non-typical setup, you can set this to customize the snapshot subpath.
168
+ */
169
+ customizeSnapshotSubpath?: (testFilePath: string) => string;
154
170
  /**
155
171
  * Timeout for taking the snapshot.
156
172
  *
157
- * Default: 30000
173
+ * Default: 30000 ms
158
174
  */
159
175
  timeout?: number | undefined;
160
176
  }
@@ -1,5 +1,5 @@
1
- import { V as VisOptions } from './types-BNaVF9pG.js';
2
- export { I as ImageSnapshotMatcher, M as MatchImageSnapshotOptions, c as cdp, a as commands, p as page, s as server, t as toMatchImageSnapshot } from './types-BNaVF9pG.js';
1
+ import { V as VisOptions } from './types-DZLg7FIU.js';
2
+ export { I as ImageSnapshotMatcher, M as MatchImageSnapshotOptions, c as cdp, a as commands, p as page, s as server, t as toMatchImageSnapshot } from './types-DZLg7FIU.js';
3
3
  import '@vitest/browser/context';
4
4
  import 'vitest';
5
5
  import '@vitest/expect';
@@ -1,5 +1,5 @@
1
- import { toMatchImageSnapshot, state, page } from './chunk-UIFACFHT.js';
2
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-UIFACFHT.js';
1
+ import { toMatchImageSnapshot, state, page } from './chunk-MBASHGUW.js';
2
+ export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-MBASHGUW.js';
3
3
  import { expect, beforeAll, afterEach } from 'vitest';
4
4
 
5
5
  // src/should_take_snapshot.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-vis",
3
- "version": "0.11.2",
3
+ "version": "0.12.1",
4
4
  "description": "Storybook Vitest Image Snapshot addon",
5
5
  "keywords": [
6
6
  "image-snapshot",
package/readme.md CHANGED
@@ -211,18 +211,33 @@ createVisConfig({
211
211
  })
212
212
  ```
213
213
 
214
- ### .gitignore snapshot folders
214
+ ### Ignore snapshot folders
215
215
 
216
216
  Some snapshot folders should be ignored by git.
217
217
 
218
218
  With the default snapshot folder structure, you might want to add the following to your `.gitignore`:
219
219
 
220
220
  ```sh
221
+ # .gitignore
221
222
  **/__vis__/*/__diff_output__
222
223
  **/__vis__/*/__results__
223
224
  **/__vis__/local
224
225
  ```
225
226
 
227
+ ## Troubleshooting
228
+
229
+ > Internal server error: Failed to resolve import "pathe"
230
+
231
+ This is likely [a compatibility issue with `pnpm` and `vite` in monorepo](https://discord.com/channels/917386801235247114/1305110710229008435/1305325581839368202).
232
+
233
+ To work around this, you can add [`shamefully-hoist`](https://pnpm.io/npmrc#shamefully-hoist) to your `.npmrc`:
234
+
235
+ ```sh
236
+ # .npmrc
237
+
238
+ shamefully-hoist=true
239
+ ```
240
+
226
241
  [jest-image-snapshot]: https://github.com/americanexpress/jest-image-snapshot
227
242
  [playwright]: https://playwright.dev/docs/screenshots
228
243
  [storybook-addon-vis]: https://github.com/repobuddy/storybook-addon-vis
@@ -1,17 +1,16 @@
1
- import { basename } from 'pathe'
2
1
  import { expect, it } from 'vitest'
3
2
  import { commands, page } from './context.js'
4
3
  import { toSnapshotId } from './image_snapshot.logic.js'
5
4
 
6
5
  it('save file under __results__', async ({ task }) => {
7
6
  const f1 = await page.imageSnapshot()
8
- const filename = basename(task.file.name)
7
+ const filename = task.file.name.slice('src/'.length)
9
8
  expect(f1.resultPath).toMatch(
10
- `__vis__/${await commands.getSnapshotPlatform()}/__results__/${filename}/${toSnapshotId(task.name)}-1.png`,
9
+ `../../../__vis__/${await commands.getSnapshotPlatform()}/__results__/${filename}/${toSnapshotId(task.name)}-1.png`,
11
10
  )
12
11
 
13
12
  const f2 = await page.imageSnapshot()
14
13
  expect(f2.resultPath).toMatch(
15
- `__vis__/${await commands.getSnapshotPlatform()}/__results__/${filename}/${toSnapshotId(task.name)}-2.png`,
14
+ `../../../__vis__/${await commands.getSnapshotPlatform()}/__results__/${filename}/${toSnapshotId(task.name)}-2.png`,
16
15
  )
17
16
  })
@@ -77,7 +77,7 @@ export const MeetFailureThreshold: StoryObj = {
77
77
  // render: () => <div data-testid="subject">unit text</div>,
78
78
  async play({ canvas }) {
79
79
  const subject = canvas.getByTestId('subject')
80
- expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
80
+ await expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
81
81
  failureThreshold: 70,
82
82
  })
83
83
  },
@@ -1,5 +1,6 @@
1
1
  import type { AsyncExpectationResult } from '@vitest/expect'
2
2
  import dedent from 'dedent'
3
+ import { resolve } from 'pathe'
3
4
  import pixelmatch, { type PixelmatchOptions } from 'pixelmatch'
4
5
  import { getCurrentTest } from 'vitest/suite'
5
6
  import { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'
@@ -108,12 +109,12 @@ async function toMatchImageSnapshotInternal(
108
109
  ? options?.failureThresholdType === 'percent'
109
110
  ? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
110
111
  : `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
111
- : `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
112
+ : `Expected simage to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
112
113
  }
113
114
 
114
- Expected: '${subject.baselinePath}'
115
- Actual: '${subject.resultPath}'
116
- Difference: '${subject.diffPath}'`,
115
+ Expected: '${resolve(state.getCurrentDir(), subject.baselinePath)}'
116
+ Actual: '${resolve(state.getCurrentDir(), subject.resultPath)}'
117
+ Difference: '${resolve(state.getCurrentDir(), subject.diffPath)}'`,
117
118
  }
118
119
  }
119
120
  return success
package/src/state.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { basename, join } from 'pathe'
1
+ import { dirname, join, relative } from 'pathe'
2
2
  import type { StoryContext } from 'storybook/internal/types'
3
3
  import { omit, required } from 'type-plus'
4
4
  import { getCurrentTest } from 'vitest/suite'
@@ -11,12 +11,12 @@ import type { VisOptions } from './types.js'
11
11
  function createStore() {
12
12
  // test suite (runner.beforeAll) states
13
13
  let testFilepath: string
14
- let testFilename: string
14
+ let currentDir: string
15
15
  let baselineDir: string
16
16
  let resultDir: string
17
17
  let diffDir: string
18
18
  let snapshot: Record<string, Record<string, { index: number }>>
19
- let suiteOptions: VisOptions = {}
19
+ let suiteOptions: VisOptions = Object.create(null)
20
20
 
21
21
  // story states (story.beforeEach)
22
22
  let tags: string[] | undefined
@@ -29,21 +29,23 @@ function createStore() {
29
29
  resultDir,
30
30
  diffDir,
31
31
  snapshot: {},
32
- async setupSuite(suite: { file: { filepath: string }; name: string }, options?: VisOptions) {
32
+ async setupSuite(suite: { file: { filepath: string }; name: string }, options: VisOptions = {}) {
33
33
  // console.debug('setupSuite', suite.name)
34
-
35
- const snapshotPath = options?.snapshotDir ?? `__vis__/${await commands.getSnapshotPlatform()}`
36
-
37
34
  testFilepath = suite.file.filepath
38
- testFilename = basename(testFilepath)
39
- baselineDir = join(snapshotPath, testFilename)
40
- resultDir = join(snapshotPath, '__results__', testFilename)
41
- diffDir = join(snapshotPath, '__diff_output__', testFilename)
35
+ const projectDir = testFilepath.slice(0, -suite.name.length)
42
36
 
43
- suiteOptions = options ?? {}
37
+ const snapshotPath = resolveSnapshotPath(await getPlatform(), options)
38
+ const snapshotFullPath = join(projectDir, snapshotPath)
39
+ currentDir = dirname(testFilepath)
40
+ const suiteDir = trimSuiteDir(suite.name, options)
41
+ baselineDir = relative(currentDir, join(snapshotFullPath, suiteDir))
42
+ resultDir = relative(currentDir, join(snapshotFullPath, '__results__', suiteDir))
43
+ diffDir = relative(currentDir, join(snapshotFullPath, '__diff_output__', suiteDir))
44
+
45
+ suiteOptions = options
44
46
  snapshot = snapshot ?? {}
45
47
  if (!snapshot[testFilepath]) {
46
- snapshot[testFilepath] = {}
48
+ snapshot[testFilepath] = Object.create(null)
47
49
  await commands.rmDir(resultDir)
48
50
  await commands.rmDir(diffDir)
49
51
  }
@@ -61,11 +63,14 @@ function createStore() {
61
63
  getName() {
62
64
  return taskName
63
65
  },
66
+ getCurrentDir() {
67
+ return currentDir
68
+ },
64
69
  getTimeout(timeout?: number | undefined) {
65
70
  return timeout ?? suiteOptions.timeout ?? 30000
66
71
  },
67
72
  mergeMatchImageSnapshotOptions(options?: MatchImageSnapshotOptions) {
68
- return required(omit(suiteOptions, 'snapshotDir'), parameters?.snapshot, options)
73
+ return required(omit(suiteOptions, 'snapshotRootDir', 'customizeSnapshotSubpath'), parameters?.snapshot, options)
69
74
  },
70
75
  getSnapshotFilePaths(options?: ImageSnapshotOptions | undefined) {
71
76
  const test = getCurrentTest()
@@ -90,3 +95,26 @@ function createStore() {
90
95
  }
91
96
 
92
97
  export const state = createStore()
98
+
99
+ function resolveSnapshotPath(platform: string, options: VisOptions) {
100
+ return options.snapshotRootDir ?? `__vis__/${platform}`
101
+ }
102
+
103
+ function trimSuiteDir(suiteName: string, options: VisOptions) {
104
+ const customizeSnapshotSubpath = options.customizeSnapshotSubpath ?? defaultCustomizeSnapshotSubpath
105
+ return customizeSnapshotSubpath(suiteName)
106
+ }
107
+
108
+ function defaultCustomizeSnapshotSubpath(suiteName: string) {
109
+ const [suiteDir] = suiteName.split('/', 1)
110
+ if (['tests', 'test', 'src', 'source', 'js', 'ts', 'lib'].includes(suiteDir))
111
+ return suiteName.slice(suiteDir.length + 1)
112
+ return suiteName
113
+ }
114
+
115
+ let platformP: Promise<string>
116
+ function getPlatform() {
117
+ if (platformP) return platformP
118
+
119
+ return (platformP = commands.getSnapshotPlatform())
120
+ }
package/src/types.ts CHANGED
@@ -8,15 +8,32 @@ import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot
8
8
  */
9
9
  export interface VisOptions extends MatchImageSnapshotOptions, CustomizeSnapshotIdOptions {
10
10
  /**
11
- * The snapshot folder relative to the test file.
11
+ * The snapshot folder relative to the root of the project.
12
12
  *
13
- * Default: `__snapshots__/<local | CI platform>`
13
+ * Default: `__vis__/<local | CI platform>`
14
14
  */
15
- snapshotDir?: string | undefined
15
+ snapshotRootDir?: string | undefined
16
+
17
+ /**
18
+ * Customize the snapshot subpath.
19
+ *
20
+ * The snapshot subpath is used along with `snapshotRootDir` to determine the folder of the snapshots:
21
+ *
22
+ * - baseline: `<snapshotRootDir>/<snapshotSubpath>/<snapshotId>.png`
23
+ * - result: `<snapshotRootDir>/<snapshotSubpath>/__results__/<snapshotId>.png`
24
+ * - diff: `<snapshotRootDir>/<snapshotSubpath>/__diff_output__/<snapshotId>.png`
25
+ *
26
+ * Typically, you place your test files in a `tests` folder or `src` folder along with your source code.
27
+ * By default, the snapshot subpath is the test file path with that folder removed.
28
+ * This reduces the nesting of the snapshot folders.
29
+ *
30
+ * If you have a non-typical setup, you can set this to customize the snapshot subpath.
31
+ */
32
+ customizeSnapshotSubpath?: (testFilePath: string) => string
16
33
  /**
17
34
  * Timeout for taking the snapshot.
18
35
  *
19
- * Default: 30000
36
+ * Default: 30000 ms
20
37
  */
21
38
  timeout?: number | undefined
22
39
  }
package/src/vis.spec.ts CHANGED
@@ -4,7 +4,7 @@ import { createVisConfig } from './vis.js'
4
4
 
5
5
  it('can define the snapshot root folder relative to the root of the project', async () => {
6
6
  await createVisConfig({
7
- snapshotDir: '_sp_',
7
+ snapshotRootDir: '_sp_',
8
8
  }).beforeAll({ name: 'some.test.ts', file: { filepath: 'dummy/some.test.ts' } }),
9
9
  expect(state.getSnapshotFilePaths()).toMatchObject({
10
10
  baselinePath: '_sp_/some.test.ts/can-define-the-snapshot-root-folder-relative-to-the-root-of-the-project-1.png',
@@ -37,7 +37,7 @@ it('can define default failureThreshold', async () => {
37
37
 
38
38
  it('can define default customizeSnapshotId', async () => {
39
39
  await createVisConfig({
40
- snapshotDir: '_sp_',
40
+ snapshotRootDir: '_sp_',
41
41
  customizeSnapshotId: (id) => id.slice(0, id.indexOf('-')),
42
42
  }).beforeAll({ name: 'some.test.ts', file: { filepath: 'dummy/some.test.ts' } })
43
43
 
@@ -6,14 +6,10 @@ import * as ImageDataStories from './image_data.stories.js'
6
6
 
7
7
  const { ConversionRoundtrip } = composeStories(ImageDataStories)
8
8
 
9
- it('should delete the results and diffs folder when the during `beforeAll`', async ({ task }) => {
9
+ it('should delete the results and diffs folder when the during `beforeAll`', async () => {
10
10
  const snapshotPlatform = await commands.getSnapshotPlatform()
11
- await expect(
12
- commands.existDir(`__vis__/${snapshotPlatform}/__diff_output__/${basename(task.file.name)}`),
13
- ).resolves.toBe(false)
14
- await expect(commands.existDir(`__vis__/${snapshotPlatform}/__results__/${basename(task.file.name)}`)).resolves.toBe(
15
- false,
16
- )
11
+ await expect(commands.existDir(`__vis__/${snapshotPlatform}/__diff_output__`)).resolves.toBe(false)
12
+ await expect(commands.existDir(`__vis__/${snapshotPlatform}/__results__`)).resolves.toBe(false)
17
13
  await ConversionRoundtrip.run()
18
14
  await expect(page.imageSnapshot()).toMatchImageSnapshot()
19
15
  })
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/state.ts","../src/@vitest/browser/page.has_image_snapshot.ts","../src/image_data.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/context.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts"],"names":["state","ctx","id","index","getCurrentTest"],"mappings":"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","file":"chunk-UIFACFHT.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\n","import { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot } from './types.js'\n\nexport function isImageSnapshot(subject: any): subject is ImageSnapshot {\n\treturn !!subject && subject.type === imageSnapshotSymbol\n}\n\nexport function assertImageSnapshot(subject: any): asserts subject is ImageSnapshot {\n\tif (!isImageSnapshot(subject)) {\n\t\tthrow new Error('Expected subject to be an image snapshot')\n\t}\n}\n\nexport function toSnapshotId(taskName: string) {\n\treturn `${taskName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`\n}\n","import { basename, join } from 'pathe'\nimport type { StoryContext } from 'storybook/internal/types'\nimport { omit, required } from 'type-plus'\nimport { getCurrentTest } from 'vitest/suite'\nimport { commands } from './@vitest/browser/context.js'\nimport { toSnapshotId } from './@vitest/browser/image_snapshot.logic'\nimport type { ImageSnapshotOptions } from './@vitest/browser/types'\nimport type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\nimport type { VisOptions } from './types.js'\n\nfunction createStore() {\n\t// test suite (runner.beforeAll) states\n\tlet testFilepath: string\n\tlet testFilename: string\n\tlet baselineDir: string\n\tlet resultDir: string\n\tlet diffDir: string\n\tlet snapshot: Record<string, Record<string, { index: number }>>\n\tlet suiteOptions: VisOptions = {}\n\n\t// story states (story.beforeEach)\n\tlet tags: string[] | undefined\n\tlet parameters: Record<string, any> | undefined\n\tlet taskName: string\n\tlet id: string\n\n\tconst state = {\n\t\tbaselineDir,\n\t\tresultDir,\n\t\tdiffDir,\n\t\tsnapshot: {},\n\t\tasync setupSuite(suite: { file: { filepath: string }; name: string }, options?: VisOptions) {\n\t\t\t// console.debug('setupSuite', suite.name)\n\n\t\t\tconst snapshotPath = options?.snapshotDir ?? `__vis__/${await commands.getSnapshotPlatform()}`\n\n\t\t\ttestFilepath = suite.file.filepath\n\t\t\ttestFilename = basename(testFilepath)\n\t\t\tbaselineDir = join(snapshotPath, testFilename)\n\t\t\tresultDir = join(snapshotPath, '__results__', testFilename)\n\t\t\tdiffDir = join(snapshotPath, '__diff_output__', testFilename)\n\n\t\t\tsuiteOptions = options ?? {}\n\t\t\tsnapshot = snapshot ?? {}\n\t\t\tif (!snapshot[testFilepath]) {\n\t\t\t\tsnapshot[testFilepath] = {}\n\t\t\t\tawait commands.rmDir(resultDir)\n\t\t\t\tawait commands.rmDir(diffDir)\n\t\t\t}\n\t\t},\n\t\tsetupStory(ctx: StoryContext) {\n\t\t\t// console.debug('setupStory', ctx.name)\n\t\t\ttags = ctx.tags\n\t\t\tparameters = ctx.parameters\n\t\t},\n\t\tshouldTakeSnapshot() {\n\t\t\t// console.debug('shouldTakeSnapshot', taskName, tags)\n\t\t\tif (!tags) return false\n\t\t\treturn tags.lastIndexOf('!snapshot') < tags.lastIndexOf('snapshot')\n\t\t},\n\t\tgetName() {\n\t\t\treturn taskName\n\t\t},\n\t\tgetTimeout(timeout?: number | undefined) {\n\t\t\treturn timeout ?? suiteOptions.timeout ?? 30000\n\t\t},\n\t\tmergeMatchImageSnapshotOptions(options?: MatchImageSnapshotOptions) {\n\t\t\treturn required(omit(suiteOptions, 'snapshotDir'), parameters?.snapshot, options)\n\t\t},\n\t\tgetSnapshotFilePaths(options?: ImageSnapshotOptions | undefined) {\n\t\t\tconst test = getCurrentTest()\n\t\t\ttaskName = test.name\n\t\t\tid = toSnapshotId(taskName)\n\t\t\tsnapshot[testFilepath][id] = snapshot[testFilepath][id] ?? { index: 1 }\n\n\t\t\tconst index = snapshot[testFilepath][id]!.index\n\t\t\tconst customizeSnapshotId =\n\t\t\t\tsuiteOptions.customizeSnapshotId ?? options?.customizeSnapshotId ?? ((id, index) => `${id}-${index}`)\n\t\t\tconst snapshotFilename = `${customizeSnapshotId(id, index)}.png`\n\t\t\tconst baselinePath = join(baselineDir, snapshotFilename)\n\t\t\tconst resultPath = join(resultDir, snapshotFilename)\n\t\t\tconst diffPath = join(diffDir, snapshotFilename)\n\t\t\treturn { snapshotFilename, baselinePath, resultPath, diffPath }\n\t\t},\n\t\tincrementSnapshotIndex() {\n\t\t\tsnapshot[testFilepath][id]!.index++\n\t\t},\n\t}\n\treturn state\n}\n\nexport const state = createStore()\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { join } from 'pathe'\nimport { state } from '../../state'\nimport { commands } from './context'\nimport type { CustomizeSnapshotIdOptions } from './types'\n\nexport interface HasImageSnapshotAction {\n\thasImageSnapshot(this: BrowserPage, options?: CustomizeSnapshotIdOptions | undefined): Promise<boolean>\n}\n\n/**\n * Check if the snapshot image exists.\n */\nexport async function hasImageSnapshot(this: BrowserPage, options?: CustomizeSnapshotIdOptions | undefined) {\n\tconst { baselinePath } = state.getSnapshotFilePaths(options)\n\treturn commands.existFile(baselinePath)\n}\n","export function toImageData(base64: string) {\n\treturn new Promise<ImageData>((resolve, reject) => {\n\t\tconst img = new Image()\n\t\timg.src = `data:image/png;base64,${base64}`\n\n\t\timg.onload = () => {\n\t\t\tconst canvas = document.createElement('canvas')\n\t\t\tcanvas.width = img.width\n\t\t\tcanvas.height = img.height\n\t\t\tconst ctx = canvas.getContext('2d')!\n\t\t\tctx.drawImage(img, 0, 0)\n\t\t\tresolve(ctx.getImageData(0, 0, canvas.width, canvas.height))\n\t\t}\n\n\t\timg.onerror = () => {\n\t\t\treject(new Error('Failed to load image'))\n\t\t}\n\t})\n}\n\nexport async function toDataURL(imageData: ImageData) {\n\tconst canvas = document.createElement('canvas')\n\tcanvas.width = imageData.width\n\tcanvas.height = imageData.height\n\tconst ctx = canvas.getContext('2d')!\n\tctx.putImageData(imageData, 0, 0)\n\treturn new Promise<string>((resolve) => {\n\t\tcanvas.toBlob((blob) => {\n\t\t\tconst reader = new FileReader()\n\t\t\treader.onload = () => {\n\t\t\t\tresolve(reader.result as string)\n\t\t\t}\n\t\t\treader.readAsDataURL(blob!)\n\t\t})\n\t})\n}\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { toImageData } from '../../image_data.js'\nimport { state } from '../../state.js'\nimport { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot, ImageSnapshotOptions } from './types.js'\n\nexport interface ImageSnapshotAction {\n\timageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions | undefined): Promise<ImageSnapshot>\n}\n\nexport async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst { snapshotFilename, baselinePath, resultPath, diffPath } = state.getSnapshotFilePaths(options)\n\t// console.debug('taking snapshot', state.getName(), snapshotFilename)\n\tconst screenshot = await this.screenshot({\n\t\tbase64: true,\n\t\tpath: resultPath,\n\t\telement: options?.element,\n\t\ttimeout: state.getTimeout(options?.timeout),\n\t})\n\tstate.incrementSnapshotIndex()\n\tconst image = await toImageData(screenshot.base64)\n\n\treturn {\n\t\ttype: imageSnapshotSymbol,\n\t\tsnapshotFilename,\n\t\tbaselinePath,\n\t\tresultPath,\n\t\tdiffPath,\n\t\tbase64: screenshot.base64,\n\t\timage,\n\t}\n}\n","import {\n\tgetByAltText,\n\tgetByLabelText,\n\tgetByPlaceholderText,\n\tgetByRole,\n\tgetByTestId,\n\tgetByText,\n\tgetByTitle,\n} from '@storybook/test'\nimport type { BrowserCommands, BrowserPage, CDPSession, Platform } from '@vitest/browser/context'\nimport type { SerializedConfig } from 'vitest'\nimport { imageSnapshotStubSymbol } from './constants'\nimport { hasImageSnapshot } from './page.has_image_snapshot'\nimport { imageSnapshot } from './page.image_snapshot'\n\nlet ctx: Awaited<typeof import('@vitest/browser/context')>\n\nif ((globalThis as any).__vitest_browser__) {\n\timport('@vitest/browser/context').then((m) => {\n\t\tctx = m\n\t\tpage.extend({ imageSnapshot, hasImageSnapshot })\n\t})\n}\n\n// TODO: stub the functions within the `CDPSession`\n// when we better understands the use cases\nexport function cdp(): CDPSession {\n\treturn ctx?.cdp() ?? ({} as any)\n}\n\nexport const commands = new Proxy<BrowserCommands>({} as any, {\n\tget(target, prop) {\n\t\treturn (target as any)[prop] ?? (ctx?.commands as any)[prop]\n\t},\n})\n\nexport const page = new Proxy<BrowserPage>(\n\t{\n\t\tgetByAltText,\n\t\tgetByRole,\n\t\tgetByTestId,\n\t\tgetByText,\n\t\tgetByTitle,\n\t\tgetByPlaceholder: getByPlaceholderText,\n\t\tgetByLabelText,\n\t} as any,\n\t{\n\t\tget(target, prop) {\n\t\t\treturn ((target as any)[prop] ?? ctx?.page)\n\t\t\t\t? (ctx?.page as any)[prop]\n\t\t\t\t: () => {\n\t\t\t\t\t\tif (prop === 'imageSnapshot') {\n\t\t\t\t\t\t\treturn { type: imageSnapshotStubSymbol }\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (prop === 'hasImageSnapshot') {\n\t\t\t\t\t\t\treturn false\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconsole.info(`\\`page.${prop.toString()}\\` does not exist when running in browser`)\n\t\t\t\t\t}\n\t\t},\n\t},\n)\n\nexport const server = new Proxy<{\n\t/**\n\t * Platform the Vitest server is running on.\n\t * The same as calling `process.platform` on the server.\n\t */\n\tplatform: Platform\n\t/**\n\t * Runtime version of the Vitest server.\n\t * The same as calling `process.version` on the server.\n\t */\n\tversion: string\n\t/**\n\t * Name of the browser provider.\n\t */\n\tprovider: string\n\t/**\n\t * Name of the current browser.\n\t */\n\tbrowser: string\n\t/**\n\t * Available commands for the browser.\n\t * @see {@link https://vitest.dev/guide/browser/commands}\n\t */\n\tcommands: BrowserCommands\n\t/**\n\t * Serialized test config.\n\t */\n\tconfig: SerializedConfig\n}>({} as any, {\n\tget(target, prop) {\n\t\treturn (target as any)[prop] ?? (ctx?.server as any)[prop]\n\t},\n})\n","export const createImageResizer =\n\t({ width, height }: { width: number; height: number }) =>\n\t(image: ImageData) => {\n\t\tif (image.width === width && image.height === height) {\n\t\t\treturn image\n\t\t}\n\t\tconst inArea = (x: number, y: number) => y <= image.height && x <= image.width\n\t\tconst result = new ImageData(width, height, { colorSpace: image.colorSpace })\n\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tfor (let x = 0; x < width; x++) {\n\t\t\t\tconst idx = (width * y + x) << 2\n\t\t\t\tif (inArea(x, y)) {\n\t\t\t\t\tconst old = (image.width * y + x) << 2\n\t\t\t\t\tresult.data[idx] = image.data[old]\n\t\t\t\t\tresult.data[idx + 1] = image.data[old + 1]\n\t\t\t\t\tresult.data[idx + 2] = image.data[old + 2]\n\t\t\t\t\tresult.data[idx + 3] = image.data[old + 3]\n\t\t\t\t} else {\n\t\t\t\t\tresult.data[idx] = 0\n\t\t\t\t\tresult.data[idx + 1] = 0\n\t\t\t\t\tresult.data[idx + 2] = 0\n\t\t\t\t\tresult.data[idx + 3] = 64\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result\n\t}\n\n// slower\nexport const createImageResizer2 =\n\t({ width, height }: { width: number; height: number }) =>\n\t(image: ImageData) => {\n\t\tif (image.width === width && image.height === height) {\n\t\t\treturn image\n\t\t}\n\t\tconst inArea = (x: number, y: number) => y <= image.height && x <= image.width\n\t\tconst result = new ImageData(width, height, { colorSpace: image.colorSpace })\n\n\t\tconst copyBytes = image.width * 4\n\t\tfor (let y = 0; y < height; y++) {\n\t\t\tresult.data.set(image.data.slice(y * image.width, copyBytes), y * width * 4)\n\t\t\tfor (let x = image.width; x < width; x++) {\n\t\t\t\tconst idx = (width * y + x) << 2\n\t\t\t\tif (!inArea(x, y)) {\n\t\t\t\t\tresult.data[idx] = 0\n\t\t\t\t\tresult.data[idx + 1] = 0\n\t\t\t\t\tresult.data[idx + 2] = 0\n\t\t\t\t\tresult.data[idx + 3] = 64\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn result\n\t}\n","import type { AsyncExpectationResult } from '@vitest/expect'\nimport dedent from 'dedent'\nimport pixelmatch, { type PixelmatchOptions } from 'pixelmatch'\nimport { getCurrentTest } from 'vitest/suite'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands, page, server } from './@vitest/browser/context.js'\nimport { assertImageSnapshot, isImageSnapshot } from './@vitest/browser/image_snapshot.logic.js'\nimport { toDataURL, toImageData } from './image_data.js'\nimport { createImageResizer } from './image_resizer.js'\nimport { state } from './state.js'\n\nexport interface ImageSnapshotMatcher {\n\ttoMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>\n}\n\nexport type MatchImageSnapshotOptions = {\n\t/**\n\t * Custom options passed to 'pixelmatch'\n\t */\n\tdiffOptions?: PixelmatchOptions | undefined\n\t/**\n\t * Failure threshold should measure in `pixel` or `percent`.\n\t *\n\t * Default is `pixel`.\n\t */\n\tfailureThresholdType?: 'pixel' | 'percent' | undefined\n\t/**\n\t * Failure tolerance threshold.\n\t *\n\t * Default is `0`.\n\t */\n\tfailureThreshold?: number | undefined\n}\n\nexport async function toMatchImageSnapshot(\n\tactual: any,\n\toptions?: MatchImageSnapshotOptions | undefined,\n): AsyncExpectationResult {\n\tconst promise = toMatchImageSnapshotInternal(actual, options)\n\tconst test = getCurrentTest()\n\tif (!test) return promise\n\ttest.promises ??= []\n\ttest.promises.push(promise)\n\treturn promise\n}\n\nasync function toMatchImageSnapshotInternal(\n\tactual: any,\n\toptions?: MatchImageSnapshotOptions | undefined,\n): AsyncExpectationResult {\n\tlet subject = await actual\n\tif (subject?.type === imageSnapshotStubSymbol) {\n\t\treturn success\n\t}\n\tif (!isImageSnapshot(subject)) {\n\t\tif (subject?.path && subject?.base64) {\n\t\t\treturn {\n\t\t\t\tpass: false,\n\t\t\t\tactual: subject,\n\t\t\t\tmessage: () =>\n\t\t\t\t\t'`toMatchImageSnapshot()` expects the subject to be the result of `page.imageSnapshot()`, but seems like you are using `page.screenshot()`?',\n\t\t\t}\n\t\t}\n\n\t\tif (typeof subject !== 'object') {\n\t\t\treturn {\n\t\t\t\tpass: false,\n\t\t\t\tactual: subject,\n\t\t\t\tmessage: () =>\n\t\t\t\t\t`\\`toMatchImageSnapshot()\\` expects the subject to be an element, locator, or result of \\`page.imageSnapshot()\\`, but got: \\`${subject}\\``,\n\t\t\t}\n\t\t}\n\t\tsubject = await page.imageSnapshot({ element: subject })\n\t}\n\n\tassertImageSnapshot(subject)\n\n\tconst baseline = await tryReadSnapshot(subject.baselinePath)\n\tif (!baseline) {\n\t\tawait commands.copyFile(subject.resultPath, subject.baselinePath)\n\t\treturn success\n\t}\n\n\tconst originalImage = await toImageData(baseline)\n\tconst [resultImage, baselineImage] = alignImagesToSameSize(subject.image, originalImage)\n\n\t// const test = getCurrentTest()\n\t// console.log('test', test)\n\tconst { pass, diffAmount, diffImage } = compareImage(\n\t\tbaselineImage,\n\t\tresultImage,\n\t\tstate.mergeMatchImageSnapshotOptions(options),\n\t)\n\n\tif (!pass) {\n\t\tif (server.config.snapshotOptions.updateSnapshot === 'all') {\n\t\t\tawait writeSnapshot(`${subject.baselinePath}`, resultImage)\n\t\t\treturn success\n\t\t}\n\t\tawait writeSnapshot(`${subject.diffPath}`, diffImage)\n\t\treturn {\n\t\t\tpass: false,\n\t\t\tmessage: () =>\n\t\t\t\tdedent`Snapshot \\`${state.getName()}\\` mismatched\n\n\t\t\t${\n\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\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: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t}\n\n\t\t\tExpected: '${subject.baselinePath}'\n\t\t\tActual: '${subject.resultPath}'\n\t\t\tDifference: '${subject.diffPath}'`,\n\t\t}\n\t}\n\treturn success\n}\n\nconst success = {\n\tpass: true,\n\tmessage: () => '',\n}\n\nfunction tryReadSnapshot(path: string): Promise<string | undefined> {\n\treturn commands.readFile(path, { encoding: 'base64' }).catch(() => undefined)\n}\nasync function writeSnapshot(path: string, image: ImageData) {\n\tconst content = (await toDataURL(image)).split(',')[1]!\n\treturn commands.writeFile(path, content, 'base64url')\n}\n\nfunction alignImagesToSameSize(image1: ImageData, image2: ImageData): [image1: ImageData, image2: ImageData] {\n\tif (isSameSize(image1, image2)) {\n\t\treturn [image1, image2]\n\t}\n\tconst size = getMaxSize(image1, image2)\n\tconst resize = createImageResizer(size)\n\treturn [resize(image1), resize(image2)]\n}\n\nfunction getMaxSize(image1: ImageData, image2: ImageData) {\n\tconst width = Math.max(image1.width, image2.width)\n\tconst height = Math.max(image1.height, image2.height)\n\treturn { width, height }\n}\n\nfunction isSameSize(image1: ImageData, image2: ImageData) {\n\treturn image1.width === image2.width && image1.height === image2.height\n}\n\nfunction compareImage(\n\tbaselineImage: ImageData,\n\tresultImage: ImageData,\n\t{ failureThreshold = 0, failureThresholdType = 'pixel', diffOptions }: MatchImageSnapshotOptions = {},\n) {\n\tconst { width, height } = resultImage\n\tconst diffImage = new ImageData(width, height)\n\n\tconst pixelDiff = pixelmatch(resultImage.data, baselineImage.data, diffImage.data, width, height, diffOptions)\n\tconst diffAmount = toThresholdUnit({ failureThresholdType, width, height }, pixelDiff)\n\n\treturn {\n\t\tpass: diffAmount <= failureThreshold,\n\t\tdiffAmount,\n\t\tdiffImage,\n\t}\n}\n\nfunction toThresholdUnit(\n\t{ failureThresholdType, width, height }: { failureThresholdType: 'pixel' | 'percent'; width: number; height: number },\n\tpixelDiff: number,\n): number {\n\tswitch (failureThresholdType) {\n\t\tcase 'pixel':\n\t\t\treturn pixelDiff\n\t\tcase 'percent':\n\t\t\treturn (pixelDiff / (width * height)) * 100\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid failureThresholdType: ${failureThresholdType}`)\n\t}\n}\n"]}