jest-image-snapshot 4.0.0 → 4.2.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.
@@ -10,13 +10,14 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
 
12
12
  steps:
13
- - uses: actions/stale@v1
13
+ - uses: actions/stale@v3
14
14
  with:
15
15
  repo-token: ${{ secrets.GITHUB_TOKEN }}
16
16
  stale-issue-message: 'This issue is stale because it has been open 30 days with no activity.'
17
17
  stale-pr-message: 'This pull request is stale because it has been open 30 days with no activity.'
18
18
  stale-issue-label: 'stale-issue'
19
- exempt-issue-label: 'stale-issue'
20
- exempt-pr-label: 'stale-pr'
19
+ exempt-issue-labels: 'enhancement,documentation,good-first-issue,question,bug'
21
20
  stale-pr-label: 'stale-pr'
21
+ exempt-pr-labels: 'work-in-progress'
22
22
  days-before-stale: 30
23
+ days-before-close: -1
package/.travis.yml CHANGED
@@ -2,6 +2,7 @@ language: node_js
2
2
  node_js:
3
3
  - 10
4
4
  - 12
5
+ - 14
5
6
  before_install:
6
7
  # Create a master branch for commitlint
7
8
  # https://github.com/conventional-changelog/commitlint/issues/6
@@ -13,6 +14,7 @@ jobs:
13
14
  node_js:
14
15
  - 10
15
16
  - 12
17
+ - 14
16
18
  deploy:
17
19
  on:
18
20
  branch: master
