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