diff --git a/__tests__/diff-snapshot.spec.js b/__tests__/diff-snapshot.spec.js index 864df01..a9c78b7 100644 --- a/__tests__/diff-snapshot.spec.js +++ b/__tests__/diff-snapshot.spec.js @@ -15,6 +15,7 @@ /* eslint-disable global-require */ const fs = require('fs'); const path = require('path'); +const mockSpawn = require('mock-spawn')(); describe('diff-snapshot', () => { beforeEach(() => { @@ -47,6 +48,9 @@ describe('diff-snapshot', () => { writeFileSync: mockWriteFileSync, readFileSync: jest.fn(), }); + + mockSpawn.setDefault(mockSpawn.simple(0)); + jest.mock('child_process', () => ({ spawnSync: mockSpawn })); jest.mock('fs', () => mockFs); jest.mock('mkdirp', () => ({ sync: mockMkdirpSync })); const { diffImageToSnapshot } = require('../src/diff-snapshot'); @@ -127,7 +131,7 @@ describe('diff-snapshot', () => { // Check that pixelmatch was called expect(mockPixelMatch).toHaveBeenCalledTimes(1); // Check that that it did not attempt to write a diff - expect(mockWriteFileSync).not.toHaveBeenCalled(); + expect(mockSpawn.calls).toEqual([]); }); it('should write a diff image if the test fails', () => { @@ -156,11 +160,10 @@ describe('diff-snapshot', () => { 100, { threshold: 0.01 } ); - expect(mockWriteFileSync).toHaveBeenCalledTimes(1); - expect(mockWriteFileSync).toHaveBeenCalledWith( - path.join(mockSnapshotsDir, '__diff_output__', 'id1-diff.png'), - expect.any(Buffer) - ); + + expect(mockSpawn.calls[0].args[0]).toBe(path.resolve('./src/write-result-diff-image.js')); + expect(mockSpawn.calls[0].command).toBe('node'); + expect(mockSpawn.calls[0].opts.input).toEqual(expect.any(Buffer)); }); it('should pass <= failureThreshold pixel', () => { diff --git a/__tests__/integration.spec.js b/__tests__/integration.spec.js index b646214..d975073 100644 --- a/__tests__/integration.spec.js +++ b/__tests__/integration.spec.js @@ -16,6 +16,7 @@ const fs = require('fs'); const path = require('path'); const rimraf = require('rimraf'); const uniqueId = require('lodash/uniqueId'); +const isPng = require('is-png'); describe('toMatchImageSnapshot', () => { const imagePath = path.resolve(__dirname, './stubs', 'TestImage.png'); @@ -122,7 +123,7 @@ describe('toMatchImageSnapshot', () => { it('writes a result image for failing tests', () => { const customSnapshotIdentifier = getIdentifierIndicatingCleanupIsRequired(); - + const pathToResultImage = path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`); // First we need to write a new snapshot image expect( () => expect(imageData).toMatchImageSnapshot({ customSnapshotIdentifier }) @@ -133,9 +134,11 @@ describe('toMatchImageSnapshot', () => { () => expect(failImageData).toMatchImageSnapshot({ customSnapshotIdentifier }) ).toThrow(); - expect( - fs.existsSync(path.join(__dirname, diffOutputDir(), `${customSnapshotIdentifier}-diff.png`)) - ).toBe(true); + expect(fs.existsSync(pathToResultImage)).toBe(true); + + const imageBuffer = fs.readFileSync(pathToResultImage); + // just because file was written does not mean it is a png image + expect(isPng(imageBuffer)).toBe(true); }); }); }); diff --git a/package.json b/package.json index 762c5ef..a6be1e3 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,12 @@ ], "jest": { "preset": "amex-jest-preset", + "collectCoverageFrom": [ + "src/*.js", + "!src/write-result-diff-image.js", + "!**/node_modules/**", + "!test-results/**" + ], "testMatch": ["/__tests__/**/*.js"], "coveragePathIgnorePatterns": ["/node_modules/", "/examples"] }, @@ -34,12 +40,15 @@ "amex-jest-preset": "^4.0.2", "eslint": "^4.15.0", "eslint-config-amex": "^7.0.0", + "is-png": "^1.1.0", "jest": "^22.0.0", "lodash": "^4.17.4", + "mock-spawn": "^0.2.6", "rimraf": "^2.6.2" }, "dependencies": { "chalk": "^1.1.3", + "get-stdin": "^5.0.1", "lodash": "^4.17.4", "mkdirp": "^0.5.1", "pixelmatch": "^4.0.2", diff --git a/src/diff-snapshot.js b/src/diff-snapshot.js index 8068882..c58f90a 100644 --- a/src/diff-snapshot.js +++ b/src/diff-snapshot.js @@ -14,6 +14,7 @@ const fs = require('fs'); const path = require('path'); +const childProcess = require('child_process'); const pixelmatch = require('pixelmatch'); const mkdirp = require('mkdirp'); const { PNG } = require('pngjs'); @@ -75,8 +76,27 @@ function diffImageToSnapshot(options) { if (!pass) { mkdirp.sync(outputDir); - const buffer = PNG.sync.write(diffImage); - fs.writeFileSync(diffOutputPath, buffer); + const compositeResultImage = new PNG({ + width: imageWidth * 3, + height: imageHeight, + }); + // copy baseline, diff, and received images into composite result image + PNG.bitblt( + baselineImage, compositeResultImage, 0, 0, imageWidth, imageHeight, 0, 0 + ); + PNG.bitblt( + diffImage, compositeResultImage, 0, 0, imageWidth, imageHeight, imageWidth, 0 + ); + PNG.bitblt( + receivedImage, compositeResultImage, 0, 0, imageWidth, imageHeight, imageWidth * 2, 0 + ); + + const input = { imagePath: diffOutputPath, image: compositeResultImage }; + + // writing diff in separate process to avoid perf issues associated with Math in Jest VM (https://github.com/facebook/jest/issues/5163) + const writeDiffProcess = childProcess.spawnSync('node', [`${__dirname}/write-result-diff-image.js`], { input: Buffer.from(JSON.stringify(input)) }); + // in case of error print to console + if (writeDiffProcess.stderr.toString()) { console.log(writeDiffProcess.stderr.toString()); } // eslint-disable-line no-console, max-len } result = { diff --git a/src/write-result-diff-image.js b/src/write-result-diff-image.js new file mode 100644 index 0000000..be05c77 --- /dev/null +++ b/src/write-result-diff-image.js @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +const { PNG } = require('pngjs'); +const fs = require('fs'); +const getStdin = require('get-stdin'); + +getStdin.buffer().then((buffer) => { + try { + const input = JSON.parse(buffer); + const { imagePath, image } = input; + + image.data = Buffer.from(image.data); + + const pngBuffer = PNG.sync.write(image); + fs.writeFileSync(imagePath, pngBuffer); + process.exit(0); + } catch (error) { + console.error(error); // eslint-disable-line no-console + process.exit(1); + } +});