storybook-addon-vis 0.6.3 → 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.
@@ -0,0 +1,12 @@
1
+ import { state } from './chunk-M4DV45WL.js';
2
+
3
+ // src/storybook/preview.ts
4
+ var visStorybookPreview = {
5
+ beforeEach(ctx) {
6
+ state.parameters = ctx.parameters;
7
+ }
8
+ };
9
+
10
+ export { visStorybookPreview };
11
+ //# sourceMappingURL=chunk-3NZUGVTR.js.map
12
+ //# sourceMappingURL=chunk-3NZUGVTR.js.map
@@ -0,0 +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-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,23 +1,13 @@
1
+ import { imageSnapshotStubSymbol, state, imageSnapshotSymbol, isImageSnapshot, assertImageSnapshot } from './chunk-M4DV45WL.js';
1
2
  import { getByAltText, getByRole, getByTestId, getByText, getByTitle, getByPlaceholderText, getByLabelText } from '@storybook/test';
2
- import { join } from 'pathe';
3
+ import 'pathe';
4
+ import { getCurrentTest } from 'vitest/suite';
3
5
  import dedent from 'dedent';
4
6
  import pixelmatch from 'pixelmatch';
7
+ import { required } from 'type-plus';
5
8
 
6
- // src/state.ts
7
- var state = {
8
- name: "",
9
- testFilepath: "",
10
- taskName: "",
11
- snapshot: {}
12
- };
13
-
14
- // src/@vitest/browser/constants.ts
15
- var imageSnapshotSymbol = Symbol.for("storybook-addon-vis/imageSnapshot");
16
- var imageSnapshotStubSymbol = Symbol.for("storybook-addon-vis/imageSnapshotStub");
17
9
  async function hasImageSnapshot(options) {
18
- const index = state.snapshot[state.testFilepath][state.id].index;
19
- const snapshotFilename = options?.customizeSnapshotId ? `${options.customizeSnapshotId(state.id, index)}.png` : `${state.id}-${index}.png`;
20
- const baselinePath = join(state.baselineDir, snapshotFilename);
10
+ const { baselinePath } = state.getSnapshotFilePaths(options);
21
11
  return commands.existFile(baselinePath);
22
12
  }
23
13
 
