storybook-addon-vis 0.0.12 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +12 -27
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/vitest-plugin.d.ts +9 -1
- package/dist/vitest-setup.d.ts +9 -1
- package/package.json +1 -1
- package/readme.md +16 -1
- package/src/@vitest/browser/types.ts +9 -1
- package/src/param.stories.tsx +24 -0
- package/src/param.ts +2 -26
- package/src/param.spec.ts +0 -8
package/dist/index.d.ts
CHANGED
|
@@ -17,9 +17,17 @@ type ImageSnapshot = {
|
|
|
17
17
|
image: ImageData;
|
|
18
18
|
};
|
|
19
19
|
type MatchImageSnapshotOptions = {
|
|
20
|
+
/**
|
|
21
|
+
* Custom options passed to 'pixelmatch'
|
|
22
|
+
*/
|
|
20
23
|
diffOptions?: PixelmatchOptions | undefined;
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Failure threshold should measure in `pixel` or `percent`.
|
|
26
|
+
*
|
|
27
|
+
* Default is `pixel`.
|
|
28
|
+
*/
|
|
22
29
|
failureThresholdType?: 'pixel' | 'percent' | undefined;
|
|
30
|
+
failureThreshold?: number | undefined;
|
|
23
31
|
};
|
|
24
32
|
|
|
25
33
|
declare module '@vitest/browser/context' {
|
|
@@ -75,31 +83,8 @@ declare const server: {
|
|
|
75
83
|
config: SerializedConfig;
|
|
76
84
|
};
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
* Add a delay to the story snapshot comparison to allow for async rendering.
|
|
81
|
-
*
|
|
82
|
-
* This only apply to the automatic snapshot taken when the story is rendered.
|
|
83
|
-
* In general, it's a good idea to not rely on this delay,
|
|
84
|
-
* and instead use the make sure your component is fully rendered before the `play()` function returns.
|
|
85
|
-
*/
|
|
86
|
-
delay?: number | undefined;
|
|
87
|
-
/**
|
|
88
|
-
* The method by which images are compared.
|
|
89
|
-
* `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison.
|
|
90
|
-
* @default 'pixelmatch'
|
|
91
|
-
*/
|
|
92
|
-
comparisonMethod?: 'pixelmatch' | 'ssim' | undefined;
|
|
93
|
-
/**
|
|
94
|
-
* Custom config passed to 'pixelmatch' or 'ssim'
|
|
95
|
-
*/
|
|
96
|
-
customDiffConfig?: PixelmatchOptions | undefined;
|
|
97
|
-
failureThreshold?: number | undefined;
|
|
98
|
-
failureThresholdType?: 'percent' | 'pixel' | undefined;
|
|
99
|
-
blur?: number | undefined;
|
|
100
|
-
};
|
|
101
|
-
declare function defineSnapshotParam(snapshot: SnapshotStoryParam): {
|
|
102
|
-
snapshot: SnapshotStoryParam;
|
|
86
|
+
declare function defineSnapshotParam(snapshot: MatchImageSnapshotOptions): {
|
|
87
|
+
snapshot: MatchImageSnapshotOptions;
|
|
103
88
|
};
|
|
104
89
|
|
|
105
|
-
export { type ImageSnapshot, type ImageSnapshotOptions, type MatchImageSnapshotOptions,
|
|
90
|
+
export { type ImageSnapshot, type ImageSnapshotOptions, type MatchImageSnapshotOptions, cdp, commands, defineSnapshotParam, page, server, toMatchImageSnapshot };
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import C from'dedent';import F from'pixelmatch';import {getByAltText,getByRole,getByTestId,getByText,getByTitle,getByPlaceholderText,getByLabelText}from'@storybook/test';import {basename,join}from'pathe';var g=Symbol.for("storybook-addon-vis/imageSnapshot"),f=Symbol.for("storybook-addon-vis/imageSnapshotStub");function d(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 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(s=>{let n=new FileReader;n.onload=()=>{o(n.result);},n.readAsDataURL(s);});})}var p={name:"",testFilepath:"",taskName:"",snapshot:{}};function u(t){return !!t&&t.type===g}function x(t){if(!u(t))throw new Error("Expected subject to be an image snapshot")}function S(t){return `${t.replace(/[^a-z0-9]/gi,"-").toLowerCase()}`}async function I(t){let e=basename(p.testFilepath),a=`${S(p.taskName)}-${p.snapshot[p.taskName].index++}.png`,o=join(p.baselineDir,e,a),s=join(p.resultDir,e,a),n=join(p.diffDir,e,a),r=await this.screenshot({base64:!0,path:s,element:t?.element}),i=await d(r.base64);return {type:g,testfilename:e,snapshotFilename:a,baselinePath:o,resultPath:s,diffPath:n,base64:r.base64,image:i}}var h;globalThis.__vitest_browser__&&import('@vitest/browser/context').then(t=>{h=t,M.extend({imageSnapshot:I});});function at(){return h?.cdp()??{}}var l=new Proxy({},{get(t,e){return t[e]??(h?.commands)[e]}}),M=new Proxy({getByAltText:getByAltText,getByRole:getByRole,getByTestId:getByTestId,getByText:getByText,getByTitle:getByTitle,getByPlaceholder:getByPlaceholderText,getByLabelText:getByLabelText},{get(t,e){return t[e]??h?.page?(h?.page)[e]:()=>{if(e==="imageSnapshot")return {type:f};console.info(`\`page.${e.toString()}\` does not exist when running in browser`);}}}),ot=new Proxy({},{get(t,e){return t[e]??(h?.server)[e]}});async function ct(t,e){let a=await t;if(a?.type===f)return y;if(!u(a)){if(a?.path&&a?.base64)return {pass:!1,actual:t,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:t,message:()=>`\`toMatchImageSnapshot()\` expects the subject to be an element, locator, or result of \`page.imageSnapshot()\`, but got: \`${t}\``}}x(a);let o=await R(a.baselinePath);if(!o)return await l.copyFile(a.resultPath,a.baselinePath),y;let s=await d(o),[n,r]=A(a.image,s),{pass:i,diffAmount:m,diffImage:c}=_(r,n,e);return i?y:(await z(`${a.diffPath}`,c),{pass:!1,actual:t,message:()=>C`${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
2
|
|
|
3
3
|
Expected: '${a.baselinePath}'
|
|
4
4
|
Actual: '${a.resultPath}'
|
|
5
|
-
Difference: '${a.diffPath}'`})}var
|
|
5
|
+
Difference: '${a.diffPath}'`})}var y={pass:!0,message:()=>""};function R(t){return l.readFile(t,{encoding:"base64"}).catch(()=>{})}async function z(t,e){let a=(await w(e)).split(",")[1];return l.writeFile(t,a,"base64url")}function A(t,e){if(O(t,e))return [t,e];let a=k(t,e),o=L(a);return [o(t),o(e)]}function k(t,e){let a=Math.max(t.width,e.width),o=Math.max(t.height,e.height);return {width:a,height:o}}function O(t,e){return t.width===e.width&&t.height===e.height}var L=({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};function _(t,e,{failureThreshold:a=0,failureThresholdType:o="pixel",diffOptions:s}={}){let{width:n,height:r}=e,i=new ImageData(n,r),m=F(e.data,t.data,i.data,n,r,s),c=N({failureThresholdType:o,width:n,height:r},m);return {pass:c<=a,diffAmount:c,diffImage:i}}function N({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 ft(t){return {snapshot:t}}export{at as cdp,l as commands,ft as defineSnapshotParam,M as page,ot as server,ct as toMatchImageSnapshot};//# sourceMappingURL=index.js.map
|
|
6
6
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/image_data.ts","../src/state.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/context.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","isImageSnapshot","subject","assertImageSnapshot","toSnapshotId","taskName","imageSnapshot","options","testfilename","basename","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","createImageResizer","width","height","inArea","x","y","result","idx","old","failureThreshold","failureThresholdType","diffOptions","pixelDiff","pixelmatch","toThresholdUnit","defineSnapshotParam","snapshot"],"mappings":"4MAAO,IAAMA,CAAAA,CAAsB,MAAO,CAAA,GAAA,CAAI,mCAAmC,CAAA,CAEpEC,CAA0B,CAAA,MAAA,CAAO,IAAI,uCAAuC,CAAA,CCFlF,SAASC,CAAYC,CAAAA,CAAAA,CAAgB,CAC3C,OAAO,IAAI,OAAmB,CAAA,CAACC,CAASC,CAAAA,CAAAA,GAAW,CAClD,IAAMC,EAAM,IAAI,KAAA,CAChBA,CAAI,CAAA,GAAA,CAAM,CAAyBH,sBAAAA,EAAAA,CAAM,CAEzCG,CAAAA,CAAAA,CAAAA,CAAI,OAAS,IAAM,CAClB,IAAMC,CAAAA,CAAS,SAAS,aAAc,CAAA,QAAQ,CAC9CA,CAAAA,CAAAA,CAAO,MAAQD,CAAI,CAAA,KAAA,CACnBC,CAAO,CAAA,MAAA,CAASD,CAAI,CAAA,MAAA,CACpB,IAAME,CAAAA,CAAMD,EAAO,UAAW,CAAA,IAAI,CAClCC,CAAAA,CAAAA,CAAI,SAAUF,CAAAA,CAAAA,CAAK,CAAG,CAAA,CAAC,EACvBF,CAAQI,CAAAA,CAAAA,CAAI,YAAa,CAAA,CAAA,CAAG,CAAGD,CAAAA,CAAAA,CAAO,KAAOA,CAAAA,CAAAA,CAAO,MAAM,CAAC,EAC5D,CAEAD,CAAAA,CAAAA,CAAI,QAAU,IAAM,CACnBD,CAAO,CAAA,IAAI,MAAM,sBAAsB,CAAC,EACzC,EACD,CAAC,CACF,CAEA,eAAsBI,EAAUC,CAAsB,CAAA,CACrD,IAAMH,CAAAA,CAAS,SAAS,aAAc,CAAA,QAAQ,CAC9C,CAAA,OAAAA,EAAO,KAAQG,CAAAA,CAAAA,CAAU,KACzBH,CAAAA,CAAAA,CAAO,MAASG,CAAAA,CAAAA,CAAU,MACdH,CAAAA,CAAAA,CAAO,WAAW,IAAI,CAAA,CAC9B,YAAaG,CAAAA,CAAAA,CAAW,EAAG,CAAC,CAAA,CACzB,IAAI,OAAA,CAAiBN,GAAY,CACvCG,CAAAA,CAAO,MAAQI,CAAAA,CAAAA,EAAS,CACvB,IAAMC,CAAS,CAAA,IAAI,WACnBA,CAAO,CAAA,MAAA,CAAS,IAAM,CACrBR,CAAQQ,CAAAA,CAAAA,CAAO,MAAgB,EAChC,EACAA,CAAO,CAAA,aAAA,CAAcD,CAAK,EAC3B,CAAC,EACF,CAAC,CACF,CCnCO,IAAME,CAAAA,CAAQ,CACpB,IAAA,CAAM,GACN,YAAc,CAAA,EAAA,CACd,QAAU,CAAA,EAAA,CACV,SAAU,EACX,CCFO,CAAA,SAASC,CAAgBC,CAAAA,CAAAA,CAAwC,CACvE,OAAO,CAAC,CAACA,CAAAA,EAAWA,CAAQ,CAAA,IAAA,GAASf,CACtC,CAEO,SAASgB,CAAAA,CAAoBD,EAAgD,CACnF,GAAI,CAACD,CAAAA,CAAgBC,CAAO,CAAA,CAC3B,MAAM,IAAI,MAAM,0CAA0C,CAE5D,CAEO,SAASE,EAAaC,CAAkB,CAAA,CAC9C,OAAO,CAAA,EAAGA,EAAS,OAAQ,CAAA,aAAA,CAAe,GAAG,CAAA,CAAE,WAAY,EAAC,CAC7D,CAAA,CCPA,eAAsBC,CAAiCC,CAAAA,CAAAA,CAAwD,CAE9G,IAAMC,CAAeC,CAAAA,QAAAA,CAAST,CAAM,CAAA,YAAY,EAC1CU,CAAmB,CAAA,CAAA,EAAGN,CAAaJ,CAAAA,CAAAA,CAAM,QAAQ,CAAC,CAAIA,CAAAA,EAAAA,CAAAA,CAAM,SAASA,CAAM,CAAA,QAAQ,CAAG,CAAA,KAAA,EAAO,OAC7FW,CAAeC,CAAAA,IAAAA,CAAKZ,CAAM,CAAA,WAAA,CAAaQ,EAAcE,CAAgB,CAAA,CACrEG,CAAaD,CAAAA,IAAAA,CAAKZ,CAAM,CAAA,SAAA,CAAWQ,CAAcE,CAAAA,CAAgB,EACjEI,CAAWF,CAAAA,IAAAA,CAAKZ,CAAM,CAAA,OAAA,CAASQ,EAAcE,CAAgB,CAAA,CAC7DK,CAAa,CAAA,MAAM,KAAK,UAAW,CAAA,CACxC,MAAQ,CAAA,CAAA,CAAA,CACR,IAAMF,CAAAA,CAAAA,CACN,OAASN,CAAAA,CAAAA,EAAS,OACnB,CAAC,CAAA,CACKS,CAAQ,CAAA,MAAM3B,EAAY0B,CAAW,CAAA,MAAM,CAEjD,CAAA,OAAO,CACN,IAAM5B,CAAAA,CAAAA,CAEN,YAAAqB,CAAAA,CAAAA,CACA,gBAAAE,CAAAA,CAAAA,CACA,YAAAC,CAAAA,CAAAA,CACA,WAAAE,CACA,CAAA,QAAA,CAAAC,CACA,CAAA,MAAA,CAAQC,CAAW,CAAA,MAAA,CACnB,KAAAC,CAAAA,CACD,CACD,CCnBA,IAAIrB,CAEC,CAAA,UAAA,CAAmB,kBACvB,EAAA,OAAO,yBAAyB,CAAA,CAAE,KAAMsB,CAAM,EAAA,CAC7CtB,CAAMsB,CAAAA,CAAAA,CACNC,EAAK,MAAO,CAAA,CAAE,aAAAZ,CAAAA,CAAc,CAAC,EAC9B,CAAC,CAKK,CAAA,SAASa,EAAkB,EAAA,CACjC,OAAOxB,CAAAA,EAAK,KAAS,EAAA,EACtB,CAEayB,IAAAA,CAAAA,CAAW,IAAI,KAAA,CAAuB,EAAW,CAAA,CAC7D,GAAIC,CAAAA,CAAAA,CAAQC,CAAM,CAAA,CACjB,OAAQD,CAAAA,CAAeC,CAAI,CAAM3B,EAAAA,CAAAA,CAAAA,EAAK,QAAiB2B,EAAAA,CAAI,CAC5D,CACD,CAAC,CAEYJ,CAAAA,CAAAA,CAAO,IAAI,KACvB,CAAA,CACC,YAAAK,CAAAA,YAAAA,CACA,SAAAC,CAAAA,SAAAA,CACA,WAAAC,CAAAA,WAAAA,CACA,UAAAC,SACA,CAAA,UAAA,CAAAC,UACA,CAAA,gBAAA,CAAkBC,oBAClB,CAAA,cAAA,CAAAC,cACD,CAAA,CACA,CACC,GAAIR,CAAAA,CAAAA,CAAQC,CAAM,CAAA,CACjB,OAASD,CAAAA,CAAeC,CAAI,CAAA,EAAK3B,GAAK,IAClCA,CAAAA,CAAAA,CAAAA,EAAK,IAAa2B,EAAAA,CAAI,EACvB,IAAM,CACN,GAAIA,CAAAA,GAAS,gBACZ,OAAO,CAAE,IAAMlC,CAAAA,CAAwB,CAExC,CAAA,OAAA,CAAQ,IAAK,CAAA,CAAA,OAAA,EAAUkC,EAAK,QAAS,EAAC,CAA2C,yCAAA,CAAA,EAClF,CACH,CACD,CACD,CAEaQ,CAAAA,EAAAA,CAAS,IAAI,KA4BvB,CAAA,EAAW,CAAA,CACb,GAAIT,CAAAA,CAAAA,CAAQC,CAAM,CAAA,CACjB,OAAQD,CAAeC,CAAAA,CAAI,CAAM3B,EAAAA,CAAAA,CAAAA,EAAK,QAAe2B,CAAI,CAC1D,CACD,CAAC,EClFD,eAAsBS,EAAAA,CACrBC,CACAzB,CAAAA,CAAAA,CACyB,CACzB,IAAML,CAAU,CAAA,MAAM8B,EACtB,GAAI9B,CAAAA,EAAS,IAASd,GAAAA,CAAAA,CACrB,OAAO6C,CAAAA,CAER,GAAI,CAAChC,EAAgBC,CAAO,CAAA,CAAG,CAC9B,GAAIA,CAAS,EAAA,IAAA,EAAQA,CAAS,EAAA,MAAA,CAC7B,OAAO,CACN,IAAA,CAAM,CACN,CAAA,CAAA,MAAA,CAAA8B,EACA,OAAS,CAAA,IACR,4IACF,CAAA,CAGD,GAAI,OAAO9B,CAAAA,EAAY,QACtB,CAAA,OAAO,CACN,IAAA,CAAM,CACN,CAAA,CAAA,MAAA,CAAA8B,EACA,OAAS,CAAA,IACR,CAA+HA,4HAAAA,EAAAA,CAAM,CACvI,EAAA,CAAA,CAGF,CACA7B,CAAAA,CAAoBD,CAAO,CAC3B,CAAA,IAAMgC,CAAW,CAAA,MAAMC,CAAgBjC,CAAAA,CAAAA,CAAQ,YAAY,CAAA,CAC3D,GAAI,CAACgC,CAAAA,CACJ,OAAMd,MAAAA,CAAAA,CAAS,SAASlB,CAAQ,CAAA,UAAA,CAAYA,CAAQ,CAAA,YAAY,EACzD+B,CAGR,CAAA,IAAMG,CAAgB,CAAA,MAAM/C,CAAY6C,CAAAA,CAAQ,CAC1C,CAAA,CAACG,EAAaC,CAAa,CAAA,CAAIC,CAAsBrC,CAAAA,CAAAA,CAAQ,KAAOkC,CAAAA,CAAa,CAEjF,CAAA,CAAE,KAAAI,CAAM,CAAA,UAAA,CAAAC,CAAY,CAAA,SAAA,CAAAC,CAAU,CAAA,CAAIC,CAAaL,CAAAA,CAAAA,CAAeD,EAAa9B,CAAO,CAAA,CACxF,OAAKiC,CAAAA,CAmBEP,GAlBN,MAAMW,CAAAA,CAAc,CAAG1C,EAAAA,CAAAA,CAAQ,QAAQ,CAAIwC,CAAAA,CAAAA,CAAS,CAC7C,CAAA,CACN,IAAM,CAAA,CAAA,CAAA,CACN,MAAAV,CAAAA,CAAAA,CACA,QAAS,IACRa,CAAAA,CAAAA,EACCtC,CAAS,EAAA,gBAAA,CACNA,GAAS,oBAAyB,GAAA,SAAA,CACjC,CAAkCA,+BAAAA,EAAAA,CAAAA,CAAQ,gBAAgB,CAAuBkC,oBAAAA,EAAAA,CAAU,CAC3F,EAAA,CAAA,CAAA,CAAA,+BAAA,EAAkClC,CAAQ,CAAA,gBAAgB,CAA6BkC,0BAAAA,EAAAA,CAAU,WAClG,CAA6ClC,0CAAAA,EAAAA,CAAAA,EAAS,oBAAyB,GAAA,SAAA,CAAY,GAAGkC,CAAU,CAAA,CAAA,CAAA,CAAM,CAAGA,EAAAA,CAAU,SAAS,CACxI,CAAA,CAAA,CAAA;AAAA;AAAA,gBAAA,EAEcvC,EAAQ,YAAY,CAAA;AAAA,gBAAA,EACpBA,EAAQ,UAAU,CAAA;AAAA,gBAClBA,EAAAA,CAAAA,CAAQ,QAAQ,CAChC,CAAA,CAAA,CAAA,CAGF,CAEA,IAAM+B,CAAAA,CAAU,CACf,IAAM,CAAA,CAAA,CAAA,CACN,QAAS,IAAM,EAChB,EAEA,SAASE,CAAAA,CAAgBW,EAA2C,CACnE,OAAO1B,EAAS,QAAS0B,CAAAA,CAAAA,CAAM,CAAE,QAAU,CAAA,QAAS,CAAC,CAAE,CAAA,KAAA,CAAM,IAAG,EAAY,CAC7E,CACA,eAAeF,CAAAA,CAAcE,EAAc9B,CAAkB,CAAA,CAC5D,IAAM+B,CAAW,CAAA,CAAA,MAAMnD,EAAUoB,CAAK,CAAA,EAAG,MAAM,GAAG,CAAA,CAAE,CAAC,CACrD,CAAA,OAAOI,EAAS,SAAU0B,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,CAASC,CAAAA,CAAAA,CAAmBH,CAAI,CACtC,CAAA,OAAO,CAACE,CAAOL,CAAAA,CAAM,EAAGK,CAAOJ,CAAAA,CAAM,CAAC,CACvC,CAEA,SAASG,CAAAA,CAAWJ,CAAmBC,CAAAA,CAAAA,CAAmB,CACzD,IAAMM,CAAAA,CAAQ,KAAK,GAAIP,CAAAA,CAAAA,CAAO,MAAOC,CAAO,CAAA,KAAK,EAC3CO,CAAS,CAAA,IAAA,CAAK,IAAIR,CAAO,CAAA,MAAA,CAAQC,EAAO,MAAM,CAAA,CACpD,OAAO,CAAE,KAAA,CAAAM,EAAO,MAAAC,CAAAA,CAAO,CACxB,CAEA,SAASN,EAAWF,CAAmBC,CAAAA,CAAAA,CAAmB,CACzD,OAAOD,CAAAA,CAAO,QAAUC,CAAO,CAAA,KAAA,EAASD,EAAO,MAAWC,GAAAA,CAAAA,CAAO,MAClE,CAEA,IAAMK,EACL,CAAC,CAAE,KAAAC,CAAAA,CAAAA,CAAO,MAAAC,CAAAA,CAAO,IAChBxC,CAAqB,EAAA,CACrB,GAAIA,CAAM,CAAA,KAAA,GAAUuC,GAASvC,CAAM,CAAA,MAAA,GAAWwC,EAC7C,OAAOxC,CAAAA,CAER,IAAMyC,CAAS,CAAA,CAACC,EAAWC,CAAcA,GAAAA,CAAAA,EAAK3C,EAAM,MAAU0C,EAAAA,CAAAA,EAAK1C,EAAM,KACnE4C,CAAAA,CAAAA,CAAS,IAAI,SAAUL,CAAAA,CAAAA,CAAOC,EAAQ,CAAE,UAAA,CAAYxC,EAAM,UAAW,CAAC,EAE5E,IAAS2C,IAAAA,CAAAA,CAAI,EAAGA,CAAIH,CAAAA,CAAAA,CAAQG,IAC3B,IAASD,IAAAA,CAAAA,CAAI,EAAGA,CAAIH,CAAAA,CAAAA,CAAOG,CAAK,EAAA,CAAA,CAC/B,IAAMG,CAAAA,CAAON,EAAQI,CAAID,CAAAA,CAAAA,EAAM,EAC/B,GAAID,CAAAA,CAAOC,EAAGC,CAAC,CAAA,CAAG,CACjB,IAAMG,CAAAA,CAAO9C,EAAM,KAAQ2C,CAAAA,CAAAA,CAAID,GAAM,CACrCE,CAAAA,CAAAA,CAAO,KAAKC,CAAG,CAAA,CAAI7C,EAAM,IAAK8C,CAAAA,CAAG,EACjCF,CAAO,CAAA,IAAA,CAAKC,EAAM,CAAC,CAAA,CAAI7C,EAAM,IAAK8C,CAAAA,CAAAA,CAAM,CAAC,CACzCF,CAAAA,CAAAA,CAAO,KAAKC,CAAM,CAAA,CAAC,EAAI7C,CAAM,CAAA,IAAA,CAAK8C,EAAM,CAAC,CAAA,CACzCF,EAAO,IAAKC,CAAAA,CAAAA,CAAM,CAAC,CAAI7C,CAAAA,CAAAA,CAAM,KAAK8C,CAAM,CAAA,CAAC,EAC1C,CACCF,KAAAA,CAAAA,CAAO,KAAKC,CAAG,CAAA,CAAI,EACnBD,CAAO,CAAA,IAAA,CAAKC,EAAM,CAAC,CAAA,CAAI,EACvBD,CAAO,CAAA,IAAA,CAAKC,EAAM,CAAC,CAAA,CAAI,EACvBD,CAAO,CAAA,IAAA,CAAKC,EAAM,CAAC,CAAA,CAAI,GAEzB,CAED,OAAOD,CACR,CAED,CAAA,SAASjB,EACRL,CACAD,CAAAA,CAAAA,CACA,CAAE,gBAAA0B,CAAAA,CAAAA,CAAmB,EAAG,oBAAAC,CAAAA,CAAAA,CAAuB,OAAS,CAAA,WAAA,CAAAC,CAAY,CAAA,CAA+B,EAClG,CAAA,CACD,GAAM,CAAE,KAAA,CAAAV,EAAO,MAAAC,CAAAA,CAAO,EAAInB,CACpBK,CAAAA,CAAAA,CAAY,IAAI,SAAUa,CAAAA,CAAAA,CAAOC,CAAM,CAEvCU,CAAAA,CAAAA,CAAYC,EAAW9B,CAAY,CAAA,IAAA,CAAMC,EAAc,IAAMI,CAAAA,CAAAA,CAAU,KAAMa,CAAOC,CAAAA,CAAAA,CAAQS,CAAW,CACvGxB,CAAAA,CAAAA,CAAa2B,EAAgB,CAAE,oBAAA,CAAAJ,EAAsB,KAAAT,CAAAA,CAAAA,CAAO,OAAAC,CAAO,CAAA,CAAGU,CAAS,CAErF,CAAA,OAAO,CACN,IAAMzB,CAAAA,CAAAA,EAAcsB,CACpB,CAAA,UAAA,CAAAtB,CACA,CAAA,SAAA,CAAAC,CACD,CACD,CAEA,SAAS0B,CACR,CAAA,CAAE,qBAAAJ,CAAsB,CAAA,KAAA,CAAAT,EAAO,MAAAC,CAAAA,CAAO,EACtCU,CACS,CAAA,CACT,OAAQF,CAAsB,EAC7B,IAAK,OACJ,CAAA,OAAOE,EACR,IAAK,SAAA,CACJ,OAAQA,CAAaX,EAAAA,CAAAA,CAAQC,GAAW,GACzC,CAAA,QACC,MAAM,IAAI,KAAA,CAAM,iCAAiCQ,CAAoB,CAAA,CAAE,CACzE,CACD,CCvIO,SAASK,EAAoBC,CAAAA,CAAAA,CAA8B,CACjE,OAAO,CAAE,QAAAA,CAAAA,CAAS,CACnB","file":"index.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\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 { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot } from './types.js'\n\nexport function isImageSnapshot(subject: any): subject is ImageSnapshot {\n\treturn !!subject && subject.type === imageSnapshotSymbol\n}\n\nexport function assertImageSnapshot(subject: any): asserts subject is ImageSnapshot {\n\tif (!isImageSnapshot(subject)) {\n\t\tthrow new Error('Expected subject to be an image snapshot')\n\t}\n}\n\nexport function toSnapshotId(taskName: string) {\n\treturn `${taskName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`\n}\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { basename, join } from 'pathe'\nimport { toImageData } from '../../image_data.js'\nimport { state } from '../../state.js'\nimport { imageSnapshotSymbol } from './constants.js'\nimport { toSnapshotId } from './image_snapshot.logic.js'\nimport type { ImageSnapshot, ImageSnapshotOptions } from './types.js'\n\nexport async function imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot> {\n\t// const rootDir = server.config.root\n\tconst testfilename = basename(state.testFilepath)\n\tconst snapshotFilename = `${toSnapshotId(state.taskName)}-${state.snapshot[state.taskName]!.index++}.png`\n\tconst baselinePath = join(state.baselineDir, testfilename, snapshotFilename)\n\tconst resultPath = join(state.resultDir, testfilename, snapshotFilename)\n\tconst diffPath = join(state.diffDir, testfilename, 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\t// rootDir,\n\t\ttestfilename,\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() ?? {}\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 type { AsyncExpectationResult } from '@vitest/expect'\nimport dedent from 'dedent'\nimport pixelmatch from 'pixelmatch'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands } 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'\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,\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,\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: \\`${actual}\\``,\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\tawait writeSnapshot(`${subject.diffPath}`, diffImage)\n\t\treturn {\n\t\t\tpass: false,\n\t\t\tactual,\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\nconst 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\t\treturn result\n\t}\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 { PixelmatchOptions } from 'pixelmatch'\n\nexport type SnapshotStoryParam = {\n\t/**\n\t * Add a delay to the story snapshot comparison to allow for async rendering.\n\t *\n\t * This only apply to the automatic snapshot taken when the story is rendered.\n\t * In general, it's a good idea to not rely on this delay,\n\t * and instead use the make sure your component is fully rendered before the `play()` function returns.\n\t */\n\tdelay?: number | undefined\n\t/**\n\t * The method by which images are compared.\n\t * `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison.\n\t * @default 'pixelmatch'\n\t */\n\tcomparisonMethod?: 'pixelmatch' | 'ssim' | undefined\n\t/**\n\t * Custom config passed to 'pixelmatch' or 'ssim'\n\t */\n\tcustomDiffConfig?: PixelmatchOptions | undefined\n\tfailureThreshold?: number | undefined\n\tfailureThresholdType?: 'percent' | 'pixel' | undefined\n\tblur?: number | undefined\n}\n\nexport function defineSnapshotParam(snapshot: SnapshotStoryParam) {\n\treturn { snapshot }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/@vitest/browser/constants.ts","../src/image_data.ts","../src/state.ts","../src/@vitest/browser/image_snapshot.logic.ts","../src/@vitest/browser/page.image_snapshot.ts","../src/@vitest/browser/context.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","isImageSnapshot","subject","assertImageSnapshot","toSnapshotId","taskName","imageSnapshot","options","testfilename","basename","snapshotFilename","baselinePath","join","resultPath","diffPath","screenshot","image","m","page","cdp","commands","target","prop","getByAltText","getByRole","getByTestId","getByText","getByTitle","getByPlaceholderText","getByLabelText","server","toMatchImageSnapshot","actual","success","baseline","tryReadSnapshot","originalImage","resultImage","baselineImage","alignImagesToSameSize","pass","diffAmount","diffImage","compareImage","writeSnapshot","dedent","path","content","image1","image2","isSameSize","size","getMaxSize","resize","createImageResizer","width","height","inArea","x","y","result","idx","old","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","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\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 { imageSnapshotSymbol } from './constants.js'\nimport type { ImageSnapshot } from './types.js'\n\nexport function isImageSnapshot(subject: any): subject is ImageSnapshot {\n\treturn !!subject && subject.type === imageSnapshotSymbol\n}\n\nexport function assertImageSnapshot(subject: any): asserts subject is ImageSnapshot {\n\tif (!isImageSnapshot(subject)) {\n\t\tthrow new Error('Expected subject to be an image snapshot')\n\t}\n}\n\nexport function toSnapshotId(taskName: string) {\n\treturn `${taskName.replace(/[^a-z0-9]/gi, '-').toLowerCase()}`\n}\n","import type { BrowserPage } from '@vitest/browser/context'\nimport { basename, join } from 'pathe'\nimport { toImageData } from '../../image_data.js'\nimport { state } from '../../state.js'\nimport { imageSnapshotSymbol } from './constants.js'\nimport { toSnapshotId } from './image_snapshot.logic.js'\nimport type { ImageSnapshot, ImageSnapshotOptions } from './types.js'\n\nexport async function imageSnapshot(this: BrowserPage, options?: ImageSnapshotOptions): Promise<ImageSnapshot> {\n\t// const rootDir = server.config.root\n\tconst testfilename = basename(state.testFilepath)\n\tconst snapshotFilename = `${toSnapshotId(state.taskName)}-${state.snapshot[state.taskName]!.index++}.png`\n\tconst baselinePath = join(state.baselineDir, testfilename, snapshotFilename)\n\tconst resultPath = join(state.resultDir, testfilename, snapshotFilename)\n\tconst diffPath = join(state.diffDir, testfilename, 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\t// rootDir,\n\t\ttestfilename,\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() ?? {}\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 type { AsyncExpectationResult } from '@vitest/expect'\nimport dedent from 'dedent'\nimport pixelmatch from 'pixelmatch'\nimport { imageSnapshotStubSymbol } from './@vitest/browser/constants.js'\nimport { commands } 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'\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,\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,\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: \\`${actual}\\``,\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\tawait writeSnapshot(`${subject.diffPath}`, diffImage)\n\t\treturn {\n\t\t\tpass: false,\n\t\t\tactual,\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\nconst 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\t\treturn result\n\t}\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
|
@@ -15,9 +15,17 @@ type ImageSnapshot = {
|
|
|
15
15
|
image: ImageData;
|
|
16
16
|
};
|
|
17
17
|
type MatchImageSnapshotOptions = {
|
|
18
|
+
/**
|
|
19
|
+
* Custom options passed to 'pixelmatch'
|
|
20
|
+
*/
|
|
18
21
|
diffOptions?: PixelmatchOptions | undefined;
|
|
19
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Failure threshold should measure in `pixel` or `percent`.
|
|
24
|
+
*
|
|
25
|
+
* Default is `pixel`.
|
|
26
|
+
*/
|
|
20
27
|
failureThresholdType?: 'pixel' | 'percent' | undefined;
|
|
28
|
+
failureThreshold?: number | undefined;
|
|
21
29
|
};
|
|
22
30
|
|
|
23
31
|
declare module '@vitest/browser/context' {
|
package/dist/vitest-setup.d.ts
CHANGED
|
@@ -17,9 +17,17 @@ type ImageSnapshot = {
|
|
|
17
17
|
image: ImageData;
|
|
18
18
|
};
|
|
19
19
|
type MatchImageSnapshotOptions = {
|
|
20
|
+
/**
|
|
21
|
+
* Custom options passed to 'pixelmatch'
|
|
22
|
+
*/
|
|
20
23
|
diffOptions?: PixelmatchOptions | undefined;
|
|
21
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Failure threshold should measure in `pixel` or `percent`.
|
|
26
|
+
*
|
|
27
|
+
* Default is `pixel`.
|
|
28
|
+
*/
|
|
22
29
|
failureThresholdType?: 'pixel' | 'percent' | undefined;
|
|
30
|
+
failureThreshold?: number | undefined;
|
|
23
31
|
};
|
|
24
32
|
|
|
25
33
|
declare module '@vitest/browser/context' {
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -81,7 +81,7 @@ beforeEach(configureSnapshotBeforeEach)
|
|
|
81
81
|
afterEach<{ story?: StoryContext }>(async (ctx) => {
|
|
82
82
|
if (!shouldTakeSnapshot(ctx)) return
|
|
83
83
|
|
|
84
|
-
await expect(page.imageSnapshot()).toMatchImageSnapshot()
|
|
84
|
+
await expect(page.imageSnapshot()).toMatchImageSnapshot(ctx.story?.parameters.snapshot)
|
|
85
85
|
})
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -117,6 +117,21 @@ export const MyStory = {
|
|
|
117
117
|
}
|
|
118
118
|
```
|
|
119
119
|
|
|
120
|
+
You can provide options to the automatic `toMatchImageSnapshot` matcher using parameters.
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
import { defineSnapshotParam } from 'storybook-addon-vis'
|
|
124
|
+
|
|
125
|
+
export const MyStory = {
|
|
126
|
+
// Take image snapshot automatically for this story
|
|
127
|
+
tags: ['snapshot'],
|
|
128
|
+
parameters: defineSnapshotParam({
|
|
129
|
+
failureThreshold: 70,
|
|
130
|
+
})
|
|
131
|
+
// ...
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
120
135
|
You can disable snapshot capturing with the `!snapshot` tag,
|
|
121
136
|
much like `!test`.
|
|
122
137
|
|
|
@@ -18,7 +18,15 @@ export type ImageSnapshot = {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export type MatchImageSnapshotOptions = {
|
|
21
|
+
/**
|
|
22
|
+
* Custom options passed to 'pixelmatch'
|
|
23
|
+
*/
|
|
21
24
|
diffOptions?: PixelmatchOptions | undefined
|
|
22
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Failure threshold should measure in `pixel` or `percent`.
|
|
27
|
+
*
|
|
28
|
+
* Default is `pixel`.
|
|
29
|
+
*/
|
|
23
30
|
failureThresholdType?: 'pixel' | 'percent' | undefined
|
|
31
|
+
failureThreshold?: number | undefined
|
|
24
32
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react'
|
|
2
|
+
import { defineSnapshotParam } from './index.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
title: 'param',
|
|
6
|
+
tags: ['snapshot'],
|
|
7
|
+
} satisfies Meta
|
|
8
|
+
|
|
9
|
+
export const MeetFailureThreshold: StoryObj = {
|
|
10
|
+
parameters: defineSnapshotParam({
|
|
11
|
+
failureThreshold: 70,
|
|
12
|
+
}),
|
|
13
|
+
render: () => <div data-testid="subject">unit test</div>,
|
|
14
|
+
// render: () => <div data-testid="subject">unit text</div>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const MeetFailureThresholdByPercentage: StoryObj = {
|
|
18
|
+
parameters: defineSnapshotParam({
|
|
19
|
+
failureThreshold: 0.01,
|
|
20
|
+
failureThresholdType: 'percent',
|
|
21
|
+
}),
|
|
22
|
+
render: () => <div data-testid="subject">unit test</div>,
|
|
23
|
+
// render: () => <div data-testid="subject">unit text</div>,
|
|
24
|
+
}
|
package/src/param.ts
CHANGED
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { MatchImageSnapshotOptions } from './@vitest/browser/types'
|
|
2
2
|
|
|
3
|
-
export
|
|
4
|
-
/**
|
|
5
|
-
* Add a delay to the story snapshot comparison to allow for async rendering.
|
|
6
|
-
*
|
|
7
|
-
* This only apply to the automatic snapshot taken when the story is rendered.
|
|
8
|
-
* In general, it's a good idea to not rely on this delay,
|
|
9
|
-
* and instead use the make sure your component is fully rendered before the `play()` function returns.
|
|
10
|
-
*/
|
|
11
|
-
delay?: number | undefined
|
|
12
|
-
/**
|
|
13
|
-
* The method by which images are compared.
|
|
14
|
-
* `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison.
|
|
15
|
-
* @default 'pixelmatch'
|
|
16
|
-
*/
|
|
17
|
-
comparisonMethod?: 'pixelmatch' | 'ssim' | undefined
|
|
18
|
-
/**
|
|
19
|
-
* Custom config passed to 'pixelmatch' or 'ssim'
|
|
20
|
-
*/
|
|
21
|
-
customDiffConfig?: PixelmatchOptions | undefined
|
|
22
|
-
failureThreshold?: number | undefined
|
|
23
|
-
failureThresholdType?: 'percent' | 'pixel' | undefined
|
|
24
|
-
blur?: number | undefined
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function defineSnapshotParam(snapshot: SnapshotStoryParam) {
|
|
3
|
+
export function defineSnapshotParam(snapshot: MatchImageSnapshotOptions) {
|
|
28
4
|
return { snapshot }
|
|
29
5
|
}
|
package/src/param.spec.ts
DELETED