Skip to content

Commit 57741a2

Browse files
Ayc0code-forger
andauthored
feat: Add runtimeHooksPath options (#337)
* feat: add runtimeHooksPath options with onBeforeWriteToDiscPath in it * chore: add documentation for runtimeHooksPath * chore: add license to files * chore: document type for onBeforeWriteToDisc * chore: apply JAdshead's suggestions * chore: use path compat with windows --------- Co-authored-by: Michael Rochester <[email protected]>
1 parent 79a16de commit 57741a2

File tree

8 files changed

+170
-5
lines changed

8 files changed

+170
-5
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ See [the examples](./examples/README.md) for more detailed usage or read about a
124124
* `dumpDiffToConsole`: (default `false`) Will output base64 string of a diff image to console in case of failed tests (in addition to creating a diff image). This string can be copy-pasted to a browser address string to preview the diff for a failed test.
125125
* `dumpInlineDiffToConsole`: (default `false`) Will output the image to the terminal using iTerm's [Inline Images Protocol](https://iterm2.com/documentation-images.html). If the term is not compatible, it does the same thing as `dumpDiffToConsole`.
126126
* `allowSizeMismatch`: (default `false`) If set to true, the build will not fail when the screenshots to compare have different sizes.
127+
* `runtimeHooksPath`: (default `undefined`) This needs to be set to a existing file, like `require.resolve('./runtimeHooksPath.cjs')`. This file can expose a few hooks:
128+
* `onBeforeWriteToDisc`: before saving any image to the disc, this function will be called (can be used to write EXIF data to images for instance)
129+
`onBeforeWriteToDisc: (arguments: { buffer: Buffer; destination: string; testPath: string; currentTestName: string }) => Buffer`
127130

128131
```javascript
129132
it('should demonstrate this matcher`s usage with a custom pixelmatch config', () => {

__tests__/__snapshots__/index.spec.js.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ exports[`toMatchImageSnapshot passes diffImageToSnapshot everything it needs to
4242
"allowSizeMismatch": false,
4343
"blur": 0,
4444
"comparisonMethod": "pixelmatch",
45+
"currentTestName": "test",
4546
"customDiffConfig": {},
4647
"diffDir": undefined,
4748
"diffDirection": "horizontal",
@@ -51,9 +52,11 @@ exports[`toMatchImageSnapshot passes diffImageToSnapshot everything it needs to
5152
"receivedDir": undefined,
5253
"receivedImageBuffer": "pretendthisisanimagebuffer",
5354
"receivedPostfix": undefined,
55+
"runtimeHooksPath": undefined,
5456
"snapshotIdentifier": "test-spec-js-test-1-snap",
5557
"snapshotsDir": "path/to/__image_snapshots__",
5658
"storeReceivedOnFailure": false,
59+
"testPath": "path/to/test.spec.js",
5760
"updatePassedSnapshot": false,
5861
"updateSnapshot": false,
5962
}

__tests__/diff-snapshot.spec.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -879,6 +879,73 @@ describe('diff-snapshot', () => {
879879

880880
expect(mockMkdirSync).toHaveBeenCalledWith(path.join(mockSnapshotsDir, '__diff_output__'), { recursive: true });
881881
});
882+
883+
it('should pass data to a file mentioned by runtimeHooksPath when writing files', () => {
884+
jest.doMock(require.resolve('./stubs/runtimeHooksPath.js'), () => ({
885+
onBeforeWriteToDisc: jest.fn(({ buffer }) => buffer),
886+
}));
887+
const { onBeforeWriteToDisc } = require('./stubs/runtimeHooksPath');
888+
889+
const diffImageToSnapshot = setupTest({ snapshotExists: false, pixelmatchResult: 0 });
890+
const result = diffImageToSnapshot({
891+
receivedImageBuffer: mockImageBuffer,
892+
snapshotIdentifier: mockSnapshotIdentifier,
893+
snapshotsDir: mockSnapshotsDir,
894+
receivedDir: mockReceivedDir,
895+
diffDir: mockDiffDir,
896+
failureThreshold: 0,
897+
failureThresholdType: 'pixel',
898+
runtimeHooksPath: require.resolve('./stubs/runtimeHooksPath.js'),
899+
testPath: 'test.spec.js',
900+
currentTestName: 'test a',
901+
});
902+
903+
expect(result).toMatchObject({
904+
added: true,
905+
});
906+
907+
expect(onBeforeWriteToDisc).toHaveBeenCalledTimes(1);
908+
expect(onBeforeWriteToDisc).toHaveBeenCalledWith({
909+
buffer: mockImageBuffer,
910+
destination: path.normalize('/path/to/snapshots/id1.png'),
911+
testPath: 'test.spec.js',
912+
currentTestName: 'test a',
913+
});
914+
});
915+
916+
it('should work even when runtimeHooksPath is invalid', () => {
917+
const diffImageToSnapshot = setupTest({ snapshotExists: false, pixelmatchResult: 0 });
918+
expect(() => diffImageToSnapshot({
919+
receivedImageBuffer: mockImageBuffer,
920+
snapshotIdentifier: mockSnapshotIdentifier,
921+
snapshotsDir: mockSnapshotsDir,
922+
receivedDir: mockReceivedDir,
923+
diffDir: mockDiffDir,
924+
failureThreshold: 0,
925+
failureThresholdType: 'pixel',
926+
runtimeHooksPath: './non-existing-file.js',
927+
})).toThrowError(
928+
new Error("Couldn't import ./non-existing-file.js: Cannot find module './non-existing-file.js' from 'src/diff-snapshot.js'")
929+
);
930+
931+
jest.doMock(require.resolve('./stubs/runtimeHooksPath.js'), () => ({
932+
onBeforeWriteToDisc: () => {
933+
throw new Error('wrong');
934+
},
935+
}));
936+
expect(() => diffImageToSnapshot({
937+
receivedImageBuffer: mockImageBuffer,
938+
snapshotIdentifier: mockSnapshotIdentifier,
939+
snapshotsDir: mockSnapshotsDir,
940+
receivedDir: mockReceivedDir,
941+
diffDir: mockDiffDir,
942+
failureThreshold: 0,
943+
failureThresholdType: 'pixel',
944+
runtimeHooksPath: require.resolve('./stubs/runtimeHooksPath.js'),
945+
})).toThrowError(
946+
new Error("Couldn't execute onBeforeWriteToDisc: wrong")
947+
);
948+
});
882949
});
883950
});
884951

