@blazediff/matcher 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -41,10 +41,20 @@ interface MatcherOptions {
41
41
  */
42
42
  snapshotIdentifier?: string;
43
43
  /**
44
- * Force update snapshots (like running with -u flag)
45
- * @default false
44
+ * Snapshot update mode (following Vitest's logic)
45
+ * - 'all': update all snapshots (vitest -u)
46
+ * - 'new': create new snapshots only, don't update existing (default)
47
+ * - 'none': don't create or update any snapshots
48
+ * - true: same as 'all' (backwards compatibility)
49
+ * - false: same as 'new' (backwards compatibility)
50
+ * @default 'new'
51
+ */
52
+ updateSnapshots?: boolean | "all" | "new" | "none";
53
+ /**
54
+ * Custom update command to display in error messages
55
+ * @default '--update'
46
56
  */
47
- updateSnapshots?: boolean;
57
+ updateCommand?: string;
48
58
  /**
49
59
  * Color difference threshold for core/bin methods (0-1)
50
60
  * Lower = more strict
@@ -175,22 +185,17 @@ declare function fileExists(filePath: string): boolean;
175
185
 
176
186
  interface FormatOptions {
177
187
  pass: boolean;
178
- method: string;
179
- isNewSnapshot: boolean;
180
- paths: {
181
- baselinePath: string;
182
- receivedPath: string;
183
- diffPath: string;
184
- };
185
- result: {
186
- diffCount?: number;
187
- diffPercentage?: number;
188
- score?: number;
189
- };
188
+ method: ComparisonMethod;
189
+ snapshotCreated: boolean;
190
+ baselinePath: string;
191
+ receivedPath: string;
192
+ diffPath: string;
193
+ diffCount?: number;
194
+ diffPercentage?: number;
195
+ score?: number;
190
196
  threshold: number;
191
197
  thresholdType: "pixel" | "percent";
192
- isSsim: boolean;
193
- isGmsd: boolean;
198
+ updateCommand?: string;
194
199
  }
195
200
  declare function formatMessage(opts: FormatOptions): string;
196
201
 
package/dist/index.d.ts CHANGED
@@ -41,10 +41,20 @@ interface MatcherOptions {
41
41
  */
42
42
  snapshotIdentifier?: string;
43
43
  /**
44
- * Force update snapshots (like running with -u flag)
45
- * @default false
44
+ * Snapshot update mode (following Vitest's logic)
45
+ * - 'all': update all snapshots (vitest -u)
46
+ * - 'new': create new snapshots only, don't update existing (default)
47
+ * - 'none': don't create or update any snapshots
48
+ * - true: same as 'all' (backwards compatibility)
49
+ * - false: same as 'new' (backwards compatibility)
50
+ * @default 'new'
51
+ */
52
+ updateSnapshots?: boolean | "all" | "new" | "none";
53
+ /**
54
+ * Custom update command to display in error messages
55
+ * @default '--update'
46
56
  */
47
- updateSnapshots?: boolean;
57
+ updateCommand?: string;
48
58
  /**
49
59
  * Color difference threshold for core/bin methods (0-1)
50
60
  * Lower = more strict
@@ -175,22 +185,17 @@ declare function fileExists(filePath: string): boolean;
175
185
 
176
186
  interface FormatOptions {
177
187
  pass: boolean;
178
- method: string;
179
- isNewSnapshot: boolean;
180
- paths: {
181
- baselinePath: string;
182
- receivedPath: string;
183
- diffPath: string;
184
- };
185
- result: {
186
- diffCount?: number;
187
- diffPercentage?: number;
188
- score?: number;
189
- };
188
+ method: ComparisonMethod;
189
+ snapshotCreated: boolean;
190
+ baselinePath: string;
191
+ receivedPath: string;
192
+ diffPath: string;
193
+ diffCount?: number;
194
+ diffPercentage?: number;
195
+ score?: number;
190
196
  threshold: number;
191
197
  thresholdType: "pixel" | "percent";
192
- isSsim: boolean;
193
- isGmsd: boolean;
198
+ updateCommand?: string;
194
199
  }
195
200
  declare function formatMessage(opts: FormatOptions): string;
196
201
 
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
- 'use strict';var fs=require('fs'),path=require('path'),pngjsTransformer=require('@blazediff/pngjs-transformer'),bin=require('@blazediff/bin'),core=require('@blazediff/core'),gmsd=require('@blazediff/gmsd'),ssim=require('@blazediff/ssim'),hitchhikersSsim=require('@blazediff/ssim/hitchhikers-ssim'),msssim=require('@blazediff/ssim/msssim'),a=require('picocolors');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var a__default=/*#__PURE__*/_interopDefault(a);function h(t){return typeof t=="string"}function B(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function I(t){if(!fs.existsSync(t))throw new Error(`Image file not found: ${t}`);let e=await pngjsTransformer.pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function c(t,e,n,r){let i=path.dirname(t);fs.existsSync(i)||fs.mkdirSync(i,{recursive:true}),await pngjsTransformer.pngjsTransformer.write({data:e,width:n,height:r},t);}async function g(t){return h(t)?I(t):{data:new Uint8Array(t.data),width:t.width,height:t.height}}function M(t){return fs.existsSync(t)}function k(t){fs.existsSync(t)||fs.mkdirSync(t,{recursive:true});}async function $(t,e,n,r){if(!h(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!h(e))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let i=await bin.compare(t,e,n,{threshold:r.threshold,antialiasing:r.antialiasing});if(i.match)return {diffCount:0,diffPercentage:0};if(i.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(i.reason==="pixel-diff")return {diffCount:i.diffCount,diffPercentage:i.diffPercentage};if(i.reason==="file-not-exists")throw new Error(`Image file not found: ${i.file}`);return {diffCount:0,diffPercentage:0}}function O(t,e,n,r){let{width:i,height:o}=t,m=i*o;if(t.width!==e.width||t.height!==e.height)return {diffCount:m,diffPercentage:100};let u=n?new Uint8Array(m*4):void 0,f=core.diff(t.data,e.data,u,i,o,{threshold:r.threshold??.1,includeAA:r.includeAA??false});return {diffCount:f,diffPercentage:f/m*100,diffOutput:u}}function D(t,e,n,r){let{width:i,height:o}=t,m=i*o;if(t.width!==e.width||t.height!==e.height)return {score:1};let u=n?new Uint8Array(m*4):void 0;return {score:gmsd.gmsd(t.data,e.data,u,i,o,{downsample:r.downsample}),diffOutput:u}}function E(t,e,n,r,i){let{width:o,height:m}=t,u=o*m;if(t.width!==e.width||t.height!==e.height)return {score:0};let f=r?new Uint8Array(u*4):void 0,s={windowSize:i.windowSize,k1:i.k1,k2:i.k2},p;switch(n){case "ssim":p=ssim.ssim(t.data,e.data,f,o,m,s);break;case "msssim":p=msssim.msssim(t.data,e.data,f,o,m,s);break;case "hitchhikers-ssim":p=hitchhikersSsim.hitchhikersSSIM(t.data,e.data,f,o,m,s);break;default:throw new Error(`Unknown SSIM method: ${n}`)}return {score:p,diffOutput:f}}function l(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function A(t,e){if(t==="bin"&&!h(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function P(t,e,n,r,i){if(A(n,t),A(n,e),n==="bin"){let s=await $(t,e,i,r);return {diffCount:s.diffCount,diffPercentage:s.diffPercentage}}let o=await g(t),m=await g(e),u=i!==void 0;if(l(n)){let s=E(o,m,n,u,r);return {score:s.score,diffOutput:s.diffOutput}}if(n==="gmsd"){let s=D(o,m,u,r);return {score:s.score,diffOutput:s.diffOutput}}let f=O(o,m,u,r);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage,diffOutput:f.diffOutput}}var w={success:a__default.default.isColorSupported?"\u2714":"\u221A",error:a__default.default.isColorSupported?"\u2716":"\xD7",info:a__default.default.isColorSupported?"\u2139":"i",arrow:a__default.default.isColorSupported?"\u2514\u2500":"'-"};function R(t){return t.isNewSnapshot?Z(t.paths.baselinePath):t.pass?q():H(t)}function Z(t){return [`${a__default.default.green(w.success)} ${a__default.default.green("New snapshot created")}`,` ${a__default.default.dim(w.arrow)} ${a__default.default.dim(t)}`].join(`
2
- `)}function q(){return `${a__default.default.green(w.success)} ${a__default.default.green("Image matches snapshot")}`}function H(t){let{method:e,paths:n,result:r,threshold:i,thresholdType:o,isSsim:m,isGmsd:u}=t,f=[`${a__default.default.red(w.error)} ${a__default.default.red(a__default.default.bold("Image snapshot mismatch"))}`,""],s=12;if(f.push(` ${a__default.default.dim("Method".padEnd(s))}${e}`),f.push(` ${a__default.default.dim("Baseline".padEnd(s))}${a__default.default.dim(n.baselinePath)}`),f.push(` ${a__default.default.dim("Received".padEnd(s))}${a__default.default.dim(n.receivedPath)}`),f.push(` ${a__default.default.dim("Diff".padEnd(s))}${a__default.default.dim(n.diffPath)}`),f.push(""),m){let d=r.score??0,S=((1-d)*100).toFixed(2);f.push(` ${a__default.default.dim("SSIM Score".padEnd(s))}${a__default.default.yellow(d.toFixed(4))} ${a__default.default.dim("(1.0 = identical)")}`),f.push(` ${a__default.default.dim("Difference".padEnd(s))}${a__default.default.yellow(S+"%")}`);}else if(u){let d=r.score??0;f.push(` ${a__default.default.dim("GMSD Score".padEnd(s))}${a__default.default.yellow(d.toFixed(4))} ${a__default.default.dim("(0.0 = identical)")}`);}else {let d=r.diffCount??0,S=r.diffPercentage?.toFixed(2)??"0.00";f.push(` ${a__default.default.dim("Difference".padEnd(s))}${a__default.default.yellow(d.toLocaleString())} pixels ${a__default.default.dim(`(${S}%)`)}`);}let p=o==="percent"?"%":"pixels";return f.push(` ${a__default.default.dim("Threshold".padEnd(s))}${i} ${p}`),f.push(""),f.push(` ${a__default.default.cyan(w.info)} ${a__default.default.cyan("Run with --update to update the snapshot")}`),f.join(`
3
- `)}function Q(t){return t.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function V(t,e){let n=path.dirname(t.testPath),r=e.snapshotsDir??"__snapshots__",i=path.isAbsolute(r)?r:path.join(n,r),o=e.snapshotIdentifier??Q(t);return {snapshotDir:i,baselinePath:path.join(i,`${o}.png`),receivedPath:path.join(i,`${o}.received.png`),diffPath:path.join(i,`${o}.diff.png`)}}function F(t,e,n){let r=n.failureThreshold??0,i=n.failureThresholdType??"pixel";if(l(t)){let o=e.score??0;return i==="percent"?(1-o)*100<=r:o>=1-r/100}if(t==="gmsd"){let o=e.score??0;return i==="percent"?o*100<=r:o<=r/100}return i==="percent"?(e.diffPercentage??0)<=r:(e.diffCount??0)<=r}function T(t,e,n,r,i,o){return R({pass:t,method:e,isNewSnapshot:o,paths:i,result:n,threshold:r.failureThreshold??0,thresholdType:r.failureThresholdType??"pixel",isSsim:l(e),isGmsd:e==="gmsd"})}async function Y(t,e,n){let r=V(n,e),{snapshotDir:i,baselinePath:o,receivedPath:m,diffPath:u}=r;k(i);let f=M(o);if(e.updateSnapshots||!f){if(h(t)){let d=await I(t);await c(o,d.data,d.width,d.height);}else await c(o,t.data,t.width,t.height);return fs.existsSync(m)&&fs.unlinkSync(m),fs.existsSync(u)&&fs.unlinkSync(u),{pass:true,message:T(true,e.method,{},e,r,true),baselinePath:o,snapshotStatus:f?"updated":"added"}}let s=await P(t,o,e.method,e,u),p=F(e.method,s,e);if(p)return fs.existsSync(m)&&fs.unlinkSync(m),fs.existsSync(u)&&fs.unlinkSync(u),{pass:true,message:T(p,e.method,s,e,r,false),diffCount:s.diffCount,diffPercentage:s.diffPercentage,score:s.score,baselinePath:o,snapshotStatus:"matched"};if(h(t)){let d=await I(t);await c(m,d.data,d.width,d.height);}else await c(m,t.data,t.width,t.height);if(s.diffOutput){let d=await g(t);await c(u,s.diffOutput,d.width,d.height);}return {pass:false,message:T(p,e.method,s,e,r,false),diffCount:s.diffCount,diffPercentage:s.diffPercentage,score:s.score,baselinePath:o,receivedPath:m,diffPath:u,snapshotStatus:"failed"}}async function v(t,e,n){let r=await P(t,e,n.method,n),i=F(n.method,r,n);return {pass:i,message:i?"Images match.":`Images differ: ${r.diffCount??r.score} ${r.diffCount!==void 0?"pixels":"score"}`,diffCount:r.diffCount,diffPercentage:r.diffPercentage,score:r.score}}exports.compareImages=v;exports.fileExists=M;exports.formatReport=R;exports.getOrCreateSnapshot=Y;exports.isFilePath=h;exports.isImageBuffer=B;exports.loadPNG=I;exports.normalizeImageInput=g;exports.runComparison=P;exports.savePNG=c;exports.validateMethodSupportsInput=A;
1
+ 'use strict';var fs=require('fs'),path=require('path'),pngjsTransformer=require('@blazediff/pngjs-transformer'),bin=require('@blazediff/bin'),core=require('@blazediff/core'),gmsd=require('@blazediff/gmsd'),ssim=require('@blazediff/ssim'),hitchhikersSsim=require('@blazediff/ssim/hitchhikers-ssim'),msssim=require('@blazediff/ssim/msssim'),s=require('picocolors');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var s__default=/*#__PURE__*/_interopDefault(s);function c(t){return typeof t=="string"}function z(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function I(t){if(!fs.existsSync(t))throw new Error(`Image file not found: ${t}`);let e=await pngjsTransformer.pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function g(t,e,n,r){let o=path.dirname(t);fs.existsSync(o)||fs.mkdirSync(o,{recursive:true}),await pngjsTransformer.pngjsTransformer.write({data:e,width:n,height:r},t);}async function l(t){return c(t)?I(t):{data:new Uint8Array(t.data),width:t.width,height:t.height}}function D(t){return fs.existsSync(t)}function G(t){fs.existsSync(t)||fs.mkdirSync(t,{recursive:true});}async function T(t,e,n,r){if(!c(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!c(e))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let o=await bin.compare(t,e,n,{threshold:r.threshold,antialiasing:r.antialiasing});if(o.match)return {diffCount:0,diffPercentage:0};if(o.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(o.reason==="pixel-diff")return {diffCount:o.diffCount,diffPercentage:o.diffPercentage};if(o.reason==="file-not-exists")throw new Error(`Image file not found: ${o.file}`);return {diffCount:0,diffPercentage:0}}function E(t,e,n,r){let{width:o,height:i}=t,f=o*i;if(t.width!==e.width||t.height!==e.height)return {diffCount:f,diffPercentage:100};let m=n?new Uint8Array(f*4):void 0,p=core.diff(t.data,e.data,m,o,i,{threshold:r.threshold??.1,includeAA:r.includeAA??false});return {diffCount:p,diffPercentage:p/f*100,diffOutput:m}}function A(t,e,n,r){let{width:o,height:i}=t,f=o*i;if(t.width!==e.width||t.height!==e.height)return {score:1};let m=n?new Uint8Array(f*4):void 0;return {score:gmsd.gmsd(t.data,e.data,m,o,i,{downsample:r.downsample}),diffOutput:m}}function R(t,e,n,r,o){let{width:i,height:f}=t,m=i*f;if(t.width!==e.width||t.height!==e.height)return {score:0};let p=r?new Uint8Array(m*4):void 0,a={windowSize:o.windowSize,k1:o.k1,k2:o.k2},h;switch(n){case "ssim":h=ssim.ssim(t.data,e.data,p,i,f,a);break;case "msssim":h=msssim.msssim(t.data,e.data,p,i,f,a);break;case "hitchhikers-ssim":h=hitchhikersSsim.hitchhikersSSIM(t.data,e.data,p,i,f,a);break;default:throw new Error(`Unknown SSIM method: ${n}`)}return {score:h,diffOutput:p}}function w(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function U(t,e){if(t==="bin"&&!c(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function b(t,e,n,r,o){if(U(n,t),U(n,e),n==="bin"){let a=await T(t,e,o,r);return {diffCount:a.diffCount,diffPercentage:a.diffPercentage}}let i=await l(t),f=await l(e),m=o!==void 0;if(w(n)){let a=R(i,f,n,m,r);return {score:a.score,diffOutput:a.diffOutput}}if(n==="gmsd"){let a=A(i,f,m,r);return {score:a.score,diffOutput:a.diffOutput}}let p=E(i,f,m,r);return {diffCount:p.diffCount,diffPercentage:p.diffPercentage,diffOutput:p.diffOutput}}var y={success:s__default.default.isColorSupported?"\u2714":"\u221A",error:s__default.default.isColorSupported?"\u2716":"\xD7",info:s__default.default.isColorSupported?"\u2139":"i",arrow:s__default.default.isColorSupported?"\u2514\u2500":"'-"},Z=new Set(["ssim","msssim","hitchhikers-ssim"]);function P(t){return t.snapshotCreated?q(t.baselinePath):t.pass?J():K(t)}function q(t){return [`${s__default.default.green(y.success)} ${s__default.default.green("New snapshot created")}`,` ${s__default.default.dim(y.arrow)} ${s__default.default.dim(t)}`].join(`
2
+ `)}function J(){return `${s__default.default.green(y.success)} ${s__default.default.green("Image matches snapshot")}`}function K(t){let{method:e,baselinePath:n,receivedPath:r,diffPath:o,diffCount:i=0,diffPercentage:f,score:m=0,threshold:p,thresholdType:a,updateCommand:h}=t,u=[`${s__default.default.red(y.error)} ${s__default.default.red(s__default.default.bold("Image snapshot mismatch"))}`,""],d=12;if(u.push(` ${s__default.default.dim("Method".padEnd(d))}${e}`),u.push(` ${s__default.default.dim("Baseline".padEnd(d))}${s__default.default.dim(n)}`),u.push(` ${s__default.default.dim("Received".padEnd(d))}${s__default.default.dim(r)}`),u.push(` ${s__default.default.dim("Diff".padEnd(d))}${s__default.default.dim(o)}`),u.push(""),Z.has(e)){let O=((1-m)*100).toFixed(2);u.push(` ${s__default.default.dim("SSIM Score".padEnd(d))}${s__default.default.yellow(m.toFixed(4))} ${s__default.default.dim("(1.0 = identical)")}`),u.push(` ${s__default.default.dim("Difference".padEnd(d))}${s__default.default.yellow(`${O}%`)}`);}else if(e==="gmsd")u.push(` ${s__default.default.dim("GMSD Score".padEnd(d))}${s__default.default.yellow(m.toFixed(4))} ${s__default.default.dim("(0.0 = identical)")}`);else {let O=f?.toFixed(2)??"0.00";u.push(` ${s__default.default.dim("Difference".padEnd(d))}${s__default.default.yellow(i.toLocaleString())} pixels ${s__default.default.dim(`(${O}%)`)}`);}let C=a==="percent"?"%":"pixels";return u.push(` ${s__default.default.dim("Threshold".padEnd(d))}${p} ${C}`),u.push(""),u.push(` ${s__default.default.cyan(y.info)} ${s__default.default.cyan(`Run with ${h??"--update"} to update the snapshot`)}`),u.join(`
3
+ `)}function Y(t){return t.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function v(t,e){let n=path.dirname(t.testPath),r=e.snapshotsDir??"__snapshots__",o=path.isAbsolute(r)?r:path.join(n,r),i=e.snapshotIdentifier??Y(t);return {snapshotDir:o,baselinePath:path.join(o,`${i}.png`),receivedPath:path.join(o,`${i}.received.png`),diffPath:path.join(o,`${i}.diff.png`)}}function N(t,e,n){let r=n.failureThreshold??0,o=n.failureThresholdType??"pixel";if(w(t)){let i=e.score??0;return o==="percent"?(1-i)*100<=r:i>=1-r/100}if(t==="gmsd"){let i=e.score??0;return o==="percent"?i*100<=r:i<=r/100}return o==="percent"?(e.diffPercentage??0)<=r:(e.diffCount??0)<=r}async function tt(t,e,n){let r=v(n,e),{snapshotDir:o,baselinePath:i,receivedPath:f,diffPath:m}=r;G(o);let p=D(i);if(e.updateSnapshots||!p){if(c(t)){let C=await I(t);await g(i,C.data,C.width,C.height);}else await g(i,t.data,t.width,t.height);return fs.existsSync(f)&&fs.unlinkSync(f),fs.existsSync(m)&&fs.unlinkSync(m),{pass:true,message:P({pass:true,method:e.method,snapshotCreated:true,baselinePath:i,receivedPath:f,diffPath:m,diffCount:0,diffPercentage:0,score:0,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),baselinePath:i,snapshotStatus:p?"updated":"added"}}let a=await b(t,i,e.method,e,m),h=N(e.method,a,e);if(h)return fs.existsSync(f)&&fs.unlinkSync(f),fs.existsSync(m)&&fs.unlinkSync(m),{pass:true,message:P({pass:h,method:e.method,snapshotCreated:false,baselinePath:i,receivedPath:f,diffPath:m,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,baselinePath:i,snapshotStatus:"matched"};if(c(t)){let d=await I(t);await g(f,d.data,d.width,d.height);}else await g(f,t.data,t.width,t.height);if(a.diffOutput){let d=await l(t);await g(m,a.diffOutput,d.width,d.height);}return {pass:false,message:P({pass:h,method:e.method,snapshotCreated:false,baselinePath:i,receivedPath:f,diffPath:m,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,baselinePath:i,receivedPath:f,diffPath:m,snapshotStatus:"failed"}}async function et(t,e,n){let r=await b(t,e,n.method,n),o=N(n.method,r,n);return {pass:o,message:o?"Images match.":`Images differ: ${r.diffCount??r.score} ${r.diffCount!==void 0?"pixels":"score"}`,diffCount:r.diffCount,diffPercentage:r.diffPercentage,score:r.score}}exports.compareImages=et;exports.fileExists=D;exports.formatReport=P;exports.getOrCreateSnapshot=tt;exports.isFilePath=c;exports.isImageBuffer=z;exports.loadPNG=I;exports.normalizeImageInput=l;exports.runComparison=b;exports.savePNG=g;exports.validateMethodSupportsInput=U;
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import {existsSync,mkdirSync,unlinkSync}from'fs';import {dirname,isAbsolute,join}from'path';import {pngjsTransformer}from'@blazediff/pngjs-transformer';import {compare}from'@blazediff/bin';import {diff}from'@blazediff/core';import {gmsd}from'@blazediff/gmsd';import {ssim}from'@blazediff/ssim';import {hitchhikersSSIM}from'@blazediff/ssim/hitchhikers-ssim';import {msssim}from'@blazediff/ssim/msssim';import a from'picocolors';function h(t){return typeof t=="string"}function B(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function I(t){if(!existsSync(t))throw new Error(`Image file not found: ${t}`);let e=await pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function c(t,e,n,r){let i=dirname(t);existsSync(i)||mkdirSync(i,{recursive:true}),await pngjsTransformer.write({data:e,width:n,height:r},t);}async function g(t){return h(t)?I(t):{data:new Uint8Array(t.data),width:t.width,height:t.height}}function M(t){return existsSync(t)}function k(t){existsSync(t)||mkdirSync(t,{recursive:true});}async function $(t,e,n,r){if(!h(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!h(e))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let i=await compare(t,e,n,{threshold:r.threshold,antialiasing:r.antialiasing});if(i.match)return {diffCount:0,diffPercentage:0};if(i.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(i.reason==="pixel-diff")return {diffCount:i.diffCount,diffPercentage:i.diffPercentage};if(i.reason==="file-not-exists")throw new Error(`Image file not found: ${i.file}`);return {diffCount:0,diffPercentage:0}}function O(t,e,n,r){let{width:i,height:o}=t,m=i*o;if(t.width!==e.width||t.height!==e.height)return {diffCount:m,diffPercentage:100};let u=n?new Uint8Array(m*4):void 0,f=diff(t.data,e.data,u,i,o,{threshold:r.threshold??.1,includeAA:r.includeAA??false});return {diffCount:f,diffPercentage:f/m*100,diffOutput:u}}function D(t,e,n,r){let{width:i,height:o}=t,m=i*o;if(t.width!==e.width||t.height!==e.height)return {score:1};let u=n?new Uint8Array(m*4):void 0;return {score:gmsd(t.data,e.data,u,i,o,{downsample:r.downsample}),diffOutput:u}}function E(t,e,n,r,i){let{width:o,height:m}=t,u=o*m;if(t.width!==e.width||t.height!==e.height)return {score:0};let f=r?new Uint8Array(u*4):void 0,s={windowSize:i.windowSize,k1:i.k1,k2:i.k2},p;switch(n){case "ssim":p=ssim(t.data,e.data,f,o,m,s);break;case "msssim":p=msssim(t.data,e.data,f,o,m,s);break;case "hitchhikers-ssim":p=hitchhikersSSIM(t.data,e.data,f,o,m,s);break;default:throw new Error(`Unknown SSIM method: ${n}`)}return {score:p,diffOutput:f}}function l(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function A(t,e){if(t==="bin"&&!h(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function P(t,e,n,r,i){if(A(n,t),A(n,e),n==="bin"){let s=await $(t,e,i,r);return {diffCount:s.diffCount,diffPercentage:s.diffPercentage}}let o=await g(t),m=await g(e),u=i!==void 0;if(l(n)){let s=E(o,m,n,u,r);return {score:s.score,diffOutput:s.diffOutput}}if(n==="gmsd"){let s=D(o,m,u,r);return {score:s.score,diffOutput:s.diffOutput}}let f=O(o,m,u,r);return {diffCount:f.diffCount,diffPercentage:f.diffPercentage,diffOutput:f.diffOutput}}var w={success:a.isColorSupported?"\u2714":"\u221A",error:a.isColorSupported?"\u2716":"\xD7",info:a.isColorSupported?"\u2139":"i",arrow:a.isColorSupported?"\u2514\u2500":"'-"};function R(t){return t.isNewSnapshot?Z(t.paths.baselinePath):t.pass?q():H(t)}function Z(t){return [`${a.green(w.success)} ${a.green("New snapshot created")}`,` ${a.dim(w.arrow)} ${a.dim(t)}`].join(`
2
- `)}function q(){return `${a.green(w.success)} ${a.green("Image matches snapshot")}`}function H(t){let{method:e,paths:n,result:r,threshold:i,thresholdType:o,isSsim:m,isGmsd:u}=t,f=[`${a.red(w.error)} ${a.red(a.bold("Image snapshot mismatch"))}`,""],s=12;if(f.push(` ${a.dim("Method".padEnd(s))}${e}`),f.push(` ${a.dim("Baseline".padEnd(s))}${a.dim(n.baselinePath)}`),f.push(` ${a.dim("Received".padEnd(s))}${a.dim(n.receivedPath)}`),f.push(` ${a.dim("Diff".padEnd(s))}${a.dim(n.diffPath)}`),f.push(""),m){let d=r.score??0,S=((1-d)*100).toFixed(2);f.push(` ${a.dim("SSIM Score".padEnd(s))}${a.yellow(d.toFixed(4))} ${a.dim("(1.0 = identical)")}`),f.push(` ${a.dim("Difference".padEnd(s))}${a.yellow(S+"%")}`);}else if(u){let d=r.score??0;f.push(` ${a.dim("GMSD Score".padEnd(s))}${a.yellow(d.toFixed(4))} ${a.dim("(0.0 = identical)")}`);}else {let d=r.diffCount??0,S=r.diffPercentage?.toFixed(2)??"0.00";f.push(` ${a.dim("Difference".padEnd(s))}${a.yellow(d.toLocaleString())} pixels ${a.dim(`(${S}%)`)}`);}let p=o==="percent"?"%":"pixels";return f.push(` ${a.dim("Threshold".padEnd(s))}${i} ${p}`),f.push(""),f.push(` ${a.cyan(w.info)} ${a.cyan("Run with --update to update the snapshot")}`),f.join(`
3
- `)}function Q(t){return t.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function V(t,e){let n=dirname(t.testPath),r=e.snapshotsDir??"__snapshots__",i=isAbsolute(r)?r:join(n,r),o=e.snapshotIdentifier??Q(t);return {snapshotDir:i,baselinePath:join(i,`${o}.png`),receivedPath:join(i,`${o}.received.png`),diffPath:join(i,`${o}.diff.png`)}}function F(t,e,n){let r=n.failureThreshold??0,i=n.failureThresholdType??"pixel";if(l(t)){let o=e.score??0;return i==="percent"?(1-o)*100<=r:o>=1-r/100}if(t==="gmsd"){let o=e.score??0;return i==="percent"?o*100<=r:o<=r/100}return i==="percent"?(e.diffPercentage??0)<=r:(e.diffCount??0)<=r}function T(t,e,n,r,i,o){return R({pass:t,method:e,isNewSnapshot:o,paths:i,result:n,threshold:r.failureThreshold??0,thresholdType:r.failureThresholdType??"pixel",isSsim:l(e),isGmsd:e==="gmsd"})}async function Y(t,e,n){let r=V(n,e),{snapshotDir:i,baselinePath:o,receivedPath:m,diffPath:u}=r;k(i);let f=M(o);if(e.updateSnapshots||!f){if(h(t)){let d=await I(t);await c(o,d.data,d.width,d.height);}else await c(o,t.data,t.width,t.height);return existsSync(m)&&unlinkSync(m),existsSync(u)&&unlinkSync(u),{pass:true,message:T(true,e.method,{},e,r,true),baselinePath:o,snapshotStatus:f?"updated":"added"}}let s=await P(t,o,e.method,e,u),p=F(e.method,s,e);if(p)return existsSync(m)&&unlinkSync(m),existsSync(u)&&unlinkSync(u),{pass:true,message:T(p,e.method,s,e,r,false),diffCount:s.diffCount,diffPercentage:s.diffPercentage,score:s.score,baselinePath:o,snapshotStatus:"matched"};if(h(t)){let d=await I(t);await c(m,d.data,d.width,d.height);}else await c(m,t.data,t.width,t.height);if(s.diffOutput){let d=await g(t);await c(u,s.diffOutput,d.width,d.height);}return {pass:false,message:T(p,e.method,s,e,r,false),diffCount:s.diffCount,diffPercentage:s.diffPercentage,score:s.score,baselinePath:o,receivedPath:m,diffPath:u,snapshotStatus:"failed"}}async function v(t,e,n){let r=await P(t,e,n.method,n),i=F(n.method,r,n);return {pass:i,message:i?"Images match.":`Images differ: ${r.diffCount??r.score} ${r.diffCount!==void 0?"pixels":"score"}`,diffCount:r.diffCount,diffPercentage:r.diffPercentage,score:r.score}}export{v as compareImages,M as fileExists,R as formatReport,Y as getOrCreateSnapshot,h as isFilePath,B as isImageBuffer,I as loadPNG,g as normalizeImageInput,P as runComparison,c as savePNG,A as validateMethodSupportsInput};
1
+ import {existsSync,mkdirSync,unlinkSync}from'fs';import {dirname,isAbsolute,join}from'path';import {pngjsTransformer}from'@blazediff/pngjs-transformer';import {compare}from'@blazediff/bin';import {diff}from'@blazediff/core';import {gmsd}from'@blazediff/gmsd';import {ssim}from'@blazediff/ssim';import {hitchhikersSSIM}from'@blazediff/ssim/hitchhikers-ssim';import {msssim}from'@blazediff/ssim/msssim';import s from'picocolors';function c(t){return typeof t=="string"}function z(t){return typeof t=="object"&&t!==null&&"data"in t&&"width"in t&&"height"in t}async function I(t){if(!existsSync(t))throw new Error(`Image file not found: ${t}`);let e=await pngjsTransformer.read(t);return {data:new Uint8Array(e.data),width:e.width,height:e.height}}async function g(t,e,n,r){let o=dirname(t);existsSync(o)||mkdirSync(o,{recursive:true}),await pngjsTransformer.write({data:e,width:n,height:r},t);}async function l(t){return c(t)?I(t):{data:new Uint8Array(t.data),width:t.width,height:t.height}}function D(t){return existsSync(t)}function G(t){existsSync(t)||mkdirSync(t,{recursive:true});}async function T(t,e,n,r){if(!c(t))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");if(!c(e))throw new Error("Method 'bin' only supports file paths for baseline, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.");let o=await compare(t,e,n,{threshold:r.threshold,antialiasing:r.antialiasing});if(o.match)return {diffCount:0,diffPercentage:0};if(o.reason==="layout-diff")return {diffCount:Number.MAX_SAFE_INTEGER,diffPercentage:100};if(o.reason==="pixel-diff")return {diffCount:o.diffCount,diffPercentage:o.diffPercentage};if(o.reason==="file-not-exists")throw new Error(`Image file not found: ${o.file}`);return {diffCount:0,diffPercentage:0}}function E(t,e,n,r){let{width:o,height:i}=t,f=o*i;if(t.width!==e.width||t.height!==e.height)return {diffCount:f,diffPercentage:100};let m=n?new Uint8Array(f*4):void 0,p=diff(t.data,e.data,m,o,i,{threshold:r.threshold??.1,includeAA:r.includeAA??false});return {diffCount:p,diffPercentage:p/f*100,diffOutput:m}}function A(t,e,n,r){let{width:o,height:i}=t,f=o*i;if(t.width!==e.width||t.height!==e.height)return {score:1};let m=n?new Uint8Array(f*4):void 0;return {score:gmsd(t.data,e.data,m,o,i,{downsample:r.downsample}),diffOutput:m}}function R(t,e,n,r,o){let{width:i,height:f}=t,m=i*f;if(t.width!==e.width||t.height!==e.height)return {score:0};let p=r?new Uint8Array(m*4):void 0,a={windowSize:o.windowSize,k1:o.k1,k2:o.k2},h;switch(n){case "ssim":h=ssim(t.data,e.data,p,i,f,a);break;case "msssim":h=msssim(t.data,e.data,p,i,f,a);break;case "hitchhikers-ssim":h=hitchhikersSSIM(t.data,e.data,p,i,f,a);break;default:throw new Error(`Unknown SSIM method: ${n}`)}return {score:h,diffOutput:p}}function w(t){return t==="ssim"||t==="msssim"||t==="hitchhikers-ssim"}function U(t,e){if(t==="bin"&&!c(e))throw new Error("Method 'bin' only supports file paths, but received a buffer. Use method 'core', 'ssim', or 'gmsd' for buffer inputs.")}async function b(t,e,n,r,o){if(U(n,t),U(n,e),n==="bin"){let a=await T(t,e,o,r);return {diffCount:a.diffCount,diffPercentage:a.diffPercentage}}let i=await l(t),f=await l(e),m=o!==void 0;if(w(n)){let a=R(i,f,n,m,r);return {score:a.score,diffOutput:a.diffOutput}}if(n==="gmsd"){let a=A(i,f,m,r);return {score:a.score,diffOutput:a.diffOutput}}let p=E(i,f,m,r);return {diffCount:p.diffCount,diffPercentage:p.diffPercentage,diffOutput:p.diffOutput}}var y={success:s.isColorSupported?"\u2714":"\u221A",error:s.isColorSupported?"\u2716":"\xD7",info:s.isColorSupported?"\u2139":"i",arrow:s.isColorSupported?"\u2514\u2500":"'-"},Z=new Set(["ssim","msssim","hitchhikers-ssim"]);function P(t){return t.snapshotCreated?q(t.baselinePath):t.pass?J():K(t)}function q(t){return [`${s.green(y.success)} ${s.green("New snapshot created")}`,` ${s.dim(y.arrow)} ${s.dim(t)}`].join(`
2
+ `)}function J(){return `${s.green(y.success)} ${s.green("Image matches snapshot")}`}function K(t){let{method:e,baselinePath:n,receivedPath:r,diffPath:o,diffCount:i=0,diffPercentage:f,score:m=0,threshold:p,thresholdType:a,updateCommand:h}=t,u=[`${s.red(y.error)} ${s.red(s.bold("Image snapshot mismatch"))}`,""],d=12;if(u.push(` ${s.dim("Method".padEnd(d))}${e}`),u.push(` ${s.dim("Baseline".padEnd(d))}${s.dim(n)}`),u.push(` ${s.dim("Received".padEnd(d))}${s.dim(r)}`),u.push(` ${s.dim("Diff".padEnd(d))}${s.dim(o)}`),u.push(""),Z.has(e)){let O=((1-m)*100).toFixed(2);u.push(` ${s.dim("SSIM Score".padEnd(d))}${s.yellow(m.toFixed(4))} ${s.dim("(1.0 = identical)")}`),u.push(` ${s.dim("Difference".padEnd(d))}${s.yellow(`${O}%`)}`);}else if(e==="gmsd")u.push(` ${s.dim("GMSD Score".padEnd(d))}${s.yellow(m.toFixed(4))} ${s.dim("(0.0 = identical)")}`);else {let O=f?.toFixed(2)??"0.00";u.push(` ${s.dim("Difference".padEnd(d))}${s.yellow(i.toLocaleString())} pixels ${s.dim(`(${O}%)`)}`);}let C=a==="percent"?"%":"pixels";return u.push(` ${s.dim("Threshold".padEnd(d))}${p} ${C}`),u.push(""),u.push(` ${s.cyan(y.info)} ${s.cyan(`Run with ${h??"--update"} to update the snapshot`)}`),u.join(`
3
+ `)}function Y(t){return t.testName.replace(/[^a-zA-Z0-9-_\s]/g,"").replace(/\s+/g,"-").toLowerCase()||"snapshot"}function v(t,e){let n=dirname(t.testPath),r=e.snapshotsDir??"__snapshots__",o=isAbsolute(r)?r:join(n,r),i=e.snapshotIdentifier??Y(t);return {snapshotDir:o,baselinePath:join(o,`${i}.png`),receivedPath:join(o,`${i}.received.png`),diffPath:join(o,`${i}.diff.png`)}}function N(t,e,n){let r=n.failureThreshold??0,o=n.failureThresholdType??"pixel";if(w(t)){let i=e.score??0;return o==="percent"?(1-i)*100<=r:i>=1-r/100}if(t==="gmsd"){let i=e.score??0;return o==="percent"?i*100<=r:i<=r/100}return o==="percent"?(e.diffPercentage??0)<=r:(e.diffCount??0)<=r}async function tt(t,e,n){let r=v(n,e),{snapshotDir:o,baselinePath:i,receivedPath:f,diffPath:m}=r;G(o);let p=D(i);if(e.updateSnapshots||!p){if(c(t)){let C=await I(t);await g(i,C.data,C.width,C.height);}else await g(i,t.data,t.width,t.height);return existsSync(f)&&unlinkSync(f),existsSync(m)&&unlinkSync(m),{pass:true,message:P({pass:true,method:e.method,snapshotCreated:true,baselinePath:i,receivedPath:f,diffPath:m,diffCount:0,diffPercentage:0,score:0,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),baselinePath:i,snapshotStatus:p?"updated":"added"}}let a=await b(t,i,e.method,e,m),h=N(e.method,a,e);if(h)return existsSync(f)&&unlinkSync(f),existsSync(m)&&unlinkSync(m),{pass:true,message:P({pass:h,method:e.method,snapshotCreated:false,baselinePath:i,receivedPath:f,diffPath:m,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,baselinePath:i,snapshotStatus:"matched"};if(c(t)){let d=await I(t);await g(f,d.data,d.width,d.height);}else await g(f,t.data,t.width,t.height);if(a.diffOutput){let d=await l(t);await g(m,a.diffOutput,d.width,d.height);}return {pass:false,message:P({pass:h,method:e.method,snapshotCreated:false,baselinePath:i,receivedPath:f,diffPath:m,diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,threshold:e.failureThreshold??0,thresholdType:e.failureThresholdType??"pixel",updateCommand:e.updateCommand}),diffCount:a.diffCount,diffPercentage:a.diffPercentage,score:a.score,baselinePath:i,receivedPath:f,diffPath:m,snapshotStatus:"failed"}}async function et(t,e,n){let r=await b(t,e,n.method,n),o=N(n.method,r,n);return {pass:o,message:o?"Images match.":`Images differ: ${r.diffCount??r.score} ${r.diffCount!==void 0?"pixels":"score"}`,diffCount:r.diffCount,diffPercentage:r.diffPercentage,score:r.score}}export{et as compareImages,D as fileExists,P as formatReport,tt as getOrCreateSnapshot,c as isFilePath,z as isImageBuffer,I as loadPNG,l as normalizeImageInput,b as runComparison,g as savePNG,U as validateMethodSupportsInput};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blazediff/matcher",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Core matcher logic for visual regression testing with blazediff",
5
5
  "private": false,
6
6
  "publishConfig": {