package/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ # [4.2.0](https://github.com/americanexpress/jest-image-snapshot/compare/v4.1.0...v4.2.0) (2020-08-29)
2
+
3
+
4
+ ### Features
5
+
6
+ * add obsolete snapshot reporting ([#222](https://github.com/americanexpress/jest-image-snapshot/issues/222)) ([47da7c2](https://github.com/americanexpress/jest-image-snapshot/commit/47da7c23495037e869ee68154218e5d73e1e8cd5))
7
+
8
+ # [4.1.0](https://github.com/americanexpress/jest-image-snapshot/compare/v4.0.2...v4.1.0) (2020-07-23)
9
+
10
+
11
+ ### Features
12
+
13
+ * **ssim:** add integration ([#220](https://github.com/americanexpress/jest-image-snapshot/issues/220)) ([e2b304a](https://github.com/americanexpress/jest-image-snapshot/commit/e2b304a6c6aaf7e1d12c2e088105181ee108b960))
14
+
15
+ ## [4.0.2](https://github.com/americanexpress/jest-image-snapshot/compare/v4.0.1...v4.0.2) (2020-05-27)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **options:** auto-detect colors if noColors option is not specified ([d90298c](https://github.com/americanexpress/jest-image-snapshot/commit/d90298c3f102734107a7574ddf0516c19a349c66))
21
+
22
+ ## [4.0.1](https://github.com/americanexpress/jest-image-snapshot/compare/v4.0.0...v4.0.1) (2020-05-27)
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **options:** add allowSizeMismatch arg ([6529ff4](https://github.com/americanexpress/jest-image-snapshot/commit/6529ff4b2bd9a20abe33d4b68d9d793198931f18))
28
+
1
29
  # [4.0.0](https://github.com/americanexpress/jest-image-snapshot/compare/v3.1.0...v4.0.0) (2020-05-14)
2
30
 
3
31
 
package/README.md CHANGED
@@ -100,14 +100,18 @@ See [the examples](./examples/README.md) for more detailed usage or read about a
100
100
 
101
101
  `toMatchImageSnapshot()` takes an optional options object with the following properties:
102
102
 
103
- * `customDiffConfig`: Custom config passed to [pixelmatch](https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options) (See options section)
104
- * By default we have set the `threshold` to 0.01, you can increase that value by passing a customDiffConfig as demonstrated below.
105
- * Please note the `threshold` set in the `customDiffConfig` is the per pixel sensitivity threshold. For example with a source pixel colour of `#ffffff` (white) and a comparison pixel colour of `#fcfcfc` (really light grey) if you set the threshold to 0 then it would trigger a failure *on that pixel*. However if you were to use say 0.5 then it wouldn't, the colour difference would need to be much more extreme to trigger a failure on that pixel, say `#000000` (black)
103
+ * `customDiffConfig`: Custom config passed to [pixelmatch](https://github.com/mapbox/pixelmatch#pixelmatchimg1-img2-output-width-height-options) (See options section) or [ssim.js](https://github.com/obartra/ssim/wiki/Usage#options)
104
+ * Pixelmatch specific options
105
+ * By default we have set the `threshold` to 0.01, you can increase that value by passing a customDiffConfig as demonstrated below.
106
+ * Please note the `threshold` set in the `customDiffConfig` is the per pixel sensitivity threshold. For example with a source pixel colour of `#ffffff` (white) and a comparison pixel colour of `#fcfcfc` (really light grey) if you set the threshold to 0 then it would trigger a failure *on that pixel*. However if you were to use say 0.5 then it wouldn't, the colour difference would need to be much more extreme to trigger a failure on that pixel, say `#000000` (black)
107
+ * SSIM specific options
108
+ * By default we set `ssim` to 'bezkrovny'. It is the fastest option and best option most of the time. In cases where, higher precision is needed, this can be set to 'fast'. See [SSIM Performance Consideration](#ssim-performance-considerations) for a better understanding of how to use this feature.
109
+ * `comparisonMethod`: (default: `pixelmatch`) (options `pixelmatch` or `ssim`) The method by which images are compared. `pixelmatch` does a pixel by pixel comparison, whereas `ssim` does a structural similarity comparison. `ssim` is a new experimental feature for jest-image-snapshot, but may become the default comparison method in the future. For a better understanding of how to use SSIM, see [Recommendations when using SSIM Comparison](#recommendations-when-using-ssim-comparison).
106
110
  * `customSnapshotsDir`: A custom absolute path of a directory to keep this snapshot in
107
111
  * `customDiffDir`: A custom absolute path of a directory to keep this diff in
108
112
  * `customSnapshotIdentifier`: A custom name to give this snapshot. If not provided one is computed automatically. When a function is provided it is called with an object containing `testPath`, `currentTestName`, `counter` and `defaultIdentifier` as its first argument. The function must return an identifier to use for the snapshot.
109
113
  * `diffDirection`: (default: `horizontal`) (options `horizontal` or `vertical`) Changes diff image layout direction
110
- * `noColors`: (default `false`) Removes coloring from console output, useful if storing the results in a file
114
+ * `noColors`: Removes coloring from console output, useful if storing the results in a file
111
115
  * `failureThreshold`: (default `0`) Sets the threshold that would trigger a test failure based on the `failureThresholdType` selected. This is different to the `customDiffConfig.threshold` above, that is the per pixel failure threshold, this is the failure threshold for the entire comparison.
112
116
  * `failureThresholdType`: (default `pixel`) (options `percent` or `pixel`) Sets the type of threshold that would trigger a failure.
113
117
  * `updatePassedSnapshot`: (default `false`) Updates a snapshot even if it passed the threshold against the existing one.
@@ -156,6 +160,66 @@ expect.extend({ toMatchImageSnapshot });
156
160
  ### jest.retryTimes()
157
161
  Jest supports [automatic retries on test failures](https://jestjs.io/docs/en/jest-object#jestretrytimes). This can be useful for browser screenshot tests which tend to have more frequent false positives. Note that when using jest.retryTimes you'll have to use a unique customSnapshotIdentifier as that's the only way to reliably identify snapshots.
158
162
 
163
+ ### Removing Outdated Snapshots
164
+
165
+ Unlike jest-managed snapshots, the images created by `jest-image-snapshot` will not be automatically removed by the `-u` flag if they are no longer needed. You can force `jest-image-snapshot` to remove the files by including the `outdated-snapshot-reporter` in your config and running with the environment variable `JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETE`.
166
+
167
+ ```json
168
+ {
169
+ "jest": {
170
+ "reporters": [
171
+ "default",
172
+ "jest-image-snapshot/src/outdated-snapshot-reporter.js"
173
+ ]
174
+ }
175
+ }
176
+ ```
177
+
178
+ **WARNING: Do not run a *partial* test suite with this flag as it may consider snapshots of tests that weren't run to be obsolete.**
179
+
180
+ ```bash
181
+ export JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETE=1
182
+ jest
183
+ ```
184
+
185
+ ### Recommendations when using SSIM comparison
186
+ Since SSIM calculates differences in structural similarity by building a moving 'window' over an images pixels, it does not particularly benefit from pixel count comparisons, especially when you factor in that it has a lot of floating point arithmetic in javascript. However, SSIM gains two key benefits over pixel by pixel comparison:
187
+ - Reduced false positives (failing tests when the images look the same)
188
+ - Higher sensitivity to actual changes in the image itself.
189
+
190
+ Documentation supporting these claims can be found in the many analyses comparing SSIM to Peak Signal to Noise Ratio (PSNR). See [Wang, Z.; Simoncelli, E. P. (September 2008). "Maximum differentiation (MAD) competition: a methodology for comparing computational models of perceptual quantities"](https://ece.uwaterloo.ca/~z70wang/publications/MAD.pdf) and Zhang, L.; Zhang, L.; Mou, X.; Zhang, D. (September 2012). A comprehensive evaluation of full reference image quality assessment algorithms. 2012 19th IEEE International Conference on Image Processing. pp. 1477–1480. [Wikipedia](http://en.wikipedia.org/wiki/Structural_Similarity) also provides many great reference sources describing the topic.
191
+
192
+ As such, most users can benefit from setting a 1% or 0.01 threshold for any SSIM comparison. The below code shows a one line modification of the 1% threshold example.
193
+
194
+ ```javascript
195
+ it('should fail if there is more than a 1% difference (ssim)', () => {
196
+ ...
197
+ expect(image).toMatchImageSnapshot({
198
+ comparisonMethod: 'ssim',
199
+ failureThreshold: 0.01,
200
+ failureThresholdType: 'percent'
201
+ });
202
+ });
203
+ ```
204
+ ### SSIM Performance Considerations
205
+ The default SSIM comparison method used in the jest-image-snapshot implementation is 'bezkrovny' (as a `customDiffConfig` `{ssim: 'bezkrovny'}`).
206
+ Bezkrovny is a special implementation of SSIM that is optimized for speed at a small, almost inconsequential change in accuracy. It gains this benefit by downsampling (or shrinking the original image) before performing the comparisons.
207
+ This will provide the best combination of results and performance most of the time. When the need arises where higher accuracy is desired at the expense of time or a higher quality diff image is needed for debugging,
208
+ this option can be changed to `{ssim: 'fast'}`. This uses the original SSIM algorithm described in Wang, et al. 2004 on "Image Quality Assessment: From Error Visibility to Structural Similarity" (https://github.com/obartra/ssim/blob/master/assets/ssim.pdf) optimized for javascript.
209
+
210
+ The following is an example configuration for using `{ssim: 'fast'}` with toMatchImageSnapshot().
211
+ ```javascript.
212
+ {
213
+ comparisonMethod: 'ssim',
214
+ customDiffConfig: {
215
+ ssim: 'fast',
216
+ },
217
+ failureThreshold: 0.01,
218
+ failureThresholdType: 'percent'
219
+ }
220
+ ```
221
+
222
+
159
223
  ### Recipes
160
224
 
161
225
  #### Upload diff images from failed tests
@@ -172,9 +236,12 @@ To enable this image reporter, add it to your `jest.config.js` "reporters" defin
172
236
 
173
237
  #### Usage in TypeScript
174
238
 
175
- In TypeScript, you can declare `toMatchImageSnapshot` like this:
239
+ In TypeScript, you can use the [DefinitelyTyped](https://www.npmjs.com/package/@types/jest-image-snapshot) definition or declare `toMatchImageSnapshot` like the example below:
176
240
 
177
- ```
241
+ _Note: This package is not maintained by the `jest-image-snapshot` maintainers so it may be out of date or inaccurate. Because of this, we do not officially support it._
242
+
243
+
244
+ ```typescript
178
245
  declare global {
179
246
  namespace jest {
180
247
  interface Matchers<R> {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jest-image-snapshot",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "Jest matcher for image comparisons. Most commonly used for visual regression testing.",
5
5
  "main": "src/index.js",
6
6
  "engines": {
@@ -48,16 +48,16 @@
48
48
  "@commitlint/config-conventional": "^8.3.4",
49
49
  "@semantic-release/changelog": "^5.0.0",
50
50
  "@semantic-release/git": "^9.0.0",
51
- "amex-jest-preset": "^6.0.1",
51
+ "amex-jest-preset": "^6.1.0",
52
52
  "eslint": "^6.8.0",
53
53
  "eslint-config-amex": "^7.0.0",
54
54
  "husky": "^4.2.1",
55
55
  "image-size": "^0.8.3",
56
56
  "jest": "^26.0.1",
57
57
  "jest-snapshot": "^26.0.1",
58
- "semantic-release": "^17.0.4",
59
58
  "lockfile-lint": "^4.0.0",
60
- "mock-spawn": "^0.2.6"
59
+ "mock-spawn": "^0.2.6",
60
+ "semantic-release": "^17.0.4"
61
61
  },
62
62
  "dependencies": {
63
63
  "chalk": "^1.1.3",
@@ -67,7 +67,8 @@
67
67
  "mkdirp": "^0.5.1",
68
68
  "pixelmatch": "^5.1.0",
69
69
  "pngjs": "^3.4.0",
70
- "rimraf": "^2.6.2"
70
+ "rimraf": "^2.6.2",
71
+ "ssim.js": "^3.1.1"
71
72
  },
72
73
  "peerDependencies": {
73
74
  "jest": ">=20 <=26"
@@ -17,6 +17,7 @@ const fs = require('fs');
17
17
  const path = require('path');
18
18
  const mkdirp = require('mkdirp');
19
19
  const pixelmatch = require('pixelmatch');
20
+ const ssim = require('ssim.js');
20
21
  const { PNG } = require('pngjs');
21
22
  const rimraf = require('rimraf');
22
23
  const glur = require('glur');
@@ -51,6 +52,74 @@ const fillSizeDifference = (width, height) => (image) => {
51
52
  return image;
52
53
  };
53
54
  /* eslint-enabled */
55
+ /**
56
+ * This was originally embedded in diffImageToSnapshot
57
+ * when it only worked with pixelmatch. It has a default
58
+ * threshold of 0.01 defined in terms of what it means to pixelmatch.
59
+ * It has been moved here as part of the SSIM implementation to make it
60
+ * a little easier to read and find.
61
+ * More information about this can be found under the options section listed
62
+ * in https://github.com/mapbox/pixelmatch/README.md and in the original pixelmatch
63
+ * code. There is also some documentation on this in our README.md under the
64
+ * customDiffConfig option.
65
+ * @type {{threshold: number}}
66
+ */
67
+ const defaultPixelmatchDiffConfig = {
68
+ threshold: 0.01,
69
+ };
70
+ /**
71
+ * This is the default SSIM diff configuration
72
+ * for the jest-image-snapshot's use of the ssim.js
73
+ * library. Bezkrovny is a specific SSIM algorithm optimized
74
+ * for speed by downsampling the origin image into a smaller image.
75
+ * For the small loss in precision, it is roughly 9x faster than the
76
+ * SSIM preset 'fast' -- which is modeled after the original SSIM whitepaper.
77
+ * Wang, et al. 2004 on "Image Quality Assessment: From Error Visibility to Structural Similarity"
78
+ * (https://github.com/obartra/ssim/blob/master/assets/ssim.pdf)
79
+ * Most users will never need or want to change this -- unless --
80
+ * they want to get a better quality generated diff.
81
+ * @type {{ssim: string}}
82
+ */
83
+ const defaultSSIMDiffConfig = { ssim: 'bezkrovny' };
84
+
85
+ /**
86
+ * Helper function for SSIM comparison that allows us to use the existing diff
87
+ * config that works with jest-image-snapshot to pass parameters
88
+ * that will work with SSIM. It also transforms the parameters to match the spec
89
+ * required by the SSIM library.
90
+ */
91
+ const ssimMatch = (
92
+ newImageData,
93
+ baselineImageData,
94
+ diffImageData,
95
+ imageWidth,
96
+ imageHeight,
97
+ diffConfig
98
+ ) => {
99
+ const newImage = { data: newImageData, width: imageWidth, height: imageHeight };
100
+ const baselineImage = { data: baselineImageData, width: imageWidth, height: imageHeight };
101
+ // eslint-disable-next-line camelcase
102
+ const { ssim_map, mssim } = ssim.ssim(newImage, baselineImage, diffConfig);
103
+ // Converts the SSIM value to different pixels based on image width and height
104
+ // conforms to how pixelmatch works.
105
+ const diffPixels = (1 - mssim) * imageWidth * imageHeight;
106
+ const diffRgbaPixels = new DataView(diffImageData.buffer, diffImageData.byteOffset);
107
+ for (let ln = 0; ln !== imageHeight; ++ln) {
108
+ for (let pos = 0; pos !== imageWidth; ++pos) {
109
+ const rpos = (ln * imageWidth) + pos;
110
+ // initial value is transparent. We'll add in the SSIM offset.
111
+ // red (ff) green (00) blue (00) alpha (00)
112
+ const diffValue = 0xff000000 + Math.floor(0xff *
113
+ (1 - ssim_map.data[
114
+ // eslint-disable-next-line no-mixed-operators
115
+ (ssim_map.width * Math.round(ssim_map.height * ln / imageHeight)) +
116
+ // eslint-disable-next-line no-mixed-operators
117
+ Math.round(ssim_map.width * pos / imageWidth)]));
118
+ diffRgbaPixels.setUint32(rpos * 4, diffValue);
119
+ }
120
+ }
121
+ return diffPixels;
122
+ };
54
123
 
55
124
  /**
56
125
  * Aligns images sizes to biggest common value
@@ -129,8 +198,10 @@ function diffImageToSnapshot(options) {
129
198
  failureThresholdType,
130
199
  blur,
131
200
  allowSizeMismatch = false,
201
+ comparisonMethod = 'pixelmatch',
132
202
  } = options;
133
203
 
204
+ const comparisonFn = comparisonMethod === 'ssim' ? ssimMatch : pixelmatch;
134
205
  let result = {};
135
206
  const baselineSnapshotPath = path.join(snapshotsDir, `${snapshotIdentifier}-snap.png`);
136
207
  if (!fs.existsSync(baselineSnapshotPath)) {
@@ -141,9 +212,7 @@ function diffImageToSnapshot(options) {
141
212
  const diffOutputPath = path.join(diffDir, `${snapshotIdentifier}-diff.png`);
142
213
  rimraf.sync(diffOutputPath);
143
214
 
144
- const defaultDiffConfig = {
145
- threshold: 0.01,
146
- };
215
+ const defaultDiffConfig = comparisonMethod !== 'ssim' ? defaultPixelmatchDiffConfig : defaultSSIMDiffConfig;
147
216
 
148
217
  const diffConfig = Object.assign({}, defaultDiffConfig, customDiffConfig);
149
218
 
@@ -175,7 +244,7 @@ function diffImageToSnapshot(options) {
175
244
 
176
245
  let diffPixelCount = 0;
177
246
 
178
- diffPixelCount = pixelmatch(
247
+ diffPixelCount = comparisonFn(
179
248
  receivedImage.data,
180
249
  baselineImage.data,
181
250
  diffImage.data,
package/src/index.js CHANGED
@@ -18,6 +18,7 @@ const path = require('path');
18
18
  const Chalk = require('chalk').constructor;
19
19
  const { diffImageToSnapshot, runDiffImageToSnapshot } = require('./diff-snapshot');
20
20
  const fs = require('fs');
21
+ const OutdatedSnapshotReporter = require('./outdated-snapshot-reporter');
21
22
 
22
23
  const timesCalled = new Map();
23
24
 
@@ -37,6 +38,7 @@ function checkResult({
37
38
  snapshotIdentifier,
38
39
  chalk,
39
40
  dumpDiffToConsole,
41
+ allowSizeMismatch,
40
42
  }) {
41
43
  let pass = true;
42
44
  /*
@@ -65,7 +67,7 @@ function checkResult({
65
67
  const differencePercentage = result.diffRatio * 100;
66
68
  message = () => {
67
69
  let failure;
68
- if (result.diffSize) {
70
+ if (result.diffSize && !allowSizeMismatch) {
69
71
  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`;
70
72
  } else {
71
73
  failure = `Expected image to match or be a close match to snapshot but was ${differencePercentage}% different from snapshot (${result.diffPixelCount} differing pixels).\n`;
@@ -127,13 +129,15 @@ function configureToMatchImageSnapshot({
127
129
  customSnapshotsDir: commonCustomSnapshotsDir,
128
130
  customDiffDir: commonCustomDiffDir,
129
131
  diffDirection: commonDiffDirection = 'horizontal',
130
- noColors: commonNoColors = false,
132
+ noColors: commonNoColors,
131
133
  failureThreshold: commonFailureThreshold = 0,
132
134
  failureThresholdType: commonFailureThresholdType = 'pixel',
133
135
  updatePassedSnapshot: commonUpdatePassedSnapshot = false,
134
136
  blur: commonBlur = 0,
135
137
  runInProcess: commonRunInProcess = false,
136
138
  dumpDiffToConsole: commonDumpDiffToConsole = false,
139
+ allowSizeMismatch: commonAllowSizeMismatch = false,
140
+ comparisonMethod: commonComparisonMethod = 'pixelmatch',
137
141
  } = {}) {
138
142
  return function toMatchImageSnapshot(received, {
139
143
  customSnapshotIdentifier = commonCustomSnapshotIdentifier,
@@ -148,11 +152,17 @@ function configureToMatchImageSnapshot({
148
152
  blur = commonBlur,
149
153
  runInProcess = commonRunInProcess,
150
154
  dumpDiffToConsole = commonDumpDiffToConsole,
155
+ allowSizeMismatch = commonAllowSizeMismatch,
156
+ comparisonMethod = commonComparisonMethod,
151
157
  } = {}) {
152
158
  const {
153
159
  testPath, currentTestName, isNot, snapshotState,
154
160
  } = this;
155
- const chalk = new Chalk({ enabled: !noColors });
161
+ const chalkOptions = {};
162
+ if (typeof noColors !== 'undefined') {
163
+ chalkOptions.enabled = !noColors;
164
+ }
165
+ const chalk = new Chalk(chalkOptions);
156
166
 
157
167
  const retryTimes = parseInt(global[Symbol.for('RETRY_TIMES')], 10) || 0;
158
168
 
@@ -171,6 +181,7 @@ function configureToMatchImageSnapshot({
171
181
  const snapshotsDir = customSnapshotsDir || path.join(path.dirname(testPath), SNAPSHOTS_DIR);
172
182
  const diffDir = customDiffDir || path.join(snapshotsDir, '__diff_output__');
173
183
  const baselineSnapshotPath = path.join(snapshotsDir, `${snapshotIdentifier}-snap.png`);
184
+ OutdatedSnapshotReporter.markTouchedFile(baselineSnapshotPath);
174
185
 
175
186
  if (snapshotState._updateSnapshot === 'none' && !fs.existsSync(baselineSnapshotPath)) {
176
187
  return {
@@ -196,6 +207,8 @@ function configureToMatchImageSnapshot({
196
207
  failureThresholdType,
197
208
  updatePassedSnapshot,
198
209
  blur,
210
+ allowSizeMismatch,
211
+ comparisonMethod,
199
212
  });
200
213
 
201
214
  return checkResult({
@@ -205,6 +218,7 @@ function configureToMatchImageSnapshot({
205
218
  snapshotIdentifier,
206
219
  chalk,
207
220
  dumpDiffToConsole,
221
+ allowSizeMismatch,
208
222
  });
209
223
  };
210
224
  }
@@ -0,0 +1,84 @@
1
+ /*
2
+ * Copyright (c) 2020 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
+ /* eslint-disable class-methods-use-this */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const TOUCHED_FILE_LIST_PATH = path.join(
21
+ process.cwd(),
22
+ '.jest-image-snapshot-touched-files'
23
+ );
24
+
25
+ const IS_ENABLED = !!process.env.JEST_IMAGE_SNAPSHOT_TRACK_OBSOLETE;
26
+
27
+ class OutdatedSnapshotReporter {
28
+ /* istanbul ignore next - test coverage in child process */
29
+ static markTouchedFile(filePath) {
30
+ if (!IS_ENABLED) return;
31
+ const touchedListFileDescriptor = fs.openSync(TOUCHED_FILE_LIST_PATH, 'as');
32
+ fs.writeSync(touchedListFileDescriptor, `${filePath}\n`);
33
+ fs.closeSync(touchedListFileDescriptor);
34
+ }
35
+
36
+ /* istanbul ignore next - test coverage in child process */
37
+ static readTouchedFileListFromDisk() {
38
+ if (!fs.existsSync(TOUCHED_FILE_LIST_PATH)) return [];
39
+
40
+ return Array.from(
41
+ new Set(
42
+ fs
43
+ .readFileSync(TOUCHED_FILE_LIST_PATH, 'utf-8')
44
+ .split('\n')
45
+ .filter(file => file && fs.existsSync(file))
46
+ )
47
+ );
48
+ }
49
+
50
+ /* istanbul ignore next - test coverage in child process */
51
+ onRunStart() {
52
+ if (!IS_ENABLED) return;
53
+ if (fs.existsSync(TOUCHED_FILE_LIST_PATH)) {
54
+ fs.unlinkSync(TOUCHED_FILE_LIST_PATH);
55
+ }
56
+ }
57
+
58
+ /* istanbul ignore next - test coverage in child process */
59
+ onRunComplete() {
60
+ if (!IS_ENABLED) return;
61
+ const touchedFiles = OutdatedSnapshotReporter.readTouchedFileListFromDisk();
62
+ const imageSnapshotDirectories = Array.from(
63
+ new Set(touchedFiles.map(file => path.dirname(file)))
64
+ );
65
+ const allFiles = imageSnapshotDirectories
66
+ .map(dir => fs.readdirSync(dir).map(file => path.join(dir, file)))
67
+ .reduce((a, b) => a.concat(b), [])
68
+ .filter(file => file.endsWith('-snap.png'));
69
+ const obsoleteFiles = allFiles.filter(
70
+ file => !touchedFiles.includes(file)
71
+ );
72
+
73
+ if (fs.existsSync(TOUCHED_FILE_LIST_PATH)) {
74
+ fs.unlinkSync(TOUCHED_FILE_LIST_PATH);
75
+ }
76
+
77
+ obsoleteFiles.forEach((file) => {
78
+ process.stderr.write(`Deleting outdated snapshot "${file}"...\n`);
79
+ fs.unlinkSync(file);
80
+ });
81
+ }
82
+ }
83
+
84
+ module.exports = OutdatedSnapshotReporter;