Skip to content

Commit 713be37

Browse files
committed
feat(ssim): implementation of SSIM through comparison
1 parent 635a541 commit 713be37

10 files changed

+269
-45
lines changed

__tests__/__snapshots__/index.spec.js.snap

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ exports[`toMatchImageSnapshot passes diffImageToSnapshot everything it needs to
1616
Object {
1717
"allowSizeMismatch": false,
1818
"blur": 0,
19+
"comparisonMethod": "pixelmatch",
1920
"customDiffConfig": Object {},
2021
"diffDir": "path/to/__image_snapshots__/__diff_output__",
2122
"diffDirection": "horizontal",

__tests__/index.spec.js

+4
Original file line numberDiff line numberDiff line change
@@ -442,6 +442,7 @@ describe('toMatchImageSnapshot', () => {
442442
const customDiffConfig = { perceptual: true };
443443
const customSnapshotIdentifier = ({ defaultIdentifier }) =>
444444
`custom-${defaultIdentifier}`;
445+
const comparisonMethod = 'ssim';
445446
const toMatchImageSnapshot = configureToMatchImageSnapshot({
446447
customDiffConfig,
447448
customSnapshotIdentifier,
@@ -453,6 +454,7 @@ describe('toMatchImageSnapshot', () => {
453454
failureThresholdType: 'percent',
454455
updatePassedSnapshot: true,
455456
blur: 1,
457+
comparisonMethod,
456458
});
457459
expect.extend({ toMatchImageSnapshot });
458460
const matcherAtTest = toMatchImageSnapshot.bind(mockTestContext);
@@ -473,6 +475,7 @@ describe('toMatchImageSnapshot', () => {
473475
updatePassedSnapshot: true,
474476
failureThreshold: 1,
475477
failureThresholdType: 'percent',
478+
comparisonMethod,
476479
});
477480
expect(Chalk).toHaveBeenCalledWith({
478481
enabled: false,
@@ -530,6 +533,7 @@ describe('toMatchImageSnapshot', () => {
530533
updatePassedSnapshot: false,
531534
failureThreshold: 0,
532535
failureThresholdType: 'pixel',
536+
comparisonMethod: 'pixelmatch',
533537
});
534538
expect(Chalk).toHaveBeenCalledWith({
535539
enabled: false,

__tests__/integration.spec.js

+205-40
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,20 @@ describe('toMatchImageSnapshot', () => {
8484

8585
expect(diffExists(customSnapshotIdentifier)).toBe(false);
8686
});
87+
88+
it('does not write a result image for passing tests (ssim)', () => {
89+
const customSnapshotIdentifier = 'integration-6';
90+
91+
// First we need to write a new snapshot image
92+
expect(
93+
() => expect(imageData).toMatchImageSnapshot({
94+
customSnapshotIdentifier,
95+
comparisonMethod: 'ssim',
96+
})
97+
).not.toThrowError();
98+
99+
expect(diffExists(customSnapshotIdentifier)).toBe(false);
100+
});
87101
});
88102

89103
describe('updates', () => {
@@ -130,6 +144,23 @@ describe('toMatchImageSnapshot', () => {
130144
expect(fs.readFileSync(updateImageSnapshotPath)).not.toEqual(updateImageData);
131145
});
132146

147+
it('writes a result image for passing test in update mode with updatePassedSnapshots: true (ssim)', () => {
148+
const updateModeMatcher = toMatchImageSnapshot.bind({
149+
snapshotState: new SnapshotState(__filename, {
150+
updateSnapshot: 'all',
151+
}),
152+
testPath: __filename,
153+
});
154+
updateModeMatcher(updateImageData, {
155+
customSnapshotIdentifier,
156+
updatePassedSnapshots: true,
157+
failureThreshold: 2,
158+
failureThresholdType: 'pixel',
159+
comparisonMode: 'ssim',
160+
});
161+
expect(fs.readFileSync(updateImageSnapshotPath)).not.toEqual(updateImageData);
162+
});
163+
133164
it('writes a result image for failing test in update mode by default', () => {
134165
const updateModeMatcher = toMatchImageSnapshot.bind({
135166
snapshotState: new SnapshotState(__filename, {
@@ -160,6 +191,23 @@ describe('toMatchImageSnapshot', () => {
160191
});
161192
expect(fs.readFileSync(updateImageSnapshotPath)).toEqual(updateImageData);
162193
});
194+
195+
it('writes a result image for failing test in update mode with updatePassedSnapshots: false (ssim)', () => {
196+
const updateModeMatcher = toMatchImageSnapshot.bind({
197+
snapshotState: new SnapshotState(__filename, {
198+
updateSnapshot: 'all',
199+
}),
200+
testPath: __filename,
201+
});
202+
updateModeMatcher(updateImageData, {
203+
customSnapshotIdentifier,
204+
updatePassedSnapshots: false,
205+
failureThreshold: 0,
206+
failureThresholdType: 'pixel',
207+
comparisonMode: 'ssim',
208+
});
209+
expect(fs.readFileSync(updateImageSnapshotPath)).toEqual(updateImageData);
210+
});
163211
});
164212

165213
describe('failures', () => {
@@ -195,7 +243,8 @@ describe('toMatchImageSnapshot', () => {
195243
() => expect(oversizeImageData).toMatchImageSnapshot({ customSnapshotIdentifier })
196244
).toThrowError(/Expected image to be the same size as the snapshot \(100x100\), but was different \(153x145\)/);
197245

198-
expect(diffExists(customSnapshotIdentifier)).toBe(true);
246+
expect(diffExists(customSnapshotIdentifier))
247+
.toBe(true);
199248
});
200249

201250
it('fails with images without diff pixels after being resized', () => {
@@ -217,81 +266,134 @@ describe('toMatchImageSnapshot', () => {
217266
const pathToResultImage = path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`);
218267
// First we need to write a new snapshot image
219268
expect(
220-
() => expect(imageData).toMatchImageSnapshot({ customSnapshotIdentifier })
269+
() => expect(imageData)
270+
.toMatchImageSnapshot({ customSnapshotIdentifier })
221271
).not.toThrowError();
222272

