From 4f093abf7f14596f0acecb4ebe5627cce523d5a2 Mon Sep 17 00:00:00 2001 From: ninevra Date: Thu, 31 Dec 2020 06:42:00 -0800 Subject: [PATCH] Remove snapshot files when a test file stops using snapshots Fixes #1424. Co-authored-by: Mark Wubben --- lib/runner.js | 25 +++- lib/snapshot-manager.js | 39 ++++- lib/test.js | 4 + package.json | 1 + .../watcher-rerun-unlink/package.json | 1 + .../snapshots/watcher-rerun-unlink/test.js | 21 +++ test-tap/integration/watcher.js | 46 ++++++ test-tap/test.js | 30 ++++ .../fixtures/fixed-snapshot-dir/package.json | 5 + .../fixtures/fixed-snapshot-dir/test.js | 22 +++ .../fixtures/no-snapshots/package.json | 1 + .../fixtures/no-snapshots/test.js | 5 + .../fixtures/only-test/package.json | 1 + .../fixtures/only-test/test.js | 22 +++ .../fixtures/removal/package.json | 1 + .../snapshot-removal/fixtures/removal/test.js | 22 +++ .../skipped-snapshots-in-try/package.json | 1 + .../fixtures/skipped-snapshots-in-try/test.js | 23 +++ .../fixtures/skipped-snapshots/package.json | 1 + .../fixtures/skipped-snapshots/test.js | 23 +++ .../fixtures/skipped-tests/package.json | 1 + .../fixtures/skipped-tests/test.js | 22 +++ .../fixtures/snapshot-dir/package.json | 1 + .../fixtures/snapshot-dir/test/test.js | 22 +++ .../fixtures/try/package.json | 1 + test/snapshot-removal/fixtures/try/test.js | 23 +++ test/snapshot-removal/helpers/macros.js | 86 +++++++++++ test/snapshot-removal/snapshots/test.js.md | 76 ++++++++++ test/snapshot-removal/snapshots/test.js.snap | Bin 0 -> 513 bytes test/snapshot-removal/test.js | 133 ++++++++++++++++++ .../fixtures/contains-skip-assertion.js | 10 ++ .../fixtures/contains-skip-assertion.js.md | 11 ++ .../fixtures/contains-skip-assertion.js.snap | Bin 0 -> 97 bytes test/snapshot-updates/snapshots/test.js.md | 28 ++++ test/snapshot-updates/snapshots/test.js.snap | Bin 453 -> 524 bytes test/snapshot-updates/test.js | 7 + 36 files changed, 710 insertions(+), 5 deletions(-) create mode 100644 test-tap/fixture/snapshots/watcher-rerun-unlink/package.json create mode 100644 test-tap/fixture/snapshots/watcher-rerun-unlink/test.js create mode 100644 test/snapshot-removal/fixtures/fixed-snapshot-dir/package.json create mode 100644 test/snapshot-removal/fixtures/fixed-snapshot-dir/test.js create mode 100644 test/snapshot-removal/fixtures/no-snapshots/package.json create mode 100644 test/snapshot-removal/fixtures/no-snapshots/test.js create mode 100644 test/snapshot-removal/fixtures/only-test/package.json create mode 100644 test/snapshot-removal/fixtures/only-test/test.js create mode 100644 test/snapshot-removal/fixtures/removal/package.json create mode 100644 test/snapshot-removal/fixtures/removal/test.js create mode 100644 test/snapshot-removal/fixtures/skipped-snapshots-in-try/package.json create mode 100644 test/snapshot-removal/fixtures/skipped-snapshots-in-try/test.js create mode 100644 test/snapshot-removal/fixtures/skipped-snapshots/package.json create mode 100644 test/snapshot-removal/fixtures/skipped-snapshots/test.js create mode 100644 test/snapshot-removal/fixtures/skipped-tests/package.json create mode 100644 test/snapshot-removal/fixtures/skipped-tests/test.js create mode 100644 test/snapshot-removal/fixtures/snapshot-dir/package.json create mode 100644 test/snapshot-removal/fixtures/snapshot-dir/test/test.js create mode 100644 test/snapshot-removal/fixtures/try/package.json create mode 100644 test/snapshot-removal/fixtures/try/test.js create mode 100644 test/snapshot-removal/helpers/macros.js create mode 100644 test/snapshot-removal/snapshots/test.js.md create mode 100644 test/snapshot-removal/snapshots/test.js.snap create mode 100644 test/snapshot-removal/test.js create mode 100644 test/snapshot-updates/fixtures/contains-skip-assertion.js create mode 100644 test/snapshot-updates/fixtures/contains-skip-assertion.js.md create mode 100644 test/snapshot-updates/fixtures/contains-skip-assertion.js.snap diff --git a/lib/runner.js b/lib/runner.js index fe240cb26..1f5577439 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -29,6 +29,8 @@ class Runner extends Emittery { this.activeRunnables = new Set(); this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this); + this.skippedSnapshots = false; + this.boundSkipSnapshot = this.skipSnapshot.bind(this); this.interrupted = false; this.snapshots = null; this.nextTaskIndex = 0; @@ -199,8 +201,19 @@ class Runner extends Emittery { return this.snapshots.compare(options); } + skipSnapshot() { + this.skippedSnapshots = true; + } + saveSnapshotState() { - if (this.updateSnapshots && (this.runOnlyExclusive || this.skippingTests)) { + if ( + this.updateSnapshots && + ( + this.runOnlyExclusive || + this.skippingTests || + this.skippedSnapshots + ) + ) { return {cannotSave: true}; } @@ -209,9 +222,11 @@ class Runner extends Emittery { } if (this.updateSnapshots) { - // TODO: There may be unused snapshot files if no test caused the - // snapshots to be loaded. Prune them. But not if tests (including hooks!) - // were skipped. Perhaps emit a warning if this occurs? + return {touchedFiles: snapshotManager.cleanSnapshots({ + file: this.file, + fixedLocation: this.snapshotDir, + projectDir: this.projectDir + })}; } return {}; @@ -297,6 +312,7 @@ class Runner extends Emittery { task.implementation : t => task.implementation.apply(null, [t].concat(task.args)), compareTestSnapshot: this.boundCompareTestSnapshot, + skipSnapshot: this.boundSkipSnapshot, updateSnapshots: this.updateSnapshots, metadata: {...task.metadata, associatedTaskIndex}, powerAssert: this.powerAssert, @@ -349,6 +365,7 @@ class Runner extends Emittery { task.implementation : t => task.implementation.apply(null, [t].concat(task.args)), compareTestSnapshot: this.boundCompareTestSnapshot, + skipSnapshot: this.boundSkipSnapshot, updateSnapshots: this.updateSnapshots, metadata: task.metadata, powerAssert: this.powerAssert, diff --git a/lib/snapshot-manager.js b/lib/snapshot-manager.js index 119476452..5e19b1609 100644 --- a/lib/snapshot-manager.js +++ b/lib/snapshot-manager.js @@ -449,12 +449,49 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => { exports.determineSnapshotDir = determineSnapshotDir; -function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) { +function determineSnapshotPaths({file, fixedLocation, projectDir}) { const dir = determineSnapshotDir({file, fixedLocation, projectDir}); const relFile = path.relative(projectDir, resolveSourceFile(file)); const name = path.basename(relFile); const reportFile = `${name}.md`; const snapFile = `${name}.snap`; + + return { + dir, + relFile, + snapFile, + reportFile + }; +} + +function cleanFile(file) { + try { + fs.unlinkSync(file); + return [file]; + } catch (error) { + if (error.code === 'ENOENT') { + return []; + } + + throw error; + } +} + +// Remove snapshot and report if they exist. Returns an array containing the +// paths of the touched files. +function cleanSnapshots({file, fixedLocation, projectDir}) { + const {dir, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir}); + + return [ + ...cleanFile(path.join(dir, snapFile)), + ...cleanFile(path.join(dir, reportFile)) + ]; +} + +exports.cleanSnapshots = cleanSnapshots; + +function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) { + const {dir, relFile, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir}); const snapPath = path.join(dir, snapFile); let appendOnly = !updating; diff --git a/lib/test.js b/lib/test.js index b0814654e..866590d17 100644 --- a/lib/test.js +++ b/lib/test.js @@ -249,6 +249,10 @@ class Test { }; this.skipSnapshot = () => { + if (typeof options.skipSnapshot === 'function') { + options.skipSnapshot(); + } + if (options.updateSnapshots) { this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots')); } else { diff --git a/package.json b/package.json index 366bca3d7..892f8051f 100644 --- a/package.json +++ b/package.json @@ -123,6 +123,7 @@ "delay": "^4.4.0", "esm": "^3.2.25", "execa": "^5.0.0", + "fs-extra": "^9.0.1", "get-stream": "^6.0.0", "it-first": "^1.0.4", "proxyquire": "^2.1.3", diff --git a/test-tap/fixture/snapshots/watcher-rerun-unlink/package.json b/test-tap/fixture/snapshots/watcher-rerun-unlink/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test-tap/fixture/snapshots/watcher-rerun-unlink/package.json @@ -0,0 +1 @@ +{} diff --git a/test-tap/fixture/snapshots/watcher-rerun-unlink/test.js b/test-tap/fixture/snapshots/watcher-rerun-unlink/test.js new file mode 100644 index 000000000..885f9e698 --- /dev/null +++ b/test-tap/fixture/snapshots/watcher-rerun-unlink/test.js @@ -0,0 +1,21 @@ +const test = require('../../../..'); + +if (process.env.TEMPLATE) { + test('test title', t => { + t.snapshot({foo: 'bar'}); + t.snapshot({answer: 42}); + t.pass(); + }); + + test('another test', t => { + t.snapshot(new Map()); + }); +} else { + test('test title', t => { + t.pass(); + }); + + test('another test', t => { + t.pass(); + }); +} diff --git a/test-tap/integration/watcher.js b/test-tap/integration/watcher.js index e30896f4c..1cf6cdf85 100644 --- a/test-tap/integration/watcher.js +++ b/test-tap/integration/watcher.js @@ -82,6 +82,52 @@ test('watcher does not rerun test files when they write snapshot files', t => { }); }); +test('watcher does not rerun test files when they unlink snapshot files', t => { + // Run fixture as template to generate snapshots + execCli( + ['--update-snapshots'], + { + dirname: 'fixture/snapshots/watcher-rerun-unlink', + env: {AVA_FORCE_CI: 'not-ci', TEMPLATE: 'true'} + }, + err => { + t.ifError(err); + + // Run fixture in watch mode; snapshots should be removed, and watcher should not rerun + let killed = false; + + const child = execCli( + ['--verbose', '--watch', '--update-snapshots', 'test.js'], + { + dirname: 'fixture/snapshots/watcher-rerun-unlink', + env: {AVA_FORCE_CI: 'not-ci'} + }, + err => { + t.ok(killed); + t.ifError(err); + t.end(); + } + ); + + let buffer = ''; + let passedFirst = false; + child.stdout.on('data', string => { + buffer += string; + if (buffer.includes('2 tests passed') && !passedFirst) { + buffer = ''; + passedFirst = true; + setTimeout(() => { + child.kill(); + killed = true; + }, 500); + } else if (passedFirst && !killed) { + t.is(buffer.replace(/\s/g, '').replace(END_MESSAGE.replace(/\s/g, ''), ''), ''); + } + }); + } + ); +}); + test('watcher does not rerun test files when ignored files change', t => { let killed = false; diff --git a/test-tap/test.js b/test-tap/test.js index 4b648d990..58f7dbc33 100644 --- a/test-tap/test.js +++ b/test-tap/test.js @@ -696,6 +696,36 @@ test('snapshot assertion can be skipped', t => { }); }); +// Snapshots reused from test/assert.js +test('snapshot assertions call options.skipSnapshot when skipped', async t => { + const projectDir = path.join(__dirname, 'fixture'); + const manager = snapshotManager.load({ + file: path.join(projectDir, 'assert.js'), + projectDir, + fixedLocation: null, + updating: false + }); + + const skipSnapshot = sinon.spy(); + + const test = new Test({ + compareTestSnapshot: options => manager.compare(options), + skipSnapshot, + updateSnapshots: false, + metadata: {}, + title: 'passes', + fn(t) { + t.snapshot.skip({not: {a: 'match'}}); + t.snapshot.skip({not: {b: 'match'}}); + t.snapshot(React.createElement(HelloMessage, {name: 'Sindre'})); + } + }); + + await test.run(); + + t.true(skipSnapshot.calledTwice); +}); + test('snapshot assertion cannot be skipped when updating snapshots', t => { return new Test({ updateSnapshots: true, diff --git a/test/snapshot-removal/fixtures/fixed-snapshot-dir/package.json b/test/snapshot-removal/fixtures/fixed-snapshot-dir/package.json new file mode 100644 index 000000000..6aa55350f --- /dev/null +++ b/test/snapshot-removal/fixtures/fixed-snapshot-dir/package.json @@ -0,0 +1,5 @@ +{ + "ava": { + "snapshotDir": "fixedSnapshotDir" + } +} diff --git a/test/snapshot-removal/fixtures/fixed-snapshot-dir/test.js b/test/snapshot-removal/fixtures/fixed-snapshot-dir/test.js new file mode 100644 index 000000000..61e035eae --- /dev/null +++ b/test/snapshot-removal/fixtures/fixed-snapshot-dir/test.js @@ -0,0 +1,22 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test('some snapshots', t => { + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/no-snapshots/package.json b/test/snapshot-removal/fixtures/no-snapshots/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/no-snapshots/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/no-snapshots/test.js b/test/snapshot-removal/fixtures/no-snapshots/test.js new file mode 100644 index 000000000..4f9782f12 --- /dev/null +++ b/test/snapshot-removal/fixtures/no-snapshots/test.js @@ -0,0 +1,5 @@ +const test = require('ava'); + +test('without snapshots', t => { + t.pass(); +}); diff --git a/test/snapshot-removal/fixtures/only-test/package.json b/test/snapshot-removal/fixtures/only-test/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/only-test/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/only-test/test.js b/test/snapshot-removal/fixtures/only-test/test.js new file mode 100644 index 000000000..0bad8f5f2 --- /dev/null +++ b/test/snapshot-removal/fixtures/only-test/test.js @@ -0,0 +1,22 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test.only('some snapshots', t => { + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/removal/package.json b/test/snapshot-removal/fixtures/removal/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/removal/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/removal/test.js b/test/snapshot-removal/fixtures/removal/test.js new file mode 100644 index 000000000..61e035eae --- /dev/null +++ b/test/snapshot-removal/fixtures/removal/test.js @@ -0,0 +1,22 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test('some snapshots', t => { + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/skipped-snapshots-in-try/package.json b/test/snapshot-removal/fixtures/skipped-snapshots-in-try/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-snapshots-in-try/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/skipped-snapshots-in-try/test.js b/test/snapshot-removal/fixtures/skipped-snapshots-in-try/test.js new file mode 100644 index 000000000..cc43c7145 --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-snapshots-in-try/test.js @@ -0,0 +1,23 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('skipped snapshots in try', async t => { + const attempt = await t.try(tt => { + tt.snapshot('in try'); + }); + + attempt.commit(); + + t.pass(); + }); +} else { + test('skipped snapshots in try', async t => { + const attempt = await t.try(tt => { + tt.snapshot.skip('in try'); + }); + + attempt.discard(); + + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/skipped-snapshots/package.json b/test/snapshot-removal/fixtures/skipped-snapshots/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-snapshots/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/skipped-snapshots/test.js b/test/snapshot-removal/fixtures/skipped-snapshots/test.js new file mode 100644 index 000000000..a01ab31fe --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-snapshots/test.js @@ -0,0 +1,23 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test('some snapshots', t => { + t.snapshot.skip('foo'); + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/skipped-tests/package.json b/test/snapshot-removal/fixtures/skipped-tests/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-tests/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/skipped-tests/test.js b/test/snapshot-removal/fixtures/skipped-tests/test.js new file mode 100644 index 000000000..a4344b841 --- /dev/null +++ b/test/snapshot-removal/fixtures/skipped-tests/test.js @@ -0,0 +1,22 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test.skip('some snapshots', t => { + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/snapshot-dir/package.json b/test/snapshot-removal/fixtures/snapshot-dir/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/snapshot-dir/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/snapshot-dir/test/test.js b/test/snapshot-removal/fixtures/snapshot-dir/test/test.js new file mode 100644 index 000000000..61e035eae --- /dev/null +++ b/test/snapshot-removal/fixtures/snapshot-dir/test/test.js @@ -0,0 +1,22 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('some snapshots', t => { + t.snapshot('foo'); + t.snapshot('bar'); + t.pass(); + }); + + test('another snapshot', t => { + t.snapshot('baz'); + t.pass(); + }); +} else { + test('some snapshots', t => { + t.pass(); + }); + + test('another snapshot', t => { + t.pass(); + }); +} diff --git a/test/snapshot-removal/fixtures/try/package.json b/test/snapshot-removal/fixtures/try/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/snapshot-removal/fixtures/try/package.json @@ -0,0 +1 @@ +{} diff --git a/test/snapshot-removal/fixtures/try/test.js b/test/snapshot-removal/fixtures/try/test.js new file mode 100644 index 000000000..fb4d44277 --- /dev/null +++ b/test/snapshot-removal/fixtures/try/test.js @@ -0,0 +1,23 @@ +const test = require(process.env.AVA_PATH); // This fixture is copied to a temporary directory, so require AVA through its configured path. + +if (process.env.TEMPLATE) { + test('snapshots in try', async t => { + const attempt = await t.try(tt => { + tt.snapshot('in try'); + }); + + attempt.commit(); + + t.pass(); + }); +} else { + test('snapshots in try', async t => { + const attempt = await t.try(tt => { + tt.snapshot('in try'); + }); + + attempt.discard(); + + t.pass(); + }); +} diff --git a/test/snapshot-removal/helpers/macros.js b/test/snapshot-removal/helpers/macros.js new file mode 100644 index 000000000..35d4a42b5 --- /dev/null +++ b/test/snapshot-removal/helpers/macros.js @@ -0,0 +1,86 @@ +const fs = require('fs').promises; +const exec = require('../../helpers/exec'); +const path = require('path'); +const tempy = require('tempy'); +const fse = require('fs-extra'); + +function withTemporaryFixture(macro) { + const avaPath = path.resolve(path.join(__dirname, '..', '..', '..')); + + return async (t, {cwd, env, ...options}) => { + await tempy.directory.task(async temporary => { + await fse.copy(cwd, temporary); + await macro(t, { + cwd: temporary, + env: { + AVA_PATH: avaPath, + ...env + }, + ...options + }); + }); + }; +} + +module.exports.withTemporaryFixture = withTemporaryFixture; + +async function testSnapshotPruning(t, { + cwd, + env, + cli, + remove, + snapshotPath = 'test.js.snap', + reportPath = 'test.js.md', + checkRun = async (t, run) => { + await t.notThrowsAsync(run, 'Expected fixture not to throw'); + } +}) { + snapshotPath = path.join(cwd, snapshotPath); + reportPath = path.join(cwd, reportPath); + + t.teardown(async () => { + try { + await fs.unlink(snapshotPath); + await fs.unlink(reportPath); + } catch {} + }); + + // Execute fixture as template to generate snapshots + const templateResult = exec.fixture(['--update-snapshots'], { + cwd, + env: { + ...env, + AVA_FORCE_CI: 'not-ci', + TEMPLATE: 'true' + } + }); + + await t.notThrowsAsync(templateResult, 'Template crashed - there\'s a bug in the test'); + + // Check that the snapshots were created + await t.notThrowsAsync(fs.access(snapshotPath), 'Template didn\'t create a snapshot - there\'s a bug in the test'); + await t.notThrowsAsync(fs.access(reportPath), 'Template didn\'t create a report - there\'s a bug in the test'); + + // Execute fixture as run + const run = exec.fixture(cli, { + cwd, + env: { + AVA_FORCE_CI: 'not-ci', + ...env + } + }); + + await checkRun(t, run); + + if (remove) { + // Assert files don't exist + await t.throwsAsync(fs.access(snapshotPath), {code: 'ENOENT'}, 'Expected snapshot to be removed'); + await t.throwsAsync(fs.access(reportPath), {code: 'ENOENT'}, 'Expected report to be remove'); + } else { + // Assert files exist + await t.notThrowsAsync(fs.access(snapshotPath), 'Expected snapshot not to be removed'); + await t.notThrowsAsync(fs.access(reportPath), 'Expected report not to be removed'); + } +} + +module.exports.testSnapshotPruning = testSnapshotPruning; diff --git a/test/snapshot-removal/snapshots/test.js.md b/test/snapshot-removal/snapshots/test.js.md new file mode 100644 index 000000000..89e1a2a84 --- /dev/null +++ b/test/snapshot-removal/snapshots/test.js.md @@ -0,0 +1,76 @@ +# Snapshot report for `test/snapshot-removal/test.js` + +The actual snapshot is saved in `test.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## snapshots remain if skipped in a discarded try() + +> files where snapshots could not be updated + + [ + { + file: 'test.js', + }, + ] + +## snapshots remain if snapshot assertions are skipped + +> files where snapshots could not be updated + + [ + { + file: 'test.js', + }, + ] + +## snapshots remain if tests are skipped + +> files where snapshots could not be updated + + [ + { + file: 'test.js', + }, + ] + +## snapshots remain if tests run with --match + +> stderr + + 'Snapshots cannot be updated when matching specific tests.' + +## snapshots remain if tests selected by line numbers + +> stderr + + 'Snapshots cannot be updated when selecting specific tests by their line number.' + +## snapshots remain if they are still used + +> passed tests + + [ + { + file: 'test.js', + title: 'another snapshot', + }, + { + file: 'test.js', + title: 'some snapshots', + }, + ] + +> files where snapshots could not be updated + + [] + +## snapshots remain if using test.only + +> files where snapshots could not be updated + + [ + { + file: 'test.js', + }, + ] diff --git a/test/snapshot-removal/snapshots/test.js.snap b/test/snapshot-removal/snapshots/test.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..6dd5b8c82b8b5e996fcb0a4175486cd8131429dc GIT binary patch literal 513 zcmV+c0{;C$RzV-9130DVq^2YQW6EFy}z`zK}o5)pq(EKb>9Ja*Yuzx)+GOoj7AOi;F#e z&Qf1i>Ep0~5iIJ(!oa``^Z^JkvN8xVS_bDO78GaXmlP``C+6klmnbBqDwGzaB$lM6 zD3oWU<|*VRmLz9n=A|nX7o;Xp0AgWAHbF*K$D*RdN=8N)i=UsLfsu=Wk%@tk zn~~8Qq7ld{1JR6NGZq4w%Ypbb%nUaFq%5F&7@6Qq0RaK9(w{&j|AAPU5ltyGs#0FC zh5SG@Od^cTz+ez$WJ$}+NriFPLEh8LDn?VoT9R3UP$ZBD46BUPB86gT_)*DRzT*7c zRJftVh~UH-f=D6Gqywal2!%EyIE-!Jnv#KZCV}AP1#2Vhw^7Fo3C92cqsN { + // Execute fixture; this should try to unlink the nonexistent snapshots, and + // should not throw + const run = exec.fixture(['--update-snapshots'], { + cwd: exec.cwd('no-snapshots'), + env: { + AVA_FORCE_CI: 'not-ci' + } + }); + + await t.notThrowsAsync(run); +}); + +test('snapshots remain if not updating', macro, { + cwd: exec.cwd('removal'), + cli: [], + remove: false +}); + +test('snapshots remain if they are still used', macro, { + cwd: exec.cwd('removal'), + cli: ['--update-snapshots'], + remove: false, + env: { + TEMPLATE: 'true' + }, + async checkRun(t, run) { + await t.notThrowsAsync(run, 'Expected fixture not to throw'); + const result = await run; + t.snapshot(result.stats.passed, 'passed tests'); + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); + } +}); + +test('snapshots remain if tests run with --match', macro, { + cwd: exec.cwd('removal'), + cli: ['--update-snapshots', '--match=\'*snapshot*\''], + remove: false, + checkRun: async (t, run) => { + const result = await t.throwsAsync(run, undefined, 'Expected fixture to throw'); + t.snapshot(exec.cleanOutput(result.stderr), 'stderr'); + } +}); + +test('snapshots remain if tests selected by line numbers', macro, { + cwd: exec.cwd('removal'), + cli: ['test.js:3-12', '--update-snapshots'], + remove: false, + checkRun: async (t, run) => { + const result = await t.throwsAsync(run, undefined, 'Expected fixture to throw'); + t.snapshot(exec.cleanOutput(result.stderr), 'stderr'); + } +}); + +test('snapshots remain if using test.only', macro, { + cwd: exec.cwd('only-test'), + cli: ['--update-snapshots'], + remove: false, + checkRun: async (t, run) => { + await t.notThrowsAsync(run, 'Expected fixture not to throw'); + const result = await run; + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); + } +}); + +test('snapshots remain if tests are skipped', macro, { + cwd: exec.cwd('skipped-tests'), + cli: ['--update-snapshots'], + remove: false, + checkRun: async (t, run) => { + await t.notThrowsAsync(run, 'Expected fixture not to throw'); + const result = await run; + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); + } +}); + +test('snapshots remain if snapshot assertions are skipped', macro, { + cwd: exec.cwd('skipped-snapshots'), + cli: ['--update-snapshots'], + remove: false, + checkRun: async (t, run) => { + const result = await t.throwsAsync(run, { + message: /Snapshot assertions cannot be skipped when updating snapshots/ + }, 'Expected fixture to throw'); + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); + } +}); + +test('snapshots remain if used in a discarded try()', macro, { + cwd: exec.cwd('try'), + cli: ['--update-snapshots'], + remove: false +}); + +test('snapshots remain if skipped in a discarded try()', macro, { + cwd: exec.cwd('skipped-snapshots-in-try'), + cli: ['--update-snapshots'], + remove: false, + checkRun: async (t, run) => { + await t.notThrowsAsync(run, 'Expected fixture not to throw'); + const result = await run; + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); + } +}); diff --git a/test/snapshot-updates/fixtures/contains-skip-assertion.js b/test/snapshot-updates/fixtures/contains-skip-assertion.js new file mode 100644 index 000000000..960e34451 --- /dev/null +++ b/test/snapshot-updates/fixtures/contains-skip-assertion.js @@ -0,0 +1,10 @@ +const test = require('ava'); + +test('always failing snapshot', t => { + t.snapshot(Date.now()); +}); + +test('skipped assertion', t => { + t.snapshot.skip(Date.now()); // eslint-disable-line ava/no-skip-assert + t.pass(); +}); diff --git a/test/snapshot-updates/fixtures/contains-skip-assertion.js.md b/test/snapshot-updates/fixtures/contains-skip-assertion.js.md new file mode 100644 index 000000000..009f68b79 --- /dev/null +++ b/test/snapshot-updates/fixtures/contains-skip-assertion.js.md @@ -0,0 +1,11 @@ +# Snapshot report for `contains-skip-assertion.js` + +The actual snapshot is saved in `contains-skip-assertion.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## always failing snapshot + +> Snapshot 1 + + 1607992742963 diff --git a/test/snapshot-updates/fixtures/contains-skip-assertion.js.snap b/test/snapshot-updates/fixtures/contains-skip-assertion.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..4e5b373882185abd58e6697ef5803302b611f554 GIT binary patch literal 97 zcmZ<^b5sbwf=oE`=a8s-SXWW3}C<0KwJR9*Osz_3qVb8nk4~&5N0CWf=bpQYW literal 0 HcmV?d00001 diff --git a/test/snapshot-updates/snapshots/test.js.md b/test/snapshot-updates/snapshots/test.js.md index 2d85be886..a64f2b4ab 100644 --- a/test/snapshot-updates/snapshots/test.js.md +++ b/test/snapshot-updates/snapshots/test.js.md @@ -66,3 +66,31 @@ Generated by [AVA](https://avajs.dev). > Snapshot 1 'Snapshots cannot be updated when selecting specific tests by their line number.' + +## cannot update snapshots when skipping snapshot assertions + +> failed tests + + [ + { + file: 'contains-skip-assertion.js', + title: 'skipped assertion', + }, + ] + +> passed tests + + [ + { + file: 'contains-skip-assertion.js', + title: 'always failing snapshot', + }, + ] + +> files where snapshots could not be updated + + [ + { + file: 'contains-skip-assertion.js', + }, + ] diff --git a/test/snapshot-updates/snapshots/test.js.snap b/test/snapshot-updates/snapshots/test.js.snap index fd04d3f956cd3d8b10f2bd5f51ba46fb61780c7a..5b36884500faa1064f1fb67a9859c7b7415f3630 100644 GIT binary patch literal 524 zcmV+n0`vVrRzV-Db1S1h{THLjKqD^X))0D)K_juzkywvT&KA-Q43r|l-%dW>MvB|t3n z;b`&wHHJLT^CoN-3;wr<87x}N2*Kt|5PTMh{T!#iVq*IJubr{r^a@LXPG+#^2Podg z48cAu3=E7nJ}Yc?+I->m^80SL{4PFb28+tGLhuhLp25bzz|6o7b|WJzgCL`Sa9(0T zaYlYgu|jfUUS58QLQ<+iX+cV2NotBhc}8lULUC$NYH~?tUb;eYL27blT4u6BNop}r zeNv@DNk(dBkwQ*pUaCS~X>L+#ksgZSmIMvYO)N>yz%(0S3YWJw~wT5+D}( zaJ2aT8bhAvc@s8^1^?T_3>GbBgkW$F){uA*UngQdWEGxCo@>|0~Bv# zhF~8S1_ovZcCgKitPFyT{=s>P1;rWpCB+KKiFtYXB??KY3Z(@pi6yBi3gsE8c?!j; zIjPAdnR)37#RaL!nQ58H3MHwQTY&K6{3Nz2Shg>eLu^Ycm)GxLgd zi?cHe^s { const result = await t.throwsAsync(exec.fixture(['contains-skip.js:4', '-u'])); t.snapshot(exec.cleanOutput(result.stderr)); }); + +test('cannot update snapshots when skipping snapshot assertions', async t => { + const result = await t.throwsAsync(exec.fixture(['contains-skip-assertion.js', '-u'], {env: {AVA_FORCE_CI: 'not-ci'}})); + t.snapshot(result.stats.failed, 'failed tests'); + t.snapshot(result.stats.passed, 'passed tests'); + t.snapshot(result.stats.unsavedSnapshots, 'files where snapshots could not be updated'); +});