storybook-addon-vis 0.4.0 → 0.5.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/LICENSE +6 -6
- package/dist/index.d.ts +43 -21
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/vitest-plugin.d.ts +42 -18
- package/dist/vitest-plugin.js +1 -1
- package/dist/vitest-plugin.js.map +1 -1
- package/dist/vitest-setup.d.ts +44 -22
- package/dist/vitest-setup.js +4 -2
- package/dist/vitest-setup.js.map +1 -1
- package/package.json +13 -13
- package/src/@vitest/browser/page.image_snapshot.ts +6 -2
- package/src/@vitest/browser/types.ts +4 -2
- package/src/augment.ts +15 -19
- package/src/expect.to_match_image_snapshot.spec.ts +35 -1
- package/src/expect.to_match_image_snapshot.ts +14 -7
- package/src/vitest-plugin/commands/copy_file.ts +4 -0
- package/src/vitest-plugin/commands/exist_dir.ts +4 -0
- package/src/vitest-plugin/commands/get_snapshot_platform.ts +10 -1
- package/src/vitest-plugin/commands/is_ci.ts +4 -0
- package/src/vitest-plugin/commands/rm_dir.ts +4 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
MIT License
|
|
1
|
+
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (
|
|
3
|
+
Copyright © 2024 Homa Wong (unional) (homawong@gmail.com)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
|
10
10
|
furnished to do so, subject to the following conditions:
|
|
11
11
|
|
|
12
|
-
The above copyright notice and this permission notice shall be included in
|
|
13
|
-
copies or substantial portions of the Software.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
15
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
16
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
17
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
18
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
19
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
-
SOFTWARE.
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { BrowserPage, CDPSession, BrowserCommands, Platform } from '@vitest/browser/context';
|
|
1
2
|
import { PixelmatchOptions } from 'pixelmatch';
|
|
2
3
|
import { AsyncExpectationResult } from '@vitest/expect';
|
|
3
|
-
import { CDPSession, BrowserCommands, BrowserPage, Platform } from '@vitest/browser/context';
|
|
4
4
|
import { SerializedConfig } from 'vitest';
|
|
5
5
|
|
|
6
6
|
type ImageSnapshotOptions = {
|
|
7
7
|
/**
|
|
8
|
-
* Customize the filename of the snapshot
|
|
8
|
+
* Customize the snapshot id. This is used as the filename of the snapshot:
|
|
9
|
+
*
|
|
10
|
+
* `${snapshotId}.png`
|
|
9
11
|
*
|
|
10
12
|
* @param id The id of the snapshot.
|
|
11
13
|
* @param index The index of the snapshot.
|
|
12
14
|
*/
|
|
13
|
-
|
|
15
|
+
customizeSnapshotId?: (id: string, index: number) => string;
|
|
14
16
|
element?: Element;
|
|
15
17
|
};
|
|
16
18
|
type ImageSnapshot = {
|
|
@@ -36,33 +38,53 @@ type MatchImageSnapshotOptions = {
|
|
|
36
38
|
failureThreshold?: number | undefined;
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
interface SnapshotCapturer {
|
|
42
|
+
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ImageSnapshotMatcher {
|
|
46
|
+
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
declare function toMatchImageSnapshot(actual: any, options?: MatchImageSnapshotOptions | undefined): AsyncExpectationResult;
|
|
49
|
+
|
|
50
|
+
interface CopyFileCommand {
|
|
51
|
+
copyFile(src: string, dest: string): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ExistDirCommand {
|
|
55
|
+
existDir: (path: string) => Promise<boolean>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface GetSnapshotPlatformCommand {
|
|
59
|
+
/**
|
|
60
|
+
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
61
|
+
*
|
|
62
|
+
* This is useful to control where the snapshot is stored.
|
|
63
|
+
*/
|
|
64
|
+
getSnapshotPlatform: () => Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface IsCICommand {
|
|
68
|
+
isCI: () => Promise<boolean>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface RmDirCommand {
|
|
72
|
+
rmDir: (path: string) => Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
39
75
|
declare module '@vitest/browser/context' {
|
|
40
|
-
interface BrowserPage {
|
|
41
|
-
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
76
|
+
interface BrowserPage extends SnapshotCapturer {
|
|
42
77
|
}
|
|
43
|
-
interface BrowserCommands {
|
|
44
|
-
existDir: (path: string) => Promise<boolean>;
|
|
45
|
-
copyFile: (src: string, dest: string) => Promise<void>;
|
|
46
|
-
rmDir: (path: string) => Promise<void>;
|
|
47
|
-
isCI: () => Promise<boolean>;
|
|
48
|
-
/**
|
|
49
|
-
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
50
|
-
*
|
|
51
|
-
* This is useful to control where the snapshot is stored.
|
|
52
|
-
*/
|
|
53
|
-
getSnapshotPlatform: () => Promise<string>;
|
|
78
|
+
interface BrowserCommands extends CopyFileCommand, ExistDirCommand, GetSnapshotPlatformCommand, RmDirCommand, IsCICommand {
|
|
54
79
|
}
|
|
55
80
|
}
|
|
56
81
|
declare global {
|
|
57
82
|
namespace jest {
|
|
58
|
-
interface Matchers<R, T> {
|
|
59
|
-
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): void;
|
|
83
|
+
interface Matchers<R, T> extends ImageSnapshotMatcher {
|
|
60
84
|
}
|
|
61
85
|
}
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
declare function toMatchImageSnapshot(actual: any, options?: MatchImageSnapshotOptions | undefined): AsyncExpectationResult;
|
|
65
|
-
|
|
66
88
|
declare function cdp(): CDPSession;
|
|
67
89
|
declare const commands: BrowserCommands;
|
|
68
90
|
declare const page: BrowserPage;
|
|
@@ -100,4 +122,4 @@ declare function defineSnapshotParam(snapshot: MatchImageSnapshotOptions): {
|
|
|
100
122
|
snapshot: MatchImageSnapshotOptions;
|
|
101
123
|
};
|
|
102
124
|
|
|
103
|
-
export { type ImageSnapshot, type ImageSnapshotOptions, type MatchImageSnapshotOptions, cdp, commands, defineSnapshotParam, page, server, toMatchImageSnapshot };
|
|
125
|
+
export { type ImageSnapshot, type ImageSnapshotMatcher, type ImageSnapshotOptions, type MatchImageSnapshotOptions, cdp, commands, defineSnapshotParam, page, server, toMatchImageSnapshot };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import j from'dedent';import C from'pixelmatch';import {getByAltText,getByRole,getByTestId,getByText,getByTitle,getByPlaceholderText,getByLabelText}from'@storybook/test';import {join}from'pathe';var g=Symbol.for("storybook-addon-vis/imageSnapshot"),d=Symbol.for("storybook-addon-vis/imageSnapshotStub");function f(t){return new Promise((e,a)=>{let o=new Image;o.src=`data:image/png;base64,${t}`,o.onload=()=>{let s=document.createElement("canvas");s.width=o.width,s.height=o.height;let n=s.getContext("2d");n.drawImage(o,0,0),e(n.getImageData(0,0,s.width,s.height));},o.onerror=()=>{a(new Error("Failed to load image"));};})}async function y(t){let e=document.createElement("canvas");return e.width=t.width,e.height=t.height,e.getContext("2d").putImageData(t,0,0),new Promise(o=>{e.toBlob(s=>{let n=new FileReader;n.onload=()=>{o(n.result);},n.readAsDataURL(s);});})}var p={name:"",testFilepath:"",taskName:"",snapshot:{}};async function x(t){let e=p.snapshot[p.id].index++,a=t?.customizeSnapshotId?`${t.customizeSnapshotId(p.id,e)}.png`:`${p.id}-${e}.png`,o=join(p.baselineDir,a),s=join(p.resultDir,a),n=join(p.diffDir,a),r=await this.screenshot({base64:!0,path:s,element:t?.element}),i=await f(r.base64);return {type:g,snapshotFilename:a,baselinePath:o,resultPath:s,diffPath:n,base64:r.base64,image:i}}var c;globalThis.__vitest_browser__&&import('@vitest/browser/context').then(t=>{c=t,R.extend({imageSnapshot:x});});function Y(){return c?.cdp()??{}}var u=new Proxy({},{get(t,e){return t[e]??(c?.commands)[e]}}),R=new Proxy({getByAltText:getByAltText,getByRole:getByRole,getByTestId:getByTestId,getByText:getByText,getByTitle:getByTitle,getByPlaceholder:getByPlaceholderText,getByLabelText:getByLabelText},{get(t,e){return t[e]??c?.page?(c?.page)[e]:()=>{if(e==="imageSnapshot")return {type:d};console.info(`\`page.${e.toString()}\` does not exist when running in browser`);}}}),w=new Proxy({},{get(t,e){return t[e]??(c?.server)[e]}});function b(t){return !!t&&t.type===g}function I(t){if(!b(t))throw new Error("Expected subject to be an image snapshot")}var D=({width:t,height:e})=>a=>{if(a.width===t&&a.height===e)return a;let o=(n,r)=>r<=a.height&&n<=a.width,s=new ImageData(t,e,{colorSpace:a.colorSpace});for(let n=0;n<e;n++)for(let r=0;r<t;r++){let i=t*n+r<<2;if(o(r,n)){let m=a.width*n+r<<2;s.data[i]=a.data[m],s.data[i+1]=a.data[m+1],s.data[i+2]=a.data[m+2],s.data[i+3]=a.data[m+3];}else s.data[i]=0,s.data[i+1]=0,s.data[i+2]=0,s.data[i+3]=64;}return s};async function ht(t,e){let a=await t;if(a?.type===d)return l;if(!b(a)){if(a?.path&&a?.base64)return {pass:!1,actual:a,message:()=>"`toMatchImageSnapshot()` expects the subject to be the result of `page.imageSnapshot()`, but seems like you are using `page.screenshot()`?"};if(typeof a!="object")return {pass:!1,actual:a,message:()=>`\`toMatchImageSnapshot()\` expects the subject to be an element, locator, or result of \`page.imageSnapshot()\`, but got: \`${a}\``}}I(a);let o=await A(a.baselinePath);if(!o)return await u.copyFile(a.resultPath,a.baselinePath),l;let s=await f(o),[n,r]=O(a.image,s),{pass:i,diffAmount:m,diffImage:h}=L(r,n,e);return i?l:w.config.snapshotOptions.updateSnapshot==="all"?(await P(`${a.baselinePath}`,n),l):(await P(`${a.diffPath}`,h),{pass:!1,message:()=>j`Snapshot \`${p.taskName}\` mismatched
|
|
2
|
+
|
|
3
|
+
${e?.failureThreshold?e?.failureThresholdType==="percent"?`Expected image to match within ${e.failureThreshold}% but was differ by ${m}%.`:`Expected image to match within ${e.failureThreshold} pixels but was differ by ${m} pixels.`:`Expected image to match but was differ by ${e?.failureThresholdType==="percent"?`${m}%`:`${m} pixels`}.`}
|
|
2
4
|
|
|
3
5
|
Expected: '${a.baselinePath}'
|
|
4
6
|
Actual: '${a.resultPath}'
|
|
5
|
-
Difference: '${a.diffPath}'`})}var l={pass:!0,message:()=>""};function A(t){return u.readFile(t,{encoding:"base64"}).catch(()=>{})}async function P(t,e){let a=(await
|
|
7
|
+
Difference: '${a.diffPath}'`})}var l={pass:!0,message:()=>""};function A(t){return u.readFile(t,{encoding:"base64"}).catch(()=>{})}async function P(t,e){let a=(await y(e)).split(",")[1];return u.writeFile(t,a,"base64url")}function O(t,e){if(k(t,e))return [t,e];let a=F(t,e),o=D(a);return [o(t),o(e)]}function F(t,e){let a=Math.max(t.width,e.width),o=Math.max(t.height,e.height);return {width:a,height:o}}function k(t,e){return t.width===e.width&&t.height===e.height}function L(t,e,{failureThreshold:a=0,failureThresholdType:o="pixel",diffOptions:s}={}){let{width:n,height:r}=e,i=new ImageData(n,r),m=C(e.data,t.data,i.data,n,r,s),h=_({failureThresholdType:o,width:n,height:r},m);return {pass:h<=a,diffAmount:h,diffImage:i}}function _({failureThresholdType:t,width:e,height:a},o){switch(t){case"pixel":return o;case"percent":return o/(e*a)*100;default:throw new Error(`Invalid failureThresholdType: ${t}`)}}function dt(t){return {snapshot:t}}export{Y as cdp,u as commands,dt as defineSnapshotParam,R as page,w as server,ht as toMatchImageSnapshot};//# sourceMappingURL=index.js.map
|
|
6
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/context.ts","../src/image_data.ts","../src/state.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts","../src/param.ts"],"names":["imageSnapshotSymbol","imageSnapshotStubSymbol","toImageData","base64","resolve","reject","img","canvas","ctx","toDataURL","imageData","blob","reader","state","imageSnapshot","options","index","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","isImageSnapshot","subject","assertImageSnapshot","createImageResizer","width","height","inArea","x","y","result","idx","old","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","failureThreshold","failureThresholdType","diffOptions","pixelDiff","pixelmatch","toThresholdUnit","defineSnapshotParam","snapshot"],"mappings":"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","file":"index.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\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 { 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 })\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 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","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, { index: number }>\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 async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst index = state.snapshot[state.id]!.index++\n\tconst snapshotFilename = options?.customizeFilename\n\t\t? `${options.customizeFilename(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})\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 { 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 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 type { MatchImageSnapshotOptions } from './@vitest/browser/types.js'\nimport { toDataURL, toImageData } from './image_data.js'\nimport { createImageResizer } from './image_resizer.js'\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`${\n\t\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\t\t\t\t\t\t\t? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`\n\t\t\t\t\t\t\t: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t\t: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t\t}\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","import type { MatchImageSnapshotOptions } from './@vitest/browser/types'\n\nexport function defineSnapshotParam(snapshot: MatchImageSnapshotOptions) {\n\treturn { snapshot }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/context.ts","../src/image_data.ts","../src/state.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts","../src/param.ts"],"names":["imageSnapshotSymbol","imageSnapshotStubSymbol","toImageData","base64","resolve","reject","img","canvas","ctx","toDataURL","imageData","blob","reader","state","imageSnapshot","options","index","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","isImageSnapshot","subject","assertImageSnapshot","createImageResizer","width","height","inArea","x","y","result","idx","old","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","failureThreshold","failureThresholdType","diffOptions","pixelDiff","pixelmatch","toThresholdUnit","defineSnapshotParam","snapshot"],"mappings":"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","file":"index.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\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 { 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 })\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 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","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, { index: number }>\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 SnapshotCapturer {\n\timageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>\n}\n\nexport async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst index = state.snapshot[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})\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 { 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 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 type { MatchImageSnapshotOptions } from './@vitest/browser/types.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 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","import type { MatchImageSnapshotOptions } from './@vitest/browser/types'\n\nexport function defineSnapshotParam(snapshot: MatchImageSnapshotOptions) {\n\treturn { snapshot }\n}\n"]}
|
package/dist/vitest-plugin.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import * as vitest_node from 'vitest/node';
|
|
2
|
+
import { BrowserPage } from '@vitest/browser/context';
|
|
2
3
|
import { PixelmatchOptions } from 'pixelmatch';
|
|
3
4
|
|
|
4
5
|
type ImageSnapshotOptions = {
|
|
5
6
|
/**
|
|
6
|
-
* Customize the filename of the snapshot
|
|
7
|
+
* Customize the snapshot id. This is used as the filename of the snapshot:
|
|
8
|
+
*
|
|
9
|
+
* `${snapshotId}.png`
|
|
7
10
|
*
|
|
8
11
|
* @param id The id of the snapshot.
|
|
9
12
|
* @param index The index of the snapshot.
|
|
10
13
|
*/
|
|
11
|
-
|
|
14
|
+
customizeSnapshotId?: (id: string, index: number) => string;
|
|
12
15
|
element?: Element;
|
|
13
16
|
};
|
|
14
17
|
type ImageSnapshot = {
|
|
@@ -34,27 +37,48 @@ type MatchImageSnapshotOptions = {
|
|
|
34
37
|
failureThreshold?: number | undefined;
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
interface SnapshotCapturer {
|
|
41
|
+
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
interface ImageSnapshotMatcher {
|
|
45
|
+
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface CopyFileCommand {
|
|
49
|
+
copyFile(src: string, dest: string): Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ExistDirCommand {
|
|
53
|
+
existDir: (path: string) => Promise<boolean>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
interface GetSnapshotPlatformCommand {
|
|
57
|
+
/**
|
|
58
|
+
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
59
|
+
*
|
|
60
|
+
* This is useful to control where the snapshot is stored.
|
|
61
|
+
*/
|
|
62
|
+
getSnapshotPlatform: () => Promise<string>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface IsCICommand {
|
|
66
|
+
isCI: () => Promise<boolean>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface RmDirCommand {
|
|
70
|
+
rmDir: (path: string) => Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
37
73
|
declare module '@vitest/browser/context' {
|
|
38
|
-
interface BrowserPage {
|
|
39
|
-
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
74
|
+
interface BrowserPage extends SnapshotCapturer {
|
|
40
75
|
}
|
|
41
|
-
interface BrowserCommands {
|
|
42
|
-
existDir: (path: string) => Promise<boolean>;
|
|
43
|
-
copyFile: (src: string, dest: string) => Promise<void>;
|
|
44
|
-
rmDir: (path: string) => Promise<void>;
|
|
45
|
-
isCI: () => Promise<boolean>;
|
|
46
|
-
/**
|
|
47
|
-
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
48
|
-
*
|
|
49
|
-
* This is useful to control where the snapshot is stored.
|
|
50
|
-
*/
|
|
51
|
-
getSnapshotPlatform: () => Promise<string>;
|
|
76
|
+
interface BrowserCommands extends CopyFileCommand, ExistDirCommand, GetSnapshotPlatformCommand, RmDirCommand, IsCICommand {
|
|
52
77
|
}
|
|
53
78
|
}
|
|
54
79
|
declare global {
|
|
55
80
|
namespace jest {
|
|
56
|
-
interface Matchers<R, T> {
|
|
57
|
-
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): void;
|
|
81
|
+
interface Matchers<R, T> extends ImageSnapshotMatcher {
|
|
58
82
|
}
|
|
59
83
|
}
|
|
60
84
|
}
|
|
@@ -70,7 +94,7 @@ declare function storybookVis(): {
|
|
|
70
94
|
copyFile: vitest_node.BrowserCommand<[src: string, dest: string]>;
|
|
71
95
|
rmDir: vitest_node.BrowserCommand<[path: string]>;
|
|
72
96
|
isCI: () => Promise<boolean>;
|
|
73
|
-
getSnapshotPlatform: () => Promise<
|
|
97
|
+
getSnapshotPlatform: () => Promise<NodeJS.Platform | "local">;
|
|
74
98
|
};
|
|
75
99
|
};
|
|
76
100
|
};
|
package/dist/vitest-plugin.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {copyFile,stat}from'node:fs/promises';import {mkdirp}from'mkdirp';import {join,dirname}from'pathe';import
|
|
1
|
+
import {copyFile,stat}from'node:fs/promises';import {mkdirp}from'mkdirp';import {join,dirname}from'pathe';import C from'is-ci';import {rimraf}from'rimraf';var s=async({testPath:o,provider:r},t,i)=>{if(r.name==="playwright"){let m=join(dirname(o),i),d=dirname(m);return await mkdirp(d),copyFile(join(dirname(o),t),m)}throw new Error(`provider ${r.name} is not supported`)};var a=async({testPath:o,provider:r},t)=>{if(r.name==="playwright")return stat(join(dirname(o),t)).then(i=>i.isDirectory()).catch(()=>!1);throw new Error(`provider ${r.name} is not supported`)};var p=async()=>C?process.platform:"local";var f=async()=>C;var c=async({testPath:o,provider:r},t)=>{if(r.name==="playwright")return rimraf(join(dirname(o),t));throw new Error(`provider ${r.name} is not supported`)};function L(){return {name:"vitest:storybook-addon-vis",config(){return {test:{browser:{name:"chromium",commands:{existDir:a,copyFile:s,rmDir:c,isCI:f,getSnapshotPlatform:p}}}}}}}export{L as storybookVis};//# sourceMappingURL=vitest-plugin.js.map
|
|
2
2
|
//# sourceMappingURL=vitest-plugin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/vitest-plugin/commands/copy_file.ts","../src/vitest-plugin/commands/exist_dir.ts","../src/vitest-plugin/commands/get_snapshot_platform.ts","../src/vitest-plugin/commands/is_ci.ts","../src/vitest-plugin/commands/rm_dir.ts","../src/vitest-plugin.ts"],"names":["copyFile","testPath","provider","src","dest","destPath","join","dirname","destDir","mkdirp","copyFileFs","existDir","path","stat","s","getSnapshotPlatform","ci","isCI","rmDir","rimraf","storybookVis"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/vitest-plugin/commands/copy_file.ts","../src/vitest-plugin/commands/exist_dir.ts","../src/vitest-plugin/commands/get_snapshot_platform.ts","../src/vitest-plugin/commands/is_ci.ts","../src/vitest-plugin/commands/rm_dir.ts","../src/vitest-plugin.ts"],"names":["copyFile","testPath","provider","src","dest","destPath","join","dirname","destDir","mkdirp","copyFileFs","existDir","path","stat","s","getSnapshotPlatform","ci","isCI","rmDir","rimraf","storybookVis"],"mappings":"2JASO,IAAMA,CAAwD,CAAA,MAAO,CAAE,QAAA,CAAAC,EAAU,QAAAC,CAAAA,CAAS,CAAGC,CAAAA,CAAAA,CAAKC,CAAS,GAAA,CACjH,GAAIF,CAAS,CAAA,IAAA,GAAS,aAAc,CACnC,IAAMG,EAAWC,IAAKC,CAAAA,OAAAA,CAAQN,CAAQ,CAAA,CAAGG,CAAI,CAAA,CACvCI,EAAUD,OAAQF,CAAAA,CAAQ,CAChC,CAAA,OAAA,MAAMI,MAAOD,CAAAA,CAAO,EACbE,QAAWJ,CAAAA,IAAAA,CAAKC,OAAQN,CAAAA,CAAQ,CAAGE,CAAAA,CAAG,EAAGE,CAAQ,CACzD,CAEA,MAAM,IAAI,MAAM,CAAYH,SAAAA,EAAAA,CAAAA,CAAS,IAAI,CAAA,iBAAA,CAAmB,CAC7D,CAAA,CCVO,IAAMS,CAAAA,CAA2C,MAAO,CAAE,SAAAV,CAAU,CAAA,QAAA,CAAAC,CAAS,CAAGU,CAAAA,CAAAA,GAAS,CAC/F,GAAIV,CAAAA,CAAS,IAAS,GAAA,YAAA,CACrB,OAAOW,IAAAA,CAAKP,KAAKC,OAAQN,CAAAA,CAAQ,CAAGW,CAAAA,CAAI,CAAC,CAAA,CACvC,KAAME,CAAMA,EAAAA,CAAAA,CAAE,WAAY,EAAC,CAC3B,CAAA,KAAA,CAAM,IAAM,CAAK,CAAA,CAAA,CAGpB,MAAM,IAAI,KAAA,CAAM,YAAYZ,CAAS,CAAA,IAAI,CAAmB,iBAAA,CAAA,CAC7D,CChBA,CAWO,IAAMa,CAAsB,CAAA,SAC3BC,CAAK,CAAA,OAAA,CAAQ,SAAW,OCZhC,CAMO,IAAMC,CAAAA,CAAO,SAAYD,CCNhC,CAQO,IAAME,EAAwC,MAAO,CAAE,QAAAjB,CAAAA,CAAAA,CAAU,QAAAC,CAAAA,CAAS,EAAGU,CAAS,GAAA,CAC5F,GAAIV,CAAAA,CAAS,IAAS,GAAA,YAAA,CACrB,OAAOiB,MAAOb,CAAAA,IAAAA,CAAKC,QAAQN,CAAQ,CAAA,CAAGW,CAAI,CAAC,CAAA,CAG5C,MAAM,IAAI,KAAM,CAAA,CAAA,SAAA,EAAYV,EAAS,IAAI,CAAA,iBAAA,CAAmB,CAC7D,CAAA,CCNO,SAASkB,CAAAA,EAAe,CAC9B,OAAO,CACN,IAAM,CAAA,4BAAA,CACN,MAAS,EAAA,CACR,OAAO,CACN,IAAA,CAAM,CACL,OAAS,CAAA,CACR,KAAM,UACN,CAAA,QAAA,CAAU,CACT,QAAA,CAAAT,CACA,CAAA,QAAA,CAAAX,EACA,KAAAkB,CAAAA,CAAAA,CACA,IAAAD,CAAAA,CAAAA,CACA,mBAAAF,CAAAA,CACD,CACD,CACD,CACD,CACD,CACD,CACD","file":"vitest-plugin.js","sourcesContent":["import { copyFile as copyFileFs } from 'node:fs/promises'\nimport { mkdirp } from 'mkdirp'\nimport { dirname, join } from 'pathe'\nimport type { BrowserCommand } from 'vitest/node'\n\nexport interface CopyFileCommand {\n\tcopyFile(src: string, dest: string): Promise<void>\n}\n\nexport const copyFile: BrowserCommand<[src: string, dest: string]> = async ({ testPath, provider }, src, dest) => {\n\tif (provider.name === 'playwright') {\n\t\tconst destPath = join(dirname(testPath), dest)\n\t\tconst destDir = dirname(destPath)\n\t\tawait mkdirp(destDir)\n\t\treturn copyFileFs(join(dirname(testPath), src), destPath)\n\t}\n\n\tthrow new Error(`provider ${provider.name} is not supported`)\n}\n","import { stat } from 'node:fs/promises'\nimport { dirname, join } from 'pathe'\nimport type { BrowserCommand } from 'vitest/node'\n\nexport interface ExistDirCommand {\n\texistDir: (path: string) => Promise<boolean>\n}\n\nexport const existDir: BrowserCommand<[path: string]> = async ({ testPath, provider }, path) => {\n\tif (provider.name === 'playwright') {\n\t\treturn stat(join(dirname(testPath), path))\n\t\t\t.then((s) => s.isDirectory())\n\t\t\t.catch(() => false)\n\t}\n\n\tthrow new Error(`provider ${provider.name} is not supported`)\n}\n","import ci from 'is-ci'\n\nexport interface GetSnapshotPlatformCommand {\n\t/**\n\t * Get the platform id of the snapshot `{platform}[-ci]`.\n\t *\n\t * This is useful to control where the snapshot is stored.\n\t */\n\tgetSnapshotPlatform: () => Promise<string>\n}\n\nexport const getSnapshotPlatform = async () => {\n\treturn ci ? process.platform : 'local'\n}\n","import ci from 'is-ci'\n\nexport interface IsCICommand {\n\tisCI: () => Promise<boolean>\n}\n\nexport const isCI = async () => ci\n","import { dirname, join } from 'pathe'\nimport { rimraf } from 'rimraf'\nimport type { BrowserCommand } from 'vitest/node'\n\nexport interface RmDirCommand {\n\trmDir: (path: string) => Promise<void>\n}\n\nexport const rmDir: BrowserCommand<[path: string]> = async ({ testPath, provider }, path) => {\n\tif (provider.name === 'playwright') {\n\t\treturn rimraf(join(dirname(testPath), path))\n\t}\n\n\tthrow new Error(`provider ${provider.name} is not supported`)\n}\n","import type { Plugin } from 'vitest/config'\nimport './augment.js'\nimport { copyFile } from './vitest-plugin/commands/copy_file.js'\nimport { existDir } from './vitest-plugin/commands/exist_dir.js'\nimport { getSnapshotPlatform } from './vitest-plugin/commands/get_snapshot_platform.js'\nimport { isCI } from './vitest-plugin/commands/is_ci.js'\nimport { rmDir } from './vitest-plugin/commands/rm_dir.js'\n\nexport function storybookVis() {\n\treturn {\n\t\tname: 'vitest:storybook-addon-vis',\n\t\tconfig() {\n\t\t\treturn {\n\t\t\t\ttest: {\n\t\t\t\t\tbrowser: {\n\t\t\t\t\t\tname: 'chromium',\n\t\t\t\t\t\tcommands: {\n\t\t\t\t\t\t\texistDir,\n\t\t\t\t\t\t\tcopyFile,\n\t\t\t\t\t\t\trmDir,\n\t\t\t\t\t\t\tisCI,\n\t\t\t\t\t\t\tgetSnapshotPlatform,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\t\t},\n\t} satisfies Plugin\n}\n"]}
|
package/dist/vitest-setup.d.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
|
+
import { BrowserPage, CDPSession, BrowserCommands, Platform } from '@vitest/browser/context';
|
|
1
2
|
import { PixelmatchOptions } from 'pixelmatch';
|
|
2
|
-
import { CDPSession, BrowserCommands, BrowserPage, Platform } from '@vitest/browser/context';
|
|
3
|
-
import { SerializedConfig } from 'vitest';
|
|
4
3
|
import { AsyncExpectationResult } from '@vitest/expect';
|
|
4
|
+
import { SerializedConfig } from 'vitest';
|
|
5
5
|
|
|
6
6
|
type ImageSnapshotOptions = {
|
|
7
7
|
/**
|
|
8
|
-
* Customize the filename of the snapshot
|
|
8
|
+
* Customize the snapshot id. This is used as the filename of the snapshot:
|
|
9
|
+
*
|
|
10
|
+
* `${snapshotId}.png`
|
|
9
11
|
*
|
|
10
12
|
* @param id The id of the snapshot.
|
|
11
13
|
* @param index The index of the snapshot.
|
|
12
14
|
*/
|
|
13
|
-
|
|
15
|
+
customizeSnapshotId?: (id: string, index: number) => string;
|
|
14
16
|
element?: Element;
|
|
15
17
|
};
|
|
16
18
|
type ImageSnapshot = {
|
|
@@ -36,27 +38,49 @@ type MatchImageSnapshotOptions = {
|
|
|
36
38
|
failureThreshold?: number | undefined;
|
|
37
39
|
};
|
|
38
40
|
|
|
41
|
+
interface SnapshotCapturer {
|
|
42
|
+
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface ImageSnapshotMatcher {
|
|
46
|
+
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
declare function toMatchImageSnapshot(actual: any, options?: MatchImageSnapshotOptions | undefined): AsyncExpectationResult;
|
|
49
|
+
|
|
50
|
+
interface CopyFileCommand {
|
|
51
|
+
copyFile(src: string, dest: string): Promise<void>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ExistDirCommand {
|
|
55
|
+
existDir: (path: string) => Promise<boolean>;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
interface GetSnapshotPlatformCommand {
|
|
59
|
+
/**
|
|
60
|
+
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
61
|
+
*
|
|
62
|
+
* This is useful to control where the snapshot is stored.
|
|
63
|
+
*/
|
|
64
|
+
getSnapshotPlatform: () => Promise<string>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface IsCICommand {
|
|
68
|
+
isCI: () => Promise<boolean>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface RmDirCommand {
|
|
72
|
+
rmDir: (path: string) => Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
|
|
39
75
|
declare module '@vitest/browser/context' {
|
|
40
|
-
interface BrowserPage {
|
|
41
|
-
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>;
|
|
76
|
+
interface BrowserPage extends SnapshotCapturer {
|
|
42
77
|
}
|
|
43
|
-
interface BrowserCommands {
|
|
44
|
-
existDir: (path: string) => Promise<boolean>;
|
|
45
|
-
copyFile: (src: string, dest: string) => Promise<void>;
|
|
46
|
-
rmDir: (path: string) => Promise<void>;
|
|
47
|
-
isCI: () => Promise<boolean>;
|
|
48
|
-
/**
|
|
49
|
-
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
50
|
-
*
|
|
51
|
-
* This is useful to control where the snapshot is stored.
|
|
52
|
-
*/
|
|
53
|
-
getSnapshotPlatform: () => Promise<string>;
|
|
78
|
+
interface BrowserCommands extends CopyFileCommand, ExistDirCommand, GetSnapshotPlatformCommand, RmDirCommand, IsCICommand {
|
|
54
79
|
}
|
|
55
80
|
}
|
|
56
81
|
declare global {
|
|
57
82
|
namespace jest {
|
|
58
|
-
interface Matchers<R, T> {
|
|
59
|
-
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): void;
|
|
83
|
+
interface Matchers<R, T> extends ImageSnapshotMatcher {
|
|
60
84
|
}
|
|
61
85
|
}
|
|
62
86
|
}
|
|
@@ -94,8 +118,6 @@ declare const server: {
|
|
|
94
118
|
config: SerializedConfig;
|
|
95
119
|
};
|
|
96
120
|
|
|
97
|
-
declare function toMatchImageSnapshot(actual: any, options?: MatchImageSnapshotOptions | undefined): AsyncExpectationResult;
|
|
98
|
-
|
|
99
121
|
/**
|
|
100
122
|
* Determine should snapshot be taken.
|
|
101
123
|
*
|
|
@@ -132,4 +154,4 @@ declare function configureSnapshotBeforeEach(ctx: {
|
|
|
132
154
|
};
|
|
133
155
|
}): void;
|
|
134
156
|
|
|
135
|
-
export { type VisOptions, cdp, commands, configureSnapshotBeforeAll, configureSnapshotBeforeEach, page, server, shouldTakeSnapshot, toMatchImageSnapshot };
|
|
157
|
+
export { type ImageSnapshotMatcher, type VisOptions, cdp, commands, configureSnapshotBeforeAll, configureSnapshotBeforeEach, page, server, shouldTakeSnapshot, toMatchImageSnapshot };
|
package/dist/vitest-setup.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {join,basename,dirname,relative}from'pathe';import {getByAltText,getByRole,getByTestId,getByText,getByTitle,getByPlaceholderText,getByLabelText}from'@storybook/test';import
|
|
1
|
+
import {join,basename,dirname,relative}from'pathe';import {getByAltText,getByRole,getByTestId,getByText,getByTitle,getByPlaceholderText,getByLabelText}from'@storybook/test';import R from'dedent';import k from'pixelmatch';var f=Symbol.for("storybook-addon-vis/imageSnapshot"),d=Symbol.for("storybook-addon-vis/imageSnapshotStub");function l(t){return new Promise((e,a)=>{let o=new Image;o.src=`data:image/png;base64,${t}`,o.onload=()=>{let n=document.createElement("canvas");n.width=o.width,n.height=o.height;let r=n.getContext("2d");r.drawImage(o,0,0),e(r.getImageData(0,0,n.width,n.height));},o.onerror=()=>{a(new Error("Failed to load image"));};})}async function w(t){let e=document.createElement("canvas");return e.width=t.width,e.height=t.height,e.getContext("2d").putImageData(t,0,0),new Promise(o=>{e.toBlob(n=>{let r=new FileReader;r.onload=()=>{o(r.result);},r.readAsDataURL(n);});})}var s={name:"",testFilepath:"",taskName:"",snapshot:{}};async function I(t){let e=s.snapshot[s.id].index++,a=t?.customizeSnapshotId?`${t.customizeSnapshotId(s.id,e)}.png`:`${s.id}-${e}.png`,o=join(s.baselineDir,a),n=join(s.resultDir,a),r=join(s.diffDir,a),i=await this.screenshot({base64:!0,path:n,element:t?.element}),m=await l(i.base64);return {type:f,snapshotFilename:a,baselinePath:o,resultPath:n,diffPath:r,base64:i.base64,image:m}}var c;globalThis.__vitest_browser__&&import('@vitest/browser/context').then(t=>{c=t,O.extend({imageSnapshot:I});});function st(){return c?.cdp()??{}}var h=new Proxy({},{get(t,e){return t[e]??(c?.commands)[e]}}),O=new Proxy({getByAltText:getByAltText,getByRole:getByRole,getByTestId:getByTestId,getByText:getByText,getByTitle:getByTitle,getByPlaceholder:getByPlaceholderText,getByLabelText:getByLabelText},{get(t,e){return t[e]??c?.page?(c?.page)[e]:()=>{if(e==="imageSnapshot")return {type:d};console.info(`\`page.${e.toString()}\` does not exist when running in browser`);}}}),D=new Proxy({},{get(t,e){return t[e]??(c?.server)[e]}});function b(t){return !!t&&t.type===f}function P(t){if(!b(t))throw new Error("Expected subject to be an image snapshot")}function $(t){return `${t.replace(/[^a-z0-9]/gi,"-").toLowerCase()}`}var v=({width:t,height:e})=>a=>{if(a.width===t&&a.height===e)return a;let o=(r,i)=>i<=a.height&&r<=a.width,n=new ImageData(t,e,{colorSpace:a.colorSpace});for(let r=0;r<e;r++)for(let i=0;i<t;i++){let m=t*r+i<<2;if(o(i,r)){let p=a.width*r+i<<2;n.data[m]=a.data[p],n.data[m+1]=a.data[p+1],n.data[m+2]=a.data[p+2],n.data[m+3]=a.data[p+3];}else n.data[m]=0,n.data[m+1]=0,n.data[m+2]=0,n.data[m+3]=64;}return n};async function ut(t,e){let a=await t;if(a?.type===d)return u;if(!b(a)){if(a?.path&&a?.base64)return {pass:!1,actual:a,message:()=>"`toMatchImageSnapshot()` expects the subject to be the result of `page.imageSnapshot()`, but seems like you are using `page.screenshot()`?"};if(typeof a!="object")return {pass:!1,actual:a,message:()=>`\`toMatchImageSnapshot()\` expects the subject to be an element, locator, or result of \`page.imageSnapshot()\`, but got: \`${a}\``}}P(a);let o=await A(a.baselinePath);if(!o)return await h.copyFile(a.resultPath,a.baselinePath),u;let n=await l(o),[r,i]=C(a.image,n),{pass:m,diffAmount:p,diffImage:g}=U(i,r,e);return m?u:D.config.snapshotOptions.updateSnapshot==="all"?(await B(`${a.baselinePath}`,r),u):(await B(`${a.diffPath}`,g),{pass:!1,message:()=>R`Snapshot \`${s.taskName}\` mismatched
|
|
2
|
+
|
|
3
|
+
${e?.failureThreshold?e?.failureThresholdType==="percent"?`Expected image to match within ${e.failureThreshold}% but was differ by ${p}%.`:`Expected image to match within ${e.failureThreshold} pixels but was differ by ${p} pixels.`:`Expected image to match but was differ by ${e?.failureThresholdType==="percent"?`${p}%`:`${p} pixels`}.`}
|
|
2
4
|
|
|
3
5
|
Expected: '${a.baselinePath}'
|
|
4
6
|
Actual: '${a.resultPath}'
|
|
5
|
-
Difference: '${a.diffPath}'`})}var u={pass:!0,message:()=>""};function
|
|
7
|
+
Difference: '${a.diffPath}'`})}var u={pass:!0,message:()=>""};function A(t){return h.readFile(t,{encoding:"base64"}).catch(()=>{})}async function B(t,e){let a=(await w(e)).split(",")[1];return h.writeFile(t,a,"base64url")}function C(t,e){if(N(t,e))return [t,e];let a=L(t,e),o=v(a);return [o(t),o(e)]}function L(t,e){let a=Math.max(t.width,e.width),o=Math.max(t.height,e.height);return {width:a,height:o}}function N(t,e){return t.width===e.width&&t.height===e.height}function U(t,e,{failureThreshold:a=0,failureThresholdType:o="pixel",diffOptions:n}={}){let{width:r,height:i}=e,m=new ImageData(r,i),p=k(e.data,t.data,m.data,r,i,n),g=V({failureThresholdType:o,width:r,height:i},p);return {pass:g<=a,diffAmount:g,diffImage:m}}function V({failureThresholdType:t,width:e,height:a},o){switch(t){case"pixel":return o;case"percent":return o/(e*a)*100;default:throw new Error(`Invalid failureThresholdType: ${t}`)}}function St(t){return t.story?.tags?t.story.tags.lastIndexOf("!snapshot")<t.story.tags.lastIndexOf("snapshot"):!1}async function Pt(t,e){s.name=t.name,s.testFilepath=t.file.filepath,s.testFilename=basename(s.testFilepath),s.projectDir=s.testFilepath.slice(0,-s.name.length);let a=join(s.projectDir,e?.snapshotPath??`__snapshots__/${await h.getSnapshotPlatform()}`),o=dirname(s.testFilepath);s.baselineDir=relative(o,join(a,s.testFilename)),s.resultDir=relative(o,join(a,"__results__",s.testFilename)),s.diffDir=relative(o,join(a,"__diff_output__",s.testFilename)),await h.rmDir(s.resultDir),await h.rmDir(s.diffDir);}function $t(t){s.taskName=t.task.name;let e=s.id=$(s.taskName);s.snapshot[e]=s.snapshot[e]??{index:1};}export{st as cdp,h as commands,Pt as configureSnapshotBeforeAll,$t as configureSnapshotBeforeEach,O as page,D as server,St as shouldTakeSnapshot,ut as toMatchImageSnapshot};//# sourceMappingURL=vitest-setup.js.map
|
|
6
8
|
//# sourceMappingURL=vitest-setup.js.map
|
package/dist/vitest-setup.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/image_data.ts","../src/state.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","../src/tags.ts","../src/vitest-setup.ts"],"names":["imageSnapshotSymbol","imageSnapshotStubSymbol","toImageData","base64","resolve","reject","img","canvas","ctx","toDataURL","imageData","blob","reader","state","imageSnapshot","options","index","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","isImageSnapshot","subject","assertImageSnapshot","toSnapshotId","taskName","createImageResizer","width","height","inArea","x","y","result","idx","old","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","failureThreshold","failureThresholdType","diffOptions","pixelDiff","pixelmatch","toThresholdUnit","shouldTakeSnapshot","configureSnapshotBeforeAll","suite","basename","snapshotPath","currentDir","dirname","relative","configureSnapshotBeforeEach","id"],"mappings":"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","file":"vitest-setup.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\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","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, { index: number }>\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 async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst index = state.snapshot[state.id]!.index++\n\tconst snapshotFilename = options?.customizeFilename\n\t\t? `${options.customizeFilename(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})\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 { 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 })\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 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 type { MatchImageSnapshotOptions } from './@vitest/browser/types.js'\nimport { toDataURL, toImageData } from './image_data.js'\nimport { createImageResizer } from './image_resizer.js'\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`${\n\t\t\t\t\toptions?.failureThreshold\n\t\t\t\t\t\t? options?.failureThresholdType === 'percent'\n\t\t\t\t\t\t\t? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`\n\t\t\t\t\t\t\t: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`\n\t\t\t\t\t\t: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`\n\t\t\t\t}\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","/**\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\tawait commands.rmDir(state.resultDir)\n\tawait commands.rmDir(state.diffDir)\n}\n\nexport function configureSnapshotBeforeEach(ctx: { task: { name: string } }) {\n\t// console.log('configureSnapshotBeforeEach', ctx.task)\n\tstate.taskName = ctx.task.name\n\tconst id = (state.id = toSnapshotId(state.taskName))\n\tstate.snapshot[id] = state.snapshot[id] ?? { index: 1 }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/image_data.ts","../src/state.ts","../src/@vitest/browser/context.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/image_resizer.ts","../src/expect.to_match_image_snapshot.ts","../src/tags.ts","../src/vitest-setup.ts"],"names":["imageSnapshotSymbol","imageSnapshotStubSymbol","toImageData","base64","resolve","reject","img","canvas","ctx","toDataURL","imageData","blob","reader","state","imageSnapshot","options","index","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","isImageSnapshot","subject","assertImageSnapshot","toSnapshotId","taskName","createImageResizer","width","height","inArea","x","y","result","idx","old","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","failureThreshold","failureThresholdType","diffOptions","pixelDiff","pixelmatch","toThresholdUnit","shouldTakeSnapshot","configureSnapshotBeforeAll","suite","basename","snapshotPath","currentDir","dirname","relative","configureSnapshotBeforeEach","id"],"mappings":"6NAAO,IAAMA,CAAsB,CAAA,MAAA,CAAO,IAAI,mCAAmC,CAAA,CAEpEC,EAA0B,MAAO,CAAA,GAAA,CAAI,uCAAuC,CCDzF,CCDO,SAASC,CAAYC,CAAAA,CAAAA,CAAgB,CAC3C,OAAO,IAAI,OAAmB,CAAA,CAACC,EAASC,CAAW,GAAA,CAClD,IAAMC,CAAM,CAAA,IAAI,MAChBA,CAAI,CAAA,GAAA,CAAM,yBAAyBH,CAAM,CAAA,CAAA,CAEzCG,EAAI,MAAS,CAAA,IAAM,CAClB,IAAMC,CAAAA,CAAS,QAAS,CAAA,aAAA,CAAc,QAAQ,CAC9CA,CAAAA,CAAAA,CAAO,MAAQD,CAAI,CAAA,KAAA,CACnBC,EAAO,MAASD,CAAAA,CAAAA,CAAI,OACpB,IAAME,CAAAA,CAAMD,EAAO,UAAW,CAAA,IAAI,EAClCC,CAAI,CAAA,SAAA,CAAUF,EAAK,CAAG,CAAA,CAAC,CACvBF,CAAAA,CAAAA,CAAQI,EAAI,YAAa,CAAA,CAAA,CAAG,EAAGD,CAAO,CAAA,KAAA,CAAOA,EAAO,MAAM,CAAC,EAC5D,CAAA,CAEAD,EAAI,OAAU,CAAA,IAAM,CACnBD,CAAO,CAAA,IAAI,MAAM,sBAAsB,CAAC,EACzC,EACD,CAAC,CACF,CAEA,eAAsBI,CAAUC,CAAAA,CAAAA,CAAsB,CACrD,IAAMH,CAAAA,CAAS,SAAS,aAAc,CAAA,QAAQ,EAC9C,OAAAA,CAAAA,CAAO,MAAQG,CAAU,CAAA,KAAA,CACzBH,EAAO,MAASG,CAAAA,CAAAA,CAAU,MACdH,CAAAA,CAAAA,CAAO,WAAW,IAAI,CAAA,CAC9B,aAAaG,CAAW,CAAA,CAAA,CAAG,CAAC,CACzB,CAAA,IAAI,QAAiBN,CAAY,EAAA,CACvCG,EAAO,MAAQI,CAAAA,CAAAA,EAAS,CACvB,IAAMC,CAAAA,CAAS,IAAI,UACnBA,CAAAA,CAAAA,CAAO,MAAS,CAAA,IAAM,CACrBR,CAAQQ,CAAAA,CAAAA,CAAO,MAAgB,EAChC,CAAA,CACAA,EAAO,aAAcD,CAAAA,CAAK,EAC3B,CAAC,EACF,CAAC,CACF,CCnCO,IAAME,CAAQ,CAAA,CACpB,KAAM,EACN,CAAA,YAAA,CAAc,EACd,CAAA,QAAA,CAAU,GACV,QAAU,CAAA,EACX,CFMA,CAAA,eAAsBC,EAErBC,CACyB,CAAA,CACzB,IAAMC,CAAAA,CAAQH,EAAM,QAASA,CAAAA,CAAAA,CAAM,EAAE,CAAG,CAAA,KAAA,EAAA,CAClCI,EAAmBF,CAAS,EAAA,mBAAA,CAC/B,CAAGA,EAAAA,CAAAA,CAAQ,oBAAoBF,CAAM,CAAA,EAAA,CAAIG,CAAK,CAAC,CAAA,IAAA,CAAA,CAC/C,GAAGH,CAAM,CAAA,EAAE,IAAIG,CAAK,CAAA,IAAA,CAAA,CACjBE,EAAeC,IAAKN,CAAAA,CAAAA,CAAM,YAAaI,CAAgB,CAAA,CACvDG,EAAaD,IAAKN,CAAAA,CAAAA,CAAM,SAAWI,CAAAA,CAAgB,EACnDI,CAAWF,CAAAA,IAAAA,CAAKN,EAAM,OAASI,CAAAA,CAAgB,EAC/CK,CAAa,CAAA,MAAM,KAAK,UAAW,CAAA,CACxC,OAAQ,CACR,CAAA,CAAA,IAAA,CAAMF,EACN,OAASL,CAAAA,CAAAA,EAAS,OACnB,CAAC,CAAA,CACKQ,CAAQ,CAAA,MAAMrB,EAAYoB,CAAW,CAAA,MAAM,EAEjD,OAAO,CACN,KAAMtB,CACN,CAAA,gBAAA,CAAAiB,EACA,YAAAC,CAAAA,CAAAA,CACA,WAAAE,CACA,CAAA,QAAA,CAAAC,EACA,MAAQC,CAAAA,CAAAA,CAAW,OACnB,KAAAC,CAAAA,CACD,CACD,CGxBA,IAAIf,CAEC,CAAA,UAAA,CAAmB,oBACvB,OAAO,yBAAyB,EAAE,IAAMgB,CAAAA,CAAAA,EAAM,CAC7ChB,CAAMgB,CAAAA,CAAAA,CACNC,EAAK,MAAO,CAAA,CAAE,cAAAX,CAAc,CAAC,EAC9B,CAAC,CAAA,CAKK,SAASY,EAAAA,EAAkB,CACjC,OAAOlB,CAAAA,EAAK,KAAU,EAAA,EACvB,CAEO,IAAMmB,EAAW,IAAI,KAAA,CAAuB,EAAW,CAAA,CAC7D,IAAIC,CAAQC,CAAAA,CAAAA,CAAM,CACjB,OAAQD,CAAAA,CAAeC,CAAI,CAAA,EAAA,CAAMrB,GAAK,QAAiBqB,EAAAA,CAAI,CAC5D,CACD,CAAC,EAEYJ,CAAO,CAAA,IAAI,MACvB,CACC,YAAA,CAAAK,aACA,SAAAC,CAAAA,SAAAA,CACA,YAAAC,WACA,CAAA,SAAA,CAAAC,UACA,UAAAC,CAAAA,UAAAA,CACA,gBAAkBC,CAAAA,oBAAAA,CAClB,eAAAC,cACD,CAAA,CACA,CACC,GAAIR,CAAAA,CAAAA,CAAQC,EAAM,CACjB,OAASD,EAAeC,CAAI,CAAA,EAAKrB,GAAK,IAClCA,CAAAA,CAAAA,CAAAA,EAAK,MAAaqB,CAAI,CAAA,CACvB,IAAM,CACN,GAAIA,CAAS,GAAA,eAAA,CACZ,OAAO,CAAE,IAAA,CAAM5B,CAAwB,CAExC,CAAA,OAAA,CAAQ,KAAK,CAAU4B,OAAAA,EAAAA,CAAAA,CAAK,QAAS,EAAC,2CAA2C,EAClF,CACH,CACD,CACD,CAAA,CAEaQ,EAAS,IAAI,KAAA,CA4BvB,EAAC,CAAU,CACb,GAAIT,CAAAA,CAAAA,CAAQC,EAAM,CACjB,OAAQD,EAAeC,CAAI,CAAA,EAAA,CAAMrB,GAAK,MAAeqB,EAAAA,CAAI,CAC1D,CACD,CAAC,ECxFM,SAASS,CAAAA,CAAgBC,EAAwC,CACvE,OAAO,CAAC,CAACA,GAAWA,CAAQ,CAAA,IAAA,GAASvC,CACtC,CAEO,SAASwC,EAAoBD,CAAgD,CAAA,CACnF,GAAI,CAACD,CAAAA,CAAgBC,CAAO,CAC3B,CAAA,MAAM,IAAI,KAAM,CAAA,0CAA0C,CAE5D,CAEO,SAASE,CAAaC,CAAAA,CAAAA,CAAkB,CAC9C,OAAO,CAAA,EAAGA,EAAS,OAAQ,CAAA,aAAA,CAAe,GAAG,CAAE,CAAA,WAAA,EAAa,CAC7D,CAAA,CCfO,IAAMC,CACZ,CAAA,CAAC,CAAE,KAAA,CAAAC,EAAO,MAAAC,CAAAA,CAAO,IAChBtB,CAAqB,EAAA,CACrB,GAAIA,CAAM,CAAA,KAAA,GAAUqB,CAASrB,EAAAA,CAAAA,CAAM,SAAWsB,CAC7C,CAAA,OAAOtB,EAER,IAAMuB,CAAAA,CAAS,CAACC,CAAWC,CAAAA,CAAAA,GAAcA,CAAKzB,EAAAA,CAAAA,CAAM,QAAUwB,CAAKxB,EAAAA,CAAAA,CAAM,MACnE0B,CAAS,CAAA,IAAI,UAAUL,CAAOC,CAAAA,CAAAA,CAAQ,CAAE,UAAYtB,CAAAA,CAAAA,CAAM,UAAW,CAAC,CAAA,CAE5E,QAASyB,CAAI,CAAA,CAAA,CAAGA,EAAIH,CAAQG,CAAAA,CAAAA,EAAAA,CAC3B,IAASD,IAAAA,CAAAA,CAAI,EAAGA,CAAIH,CAAAA,CAAAA,CAAOG,IAAK,CAC/B,IAAMG,EAAON,CAAQI,CAAAA,CAAAA,CAAID,GAAM,CAC/B,CAAA,GAAID,EAAOC,CAAGC,CAAAA,CAAC,EAAG,CACjB,IAAMG,EAAO5B,CAAM,CAAA,KAAA,CAAQyB,CAAID,CAAAA,CAAAA,EAAM,EACrCE,CAAO,CAAA,IAAA,CAAKC,CAAG,CAAI3B,CAAAA,CAAAA,CAAM,KAAK4B,CAAG,CAAA,CACjCF,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAI3B,CAAAA,CAAAA,CAAM,KAAK4B,CAAM,CAAA,CAAC,EACzCF,CAAO,CAAA,IAAA,CAAKC,CAAM,CAAA,CAAC,EAAI3B,CAAM,CAAA,IAAA,CAAK4B,EAAM,CAAC,CAAA,CACzCF,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAA,CAAI3B,EAAM,IAAK4B,CAAAA,CAAAA,CAAM,CAAC,EAC1C,CAAA,KACCF,EAAO,IAAKC,CAAAA,CAAG,CAAI,CAAA,CAAA,CACnBD,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAI,CAAA,CAAA,CACvBD,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAI,CAAA,CAAA,CACvBD,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAI,CAAA,GAEzB,CAGD,OAAOD,CACR,CCbD,CAAA,eAAsBG,GACrBC,CACAtC,CAAAA,CAAAA,CACyB,CACzB,IAAMwB,CAAAA,CAAU,MAAMc,CACtB,CAAA,GAAId,GAAS,IAAStC,GAAAA,CAAAA,CACrB,OAAOqD,CAER,CAAA,GAAI,CAAChB,CAAgBC,CAAAA,CAAO,EAAG,CAC9B,GAAIA,CAAS,EAAA,IAAA,EAAQA,GAAS,MAC7B,CAAA,OAAO,CACN,IAAM,CAAA,CAAA,CAAA,CACN,OAAQA,CACR,CAAA,OAAA,CAAS,IACR,4IACF,CAAA,CAGD,GAAI,OAAOA,CAAAA,EAAY,SACtB,OAAO,CACN,KAAM,CACN,CAAA,CAAA,MAAA,CAAQA,CACR,CAAA,OAAA,CAAS,IACR,CAA+HA,4HAAAA,EAAAA,CAAO,IACxI,CAGF,CACAC,EAAoBD,CAAO,CAAA,CAC3B,IAAMgB,CAAW,CAAA,MAAMC,EAAgBjB,CAAQ,CAAA,YAAY,EAC3D,GAAI,CAACgB,EACJ,OAAM5B,MAAAA,CAAAA,CAAS,QAASY,CAAAA,CAAAA,CAAQ,WAAYA,CAAQ,CAAA,YAAY,EACzDe,CAGR,CAAA,IAAMG,EAAgB,MAAMvD,CAAAA,CAAYqD,CAAQ,CAC1C,CAAA,CAACG,EAAaC,CAAa,CAAA,CAAIC,EAAsBrB,CAAQ,CAAA,KAAA,CAAOkB,CAAa,CAEjF,CAAA,CAAE,IAAAI,CAAAA,CAAAA,CAAM,WAAAC,CAAY,CAAA,SAAA,CAAAC,CAAU,CAAIC,CAAAA,CAAAA,CAAaL,EAAeD,CAAa3C,CAAAA,CAAO,EACxF,OAAK8C,CAAAA,CAwBEP,EAvBFjB,CAAO,CAAA,MAAA,CAAO,gBAAgB,cAAmB,GAAA,KAAA,EACpD,MAAM4B,CAAc,CAAA,CAAA,EAAG1B,CAAQ,CAAA,YAAY,GAAImB,CAAW,CAAA,CACnDJ,IAER,MAAMW,CAAAA,CAAc,GAAG1B,CAAQ,CAAA,QAAQ,GAAIwB,CAAS,CAAA,CAC7C,CACN,IAAM,CAAA,CAAA,CAAA,CACN,QAAS,IACRG,CAAAA,CAAAA,WAAAA,EAAoBrD,EAAM,QAAQ,CAAA;AAAA;AAAA,GAGlCE,EAAAA,CAAAA,EAAS,gBACNA,CAAAA,CAAAA,EAAS,oBAAyB,GAAA,SAAA,CACjC,CAAkCA,+BAAAA,EAAAA,CAAAA,CAAQ,gBAAgB,CAAA,oBAAA,EAAuB+C,CAAU,CAAA,EAAA,CAAA,CAC3F,CAAkC/C,+BAAAA,EAAAA,CAAAA,CAAQ,gBAAgB,CAAA,0BAAA,EAA6B+C,CAAU,CAAA,QAAA,CAAA,CAClG,CAA6C/C,0CAAAA,EAAAA,CAAAA,EAAS,oBAAyB,GAAA,SAAA,CAAY,CAAG+C,EAAAA,CAAU,CAAM,CAAA,CAAA,CAAA,CAAA,EAAGA,CAAU,CAAA,OAAA,CAAS,CACxI,CAAA,CAAA,CAAA;AAAA;AAAA,gBAAA,EAEevB,EAAQ,YAAY,CAAA;AAAA,gBAAA,EACpBA,EAAQ,UAAU,CAAA;AAAA,gBAClBA,EAAAA,CAAAA,CAAQ,QAAQ,CAChC,CAAA,CAAA,CAAA,CAGF,CAEA,IAAMe,CAAAA,CAAU,CACf,IAAM,CAAA,CAAA,CAAA,CACN,QAAS,IAAM,EAChB,EAEA,SAASE,CAAAA,CAAgBW,EAA2C,CACnE,OAAOxC,EAAS,QAASwC,CAAAA,CAAAA,CAAM,CAAE,QAAU,CAAA,QAAS,CAAC,CAAE,CAAA,KAAA,CAAM,IAAG,EAAY,CAC7E,CACA,eAAeF,CAAAA,CAAcE,EAAc5C,CAAkB,CAAA,CAC5D,IAAM6C,CAAW,CAAA,CAAA,MAAM3D,EAAUc,CAAK,CAAA,EAAG,MAAM,GAAG,CAAA,CAAE,CAAC,CACrD,CAAA,OAAOI,EAAS,SAAUwC,CAAAA,CAAAA,CAAMC,EAAS,WAAW,CACrD,CAEA,SAASR,CAAAA,CAAsBS,EAAmBC,CAA2D,CAAA,CAC5G,GAAIC,CAAWF,CAAAA,CAAAA,CAAQC,CAAM,CAC5B,CAAA,OAAO,CAACD,CAAQC,CAAAA,CAAM,EAEvB,IAAME,CAAAA,CAAOC,EAAWJ,CAAQC,CAAAA,CAAM,EAChCI,CAAS/B,CAAAA,CAAAA,CAAmB6B,CAAI,CACtC,CAAA,OAAO,CAACE,CAAOL,CAAAA,CAAM,EAAGK,CAAOJ,CAAAA,CAAM,CAAC,CACvC,CAEA,SAASG,CAAWJ,CAAAA,CAAAA,CAAmBC,EAAmB,CACzD,IAAM1B,EAAQ,IAAK,CAAA,GAAA,CAAIyB,EAAO,KAAOC,CAAAA,CAAAA,CAAO,KAAK,CAC3CzB,CAAAA,CAAAA,CAAS,KAAK,GAAIwB,CAAAA,CAAAA,CAAO,OAAQC,CAAO,CAAA,MAAM,EACpD,OAAO,CAAE,MAAA1B,CAAO,CAAA,MAAA,CAAAC,CAAO,CACxB,CAEA,SAAS0B,CAAWF,CAAAA,CAAAA,CAAmBC,EAAmB,CACzD,OAAOD,EAAO,KAAUC,GAAAA,CAAAA,CAAO,OAASD,CAAO,CAAA,MAAA,GAAWC,EAAO,MAClE,CAEA,SAASN,CACRL,CAAAA,CAAAA,CACAD,EACA,CAAE,gBAAA,CAAAiB,EAAmB,CAAG,CAAA,oBAAA,CAAAC,EAAuB,OAAS,CAAA,WAAA,CAAAC,CAAY,CAA+B,CAAA,GAClG,CACD,GAAM,CAAE,KAAAjC,CAAAA,CAAAA,CAAO,OAAAC,CAAO,CAAA,CAAIa,EACpBK,CAAY,CAAA,IAAI,UAAUnB,CAAOC,CAAAA,CAAM,EAEvCiC,CAAYC,CAAAA,CAAAA,CAAWrB,EAAY,IAAMC,CAAAA,CAAAA,CAAc,KAAMI,CAAU,CAAA,IAAA,CAAMnB,EAAOC,CAAQgC,CAAAA,CAAW,EACvGf,CAAakB,CAAAA,CAAAA,CAAgB,CAAE,oBAAAJ,CAAAA,CAAAA,CAAsB,MAAAhC,CAAO,CAAA,MAAA,CAAAC,CAAO,CAAA,CAAGiC,CAAS,CAAA,CAErF,OAAO,CACN,IAAA,CAAMhB,GAAca,CACpB,CAAA,UAAA,CAAAb,EACA,SAAAC,CAAAA,CACD,CACD,CAEA,SAASiB,EACR,CAAE,oBAAA,CAAAJ,EAAsB,KAAAhC,CAAAA,CAAAA,CAAO,OAAAC,CAAO,CAAA,CACtCiC,EACS,CACT,OAAQF,GACP,IAAK,QACJ,OAAOE,CAAAA,CACR,IAAK,SACJ,CAAA,OAAQA,GAAalC,CAAQC,CAAAA,CAAAA,CAAAA,CAAW,IACzC,QACC,MAAM,IAAI,KAAM,CAAA,CAAA,8BAAA,EAAiC+B,CAAoB,CAAE,CAAA,CACzE,CACD,CCxIO,SAASK,GAAmBzE,CAAqC,CAAA,CACvE,OAAKA,CAAI,CAAA,KAAA,EAAO,KACTA,CAAI,CAAA,KAAA,CAAM,KAAK,WAAY,CAAA,WAAW,EAAIA,CAAI,CAAA,KAAA,CAAM,KAAK,WAAY,CAAA,UAAU,EADzD,CAE9B,CAAA,CCYA,eAAsB0E,EACrBC,CAAAA,CAAAA,CACApE,EACC,CACDF,CAAAA,CAAM,KAAOsE,CAAM,CAAA,IAAA,CACnBtE,EAAM,YAAesE,CAAAA,CAAAA,CAAM,KAAK,QAChCtE,CAAAA,CAAAA,CAAM,aAAeuE,QAASvE,CAAAA,CAAAA,CAAM,YAAY,CAChDA,CAAAA,CAAAA,CAAM,WAAaA,CAAM,CAAA,YAAA,CAAa,KAAM,CAAA,CAAA,CAAG,CAACA,CAAAA,CAAM,KAAK,MAAM,CAAA,CACjE,IAAMwE,CAAelE,CAAAA,IAAAA,CACpBN,EAAM,UACNE,CAAAA,CAAAA,EAAS,cAAgB,CAAiB,cAAA,EAAA,MAAMY,EAAS,mBAAoB,EAAC,EAC/E,CACM2D,CAAAA,CAAAA,CAAaC,QAAQ1E,CAAM,CAAA,YAAY,EAC7CA,CAAM,CAAA,WAAA,CAAc2E,SAASF,CAAYnE,CAAAA,IAAAA,CAAKkE,EAAcxE,CAAM,CAAA,YAAY,CAAC,CAC/EA,CAAAA,CAAAA,CAAM,UAAY2E,QAASF,CAAAA,CAAAA,CAAYnE,KAAKkE,CAAc,CAAA,aAAA,CAAexE,EAAM,YAAY,CAAC,EAC5FA,CAAM,CAAA,OAAA,CAAU2E,SAASF,CAAYnE,CAAAA,IAAAA,CAAKkE,EAAc,iBAAmBxE,CAAAA,CAAAA,CAAM,YAAY,CAAC,CAAA,CAE9F,MAAMc,CAAS,CAAA,KAAA,CAAMd,EAAM,SAAS,CAAA,CACpC,MAAMc,CAAS,CAAA,KAAA,CAAMd,EAAM,OAAO,EACnC,CAEO,SAAS4E,EAAAA,CAA4BjF,EAAiC,CAE5EK,CAAAA,CAAM,SAAWL,CAAI,CAAA,IAAA,CAAK,KAC1B,IAAMkF,CAAAA,CAAM7E,EAAM,EAAK4B,CAAAA,CAAAA,CAAa5B,EAAM,QAAQ,CAAA,CAClDA,EAAM,QAAS6E,CAAAA,CAAE,EAAI7E,CAAM,CAAA,QAAA,CAAS6E,CAAE,CAAK,EAAA,CAAE,KAAO,CAAA,CAAE,EACvD","file":"vitest-setup.js","sourcesContent":["export const imageSnapshotSymbol = Symbol.for('storybook-addon-vis/imageSnapshot')\n\nexport const imageSnapshotStubSymbol = Symbol.for('storybook-addon-vis/imageSnapshotStub')\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { join } from 'pathe'\nimport { 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 SnapshotCapturer {\n\timageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>\n}\n\nexport async function imageSnapshot(\n\tthis: BrowserPage,\n\toptions?: ImageSnapshotOptions | undefined,\n): Promise<ImageSnapshot> {\n\tconst index = state.snapshot[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})\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","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","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, { index: number }>\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 { 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 })\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 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 type { MatchImageSnapshotOptions } from './@vitest/browser/types.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 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","/**\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\tawait commands.rmDir(state.resultDir)\n\tawait commands.rmDir(state.diffDir)\n}\n\nexport function configureSnapshotBeforeEach(ctx: { task: { name: string } }) {\n\t// console.log('configureSnapshotBeforeEach', ctx.task)\n\tstate.taskName = ctx.task.name\n\tconst id = (state.id = toSnapshotId(state.taskName))\n\tstate.snapshot[id] = state.snapshot[id] ?? { index: 1 }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storybook-addon-vis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Storybook Vitest Image Snapshot addon",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"image-snapshot",
|
|
@@ -48,25 +48,25 @@
|
|
|
48
48
|
"type-plus": "^8.0.0-beta.6"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@storybook/addon-essentials": "^8.
|
|
52
|
-
"@storybook/addon-interactions": "^8.
|
|
53
|
-
"@storybook/addon-links": "^8.
|
|
54
|
-
"@storybook/blocks": "^8.
|
|
55
|
-
"@storybook/experimental-addon-test": "^8.
|
|
56
|
-
"@storybook/react": "^8.
|
|
57
|
-
"@storybook/react-vite": "^8.
|
|
58
|
-
"@storybook/test": "^8.
|
|
51
|
+
"@storybook/addon-essentials": "^8.4.1",
|
|
52
|
+
"@storybook/addon-interactions": "^8.4.1",
|
|
53
|
+
"@storybook/addon-links": "^8.4.1",
|
|
54
|
+
"@storybook/blocks": "^8.4.1",
|
|
55
|
+
"@storybook/experimental-addon-test": "^8.4.1",
|
|
56
|
+
"@storybook/react": "^8.4.1",
|
|
57
|
+
"@storybook/react-vite": "^8.4.1",
|
|
58
|
+
"@storybook/test": "^8.4.1",
|
|
59
59
|
"@types/is-ci": "^3.0.4",
|
|
60
60
|
"@types/pixelmatch": "^5.2.6",
|
|
61
61
|
"@types/react": "^18.2.65",
|
|
62
62
|
"@types/react-dom": "^18.2.21",
|
|
63
63
|
"@vitejs/plugin-react": "^4.2.1",
|
|
64
|
-
"@vitest/browser": "^2.1.
|
|
65
|
-
"@vitest/expect": "^2.1.
|
|
64
|
+
"@vitest/browser": "^2.1.4",
|
|
65
|
+
"@vitest/expect": "^2.1.4",
|
|
66
66
|
"boxen": "^7.1.1",
|
|
67
67
|
"react": "^18.2.0",
|
|
68
68
|
"react-dom": "^18.2.0",
|
|
69
|
-
"storybook": "^8.
|
|
69
|
+
"storybook": "^8.4.1",
|
|
70
70
|
"tsup": "^8.2.4",
|
|
71
71
|
"typescript": "^5.5.4",
|
|
72
72
|
"vite": "^5.3.5",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"nuke": "rimraf node_modules",
|
|
111
111
|
"prerelease": "zx scripts/prepublish-checks.js",
|
|
112
112
|
"sb": "storybook dev -p 6006",
|
|
113
|
-
"test": "vitest run",
|
|
113
|
+
"test": "vitest run --coverage",
|
|
114
114
|
"w": "vitest"
|
|
115
115
|
}
|
|
116
116
|
}
|
|
@@ -5,13 +5,17 @@ import { state } from '../../state.js'
|
|
|
5
5
|
import { imageSnapshotSymbol } from './constants.js'
|
|
6
6
|
import type { ImageSnapshot, ImageSnapshotOptions } from './types.js'
|
|
7
7
|
|
|
8
|
+
export interface SnapshotCapturer {
|
|
9
|
+
imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot>
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
export async function imageSnapshot(
|
|
9
13
|
this: BrowserPage,
|
|
10
14
|
options?: ImageSnapshotOptions | undefined,
|
|
11
15
|
): Promise<ImageSnapshot> {
|
|
12
16
|
const index = state.snapshot[state.id]!.index++
|
|
13
|
-
const snapshotFilename = options?.
|
|
14
|
-
? `${options.
|
|
17
|
+
const snapshotFilename = options?.customizeSnapshotId
|
|
18
|
+
? `${options.customizeSnapshotId(state.id, index)}.png`
|
|
15
19
|
: `${state.id}-${index}.png`
|
|
16
20
|
const baselinePath = join(state.baselineDir, snapshotFilename)
|
|
17
21
|
const resultPath = join(state.resultDir, snapshotFilename)
|
|
@@ -3,12 +3,14 @@ import type { PixelmatchOptions } from 'pixelmatch'
|
|
|
3
3
|
|
|
4
4
|
export type ImageSnapshotOptions = {
|
|
5
5
|
/**
|
|
6
|
-
* Customize the filename of the snapshot
|
|
6
|
+
* Customize the snapshot id. This is used as the filename of the snapshot:
|
|
7
|
+
*
|
|
8
|
+
* `${snapshotId}.png`
|
|
7
9
|
*
|
|
8
10
|
* @param id The id of the snapshot.
|
|
9
11
|
* @param index The index of the snapshot.
|
|
10
12
|
*/
|
|
11
|
-
|
|
13
|
+
customizeSnapshotId?: (id: string, index: number) => string
|
|
12
14
|
element?: Element //| Locator
|
|
13
15
|
}
|
|
14
16
|
|
package/src/augment.ts
CHANGED
|
@@ -1,30 +1,26 @@
|
|
|
1
1
|
/// <reference types="@vitest/browser/context" />
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { SnapshotCapturer } from './@vitest/browser/page.image_snapshot'
|
|
4
|
+
import type { ImageSnapshotMatcher } from './expect.to_match_image_snapshot'
|
|
5
|
+
import type { CopyFileCommand } from './vitest-plugin/commands/copy_file'
|
|
6
|
+
import type { ExistDirCommand } from './vitest-plugin/commands/exist_dir'
|
|
7
|
+
import type { GetSnapshotPlatformCommand } from './vitest-plugin/commands/get_snapshot_platform'
|
|
8
|
+
import type { IsCICommand } from './vitest-plugin/commands/is_ci'
|
|
9
|
+
import type { RmDirCommand } from './vitest-plugin/commands/rm_dir'
|
|
4
10
|
|
|
5
11
|
declare module '@vitest/browser/context' {
|
|
6
|
-
interface BrowserPage {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
isCI: () => Promise<boolean>
|
|
14
|
-
/**
|
|
15
|
-
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
16
|
-
*
|
|
17
|
-
* This is useful to control where the snapshot is stored.
|
|
18
|
-
*/
|
|
19
|
-
getSnapshotPlatform: () => Promise<string>
|
|
20
|
-
}
|
|
12
|
+
interface BrowserPage extends SnapshotCapturer {}
|
|
13
|
+
interface BrowserCommands
|
|
14
|
+
extends CopyFileCommand,
|
|
15
|
+
ExistDirCommand,
|
|
16
|
+
GetSnapshotPlatformCommand,
|
|
17
|
+
RmDirCommand,
|
|
18
|
+
IsCICommand {}
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
declare global {
|
|
24
22
|
namespace jest {
|
|
25
23
|
// biome-ignore lint/correctness/noUnusedVariables: augmentation must have matching type params.
|
|
26
|
-
interface Matchers<R, T> {
|
|
27
|
-
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): void
|
|
28
|
-
}
|
|
24
|
+
interface Matchers<R, T> extends ImageSnapshotMatcher {}
|
|
29
25
|
}
|
|
30
26
|
}
|
|
@@ -3,9 +3,11 @@ import { assertType } from 'type-plus'
|
|
|
3
3
|
import { expect, it } from 'vitest'
|
|
4
4
|
import { page } from './@vitest/browser/context.js'
|
|
5
5
|
import { toSnapshotId } from './@vitest/browser/image_snapshot.logic.js'
|
|
6
|
+
import * as ToMatchStories from './expect.to_match_image_snapshot.stories.js'
|
|
6
7
|
import * as ImageDataStories from './image_data.stories.js'
|
|
7
8
|
|
|
8
9
|
const { ConversionRoundtrip } = composeStories(ImageDataStories)
|
|
10
|
+
const { Success } = composeStories(ToMatchStories)
|
|
9
11
|
|
|
10
12
|
it('should reject if the subject is undefined', async () => {
|
|
11
13
|
expect(() => expect(undefined).toMatchImageSnapshot()).rejects.toThrowError(
|
|
@@ -48,7 +50,7 @@ it('should work with page.imageSnapshot()', async () => {
|
|
|
48
50
|
it('can customize snapshot filename', async ({ task }) => {
|
|
49
51
|
await ConversionRoundtrip.run()
|
|
50
52
|
const result = await page.imageSnapshot({
|
|
51
|
-
|
|
53
|
+
customizeSnapshotId(id, index) {
|
|
52
54
|
return `${id}-custom-${index}`
|
|
53
55
|
},
|
|
54
56
|
})
|
|
@@ -58,3 +60,35 @@ it('can customize snapshot filename', async ({ task }) => {
|
|
|
58
60
|
it('should fail when the subject is a rejected promise', async () => {
|
|
59
61
|
expect(expect(Promise.reject('error')).toMatchImageSnapshot()).rejects.toThrowError('error')
|
|
60
62
|
})
|
|
63
|
+
|
|
64
|
+
it(`should fail with 'Snapshot \`{test/story name}\` mismatched`, async ({ task }) => {
|
|
65
|
+
await ConversionRoundtrip.run()
|
|
66
|
+
await expect(
|
|
67
|
+
page.imageSnapshot({
|
|
68
|
+
customizeSnapshotId: (id) => id,
|
|
69
|
+
}),
|
|
70
|
+
).toMatchImageSnapshot()
|
|
71
|
+
await Success.run()
|
|
72
|
+
try {
|
|
73
|
+
await expect(
|
|
74
|
+
page.imageSnapshot({
|
|
75
|
+
customizeSnapshotId: (id) => id,
|
|
76
|
+
}),
|
|
77
|
+
).toMatchImageSnapshot()
|
|
78
|
+
// NOTE: test WILL reach there when updating snapshot as the assertion will succeed.
|
|
79
|
+
// So right now we can't assert the negative part of this test.
|
|
80
|
+
// We can improve this when the updateSnapshot option is easily available.
|
|
81
|
+
// throw new Error('should not reach')
|
|
82
|
+
} catch (e) {
|
|
83
|
+
expect(e).toBeInstanceOf(Error)
|
|
84
|
+
assertType.as<Error>(e)
|
|
85
|
+
await expect(e.message).toMatch(`Snapshot \`${task.name}\` mismatched`)
|
|
86
|
+
} finally {
|
|
87
|
+
await ConversionRoundtrip.run()
|
|
88
|
+
await expect(
|
|
89
|
+
page.imageSnapshot({
|
|
90
|
+
customizeSnapshotId: (id) => id,
|
|
91
|
+
}),
|
|
92
|
+
).toMatchImageSnapshot()
|
|
93
|
+
}
|
|
94
|
+
})
|
|
@@ -7,6 +7,11 @@ import { assertImageSnapshot, isImageSnapshot } from './@vitest/browser/image_sn
|
|
|
7
7
|
import type { MatchImageSnapshotOptions } from './@vitest/browser/types.js'
|
|
8
8
|
import { toDataURL, toImageData } from './image_data.js'
|
|
9
9
|
import { createImageResizer } from './image_resizer.js'
|
|
10
|
+
import { state } from './state.js'
|
|
11
|
+
|
|
12
|
+
export interface ImageSnapshotMatcher {
|
|
13
|
+
toMatchImageSnapshot(options?: MatchImageSnapshotOptions): Promise<void>
|
|
14
|
+
}
|
|
10
15
|
|
|
11
16
|
export async function toMatchImageSnapshot(
|
|
12
17
|
actual: any,
|
|
@@ -56,13 +61,15 @@ export async function toMatchImageSnapshot(
|
|
|
56
61
|
return {
|
|
57
62
|
pass: false,
|
|
58
63
|
message: () =>
|
|
59
|
-
dedent
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
64
|
+
dedent`Snapshot \`${state.taskName}\` mismatched
|
|
65
|
+
|
|
66
|
+
${
|
|
67
|
+
options?.failureThreshold
|
|
68
|
+
? options?.failureThresholdType === 'percent'
|
|
69
|
+
? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
|
|
70
|
+
: `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
|
|
71
|
+
: `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`
|
|
72
|
+
}
|
|
66
73
|
|
|
67
74
|
Expected: '${subject.baselinePath}'
|
|
68
75
|
Actual: '${subject.resultPath}'
|
|
@@ -3,6 +3,10 @@ import { mkdirp } from 'mkdirp'
|
|
|
3
3
|
import { dirname, join } from 'pathe'
|
|
4
4
|
import type { BrowserCommand } from 'vitest/node'
|
|
5
5
|
|
|
6
|
+
export interface CopyFileCommand {
|
|
7
|
+
copyFile(src: string, dest: string): Promise<void>
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
export const copyFile: BrowserCommand<[src: string, dest: string]> = async ({ testPath, provider }, src, dest) => {
|
|
7
11
|
if (provider.name === 'playwright') {
|
|
8
12
|
const destPath = join(dirname(testPath), dest)
|
|
@@ -2,6 +2,10 @@ import { stat } from 'node:fs/promises'
|
|
|
2
2
|
import { dirname, join } from 'pathe'
|
|
3
3
|
import type { BrowserCommand } from 'vitest/node'
|
|
4
4
|
|
|
5
|
+
export interface ExistDirCommand {
|
|
6
|
+
existDir: (path: string) => Promise<boolean>
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
export const existDir: BrowserCommand<[path: string]> = async ({ testPath, provider }, path) => {
|
|
6
10
|
if (provider.name === 'playwright') {
|
|
7
11
|
return stat(join(dirname(testPath), path))
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import ci from 'is-ci'
|
|
2
2
|
|
|
3
|
+
export interface GetSnapshotPlatformCommand {
|
|
4
|
+
/**
|
|
5
|
+
* Get the platform id of the snapshot `{platform}[-ci]`.
|
|
6
|
+
*
|
|
7
|
+
* This is useful to control where the snapshot is stored.
|
|
8
|
+
*/
|
|
9
|
+
getSnapshotPlatform: () => Promise<string>
|
|
10
|
+
}
|
|
11
|
+
|
|
3
12
|
export const getSnapshotPlatform = async () => {
|
|
4
|
-
return
|
|
13
|
+
return ci ? process.platform : 'local'
|
|
5
14
|
}
|
|
@@ -2,6 +2,10 @@ import { dirname, join } from 'pathe'
|
|
|
2
2
|
import { rimraf } from 'rimraf'
|
|
3
3
|
import type { BrowserCommand } from 'vitest/node'
|
|
4
4
|
|
|
5
|
+
export interface RmDirCommand {
|
|
6
|
+
rmDir: (path: string) => Promise<void>
|
|
7
|
+
}
|
|
8
|
+
|
|
5
9
|
export const rmDir: BrowserCommand<[path: string]> = async ({ testPath, provider }, path) => {
|
|
6
10
|
if (provider.name === 'playwright') {
|
|
7
11
|
return rimraf(join(dirname(testPath), path))
|