223273
// then test against a different image
224274
expect(
225-
() => expect(failImageData).toMatchImageSnapshot({ customSnapshotIdentifier })
275+
() => expect(failImageData)
276+
.toMatchImageSnapshot({ customSnapshotIdentifier })
226277
).toThrow();
227278

228-
expect(fs.existsSync(pathToResultImage)).toBe(true);
279+
expect(fs.existsSync(pathToResultImage))
280+
.toBe(true);
229281

230282
// just because file was written does not mean it is a png image
231283
expect(sizeOf(pathToResultImage)).toHaveProperty('type', 'png');
232284
});
233285

234-
it('writes a result image for failing tests with horizontal layout', () => {
286+
it('writes a result image for failing tests (ssim)', () => {
287+
const largeImageData = fs.readFileSync(fromStubs('LargeTestImage.png'));
288+
const largeFailureImageData = fs.readFileSync(fromStubs('LargeTestImageFailure.png'));
289+
const largeImageFailureDiffData =
290+
fs.readFileSync(fromStubs('LargeTestImage-LargeTestImageFailure-ssim-diff.png'));
235291
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
236292
const pathToResultImage = path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`);
237293
// First we need to write a new snapshot image
238294
expect(
239-
() => expect(imageData).toMatchImageSnapshot({
240-
customSnapshotIdentifier,
241-
diffDirection: 'horizontal',
242-
})
243-
).not.toThrowError();
295+
() => expect(largeImageData)
296+
.toMatchImageSnapshot({
297+
customSnapshotIdentifier, comparisonMethod: 'ssim',
298+
})
299+
)
300+
.not
301+
.toThrowError();
244302

245303
// then test against a different image
246304
expect(
247-
() => expect(failImageData).toMatchImageSnapshot({
248-
customSnapshotIdentifier,
249-
diffDirection: 'horizontal',
250-
})
251-
).toThrow();
305+
() => expect(largeFailureImageData)
306+
.toMatchImageSnapshot({
307+
customSnapshotIdentifier, comparisonMethod: 'ssim',
308+
})
309+
)
310+
.toThrow();
252311

253-
expect(fs.existsSync(pathToResultImage)).toBe(true);
312+
expect(fs.existsSync(pathToResultImage))
313+
.toBe(true);
254314

255-
expect(sizeOf(pathToResultImage)).toMatchObject({
256-
width: 300,
257-
height: 100,
258-
type: 'png',
259-
});
315+
expect(fs.readFileSync(pathToResultImage)).toEqual(largeImageFailureDiffData);
316+
// just because file was written does not mean it is a png image
317+
expect(sizeOf(pathToResultImage))
318+
.toHaveProperty('type', 'png');
260319
});
261320

262-
it('writes a result image for failing tests with vertical layout', () => {
321+
it('writes a result image for failing tests with horizontal layout', () => {
263322
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
264323
const pathToResultImage = path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`);
265324
// First we need to write a new snapshot image
266325
expect(
267-
() => expect(imageData).toMatchImageSnapshot({
268-
customSnapshotIdentifier,
269-
diffDirection: 'vertical',
270-
})
271-
).not.toThrowError();
326+
() => expect(imageData)
327+
.toMatchImageSnapshot({
328+
customSnapshotIdentifier,
329+
diffDirection: 'horizontal',
330+
})
331+
)
332+
.not
333+
.toThrowError();
272334

273335
// then test against a different image
274336
expect(
275-
() => expect(failImageData).toMatchImageSnapshot({
276-
customSnapshotIdentifier,
277-
diffDirection: 'vertical',
278-
})
279-
).toThrow();
337+
() => expect(failImageData)
338+
.toMatchImageSnapshot({
339+
customSnapshotIdentifier,
340+
diffDirection: 'horizontal',
341+
})
342+
)
343+
.toThrow();
344+
345+
expect(fs.existsSync(pathToResultImage))
346+
.toBe(true);
347+
348+
expect(sizeOf(pathToResultImage))
349+
.toMatchObject({
350+
width: 300,
351+
height: 100,
352+
type: 'png',
353+
});
354+
});
280355

