UNPKG

6.63 kBJavaScriptView Raw
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/* eslint-disable no-underscore-dangle */
15const kebabCase = require('lodash/kebabCase');
16const merge = require('lodash/merge');
17const path = require('path');
18const Chalk = require('chalk').constructor;
19const { runDiffImageToSnapshot } = require('./diff-snapshot');
20const fs = require('fs');
21
22const timesCalled = new Map();
23
24const SNAPSHOTS_DIR = '__image_snapshots__';
25
26function updateSnapshotState(originalSnapshotState, partialSnapshotState) {
27 if (global.UNSTABLE_SKIP_REPORTING) {
28 return originalSnapshotState;
29 }
30 return merge(originalSnapshotState, partialSnapshotState);
31}
32
33function checkResult({
34 result,
35 snapshotState,
36 retryTimes,
37 snapshotIdentifier,
38 chalk,
39}) {
40 let pass = true;
41 /*
42 istanbul ignore next
43 `message` is implementation detail. Actual behavior is tested in integration.spec.js
44 */
45 let message = () => '';
46
47 if (result.updated) {
48 // once transition away from jasmine is done this will be a lot more elegant and pure
49 // https://github.com/facebook/jest/pull/3668
50 updateSnapshotState(snapshotState, { updated: snapshotState.updated + 1 });
51 } else if (result.added) {
52 updateSnapshotState(snapshotState, { added: snapshotState.added + 1 });
53 } else {
54 ({ pass } = result);
55
56 updateSnapshotState(snapshotState, { matched: snapshotState.matched + 1 });
57
58 if (!pass) {
59 const currentRun = timesCalled.get(snapshotIdentifier);
60 if (!retryTimes || (currentRun > retryTimes)) {
61 updateSnapshotState(snapshotState, { unmatched: snapshotState.unmatched + 1 });
62 }
63
64 const differencePercentage = result.diffRatio * 100;
65 message = () => {
66 let failure;
67 if (result.diffSize) {
68 failure = `Expected image to be the same size as the snapshot (${result.imageDimensions.baselineWidth}x${result.imageDimensions.baselineHeight}), but was different (${result.imageDimensions.receivedWidth}x${result.imageDimensions.receivedHeight}).\n`
69 + `${chalk.bold.red('See diff for details:')} ${chalk.red(result.diffOutputPath)}`;
70 } else {
71 failure = `Expected image to match or be a close match to snapshot but was ${differencePercentage}% different from snapshot (${result.diffPixelCount} differing pixels).\n`
72 + `${chalk.bold.red('See diff for details:')} ${chalk.red(result.diffOutputPath)}`;
73 }
74
75 return failure;
76 };
77 }
78 }
79
80 return {
81 message,
82 pass,
83 };
84}
85
86function createSnapshotIdentifier({
87 retryTimes,
88 testPath,
89 currentTestName,
90 customSnapshotIdentifier,
91 snapshotState,
92}) {
93 const snapshotIdentifier = customSnapshotIdentifier || kebabCase(`${path.basename(testPath)}-${currentTestName}-${snapshotState._counters.get(currentTestName)}`);
94 if (retryTimes) {
95 if (!customSnapshotIdentifier) throw new Error('A unique customSnapshotIdentifier must be set when jest.retryTimes() is used');
96
97 timesCalled.set(snapshotIdentifier, (timesCalled.get(snapshotIdentifier) || 0) + 1);
98 }
99 return snapshotIdentifier;
100}
101
102function configureToMatchImageSnapshot({
103 customDiffConfig: commonCustomDiffConfig = {},
104 customSnapshotsDir: commonCustomSnapshotsDir,
105 customDiffDir: commonCustomDiffDir,
106 diffDirection: commonDiffDirection = 'horizontal',
107 noColors: commonNoColors = false,
108 failureThreshold: commonFailureThreshold = 0,
109 failureThresholdType: commonFailureThresholdType = 'pixel',
110 updatePassedSnapshot: commonUpdatePassedSnapshot = false,
111} = {}) {
112 return function toMatchImageSnapshot(received, {
113 customSnapshotIdentifier = '',
114 customSnapshotsDir = commonCustomSnapshotsDir,
115 customDiffDir = commonCustomDiffDir,
116 diffDirection = commonDiffDirection,
117 customDiffConfig = {},
118 noColors = commonNoColors,
119 failureThreshold = commonFailureThreshold,
120 failureThresholdType = commonFailureThresholdType,
121 updatePassedSnapshot = commonUpdatePassedSnapshot,
122 } = {}) {
123 const {
124 testPath, currentTestName, isNot, snapshotState,
125 } = this;
126 const chalk = new Chalk({ enabled: !noColors });
127
128 const retryTimes = parseInt(global[Symbol.for('RETRY_TIMES')], 10) || 0;
129
130 if (isNot) { throw new Error('Jest: `.not` cannot be used with `.toMatchImageSnapshot()`.'); }
131
132 updateSnapshotState(snapshotState, { _counters: snapshotState._counters.set(currentTestName, (snapshotState._counters.get(currentTestName) || 0) + 1) }); // eslint-disable-line max-len
133
134 const snapshotIdentifier = createSnapshotIdentifier({
135 retryTimes,
136 testPath,
137 currentTestName,
138 customSnapshotIdentifier,
139 snapshotState,
140 });
141
142 const snapshotsDir = customSnapshotsDir || path.join(path.dirname(testPath), SNAPSHOTS_DIR);
143 const diffDir = customDiffDir || path.join(snapshotsDir, '__diff_output__');
144 const baselineSnapshotPath = path.join(snapshotsDir, `${snapshotIdentifier}-snap.png`);
145
146 if (snapshotState._updateSnapshot === 'none' && !fs.existsSync(baselineSnapshotPath)) {
147 return {
148 pass: false,
149 message: () => `New snapshot was ${chalk.bold.red('not written')}. The update flag must be explicitly ` +
150 'passed to write a new snapshot.\n\n + This is likely because this test is run in a continuous ' +
151 'integration (CI) environment in which snapshots are not written by default.\n\n',
152 };
153 }
154
155 const result =
156 runDiffImageToSnapshot({
157 receivedImageBuffer: received,
158 snapshotsDir,
159 diffDir,
160 diffDirection,
161 snapshotIdentifier,
162 updateSnapshot: snapshotState._updateSnapshot === 'all',
163 customDiffConfig: Object.assign({}, commonCustomDiffConfig, customDiffConfig),
164 failureThreshold,
165 failureThresholdType,
166 updatePassedSnapshot,
167 });
168
169 return checkResult({
170 result,
171 snapshotState,
172 retryTimes,
173 snapshotIdentifier,
174 chalk,
175 });
176 };
177}
178
179module.exports = {
180 toMatchImageSnapshot: configureToMatchImageSnapshot(),
181 configureToMatchImageSnapshot,
182 updateSnapshotState,
183};