__tests__/index.spec.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,15 +441,20 @@ describe('toMatchImageSnapshot', () => {
441441
allowSizeMismatch: false,
442442
blur: 0,
443443
comparisonMethod: 'pixelmatch',
444+
currentTestName: 'test1',
444445
customDiffConfig: {},
446+
diffDir: undefined,
445447
diffDirection: 'horizontal',
446448
onlyDiff: false,
447449
failureThreshold: 0,
448450
failureThresholdType: 'pixel',
451+
receivedDir: undefined,
449452
receivedImageBuffer: undefined,
453+
runtimeHooksPath: undefined,
450454
snapshotIdentifier: 'test-spec-js-test-1-1-snap',
451455
snapshotsDir: process.platform === 'win32' ? 'path\\to\\__image_snapshots__' : 'path/to/__image_snapshots__',
452456
storeReceivedOnFailure: false,
457+
testPath: path.normalize('path/to/test.spec.js'),
453458
updatePassedSnapshot: false,
454459
updateSnapshot: false,
455460
});
@@ -508,13 +513,17 @@ describe('toMatchImageSnapshot', () => {
508513
customDiffConfig: {
509514
perceptual: true,
510515
},
516+
currentTestName: 'test1',
511517
snapshotIdentifier: 'custom-test-spec-js-test-1-1',
512518
snapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'),
513519
receivedDir: path.join('path', 'to', 'my-custom-received-dir'),
520+
receivedImageBuffer: undefined,
521+
runtimeHooksPath: undefined,
514522
storeReceivedOnFailure: true,
515523
diffDir: path.join('path', 'to', 'my-custom-diff-dir'),
516524
diffDirection: 'vertical',
517525
onlyDiff: false,
526+
testPath: path.join('path', 'to', 'test.spec.js'),
518527
updateSnapshot: false,
519528
updatePassedSnapshot: true,
520529
failureThreshold: 1,
@@ -571,16 +580,20 @@ describe('toMatchImageSnapshot', () => {
571580
},
572581
snapshotIdentifier: 'test-spec-js-test-1-1-snap',
573582
snapshotsDir: path.join('path', 'to', 'my-custom-snapshots-dir'),
583+
receivedImageBuffer: undefined,
584+
runtimeHooksPath: undefined,
574585
receivedDir: path.join('path', 'to', 'my-custom-received-dir'),
575586
diffDir: path.join('path', 'to', 'my-custom-diff-dir'),
576587
diffDirection: 'horizontal',
577588
onlyDiff: false,
578589
storeReceivedOnFailure: true,
590+
testPath: path.join('path', 'to', 'test.spec.js'),
579591
updateSnapshot: false,
580592
updatePassedSnapshot: false,
581593
failureThreshold: 0,
582594
failureThresholdType: 'pixel',
583595
comparisonMethod: 'pixelmatch',
596+
currentTestName: 'test1',
584597
});
585598
expect(Chalk).toHaveBeenCalledWith({
586599
level: 0, // noColors
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
* Copyright (c) 2017 American Express Travel Related Services Company, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
15+
exports.onBeforeWriteToDisc = buffer => buffer;

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"!test-results/**"
3131
],
3232
"testMatch": [
33-
"<rootDir>/__tests__/**/*.js"
33+
"<rootDir>/__tests__/**/*.spec.js"
3434
],
3535
"coveragePathIgnorePatterns": [
3636
"/node_modules/",

src/diff-snapshot.js

Lines changed: 63 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,38 @@ function composeDiff(options) {
200200
return composer;
201201
}
202202

203+
function writeFileWithHooks({
204+
pathToFile,
205+
content,
206+
runtimeHooksPath,
207+
testPath,
208+
currentTestName,
209+
}) {
210+
let finalContent = content;
211+
if (runtimeHooksPath) {
212+
let runtimeHooks;
213+
try {
214+
// As `diffImageToSnapshot` can be called in a worker, and as we cannot pass a function
215+
// to a worker, we need to use an external file path that can be imported
216+
// eslint-disable-next-line import/no-dynamic-require, global-require
217+
runtimeHooks = require(runtimeHooksPath);
218+
} catch (e) {
219+
throw new Error(`Couldn't import ${runtimeHooksPath}: ${e.message}`);
220+
}
221+
try {
222+
finalContent = runtimeHooks.onBeforeWriteToDisc({
223+
buffer: content,
224+
destination: pathToFile,
225+
testPath,
226+
currentTestName,
227+
});
228+
} catch (e) {
229+
throw new Error(`Couldn't execute onBeforeWriteToDisc: ${e.message}`);
230+
}
231+
}
232+
fs.writeFileSync(pathToFile, finalContent);
233+
}
234+
203235
function diffImageToSnapshot(options) {
204236
const {
205237
receivedImageBuffer,
@@ -219,14 +251,23 @@ function diffImageToSnapshot(options) {
219251
blur,
220252
allowSizeMismatch = false,
221253
comparisonMethod = 'pixelmatch',
254+
testPath,
255+
currentTestName,
256+
runtimeHooksPath,
222257
} = options;
223258

224259
const comparisonFn = comparisonMethod === 'ssim' ? ssimMatch : pixelmatch;
225260
let result = {};
226261
const baselineSnapshotPath = path.join(snapshotsDir, `${snapshotIdentifier}.png`);
227262
if (!fs.existsSync(baselineSnapshotPath)) {
228263
fs.mkdirSync(path.dirname(baselineSnapshotPath), { recursive: true });
229-
fs.writeFileSync(baselineSnapshotPath, receivedImageBuffer);
264+
writeFileWithHooks({
265+
pathToFile: baselineSnapshotPath,
266+
content: receivedImageBuffer,
267+
runtimeHooksPath,
268+
testPath,
269+
currentTestName,
270+
});
230271
result = { added: true };
231272
} else {
232273
const receivedSnapshotPath = path.join(receivedDir, `${snapshotIdentifier}${receivedPostfix}.png`);
@@ -294,7 +335,13 @@ function diffImageToSnapshot(options) {
294335
if (isFailure({ pass, updateSnapshot })) {
295336
if (storeReceivedOnFailure) {
296337
fs.mkdirSync(path.dirname(receivedSnapshotPath), { recursive: true });
297-
fs.writeFileSync(receivedSnapshotPath, receivedImageBuffer);
338+
writeFileWithHooks({
339+
pathToFile: receivedSnapshotPath,
340+
content: receivedImageBuffer,
341+
runtimeHooksPath,
342+
testPath,
343+
currentTestName,
344+
});
298345
result = { receivedSnapshotPath };
299346
}
300347

@@ -320,7 +367,13 @@ function diffImageToSnapshot(options) {
320367
// Set filter type to Paeth to avoid expensive auto scanline filter detection
321368
// For more information see https://www.w3.org/TR/PNG-Filters.html
322369
const pngBuffer = PNG.sync.write(compositeResultImage, { filterType: 4 });
323-
fs.writeFileSync(diffOutputPath, pngBuffer);
370+
writeFileWithHooks({
371+
pathToFile: diffOutputPath,
372+
content: pngBuffer,
373+
runtimeHooksPath,
374+
testPath,
375+
currentTestName,
376+
});
324377

325378
result = {
326379
...result,
@@ -334,7 +387,13 @@ function diffImageToSnapshot(options) {
334387
};
335388
} else if (shouldUpdate({ pass, updateSnapshot, updatePassedSnapshot })) {
336389
fs.mkdirSync(path.dirname(baselineSnapshotPath), { recursive: true });
337-
fs.writeFileSync(baselineSnapshotPath, receivedImageBuffer);
390+
writeFileWithHooks({
391+
pathToFile: baselineSnapshotPath,
392+
content: receivedImageBuffer,
393+
runtimeHooksPath,
394+
testPath,
395+
currentTestName,
396+
});
338397
result = { updated: true };
339398
} else {
340399
result = {

src/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ function configureToMatchImageSnapshot({
140140
customReceivedPostfix: commonCustomReceivedPostfix,
141141
customDiffDir: commonCustomDiffDir,
142142
onlyDiff: commonOnlyDiff = false,
143+
runtimeHooksPath: commonRuntimeHooksPath = undefined,
143144
diffDirection: commonDiffDirection = 'horizontal',
144145
noColors: commonNoColors,
145146
failureThreshold: commonFailureThreshold = 0,
@@ -160,6 +161,7 @@ function configureToMatchImageSnapshot({
160161
customReceivedPostfix = commonCustomReceivedPostfix,
161162
customDiffDir = commonCustomDiffDir,
162163
onlyDiff = commonOnlyDiff,
164+
runtimeHooksPath = commonRuntimeHooksPath,
163165
diffDirection = commonDiffDirection,
164166
customDiffConfig = {},
165167
noColors = commonNoColors,
@@ -224,6 +226,8 @@ function configureToMatchImageSnapshot({
224226
receivedPostfix,
225227
diffDir,
226228
diffDirection,
229+
testPath,
230+
currentTestName,
227231
onlyDiff,
228232
snapshotIdentifier,
229233
updateSnapshot: snapshotState._updateSnapshot === 'all',
@@ -234,6 +238,7 @@ function configureToMatchImageSnapshot({
234238
blur,
235239
allowSizeMismatch,
236240
comparisonMethod,
241+
runtimeHooksPath,
237242
});
238243

239244
return checkResult({

0 commit comments

Comments
 (0)