281-
expect(fs.existsSync(pathToResultImage)).toBe(true);
356+
it('writes a result image for failing tests with vertical layout', () => {
357+
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
358+
const pathToResultImage = path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`);
359+
// First we need to write a new snapshot image
360+
expect(
361+
() => expect(imageData)
362+
.toMatchImageSnapshot({
363+
customSnapshotIdentifier,
364+
diffDirection: 'vertical',
365+
})
366+
)
367+
.not
368+
.toThrowError();
282369

283-
expect(sizeOf(pathToResultImage)).toMatchObject({
284-
width: 100,
285-
height: 300,
286-
type: 'png',
287-
});
370+
// then test against a different image
371+
expect(
372+
() => expect(failImageData)
373+
.toMatchImageSnapshot({
374+
customSnapshotIdentifier,
375+
diffDirection: 'vertical',
376+
})
377+
)
378+
.toThrow();
379+
380+
expect(fs.existsSync(pathToResultImage))
381+
.toBe(true);
382+
383+
expect(sizeOf(pathToResultImage))
384+
.toMatchObject({
385+
width: 100,
386+
height: 300,
387+
type: 'png',
388+
});
288389
});
289390

290391
it('removes result image from previous test runs for the same snapshot', () => {
291392
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
292393
// First we need to write a new snapshot image
293394
expect(
294-
() => expect(imageData).toMatchImageSnapshot({ customSnapshotIdentifier })
395+
() => expect(imageData)
396+
.toMatchImageSnapshot({ customSnapshotIdentifier })
295397
).not.toThrowError();
296398

297399
// then test against a different image (to generate a results image)
@@ -304,7 +406,8 @@ describe('toMatchImageSnapshot', () => {
304406
() => expect(imageData).toMatchImageSnapshot({ customSnapshotIdentifier })
305407
).not.toThrowError();
306408

307-
expect(diffExists(customSnapshotIdentifier)).toBe(false);
409+
expect(diffExists(customSnapshotIdentifier))
410+
.toBe(false);
308411
});
309412

310413
it('handles diffs for large images', () => {
@@ -321,5 +424,67 @@ describe('toMatchImageSnapshot', () => {
321424
() => expect(largeFailureImageData).toMatchImageSnapshot({ customSnapshotIdentifier })
322425
).toThrow(/Expected image to match or be a close match/);
323426
});
427+
428+
describe('Desktop Images Test', () => {
429+
it('not to throw at 6pct with pixelmatch with', () => {
430+
const largeImageData = fs.readFileSync(fromStubs('Desktop 1_082.png'));
431+
const largeFailureImageData = fs.readFileSync(fromStubs('Desktop 1_083.png'));
432+
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
433+
// First we need to write a new snapshot image
434+
expect(
435+
() => expect(largeImageData)
436+
.toMatchImageSnapshot({
437+
failureThreshold: 0.06,
438+
failureThresholdType: 'percent',
439+
customSnapshotIdentifier,
440+
})
441+
)
442+
.not
443+
.toThrowError();
444+
445+
// then test against a different image
446+
expect(
447+
() => expect(largeFailureImageData)
448+
.toMatchImageSnapshot({
449+
failureThreshold: 0.06,
450+
failureThresholdType: 'percent',
451+
customSnapshotIdentifier,
452+
})
453+
)
454+
.not
455+
.toThrowError();
456+
});
457+
it('to throw at 1pct with SSIM', () => {
458+
const largeImageData = fs.readFileSync(fromStubs('Desktop 1_082.png'));
459+
const largeFailureImageData = fs.readFileSync(fromStubs('Desktop 1_083.png'));
460+
const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired();
461+
// First we need to write a new snapshot image
462+
expect(
463+
() => expect(largeImageData)
464+
.toMatchImageSnapshot({
465+
comparisonMethod: 'ssim',
466+
failureThreshold: 0.01,
467+
failureThresholdType: 'percent',
468+
customSnapshotIdentifier,
469+
})
470+
)
471+
.not
472+
.toThrowError();
473+
474+
// then test against a different image
475+
expect(
476+
() => expect(largeFailureImageData)
477+
.toMatchImageSnapshot({
478+
comparisonMethod: 'ssim',
479+
failureThreshold: 0.01,
480+
failureThresholdType: 'percent',
481+
customSnapshotIdentifier,
482+
// required for coverage
483+
runInProcess: true,
484+
})
485+
)
486+
.toThrow(/Expected image to match or be a close match/);
487+
});
488+
});
324489
});
325490
});

__tests__/stubs/Desktop 1_082.png

244 KB
Loading

__tests__/stubs/Desktop 1_083.png

235 KB
Loading
Loading

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@
6767
"mkdirp": "^0.5.1",
6868
"pixelmatch": "^5.1.0",
6969
"pngjs": "^3.4.0",
70-
"rimraf": "^2.6.2"
70+
"rimraf": "^2.6.2",
71+
"ssim.js": "^3.1.1"
7172
},
7273
"peerDependencies": {
7374
"jest": ">=20 <=26"

0 commit comments

Comments
 (0)