storybook-addon-vis 0.7.0 → 0.8.0

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,4 +1,4 @@
1
- import { state } from './chunk-GN5INPRD.js';
1
+ import { state } from './chunk-M4DV45WL.js';
2
2
 
3
3
  // src/storybook/preview.ts
4
4
  var visStorybookPreview = {
@@ -8,5 +8,5 @@ var visStorybookPreview = {
8
8
  };
9
9
 
10
10
  export { visStorybookPreview };
11
- //# sourceMappingURL=chunk-V67IWJFT.js.map
12
- //# sourceMappingURL=chunk-V67IWJFT.js.map
11
+ //# sourceMappingURL=chunk-3NZUGVTR.js.map
12
+ //# sourceMappingURL=chunk-3NZUGVTR.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/storybook/preview.ts"],"names":[],"mappings":";;;AAGO,IAAM,mBAAoD,GAAA;AAAA,EAChE,WAAW,GAAmB,EAAA;AAC7B,IAAA,KAAA,CAAM,aAAa,GAAI,CAAA,UAAA;AAAA;AAEzB","file":"chunk-V67IWJFT.js","sourcesContent":["import type { ProjectAnnotations, Renderer, StoryContext } from 'storybook/internal/types'\nimport { state } from '../state'\n\nexport const visStorybookPreview: ProjectAnnotations<Renderer> = {\n\tbeforeEach(ctx: StoryContext) {\n\t\tstate.parameters = ctx.parameters\n\t},\n}\n"]}
1
+ {"version":3,"sources":["../src/storybook/preview.ts"],"names":[],"mappings":";;;AAGO,IAAM,mBAAoD,GAAA;AAAA,EAChE,WAAW,GAAmB,EAAA;AAC7B,IAAA,KAAA,CAAM,aAAa,GAAI,CAAA,UAAA;AAAA;AAEzB","file":"chunk-3NZUGVTR.js","sourcesContent":["import type { ProjectAnnotations, Renderer, StoryContext } from 'storybook/internal/types'\nimport { state } from '../state'\n\nexport const visStorybookPreview: ProjectAnnotations<Renderer> = {\n\tbeforeEach(ctx: StoryContext) {\n\t\tstate.parameters = ctx.parameters\n\t},\n}\n"]}
@@ -1,17 +1,13 @@
1
- import { state } from './chunk-GN5INPRD.js';
1
+ import { imageSnapshotStubSymbol, state, imageSnapshotSymbol, isImageSnapshot, assertImageSnapshot } from './chunk-M4DV45WL.js';
2
2
  import { getByAltText, getByRole, getByTestId, getByText, getByTitle, getByPlaceholderText, getByLabelText } from '@storybook/test';
3
- import { join } from 'pathe';
3
+ import 'pathe';
4
+ import { getCurrentTest } from 'vitest/suite';
4
5
  import dedent from 'dedent';
5
6
  import pixelmatch from 'pixelmatch';
6
7
  import { required } from 'type-plus';
7
8
 
8
- // src/@vitest/browser/constants.ts
9
- var imageSnapshotSymbol = Symbol.for("storybook-addon-vis/imageSnapshot");
10
- var imageSnapshotStubSymbol = Symbol.for("storybook-addon-vis/imageSnapshotStub");
11
9
  async function hasImageSnapshot(options) {
12
- const index = state.snapshot[state.testFilepath][state.id].index;
13
- const snapshotFilename = options?.customizeSnapshotId ? `${options.customizeSnapshotId(state.id, index)}.png` : `${state.id}-${index}.png`;
14
- const baselinePath = join(state.baselineDir, snapshotFilename);
10
+ const { baselinePath } = state.getSnapshotFilePaths(options);
15
11
  return commands.existFile(baselinePath);
16
12
  }
17
13
 
@@ -52,17 +48,14 @@ async function toDataURL(imageData) {
52
48
 
53
49
  // src/@vitest/browser/page.image_snapshot.ts
54
50
  async function imageSnapshot(options) {
55
- const index = state.snapshot[state.testFilepath][state.id].index++;
56
- const snapshotFilename = options?.customizeSnapshotId ? `${options.customizeSnapshotId(state.id, index)}.png` : `${state.id}-${index}.png`;
57
- const baselinePath = join(state.baselineDir, snapshotFilename);
58
- const resultPath = join(state.resultDir, snapshotFilename);
59
- const diffPath = join(state.diffDir, snapshotFilename);
51
+ const { snapshotFilename, baselinePath, resultPath, diffPath } = state.getSnapshotFilePaths(options);
60
52
  const screenshot = await this.screenshot({
61
53
  base64: true,
62
54
  path: resultPath,
63
55
  element: options?.element,
64
56
  timeout: options?.timeout ?? 3e3
65
57
  });
58
+ state.incrementSnapshotIndex();
66
59
  const image = await toImageData(screenshot.base64);
67
60
  return {
68
61
  type: imageSnapshotSymbol,
@@ -118,19 +111,6 @@ var server = new Proxy({}, {
118
111
  }
119
112
  });
120
113
 
121
- // src/@vitest/browser/image_snapshot.logic.ts
122
- function isImageSnapshot(subject) {
123
- return !!subject && subject.type === imageSnapshotSymbol;
124
- }
125
- function assertImageSnapshot(subject) {
126
- if (!isImageSnapshot(subject)) {
127
- throw new Error("Expected subject to be an image snapshot");
128
- }
129
- }
130
- function toSnapshotId(taskName) {
131
- return `${taskName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}`;
132
- }
133
-
134
114
  // src/image_resizer.ts
135
115
  var createImageResizer = ({ width, height }) => (image) => {
136
116
  if (image.width === width && image.height === height) {
@@ -160,6 +140,13 @@ var createImageResizer = ({ width, height }) => (image) => {
160
140
 
161
141
  // src/expect.to_match_image_snapshot.ts
162
142
  async function toMatchImageSnapshot(actual, options) {
143
+ const promise = toMatchImageSnapshotInternal(actual, options);
144
+ const test = getCurrentTest();
145
+ test.promises ??= [];
146
+ test.promises.push(promise);
147
+ return promise;
148
+ }
149
+ async function toMatchImageSnapshotInternal(actual, options) {
163
150
  const subject = await actual;
164
151
  if (subject?.type === imageSnapshotStubSymbol) {
165
152
  return success;
@@ -258,6 +245,6 @@ function toThresholdUnit({ failureThresholdType, width, height }, pixelDiff) {
258
245
  }
259
246
  }
260
247
 
261
- export { cdp, commands, page, server, toMatchImageSnapshot, toSnapshotId };
262
- //# sourceMappingURL=chunk-LAWHEQMO.js.map
263
- //# sourceMappingURL=chunk-LAWHEQMO.js.map
248
+ export { cdp, commands, page, server, toMatchImageSnapshot };
249
+ //# sourceMappingURL=chunk-3ZUXLFHJ.js.map
250
+ //# sourceMappingURL=chunk-3ZUXLFHJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../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":["ctx","getCurrentTest"],"mappings":"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","file":"chunk-3ZUXLFHJ.js","sourcesContent":["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 { join } from 'pathe'\nimport { getCurrentTest } from 'vitest/suite'\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\tconst screenshot = await this.screenshot({\n\t\tbase64: true,\n\t\tpath: resultPath,\n\t\telement: options?.element,\n\t\ttimeout: options?.timeout ?? 3000,\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\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 { required } from 'type-plus'\nimport { getCurrentTest } from 'vitest/suite'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands, server } from './@vitest/browser/context.js'\nimport { assertImageSnapshot, isImageSnapshot, toSnapshotId } 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\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\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\tconst 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\t// subject = await page.imageSnapshot({ element: actual })\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\toptions = required(state.parameters?.snapshot, options)\n\tconst { pass, diffAmount, diffImage } = compareImage(baselineImage, resultImage, options)\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.taskName}\\` 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"]}
@@ -0,0 +1,48 @@
1
+ import { join } from 'pathe';
2
+ import { getCurrentTest } from 'vitest/suite';
3
+
4
+ // src/state.ts
5
+
6
+ // src/@vitest/browser/constants.ts
7
+ var imageSnapshotSymbol = Symbol.for("storybook-addon-vis/imageSnapshot");
8
+ var imageSnapshotStubSymbol = Symbol.for("storybook-addon-vis/imageSnapshotStub");
9
+
10
+ // src/@vitest/browser/image_snapshot.logic.ts
11
+ function isImageSnapshot(subject) {
12
+ return !!subject && subject.type === imageSnapshotSymbol;
13
+ }
14
+ function assertImageSnapshot(subject) {
15
+ if (!isImageSnapshot(subject)) {
16
+ throw new Error("Expected subject to be an image snapshot");
17
+ }
18
+ }
19
+ function toSnapshotId(taskName) {
20
+ return `${taskName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}`;
21
+ }
22
+
23
+ // src/state.ts
24
+ var state = {
25
+ name: "",
26
+ testFilepath: "",
27
+ taskName: "",
28
+ snapshot: {},
29
+ getSnapshotFilePaths(options) {
30
+ const test = getCurrentTest();
31
+ state.taskName = test.name;
32
+ const id = state.id = toSnapshotId(state.taskName);
33
+ state.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 };
34
+ const index = state.snapshot[state.testFilepath][state.id].index;
35
+ const snapshotFilename = options?.customizeSnapshotId ? `${options.customizeSnapshotId(state.id, index)}.png` : `${state.id}-${index}.png`;
36
+ const baselinePath = join(state.baselineDir, snapshotFilename);
37
+ const resultPath = join(state.resultDir, snapshotFilename);
38
+ const diffPath = join(state.diffDir, snapshotFilename);
39
+ return { snapshotFilename, baselinePath, resultPath, diffPath };
40
+ },
41
+ incrementSnapshotIndex() {
42
+ state.snapshot[state.testFilepath][state.id].index++;
43
+ }
44
+ };
45
+
46
+ export { assertImageSnapshot, imageSnapshotStubSymbol, imageSnapshotSymbol, isImageSnapshot, state };
47
+ //# sourceMappingURL=chunk-M4DV45WL.js.map
48
+ //# sourceMappingURL=chunk-M4DV45WL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/state.ts"],"names":[],"mappings":";;;;;;AAAa,IAAA,mBAAA,GAAsB,MAAO,CAAA,GAAA,CAAI,mCAAmC;AAEpE,IAAA,uBAAA,GAA0B,MAAO,CAAA,GAAA,CAAI,uCAAuC;;;ACClF,SAAS,gBAAgB,OAAwC,EAAA;AACvE,EAAA,OAAO,CAAC,CAAC,OAAW,IAAA,OAAA,CAAQ,IAAS,KAAA,mBAAA;AACtC;AAEO,SAAS,oBAAoB,OAAgD,EAAA;AACnF,EAAI,IAAA,CAAC,eAAgB,CAAA,OAAO,CAAG,EAAA;AAC9B,IAAM,MAAA,IAAI,MAAM,0CAA0C,CAAA;AAAA;AAE5D;AAEO,SAAS,aAAa,QAAkB,EAAA;AAC9C,EAAA,OAAO,GAAG,QAAS,CAAA,OAAA,CAAQ,eAAe,GAAG,CAAA,CAAE,aAAa,CAAA,CAAA;AAC7D;;;ACTO,IAAM,KAAQ,GAAA;AAAA,EACpB,IAAM,EAAA,EAAA;AAAA,EACN,YAAc,EAAA,EAAA;AAAA,EACd,QAAU,EAAA,EAAA;AAAA,EACV,UAAU,EAAC;AAAA,EACX,qBAAqB,OAA4C,EAAA;AAChE,IAAA,MAAM,OAAO,cAAe,EAAA;AAC5B,IAAA,KAAA,CAAM,WAAW,IAAK,CAAA,IAAA;AACtB,IAAA,MAAM,EAAM,GAAA,KAAA,CAAM,EAAK,GAAA,YAAA,CAAa,MAAM,QAAQ,CAAA;AAClD,IAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA,CAAE,EAAE,CAAI,GAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAE,CAAA,EAAE,CAAK,IAAA,EAAE,OAAO,CAAE,EAAA;AAE9F,IAAM,MAAA,KAAA,GAAQ,MAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAE,CAAA,KAAA,CAAM,EAAE,CAAG,CAAA,KAAA;AAC5D,IAAA,MAAM,gBAAmB,GAAA,OAAA,EAAS,mBAC/B,GAAA,CAAA,EAAG,QAAQ,mBAAoB,CAAA,KAAA,CAAM,EAAI,EAAA,KAAK,CAAC,CAC/C,IAAA,CAAA,GAAA,CAAA,EAAG,KAAM,CAAA,EAAE,IAAI,KAAK,CAAA,IAAA,CAAA;AACvB,IAAA,MAAM,YAAe,GAAA,IAAA,CAAK,KAAM,CAAA,WAAA,EAAa,gBAAgB,CAAA;AAC7D,IAAA,MAAM,UAAa,GAAA,IAAA,CAAK,KAAM,CAAA,SAAA,EAAW,gBAAgB,CAAA;AACzD,IAAA,MAAM,QAAW,GAAA,IAAA,CAAK,KAAM,CAAA,OAAA,EAAS,gBAAgB,CAAA;AACrD,IAAA,OAAO,EAAE,gBAAA,EAAkB,YAAc,EAAA,UAAA,EAAY,QAAS,EAAA;AAAA,GAC/D;AAAA,EACA,sBAAyB,GAAA;AACxB,IAAA,KAAA,CAAM,SAAS,KAAM,CAAA,YAAY,CAAE,CAAA,KAAA,CAAM,EAAE,CAAG,CAAA,KAAA,EAAA;AAAA;AAEhD","file":"chunk-M4DV45WL.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 { join } from 'pathe'\nimport { getCurrentTest } from 'vitest/suite'\nimport { toSnapshotId } from './@vitest/browser/image_snapshot.logic'\nimport type { ImageSnapshotOptions } from './@vitest/browser/types'\nimport type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\n\nexport const state = {\n\tname: '',\n\ttestFilepath: '',\n\ttaskName: '',\n\tsnapshot: {},\n\tgetSnapshotFilePaths(options?: ImageSnapshotOptions | undefined) {\n\t\tconst test = getCurrentTest()\n\t\tstate.taskName = test.name\n\t\tconst id = (state.id = toSnapshotId(state.taskName))\n\t\tstate.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 }\n\n\t\tconst index = state.snapshot[state.testFilepath][state.id]!.index\n\t\tconst snapshotFilename = options?.customizeSnapshotId\n\t\t\t? `${options.customizeSnapshotId(state.id, index)}.png`\n\t\t\t: `${state.id}-${index}.png`\n\t\tconst baselinePath = join(state.baselineDir, snapshotFilename)\n\t\tconst resultPath = join(state.resultDir, snapshotFilename)\n\t\tconst diffPath = join(state.diffDir, snapshotFilename)\n\t\treturn { snapshotFilename, baselinePath, resultPath, diffPath }\n\t},\n\tincrementSnapshotIndex() {\n\t\tstate.snapshot[state.testFilepath][state.id]!.index++\n\t},\n} as unknown as {\n\tid: string\n\tname: string\n\ttestFilepath: string\n\ttestFilename: string\n\tprojectDir: string\n\tbaselineDir: string\n\tresultDir: string\n\tdiffDir: string\n\ttaskName: string\n\tparameters: {\n\t\tsnapshot?: MatchImageSnapshotOptions | undefined\n\t\t[key: string]: any\n\t}\n\tsnapshot: Record<string, Record<string, { index: number }>>\n\tgetSnapshotFilePaths(options?: ImageSnapshotOptions | undefined): {\n\t\tsnapshotFilename: string\n\t\tbaselinePath: string\n\t\tresultPath: string\n\t\tdiffPath: string\n\t}\n\tincrementSnapshotIndex(): void\n}\n"]}
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- export { visStorybookPreview } from './chunk-V67IWJFT.js';
2
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-LAWHEQMO.js';
3
- import './chunk-GN5INPRD.js';
1
+ export { visStorybookPreview } from './chunk-3NZUGVTR.js';
2
+ export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-3ZUXLFHJ.js';
3
+ import './chunk-M4DV45WL.js';
4
4
 
5
5
  // src/param.ts
6
6
  function defineSnapshotParam(snapshot) {
package/dist/preview.js CHANGED
@@ -1,5 +1,5 @@
1
- import { visStorybookPreview } from './chunk-V67IWJFT.js';
2
- import './chunk-GN5INPRD.js';
1
+ import { visStorybookPreview } from './chunk-3NZUGVTR.js';
2
+ import './chunk-M4DV45WL.js';
3
3
 
4
4
  // src/preview.ts
5
5
  var preview_default = visStorybookPreview;
@@ -28,19 +28,13 @@ type VisOptions = {
28
28
  */
29
29
  snapshotPath: string;
30
30
  };
31
- declare function configureSnapshotBeforeAll(suite: {
32
- file: {
33
- filepath: string;
34
- };
35
- name: string;
36
- }, options?: VisOptions): Promise<void>;
37
- declare function configureSnapshotBeforeEach(ctx: {
38
- task: {
39
- name: string;
31
+ declare function setupVitestVis(options?: VisOptions): {
32
+ beforeAll(suite: {
40
33
  file: {
41
34
  filepath: string;
42
35
  };
43
- };
44
- }): void;
36
+ name: string;
37
+ }): Promise<void>;
38
+ };
45
39
 
46
- export { type VisOptions, configureSnapshotBeforeAll, configureSnapshotBeforeEach, shouldTakeSnapshot };
40
+ export { type VisOptions, setupVitestVis, shouldTakeSnapshot };
@@ -1,6 +1,6 @@
1
- import { commands, toSnapshotId } from './chunk-LAWHEQMO.js';
2
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-LAWHEQMO.js';
3
- import { state } from './chunk-GN5INPRD.js';
1
+ import { commands } from './chunk-3ZUXLFHJ.js';
2
+ export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-3ZUXLFHJ.js';
3
+ import { state } from './chunk-M4DV45WL.js';
4
4
  import { basename, join, dirname, relative } from 'pathe';
5
5
 
6
6
  // src/tags.ts
@@ -10,31 +10,30 @@ function shouldTakeSnapshot(ctx) {
10
10
  }
11
11
 
12
12
  // src/vitest-setup.ts
13
- async function configureSnapshotBeforeAll(suite, options) {
14
- state.name = suite.name;
15
- state.testFilepath = suite.file.filepath;
16
- state.testFilename = basename(state.testFilepath);
17
- state.projectDir = state.testFilepath.slice(0, -state.name.length);
18
- const snapshotPath = join(
19
- state.projectDir,
20
- options?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`
21
- );
22
- const currentDir = dirname(state.testFilepath);
23
- state.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename));
24
- state.resultDir = relative(currentDir, join(snapshotPath, "__results__", state.testFilename));
25
- state.diffDir = relative(currentDir, join(snapshotPath, "__diff_output__", state.testFilename));
26
- if (!state.snapshot[state.testFilepath]) {
27
- state.snapshot[state.testFilepath] = {};
28
- await commands.rmDir(state.resultDir);
29
- await commands.rmDir(state.diffDir);
30
- }
31
- }
32
- function configureSnapshotBeforeEach(ctx) {
33
- state.taskName = ctx.task.name;
34
- const id = state.id = toSnapshotId(state.taskName);
35
- state.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 };
13
+ function setupVitestVis(options) {
14
+ return {
15
+ async beforeAll(suite) {
16
+ state.name = suite.name;
17
+ state.testFilepath = suite.file.filepath;
18
+ state.testFilename = basename(state.testFilepath);
19
+ state.projectDir = state.testFilepath.slice(0, -state.name.length);
20
+ const snapshotPath = join(
21
+ state.projectDir,
22
+ options?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`
23
+ );
24
+ const currentDir = dirname(state.testFilepath);
25
+ state.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename));
26
+ state.resultDir = relative(currentDir, join(snapshotPath, "__results__", state.testFilename));
27
+ state.diffDir = relative(currentDir, join(snapshotPath, "__diff_output__", state.testFilename));
28
+ if (!state.snapshot[state.testFilepath]) {
29
+ state.snapshot[state.testFilepath] = {};
30
+ await commands.rmDir(state.resultDir);
31
+ await commands.rmDir(state.diffDir);
32
+ }
33
+ }
34
+ };
36
35
  }
37
36
 
38
- export { configureSnapshotBeforeAll, configureSnapshotBeforeEach, shouldTakeSnapshot };
37
+ export { setupVitestVis, shouldTakeSnapshot };
39
38
  //# sourceMappingURL=vitest-setup.js.map
40
39
  //# sourceMappingURL=vitest-setup.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tags.ts","../src/vitest-setup.ts"],"names":[],"mappings":";;;;;;AAOO,SAAS,mBAAmB,GAAqC,EAAA;AACvE,EAAA,IAAI,CAAC,GAAA,CAAI,KAAO,EAAA,IAAA,EAAa,OAAA,KAAA;AAC7B,EAAO,OAAA,GAAA,CAAI,KAAM,CAAA,IAAA,CAAK,WAAY,CAAA,WAAW,IAAI,GAAI,CAAA,KAAA,CAAM,IAAK,CAAA,WAAA,CAAY,UAAU,CAAA;AACvF;;;ACYA,eAAsB,0BAAA,CACrB,OACA,OACC,EAAA;AACD,EAAA,KAAA,CAAM,OAAO,KAAM,CAAA,IAAA;AACnB,EAAM,KAAA,CAAA,YAAA,GAAe,MAAM,IAAK,CAAA,QAAA;AAChC,EAAM,KAAA,CAAA,YAAA,GAAe,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA;AAChD,EAAM,KAAA,CAAA,UAAA,GAAa,MAAM,YAAa,CAAA,KAAA,CAAM,GAAG,CAAC,KAAA,CAAM,KAAK,MAAM,CAAA;AACjE,EAAA,MAAM,YAAe,GAAA,IAAA;AAAA,IACpB,KAAM,CAAA,UAAA;AAAA,IACN,SAAS,YAAgB,IAAA,CAAA,cAAA,EAAiB,MAAM,QAAA,CAAS,qBAAqB,CAAA;AAAA,GAC/E;AACA,EAAM,MAAA,UAAA,GAAa,OAAQ,CAAA,KAAA,CAAM,YAAY,CAAA;AAC7C,EAAA,KAAA,CAAM,cAAc,QAAS,CAAA,UAAA,EAAY,KAAK,YAAc,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAC/E,EAAM,KAAA,CAAA,SAAA,GAAY,SAAS,UAAY,EAAA,IAAA,CAAK,cAAc,aAAe,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAC5F,EAAM,KAAA,CAAA,OAAA,GAAU,SAAS,UAAY,EAAA,IAAA,CAAK,cAAc,iBAAmB,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAE9F,EAAA,IAAI,CAAC,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAG,EAAA;AACxC,IAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA,GAAI,EAAC;AACtC,IAAM,MAAA,QAAA,CAAS,KAAM,CAAA,KAAA,CAAM,SAAS,CAAA;AACpC,IAAM,MAAA,QAAA,CAAS,KAAM,CAAA,KAAA,CAAM,OAAO,CAAA;AAAA;AAEpC;AAEO,SAAS,4BAA4B,GAOzC,EAAA;AACF,EAAM,KAAA,CAAA,QAAA,GAAW,IAAI,IAAK,CAAA,IAAA;AAC1B,EAAA,MAAM,EAAM,GAAA,KAAA,CAAM,EAAK,GAAA,YAAA,CAAa,MAAM,QAAQ,CAAA;AAClD,EAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA,CAAE,EAAE,CAAI,GAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAE,CAAA,EAAE,CAAK,IAAA,EAAE,OAAO,CAAE,EAAA;AAC/F","file":"vitest-setup.js","sourcesContent":["/**\n * Determine should snapshot be taken.\n *\n * not story: false\n * no `snapshot` tag: false\n * disabled by `!snapshot` tag: false\n */\nexport function shouldTakeSnapshot(ctx: { story?: { tags: string[] } }) {\n\tif (!ctx.story?.tags) return false\n\treturn ctx.story.tags.lastIndexOf('!snapshot') < ctx.story.tags.lastIndexOf('snapshot')\n}\n","import { basename, dirname, join, relative } from 'pathe'\nimport './augment.js'\nimport { commands } from './@vitest/browser/context.js'\nimport { toSnapshotId } from './@vitest/browser/image_snapshot.logic.js'\nimport { state } from './state.js'\n\nexport * from './@vitest/browser/context.js'\nexport * from './expect.to_match_image_snapshot.js'\nexport * from './tags.js'\n\n/**\n * The project parameters for the snapshot.\n *\n * These parameters are set per project in the `.storybook/preview.ts` file.\n */\nexport type VisOptions = {\n\t/**\n\t * The snapshot folder relative to the root of the project.\n\t */\n\tsnapshotPath: string\n}\n\nexport async function configureSnapshotBeforeAll(\n\tsuite: { file: { filepath: string }; name: string },\n\toptions?: VisOptions,\n) {\n\tstate.name = suite.name\n\tstate.testFilepath = suite.file.filepath\n\tstate.testFilename = basename(state.testFilepath)\n\tstate.projectDir = state.testFilepath.slice(0, -state.name.length)\n\tconst snapshotPath = join(\n\t\tstate.projectDir,\n\t\toptions?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`,\n\t)\n\tconst currentDir = dirname(state.testFilepath)\n\tstate.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename))\n\tstate.resultDir = relative(currentDir, join(snapshotPath, '__results__', state.testFilename))\n\tstate.diffDir = relative(currentDir, join(snapshotPath, '__diff_output__', state.testFilename))\n\n\tif (!state.snapshot[state.testFilepath]) {\n\t\tstate.snapshot[state.testFilepath] = {}\n\t\tawait commands.rmDir(state.resultDir)\n\t\tawait commands.rmDir(state.diffDir)\n\t}\n}\n\nexport function configureSnapshotBeforeEach(ctx: {\n\ttask: {\n\t\tname: string\n\t\tfile: {\n\t\t\tfilepath: string\n\t\t}\n\t}\n}) {\n\tstate.taskName = ctx.task.name\n\tconst id = (state.id = toSnapshotId(state.taskName))\n\tstate.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 }\n}\n"]}
1
+ {"version":3,"sources":["../src/tags.ts","../src/vitest-setup.ts"],"names":[],"mappings":";;;;;;AAOO,SAAS,mBAAmB,GAAqC,EAAA;AACvE,EAAA,IAAI,CAAC,GAAA,CAAI,KAAO,EAAA,IAAA,EAAa,OAAA,KAAA;AAC7B,EAAO,OAAA,GAAA,CAAI,KAAM,CAAA,IAAA,CAAK,WAAY,CAAA,WAAW,IAAI,GAAI,CAAA,KAAA,CAAM,IAAK,CAAA,WAAA,CAAY,UAAU,CAAA;AACvF;;;ACWO,SAAS,eAAe,OAAsB,EAAA;AACpD,EAAO,OAAA;AAAA,IACN,MAAM,UAAU,KAAqD,EAAA;AACpE,MAAA,KAAA,CAAM,OAAO,KAAM,CAAA,IAAA;AACnB,MAAM,KAAA,CAAA,YAAA,GAAe,MAAM,IAAK,CAAA,QAAA;AAChC,MAAM,KAAA,CAAA,YAAA,GAAe,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA;AAChD,MAAM,KAAA,CAAA,UAAA,GAAa,MAAM,YAAa,CAAA,KAAA,CAAM,GAAG,CAAC,KAAA,CAAM,KAAK,MAAM,CAAA;AACjE,MAAA,MAAM,YAAe,GAAA,IAAA;AAAA,QACpB,KAAM,CAAA,UAAA;AAAA,QACN,SAAS,YAAgB,IAAA,CAAA,cAAA,EAAiB,MAAM,QAAA,CAAS,qBAAqB,CAAA;AAAA,OAC/E;AACA,MAAM,MAAA,UAAA,GAAa,OAAQ,CAAA,KAAA,CAAM,YAAY,CAAA;AAC7C,MAAA,KAAA,CAAM,cAAc,QAAS,CAAA,UAAA,EAAY,KAAK,YAAc,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAC/E,MAAM,KAAA,CAAA,SAAA,GAAY,SAAS,UAAY,EAAA,IAAA,CAAK,cAAc,aAAe,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAC5F,MAAM,KAAA,CAAA,OAAA,GAAU,SAAS,UAAY,EAAA,IAAA,CAAK,cAAc,iBAAmB,EAAA,KAAA,CAAM,YAAY,CAAC,CAAA;AAE9F,MAAA,IAAI,CAAC,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAG,EAAA;AACxC,QAAA,KAAA,CAAM,QAAS,CAAA,KAAA,CAAM,YAAY,CAAA,GAAI,EAAC;AACtC,QAAM,MAAA,QAAA,CAAS,KAAM,CAAA,KAAA,CAAM,SAAS,CAAA;AACpC,QAAM,MAAA,QAAA,CAAS,KAAM,CAAA,KAAA,CAAM,OAAO,CAAA;AAAA;AACnC;AACD,GACD;AACD","file":"vitest-setup.js","sourcesContent":["/**\n * Determine should snapshot be taken.\n *\n * not story: false\n * no `snapshot` tag: false\n * disabled by `!snapshot` tag: false\n */\nexport function shouldTakeSnapshot(ctx: { story?: { tags: string[] } }) {\n\tif (!ctx.story?.tags) return false\n\treturn ctx.story.tags.lastIndexOf('!snapshot') < ctx.story.tags.lastIndexOf('snapshot')\n}\n","import { basename, dirname, join, relative } from 'pathe'\nimport { commands } from './@vitest/browser/context.js'\nimport './augment.js'\nimport { state } from './state.js'\n\nexport * from './@vitest/browser/context.js'\nexport * from './expect.to_match_image_snapshot.js'\nexport * from './tags.js'\n\n/**\n * The project parameters for the snapshot.\n *\n * These parameters are set per project in the `.storybook/preview.ts` file.\n */\nexport type VisOptions = {\n\t/**\n\t * The snapshot folder relative to the root of the project.\n\t */\n\tsnapshotPath: string\n}\n\nexport function setupVitestVis(options?: VisOptions) {\n\treturn {\n\t\tasync beforeAll(suite: { file: { filepath: string }; name: string }) {\n\t\t\tstate.name = suite.name\n\t\t\tstate.testFilepath = suite.file.filepath\n\t\t\tstate.testFilename = basename(state.testFilepath)\n\t\t\tstate.projectDir = state.testFilepath.slice(0, -state.name.length)\n\t\t\tconst snapshotPath = join(\n\t\t\t\tstate.projectDir,\n\t\t\t\toptions?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`,\n\t\t\t)\n\t\t\tconst currentDir = dirname(state.testFilepath)\n\t\t\tstate.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename))\n\t\t\tstate.resultDir = relative(currentDir, join(snapshotPath, '__results__', state.testFilename))\n\t\t\tstate.diffDir = relative(currentDir, join(snapshotPath, '__diff_output__', state.testFilename))\n\n\t\t\tif (!state.snapshot[state.testFilepath]) {\n\t\t\t\tstate.snapshot[state.testFilepath] = {}\n\t\t\t\tawait commands.rmDir(state.resultDir)\n\t\t\t\tawait commands.rmDir(state.diffDir)\n\t\t\t}\n\t\t},\n\t}\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "storybook-addon-vis",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Storybook Vitest Image Snapshot addon",
5
5
  "keywords": [
6
6
  "image-snapshot",
package/readme.md CHANGED
@@ -40,7 +40,7 @@ export default defineConfig({
40
40
  plugins: [
41
41
  storybookTest(),
42
42
  storybookVis()
43
- ],
43
+ ],
44
44
  test: {
45
45
  browser: {
46
46
  // ...
@@ -59,9 +59,8 @@ In `vitest.setup.ts`, you need to extend [vitest] `expect` and register lifecycl
59
59
  // vitest.setup.ts
60
60
  import { type StoryContext, setProjectAnnotations } from '@storybook/react'
61
61
  import {
62
- configureSnapshotBeforeAll,
63
- configureSnapshotBeforeEach,
64
- page,
62
+ page,
63
+ setupVis,
65
64
  shouldTakeSnapshot,
66
65
  toMatchImageSnapshot,
67
66
  } from 'storybook-addon-vis/vitest-setup'
@@ -69,20 +68,19 @@ import { afterEach, beforeAll, beforeEach, expect } from 'vitest'
69
68
  import * as projectAnnotations from './preview'
70
69
 
71
70
  const project = setProjectAnnotations([projectAnnotations])
71
+ const vis = setupVis(/* options */)
72
72
 
73
73
  expect.extend({ toMatchImageSnapshot })
74
74
 
75
75
  beforeAll(async (suite) => {
76
76
  project.beforeAll()
77
- await configureSnapshotBeforeAll(suite, /* options */)
77
+ await vis.beforeAll(suite)
78
78
  })
79
79
 
80
- beforeEach(configureSnapshotBeforeEach)
81
-
82
80
  afterEach<{ story?: StoryContext }>(async (ctx) => {
83
81
  if (!shouldTakeSnapshot(ctx)) return
84
82
 
85
- await expect(page.imageSnapshot()).toMatchImageSnapshot()
83
+ expect(page.imageSnapshot()).toMatchImageSnapshot()
86
84
  })
87
85
  ```
88
86
 
@@ -145,6 +143,7 @@ much like `!test`.
145
143
  ```ts
146
144
  export default {
147
145
  title: 'Button',
146
+ // Enable image snapshot for all stories in this file
148
147
  tags: ['snapshot']
149
148
  }
150
149
 
@@ -12,10 +12,6 @@ export interface HasImageSnapshotAction {
12
12
  * Check if the snapshot image exists.
13
13
  */
14
14
  export async function hasImageSnapshot(this: BrowserPage, options?: CustomizeSnapshotIdOptions | undefined) {
15
- const index = state.snapshot[state.testFilepath][state.id]!.index
16
- const snapshotFilename = options?.customizeSnapshotId
17
- ? `${options.customizeSnapshotId(state.id, index)}.png`
18
- : `${state.id}-${index}.png`
19
- const baselinePath = join(state.baselineDir, snapshotFilename)
15
+ const { baselinePath } = state.getSnapshotFilePaths(options)
20
16
  return commands.existFile(baselinePath)
21
17
  }
@@ -1,5 +1,6 @@
1
1
  import type { BrowserPage } from '@vitest/browser/context'
2
2
  import { join } from 'pathe'
3
+ import { getCurrentTest } from 'vitest/suite'
3
4
  import { toImageData } from '../../image_data.js'
4
5
  import { state } from '../../state.js'
5
6
  import { imageSnapshotSymbol } from './constants.js'
@@ -13,19 +14,14 @@ export async function imageSnapshot(
13
14
  this: BrowserPage,
14
15
  options?: ImageSnapshotOptions | undefined,
15
16
  ): Promise<ImageSnapshot> {
16
- const index = state.snapshot[state.testFilepath][state.id]!.index++
17
- const snapshotFilename = options?.customizeSnapshotId
18
- ? `${options.customizeSnapshotId(state.id, index)}.png`
19
- : `${state.id}-${index}.png`
20
- const baselinePath = join(state.baselineDir, snapshotFilename)
21
- const resultPath = join(state.resultDir, snapshotFilename)
22
- const diffPath = join(state.diffDir, snapshotFilename)
17
+ const { snapshotFilename, baselinePath, resultPath, diffPath } = state.getSnapshotFilePaths(options)
23
18
  const screenshot = await this.screenshot({
24
19
  base64: true,
25
20
  path: resultPath,
26
21
  element: options?.element,
27
22
  timeout: options?.timeout ?? 3000,
28
23
  })
24
+ state.incrementSnapshotIndex()
29
25
  const image = await toImageData(screenshot.base64)
30
26
 
31
27
  return {
@@ -57,9 +57,11 @@ it('can customize snapshot filename', async ({ task }) => {
57
57
  expect(result.snapshotFilename).toEqual(`${toSnapshotId(task.name)}-custom-1.png`)
58
58
  })
59
59
 
60
- it('should fail when the subject is a rejected promise', async () => {
61
- expect(expect(Promise.reject('error')).toMatchImageSnapshot()).rejects.toThrowError('error')
62
- })
60
+ // With recording the promise in the test object, we can't do negative test.
61
+ // The test will always fail as it is outside the loop
62
+ // it('should fail when the subject is a rejected promise', async () => {
63
+ // expect(expect(Promise.reject(new Error('error'))).toMatchImageSnapshot()).rejects.toThrowError('error')
64
+ // })
63
65
 
64
66
  it(`should fail with 'Snapshot \`{test/story name}\` mismatched`, async ({ task }) => {
65
67
  await ConversionRoundtrip.run()
@@ -85,7 +87,7 @@ it(`should fail with 'Snapshot \`{test/story name}\` mismatched`, async ({ task
85
87
  await expect(e.message).toMatch(`Snapshot \`${task.name}\` mismatched`)
86
88
  } finally {
87
89
  await ConversionRoundtrip.run()
88
- await expect(
90
+ expect(
89
91
  page.imageSnapshot({
90
92
  customizeSnapshotId: (id) => id,
91
93
  }),
@@ -12,7 +12,7 @@ export const Success: StoryObj = {
12
12
  return <img style={{ width: 128, height: 128 }} src={UNI_PNG_URL} />
13
13
  },
14
14
  async play() {
15
- await expect(page.imageSnapshot()).toMatchImageSnapshot()
15
+ expect(page.imageSnapshot()).toMatchImageSnapshot()
16
16
  },
17
17
  }
18
18
 
@@ -27,7 +27,7 @@ export const Failed: StoryObj = {
27
27
  )
28
28
  },
29
29
  async play() {
30
- await expect(page.imageSnapshot())
30
+ expect(page.imageSnapshot())
31
31
  .toMatchImageSnapshot()
32
32
  .then(
33
33
  () => {
@@ -44,7 +44,7 @@ export const Element: StoryObj = {
44
44
  },
45
45
  async play({ canvas }) {
46
46
  const image = await canvas.getByRole('img')
47
- await expect(page.imageSnapshot({ element: image })).toMatchImageSnapshot()
47
+ expect(page.imageSnapshot({ element: image })).toMatchImageSnapshot()
48
48
  },
49
49
  }
50
50
 
@@ -61,7 +61,7 @@ export const DifferentSize: StoryObj = {
61
61
  },
62
62
  async play({ canvas }) {
63
63
  const image = await canvas.getByRole('img')
64
- await expect(page.imageSnapshot({ element: image }))
64
+ expect(page.imageSnapshot({ element: image }))
65
65
  .toMatchImageSnapshot()
66
66
  .then(
67
67
  () => {
@@ -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
- await expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
80
+ expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
81
81
  failureThreshold: 70,
82
82
  })
83
83
  },
@@ -89,7 +89,7 @@ export const FailureThreshold: StoryObj = {
89
89
  // render: () => <div data-testid="subject">unit text</div>,
90
90
  async play({ canvas }) {
91
91
  const subject = canvas.getByTestId('subject')
92
- await expect(page.imageSnapshot({ element: subject }))
92
+ expect(page.imageSnapshot({ element: subject }))
93
93
  .toMatchImageSnapshot({
94
94
  failureThreshold: 10,
95
95
  })
@@ -109,7 +109,7 @@ export const FailureThresholdByPercentage: StoryObj = {
109
109
  render: () => <div data-testid="subject">unit text</div>,
110
110
  async play({ canvas }) {
111
111
  const subject = canvas.getByTestId('subject')
112
- await expect(page.imageSnapshot({ element: subject }))
112
+ expect(page.imageSnapshot({ element: subject }))
113
113
  .toMatchImageSnapshot({
114
114
  failureThreshold: 0.1,
115
115
  failureThresholdType: 'percent',
@@ -128,7 +128,7 @@ export const MeetFailureThresholdByPercentage: StoryObj = {
128
128
  // render: () => <div data-testid="subject">unit text</div>,
129
129
  async play({ canvas }) {
130
130
  const subject = canvas.getByTestId('subject')
131
- await expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
131
+ expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot({
132
132
  failureThreshold: 0.3,
133
133
  failureThresholdType: 'percent',
134
134
  })
@@ -141,7 +141,7 @@ export const ExactFailureThresholdByPercentage: StoryObj = {
141
141
  render: () => <div data-testid="subject">unit text</div>,
142
142
  async play({ canvas }) {
143
143
  const subject = canvas.getByTestId('subject')
144
- await expect(page.imageSnapshot({ element: subject }))
144
+ expect(page.imageSnapshot({ element: subject }))
145
145
  .toMatchImageSnapshot({
146
146
  failureThresholdType: 'percent',
147
147
  })
@@ -2,9 +2,10 @@ import type { AsyncExpectationResult } from '@vitest/expect'
2
2
  import dedent from 'dedent'
3
3
  import pixelmatch, { type PixelmatchOptions } from 'pixelmatch'
4
4
  import { required } from 'type-plus'
5
+ import { getCurrentTest } from 'vitest/suite'
5
6
  import { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'
6
7
  import { commands, server } from './@vitest/browser/context.js'
7
- import { assertImageSnapshot, isImageSnapshot } from './@vitest/browser/image_snapshot.logic.js'
8
+ import { assertImageSnapshot, isImageSnapshot, toSnapshotId } from './@vitest/browser/image_snapshot.logic.js'
8
9
  import { toDataURL, toImageData } from './image_data.js'
9
10
  import { createImageResizer } from './image_resizer.js'
10
11
  import { state } from './state.js'
@@ -30,6 +31,17 @@ export type MatchImageSnapshotOptions = {
30
31
  export async function toMatchImageSnapshot(
31
32
  actual: any,
32
33
  options?: MatchImageSnapshotOptions | undefined,
34
+ ): AsyncExpectationResult {
35
+ const promise = toMatchImageSnapshotInternal(actual, options)
36
+ const test = getCurrentTest()
37
+ test.promises ??= []
38
+ test.promises.push(promise)
39
+ return promise
40
+ }
41
+
42
+ async function toMatchImageSnapshotInternal(
43
+ actual: any,
44
+ options?: MatchImageSnapshotOptions | undefined,
33
45
  ): AsyncExpectationResult {
34
46
  const subject = await actual
35
47
  if (subject?.type === imageSnapshotStubSymbol) {
@@ -67,8 +79,11 @@ export async function toMatchImageSnapshot(
67
79
  const originalImage = await toImageData(baseline)
68
80
  const [resultImage, baselineImage] = alignImagesToSameSize(subject.image, originalImage)
69
81
 
82
+ // const test = getCurrentTest()
83
+ // console.log('test', test)
70
84
  options = required(state.parameters?.snapshot, options)
71
85
  const { pass, diffAmount, diffImage } = compareImage(baselineImage, resultImage, options)
86
+
72
87
  if (!pass) {
73
88
  if (server.config.snapshotOptions.updateSnapshot === 'all') {
74
89
  await writeSnapshot(`${subject.baselinePath}`, resultImage)
@@ -37,6 +37,6 @@ export const ParamAppliesToPlay: StoryObj = {
37
37
  },
38
38
  play: async ({ canvas }) => {
39
39
  const subject = canvas.getByTestId('subject')
40
- await expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot()
40
+ expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot()
41
41
  },
42
42
  }
package/src/state.ts CHANGED
@@ -1,3 +1,7 @@
1
+ import { join } from 'pathe'
2
+ import { getCurrentTest } from 'vitest/suite'
3
+ import { toSnapshotId } from './@vitest/browser/image_snapshot.logic'
4
+ import type { ImageSnapshotOptions } from './@vitest/browser/types'
1
5
  import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'
2
6
 
3
7
  export const state = {
@@ -5,7 +9,25 @@ export const state = {
5
9
  testFilepath: '',
6
10
  taskName: '',
7
11
  snapshot: {},
8
- } as {
12
+ getSnapshotFilePaths(options?: ImageSnapshotOptions | undefined) {
13
+ const test = getCurrentTest()
14
+ state.taskName = test.name
15
+ const id = (state.id = toSnapshotId(state.taskName))
16
+ state.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 }
17
+
18
+ const index = state.snapshot[state.testFilepath][state.id]!.index
19
+ const snapshotFilename = options?.customizeSnapshotId
20
+ ? `${options.customizeSnapshotId(state.id, index)}.png`
21
+ : `${state.id}-${index}.png`
22
+ const baselinePath = join(state.baselineDir, snapshotFilename)
23
+ const resultPath = join(state.resultDir, snapshotFilename)
24
+ const diffPath = join(state.diffDir, snapshotFilename)
25
+ return { snapshotFilename, baselinePath, resultPath, diffPath }
26
+ },
27
+ incrementSnapshotIndex() {
28
+ state.snapshot[state.testFilepath][state.id]!.index++
29
+ },
30
+ } as unknown as {
9
31
  id: string
10
32
  name: string
11
33
  testFilepath: string
@@ -20,4 +42,11 @@ export const state = {
20
42
  [key: string]: any
21
43
  }
22
44
  snapshot: Record<string, Record<string, { index: number }>>
45
+ getSnapshotFilePaths(options?: ImageSnapshotOptions | undefined): {
46
+ snapshotFilename: string
47
+ baselinePath: string
48
+ resultPath: string
49
+ diffPath: string
50
+ }
51
+ incrementSnapshotIndex(): void
23
52
  }
@@ -4,7 +4,7 @@ import { expect, it } from 'vitest'
4
4
  import { commands, page } from './@vitest/browser/context.js'
5
5
  import * as ImageDataStories from './image_data.stories.js'
6
6
  import { state } from './state.js'
7
- import { configureSnapshotBeforeAll } from './vitest-setup.js'
7
+ import { setupVitestVis } from './vitest-setup.js'
8
8
 
9
9
  const { ConversionRoundtrip } = composeStories(ImageDataStories)
10
10
 
@@ -21,12 +21,9 @@ it('should delete the results and diffs folder when the during `beforeAll`', asy
21
21
  })
22
22
 
23
23
  it('can define the snapshot root folder relative to the root of the project', async () => {
24
- await configureSnapshotBeforeAll(
25
- { name: 'some.test.ts', file: { filepath: 'dummy/some.test.ts' } },
26
- {
27
- snapshotPath: '_sp_',
28
- },
29
- ),
24
+ await setupVitestVis({
25
+ snapshotPath: '_sp_',
26
+ }).beforeAll({ name: 'some.test.ts', file: { filepath: 'dummy/some.test.ts' } }),
30
27
  expect(state).toMatchObject({
31
28
  baselineDir: '_sp_/some.test.ts',
32
29
  diffDir: '_sp_/__diff_output__/some.test.ts',
@@ -1,7 +1,6 @@
1
1
  import { basename, dirname, join, relative } from 'pathe'
2
- import './augment.js'
3
2
  import { commands } from './@vitest/browser/context.js'
4
- import { toSnapshotId } from './@vitest/browser/image_snapshot.logic.js'
3
+ import './augment.js'
5
4
  import { state } from './state.js'
6
5
 
7
6
  export * from './@vitest/browser/context.js'
@@ -20,39 +19,27 @@ export type VisOptions = {
20
19
  snapshotPath: string
21
20
  }
22
21
 
23
- export async function configureSnapshotBeforeAll(
24
- suite: { file: { filepath: string }; name: string },
25
- options?: VisOptions,
26
- ) {
27
- state.name = suite.name
28
- state.testFilepath = suite.file.filepath
29
- state.testFilename = basename(state.testFilepath)
30
- state.projectDir = state.testFilepath.slice(0, -state.name.length)
31
- const snapshotPath = join(
32
- state.projectDir,
33
- options?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`,
34
- )
35
- const currentDir = dirname(state.testFilepath)
36
- state.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename))
37
- state.resultDir = relative(currentDir, join(snapshotPath, '__results__', state.testFilename))
38
- state.diffDir = relative(currentDir, join(snapshotPath, '__diff_output__', state.testFilename))
39
-
40
- if (!state.snapshot[state.testFilepath]) {
41
- state.snapshot[state.testFilepath] = {}
42
- await commands.rmDir(state.resultDir)
43
- await commands.rmDir(state.diffDir)
44
- }
45
- }
22
+ export function setupVitestVis(options?: VisOptions) {
23
+ return {
24
+ async beforeAll(suite: { file: { filepath: string }; name: string }) {
25
+ state.name = suite.name
26
+ state.testFilepath = suite.file.filepath
27
+ state.testFilename = basename(state.testFilepath)
28
+ state.projectDir = state.testFilepath.slice(0, -state.name.length)
29
+ const snapshotPath = join(
30
+ state.projectDir,
31
+ options?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`,
32
+ )
33
+ const currentDir = dirname(state.testFilepath)
34
+ state.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename))
35
+ state.resultDir = relative(currentDir, join(snapshotPath, '__results__', state.testFilename))
36
+ state.diffDir = relative(currentDir, join(snapshotPath, '__diff_output__', state.testFilename))
46
37
 
47
- export function configureSnapshotBeforeEach(ctx: {
48
- task: {
49
- name: string
50
- file: {
51
- filepath: string
52
- }
38
+ if (!state.snapshot[state.testFilepath]) {
39
+ state.snapshot[state.testFilepath] = {}
40
+ await commands.rmDir(state.resultDir)
41
+ await commands.rmDir(state.diffDir)
42
+ }
43
+ },
53
44
  }
54
- }) {
55
- state.taskName = ctx.task.name
56
- const id = (state.id = toSnapshotId(state.taskName))
57
- state.snapshot[state.testFilepath][id] = state.snapshot[state.testFilepath][id] ?? { index: 1 }
58
45
  }
@@ -1,11 +0,0 @@
1
- // src/state.ts
2
- var state = {
3
- name: "",
4
- testFilepath: "",
5
- taskName: "",
6
- snapshot: {}
7
- };
8
-
9
- export { state };
10
- //# sourceMappingURL=chunk-GN5INPRD.js.map
11
- //# sourceMappingURL=chunk-GN5INPRD.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/state.ts"],"names":[],"mappings":";AAEO,IAAM,KAAQ,GAAA;AAAA,EACpB,IAAM,EAAA,EAAA;AAAA,EACN,YAAc,EAAA,EAAA;AAAA,EACd,QAAU,EAAA,EAAA;AAAA,EACV,UAAU;AACX","file":"chunk-GN5INPRD.js","sourcesContent":["import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\n\nexport const state = {\n\tname: '',\n\ttestFilepath: '',\n\ttaskName: '',\n\tsnapshot: {},\n} as {\n\tid: string\n\tname: string\n\ttestFilepath: string\n\ttestFilename: string\n\tprojectDir: string\n\tbaselineDir: string\n\tresultDir: string\n\tdiffDir: string\n\ttaskName: string\n\tparameters: {\n\t\tsnapshot?: MatchImageSnapshotOptions | undefined\n\t\t[key: string]: any\n\t}\n\tsnapshot: Record<string, Record<string, { index: number }>>\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/@vitest/browser/constants.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/@vitest/browser/image_snapshot.logic.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts"],"names":["ctx","join"],"mappings":"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","file":"chunk-LAWHEQMO.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\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 index = state.snapshot[state.testFilepath][state.id]!.index\n\tconst snapshotFilename = options?.customizeSnapshotId\n\t\t? `${options.customizeSnapshotId(state.id, index)}.png`\n\t\t: `${state.id}-${index}.png`\n\tconst baselinePath = join(state.baselineDir, snapshotFilename)\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 { join } from 'pathe'\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 index = state.snapshot[state.testFilepath][state.id]!.index++\n\tconst snapshotFilename = options?.customizeSnapshotId\n\t\t? `${options.customizeSnapshotId(state.id, index)}.png`\n\t\t: `${state.id}-${index}.png`\n\tconst baselinePath = join(state.baselineDir, snapshotFilename)\n\tconst resultPath = join(state.resultDir, snapshotFilename)\n\tconst diffPath = join(state.diffDir, snapshotFilename)\n\tconst screenshot = await this.screenshot({\n\t\tbase64: true,\n\t\tpath: resultPath,\n\t\telement: options?.element,\n\t\ttimeout: options?.timeout ?? 3000,\n\t})\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\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","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","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 { required } from 'type-plus'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands, 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\tfailureThreshold?: number | undefined\n}\n\nexport async function toMatchImageSnapshot(\n\tactual: any,\n\toptions?: MatchImageSnapshotOptions | undefined,\n): AsyncExpectationResult {\n\tconst 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\t// subject = await page.imageSnapshot({ element: actual })\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\toptions = required(state.parameters?.snapshot, options)\n\tconst { pass, diffAmount, diffImage } = compareImage(baselineImage, resultImage, options)\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.taskName}\\` 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"]}