@@ -58,17 +48,14 @@ async function toDataURL(imageData) {
58
48
 
59
49
  // src/@vitest/browser/page.image_snapshot.ts
60
50
  async function imageSnapshot(options) {
61
- const index = state.snapshot[state.testFilepath][state.id].index++;
62
- const snapshotFilename = options?.customizeSnapshotId ? `${options.customizeSnapshotId(state.id, index)}.png` : `${state.id}-${index}.png`;
63
- const baselinePath = join(state.baselineDir, snapshotFilename);
64
- const resultPath = join(state.resultDir, snapshotFilename);
65
- const diffPath = join(state.diffDir, snapshotFilename);
51
+ const { snapshotFilename, baselinePath, resultPath, diffPath } = state.getSnapshotFilePaths(options);
66
52
  const screenshot = await this.screenshot({
67
53
  base64: true,
68
54
  path: resultPath,
69
55
  element: options?.element,
70
56
  timeout: options?.timeout ?? 3e3
71
57
  });
58
+ state.incrementSnapshotIndex();
72
59
  const image = await toImageData(screenshot.base64);
73
60
  return {
74
61
  type: imageSnapshotSymbol,
@@ -124,19 +111,6 @@ var server = new Proxy({}, {
124
111
  }
125
112
  });
126
113
 
127
- // src/@vitest/browser/image_snapshot.logic.ts
128
- function isImageSnapshot(subject) {
129
- return !!subject && subject.type === imageSnapshotSymbol;
130
- }
131
- function assertImageSnapshot(subject) {
132
- if (!isImageSnapshot(subject)) {
133
- throw new Error("Expected subject to be an image snapshot");
134
- }
135
- }
136
- function toSnapshotId(taskName) {
137
- return `${taskName.replace(/[^a-z0-9]/gi, "-").toLowerCase()}`;
138
- }
139
-
140
114
  // src/image_resizer.ts
141
115
  var createImageResizer = ({ width, height }) => (image) => {
142
116
  if (image.width === width && image.height === height) {
@@ -166,6 +140,13 @@ var createImageResizer = ({ width, height }) => (image) => {
166
140
 
167
141
  // src/expect.to_match_image_snapshot.ts
168
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) {
169
150
  const subject = await actual;
170
151
  if (subject?.type === imageSnapshotStubSymbol) {
171
152
  return success;
@@ -194,6 +175,7 @@ async function toMatchImageSnapshot(actual, options) {
194
175
  }
195
176
  const originalImage = await toImageData(baseline);
196
177
  const [resultImage, baselineImage] = alignImagesToSameSize(subject.image, originalImage);
178
+ options = required(state.parameters?.snapshot, options);
197
179
  const { pass, diffAmount, diffImage } = compareImage(baselineImage, resultImage, options);
198
180
  if (!pass) {
199
181
  if (server.config.snapshotOptions.updateSnapshot === "all") {
@@ -263,6 +245,6 @@ function toThresholdUnit({ failureThresholdType, width, height }, pixelDiff) {
263
245
  }
264
246
  }
265
247
 
266
- export { cdp, commands, page, server, state, toMatchImageSnapshot, toSnapshotId };
267
- //# sourceMappingURL=chunk-4K5KNZ64.js.map
268
- //# sourceMappingURL=chunk-4K5KNZ64.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.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  import { M as MatchImageSnapshotOptions } from './context-BX71asjt.js';
2
2
  export { C as CustomizeSnapshotIdOptions, d as ImageSnapshot, I as ImageSnapshotMatcher, b as ImageSnapshotOptions, c as cdp, a as commands, p as page, s as server, t as toMatchImageSnapshot } from './context-BX71asjt.js';
3
+ export { v as visStorybookPreview } from './preview-BsZVLryd.js';
3
4
  import '@vitest/browser/context';
4
5
  import '@vitest/expect';
5
6
  import 'pixelmatch';
6
7
  import 'vitest';
8
+ import 'storybook/internal/types';
7
9
 
8
10
  declare function defineSnapshotParam(snapshot: MatchImageSnapshotOptions): {
9
11
  snapshot: MatchImageSnapshotOptions;
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-4K5KNZ64.js';
1
+ export { visStorybookPreview } from './chunk-3NZUGVTR.js';
2
+ export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-3ZUXLFHJ.js';
3
+ import './chunk-M4DV45WL.js';
2
4
 
3
5
  // src/param.ts
4
6
  function defineSnapshotParam(snapshot) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/param.ts"],"names":[],"mappings":";;;AAEO,SAAS,oBAAoB,QAAqC,EAAA;AACxE,EAAA,OAAO,EAAE,QAAS,EAAA;AACnB","file":"index.js","sourcesContent":["import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\n\nexport function defineSnapshotParam(snapshot: MatchImageSnapshotOptions) {\n\treturn { snapshot }\n}\n"]}
1
+ {"version":3,"sources":["../src/param.ts"],"names":[],"mappings":";;;;;AAEO,SAAS,oBAAoB,QAAqC,EAAA;AACxE,EAAA,OAAO,EAAE,QAAS,EAAA;AACnB","file":"index.js","sourcesContent":["import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'\n\nexport function defineSnapshotParam(snapshot: MatchImageSnapshotOptions) {\n\treturn { snapshot }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { ProjectAnnotations, Renderer } from 'storybook/internal/types';
2
+
3
+ declare const visStorybookPreview: ProjectAnnotations<Renderer>;
4
+
5
+ export { visStorybookPreview as v };
@@ -0,0 +1,6 @@
1
+ import { v as visStorybookPreview } from './preview-BsZVLryd.js';
2
+ import 'storybook/internal/types';
3
+
4
+
5
+
6
+ export { visStorybookPreview as default };
@@ -0,0 +1,9 @@
1
+ import { visStorybookPreview } from './chunk-3NZUGVTR.js';
2
+ import './chunk-M4DV45WL.js';
3
+
4
+ // src/preview.ts
5
+ var preview_default = visStorybookPreview;
6
+
7
+ export { preview_default as default };
8
+ //# sourceMappingURL=preview.js.map
9
+ //# sourceMappingURL=preview.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preview.ts"],"names":[],"mappings":";;;;AAEA,IAAO,eAAQ,GAAA","file":"preview.js","sourcesContent":["import { visStorybookPreview } from './storybook/preview'\n\nexport default visStorybookPreview\n"]}
@@ -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,5 +1,6 @@
1
- import { state, commands, toSnapshotId } from './chunk-4K5KNZ64.js';
2
- export { cdp, commands, page, server, toMatchImageSnapshot } from './chunk-4K5KNZ64.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';
3
4
  import { basename, join, dirname, relative } from 'pathe';
4
5
 
5
6
  // src/tags.ts
@@ -9,31 +10,30 @@ function shouldTakeSnapshot(ctx) {
9
10
  }
10
11
 
11
12
  // src/vitest-setup.ts
12
- async function configureSnapshotBeforeAll(suite, options) {
13
- state.name = suite.name;
14
- state.testFilepath = suite.file.filepath;
15
- state.testFilename = basename(state.testFilepath);
16
- state.projectDir = state.testFilepath.slice(0, -state.name.length);
17
- const snapshotPath = join(
18
- state.projectDir,
19
- options?.snapshotPath ?? `__snapshots__/${await commands.getSnapshotPlatform()}`
20
- );
21
- const currentDir = dirname(state.testFilepath);
22
- state.baselineDir = relative(currentDir, join(snapshotPath, state.testFilename));
23
- state.resultDir = relative(currentDir, join(snapshotPath, "__results__", state.testFilename));
24
- state.diffDir = relative(currentDir, join(snapshotPath, "__diff_output__", state.testFilename));
25
- if (!state.snapshot[state.testFilepath]) {
26
- state.snapshot[state.testFilepath] = {};
27
- await commands.rmDir(state.resultDir);
28
- await commands.rmDir(state.diffDir);
29
- }
30
- }
31
- function configureSnapshotBeforeEach(ctx) {
32
- state.taskName = ctx.task.name;
33
- const id = state.id = toSnapshotId(state.taskName);
34
- 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
+ };
35
35
  }
36
36
 
37
- export { configureSnapshotBeforeAll, configureSnapshotBeforeEach, shouldTakeSnapshot };
37
+ export { setupVitestVis, shouldTakeSnapshot };
38
38
  //# sourceMappingURL=vitest-setup.js.map
39
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.6.3",
3
+ "version": "0.8.0",
4
4
  "description": "Storybook Vitest Image Snapshot addon",
5
5
  "keywords": [
6
6
  "image-snapshot",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "./vitest-setup": "./dist/vitest-setup.js",
30
30
  "./vitest-plugin": {
31
- "types": "./dist/vitest-plugin.d.ts",
31
+ "types": "./vitest-plugin.d.ts",
32
32
  "import": "./dist/vitest-plugin.js"
33
33
  },
34
34
  "./package.json": "./package.json"
@@ -85,7 +85,8 @@
85
85
  "bundler": {
86
86
  "exportEntries": [
87
87
  "src/index.ts",
88
- "src/vitest-setup.ts"
88
+ "src/vitest-setup.ts",
89
+ "src/preview.ts"
89
90
  ]
90
91
  },
91
92
  "storybook": {
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,33 +68,38 @@ 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(ctx.story?.parameters.snapshot)
83
+ expect(page.imageSnapshot()).toMatchImageSnapshot()
86
84
  })
87
85
  ```
88
86
 
89
- On [storybook], you need to extend the `expect` from `@storybook/test`
87
+ On [storybook], you need to extend the `expect` from `@storybook/test`,
88
+ and register `beforeEach` hook to set up the test environment.
90
89
 
91
90
  ```ts
92
91
  // .storybook/preview.tsx
93
92
  import { expect } from '@storybook/test'
94
- import { toMatchImageSnapshot } from 'storybook-addon-vis'
93
+ import { toMatchImageSnapshot, visStorybookPreview } from 'storybook-addon-vis'
95
94
 
96
95
  expect.extend({ toMatchImageSnapshot })
97
96
 
98
- // ...
97
+ const preview: Preview = {
98
+ // ...
99
+ beforeEach: visStorybookPreview.beforeEach,
100
+ }
101
+
102
+ export default preview
99
103
  ```
100
104
 
101
105
  ## Usage - automatic snapshot
@@ -139,6 +143,7 @@ much like `!test`.
139
143
  ```ts
140
144
  export default {
141
145
  title: 'Button',
146
+ // Enable image snapshot for all stories in this file
142
147
  tags: ['snapshot']
143
148
  }
144
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
  })
@@ -1,9 +1,11 @@
1
1
  import type { AsyncExpectationResult } from '@vitest/expect'
2
2
  import dedent from 'dedent'
3
3
  import pixelmatch, { type PixelmatchOptions } from 'pixelmatch'
4
+ import { required } from 'type-plus'
5
+ import { getCurrentTest } from 'vitest/suite'
4
6
  import { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'
5
7
  import { commands, server } from './@vitest/browser/context.js'
6
- import { assertImageSnapshot, isImageSnapshot } from './@vitest/browser/image_snapshot.logic.js'
8
+ import { assertImageSnapshot, isImageSnapshot, toSnapshotId } from './@vitest/browser/image_snapshot.logic.js'
7
9
  import { toDataURL, toImageData } from './image_data.js'
8
10
  import { createImageResizer } from './image_resizer.js'
9
11
  import { state } from './state.js'
@@ -29,6 +31,17 @@ export type MatchImageSnapshotOptions = {
29
31
  export async function toMatchImageSnapshot(
30
32
  actual: any,
31
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,
32
45
  ): AsyncExpectationResult {
33
46
  const subject = await actual
34
47
  if (subject?.type === imageSnapshotStubSymbol) {
@@ -54,7 +67,9 @@ export async function toMatchImageSnapshot(
54
67
  }
55
68
  // subject = await page.imageSnapshot({ element: actual })
56
69
  }
70
+
57
71
  assertImageSnapshot(subject)
72
+
58
73
  const baseline = await tryReadSnapshot(subject.baselinePath)
59
74
  if (!baseline) {
60
75
  await commands.copyFile(subject.resultPath, subject.baselinePath)
@@ -64,7 +79,11 @@ export async function toMatchImageSnapshot(
64
79
  const originalImage = await toImageData(baseline)
65
80
  const [resultImage, baselineImage] = alignImagesToSameSize(subject.image, originalImage)
66
81
 
82
+ // const test = getCurrentTest()
83
+ // console.log('test', test)
84
+ options = required(state.parameters?.snapshot, options)
67
85
  const { pass, diffAmount, diffImage } = compareImage(baselineImage, resultImage, options)
86
+
68
87
  if (!pass) {
69
88
  if (server.config.snapshotOptions.updateSnapshot === 'all') {
70
89
  await writeSnapshot(`${subject.baselinePath}`, resultImage)
package/src/index.ts CHANGED
@@ -4,3 +4,4 @@ export * from './expect.to_match_image_snapshot.js'
4
4
  export * from './@vitest/browser/context.js'
5
5
  export * from './@vitest/browser/types.js'
6
6
  export * from './param.js'
7
+ export * from './storybook/preview.js'
@@ -10,7 +10,7 @@ export const MeetFailureThreshold: StoryObj = {
10
10
  parameters: defineSnapshotParam({
11
11
  failureThreshold: 70,
12
12
  }),
13
- loaders: [async () => ({ hasSnapshot: await page.hasImageSnapshot() })],
13
+ loaders: [async () => ({ hasImageSnapshot: await page.hasImageSnapshot() })],
14
14
  render: (_, { loaded: { hasImageSnapshot } }) => (
15
15
  <div data-testid="subject">{hasImageSnapshot ? 'unit text' : 'unit test'}</div>
16
16
  ),
@@ -21,8 +21,22 @@ export const MeetFailureThresholdByPercentage: StoryObj = {
21
21
  failureThreshold: 0.02,
22
22
  failureThresholdType: 'percent',
23
23
  }),
24
- loaders: [async () => ({ hasSnapshot: await page.hasImageSnapshot() })],
24
+ loaders: [async () => ({ hasImageSnapshot: await page.hasImageSnapshot() })],
25
25
  render: (_, { loaded: { hasImageSnapshot } }) => (
26
26
  <div data-testid="subject">{hasImageSnapshot ? 'unit text' : 'unit test'}</div>
27
27
  ),
28
28
  }
29
+
30
+ export const ParamAppliesToPlay: StoryObj = {
31
+ parameters: defineSnapshotParam({
32
+ failureThreshold: 70,
33
+ }),
34
+ loaders: [async () => ({ hasImageSnapshot: await page.hasImageSnapshot() })],
35
+ render(_, { loaded: { hasImageSnapshot } }) {
36
+ return <div data-testid="subject">{hasImageSnapshot ? 'unit text' : 'unit test'}</div>
37
+ },
38
+ play: async ({ canvas }) => {
39
+ const subject = canvas.getByTestId('subject')
40
+ expect(page.imageSnapshot({ element: subject })).toMatchImageSnapshot()
41
+ },
42
+ }
package/src/preview.ts CHANGED
@@ -1 +1,3 @@
1
- export default {}
1
+ import { visStorybookPreview } from './storybook/preview'
2
+
3
+ export default visStorybookPreview
package/src/state.ts CHANGED
@@ -1,9 +1,33 @@
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'
5
+ import type { MatchImageSnapshotOptions } from './expect.to_match_image_snapshot'
6
+
1
7
  export const state = {
2
8
  name: '',
3
9
  testFilepath: '',
4
10
  taskName: '',
5
11
  snapshot: {},
6
- } 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 {
7
31
  id: string
8
32
  name: string
9
33
  testFilepath: string
@@ -13,5 +37,16 @@ export const state = {
13
37
  resultDir: string
14
38
  diffDir: string
15
39
  taskName: string
40
+ parameters: {
41
+ snapshot?: MatchImageSnapshotOptions | undefined
42
+ [key: string]: any
43
+ }
16
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
17
52
  }
@@ -0,0 +1,8 @@
1
+ import type { ProjectAnnotations, Renderer, StoryContext } from 'storybook/internal/types'
2
+ import { state } from '../state'
3
+
4
+ export const visStorybookPreview: ProjectAnnotations<Renderer> = {
5
+ beforeEach(ctx: StoryContext) {
6
+ state.parameters = ctx.parameters
7
+ },
8
+ }
@@ -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
  }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from 'vitest/config'
2
+
3
+ export function storybookVis(): Plugin
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/state.ts","../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-4K5KNZ64.js","sourcesContent":["export 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\tsnapshot: Record<string, Record<string, { index: number }>>\n}\n","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 { 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\tassertImageSnapshot(subject)\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\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"]}