Skip to content

Commit 7fadc34

Browse files
impalernovemberborn
authored andcommitted
Improve where snapshots are stored (#1489)
* Allow users to configure the snapshot location * Resolve the location through source-maps, in case the tests were precompiled into a build directory, e.g. via TypeScript
1 parent de5e8b9 commit 7fadc34

File tree

23 files changed

+247
-7
lines changed

23 files changed

+247
-7
lines changed

docs/recipes/typescript.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ Create a [`tsconfig.json`](https://github.com/Microsoft/TypeScript/wiki/tsconfig
1818
{
1919
"compilerOptions": {
2020
"module": "commonjs",
21-
"target": "es2015"
21+
"target": "es2015",
22+
"sourceMaps": true
2223
}
2324
}
2425
```

lib/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ exports.run = () => {
149149
timeout: conf.timeout,
150150
concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
151151
updateSnapshots: conf.updateSnapshots,
152+
snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
152153
color: conf.color
153154
});
154155

lib/main.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ const runner = new Runner({
1313
match: opts.match,
1414
projectDir: opts.projectDir,
1515
serial: opts.serial,
16-
updateSnapshots: opts.updateSnapshots
16+
updateSnapshots: opts.updateSnapshots,
17+
snapshotDir: opts.snapshotDir
1718
});
1819

1920
worker.setRunner(runner);

lib/runner.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ class Runner extends EventEmitter {
5252
this.projectDir = options.projectDir;
5353
this.serial = options.serial;
5454
this.updateSnapshots = options.updateSnapshots;
55+
this.snapshotDir = options.snapshotDir;
5556

5657
this.hasStarted = false;
5758
this.results = [];
@@ -184,6 +185,8 @@ class Runner extends EventEmitter {
184185
compareTestSnapshot(options) {
185186
if (!this.snapshots) {
186187
this.snapshots = snapshotManager.load({
188+
file: this.file,
189+
fixedLocation: this.snapshotDir,
187190
name: path.basename(this.file),
188191
projectDir: this.projectDir,
189192
relFile: path.relative(this.projectDir, this.file),

lib/snapshot-manager.js

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ const indentString = require('indent-string');
1111
const makeDir = require('make-dir');
1212
const md5Hex = require('md5-hex');
1313
const Buffer = require('safe-buffer').Buffer;
14+
const convertSourceMap = require('convert-source-map');
1415

1516
const concordanceOptions = require('./concordance-options').snapshotManager;
1617

@@ -355,8 +356,13 @@ class Manager {
355356
}
356357
}
357358

358-
function determineSnapshotDir(projectDir, testDir) {
359-
const parts = new Set(path.relative(projectDir, testDir).split(path.sep));
359+
function determineSnapshotDir(options) {
360+
const testDir = determineSourceMappedDir(options);
361+
if (options.fixedLocation) {
362+
const relativeTestLocation = path.relative(options.projectDir, testDir);
363+
return path.join(options.fixedLocation, relativeTestLocation);
364+
}
365+
const parts = new Set(path.relative(options.projectDir, testDir).split(path.sep));
360366
if (parts.has('__tests__')) {
361367
return path.join(testDir, '__snapshots__');
362368
} else if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns
@@ -365,8 +371,21 @@ function determineSnapshotDir(projectDir, testDir) {
365371
return testDir;
366372
}
367373

374+
function determineSourceMappedDir(options) {
375+
const source = tryRead(options.file).toString();
376+
const converter = convertSourceMap.fromSource(source) || convertSourceMap.fromMapFileSource(source, options.testDir);
377+
if (converter) {
378+
const map = converter.toObject();
379+
const firstSource = `${map.sourceRoot || ''}${map.sources[0]}`;
380+
const sourceFile = path.resolve(options.testDir, firstSource);
381+
return path.dirname(sourceFile);
382+
}
383+
384+
return options.testDir;
385+
}
386+
368387
function load(options) {
369-
const dir = determineSnapshotDir(options.projectDir, options.testDir);
388+
const dir = determineSnapshotDir(options);
370389
const reportFile = `${options.name}.md`;
371390
const snapFile = `${options.name}.snap`;
372391
const snapPath = path.join(dir, snapFile);

readme.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,20 @@ You can then check your code. If the change was intentional you can use the `--u
10451045
$ ava --update-snapshots
10461046
```
10471047

1048+
You can specify a fixed location for storing the snapshot files in AVA's [`package.json` configuration](#configuration):
1049+
1050+
```json
1051+
{
1052+
"ava": {
1053+
"snapshotLocation": "custom-directory"
1054+
}
1055+
}
1056+
```
1057+
1058+
The snapshot files will be saved in a directory structure that mirrors that of your test files.
1059+
1060+
If you are running AVA against precompiled test files, AVA will try and use source maps to determine the location of the original files. Snapshots will be stored next to these files, following the same rules as if AVA had executed the original files directly. This is great if you're writing your tests in TypeScript (see our [TypeScript recipe](docs/recipes/typescript.md)).
1061+
10481062
### Skipping assertions
10491063

10501064
Any assertion can be skipped using the `skip` modifier. Skipped assertions are still counted, so there is no need to change your planned assertion count.

test/assert.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -755,10 +755,12 @@ test('.snapshot()', t => {
755755

756756
const projectDir = path.join(__dirname, 'fixture');
757757
const manager = snapshotManager.load({
758-
projectDir,
759-
testDir: projectDir,
758+
file: __filename,
760759
name: 'assert.js',
760+
projectDir,
761761
relFile: 'test/assert.js',
762+
fixedLocation: null,
763+
testDir: projectDir,
762764
updating
763765
});
764766
const setup = title => {

test/cli.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -710,6 +710,83 @@ test('legacy snapshot files are reported to the console', t => {
710710
});
711711
});
712712

713+
test('snapshots infer their location from sourcemaps', t => {
714+
t.plan(8);
715+
const relativeFixtureDir = path.join('fixture/snapshots/test-sourcemaps');
716+
const snapDirStructure = [
717+
'src',
718+
'src/test/snapshots',
719+
'src/feature/__tests__/__snapshots__'
720+
];
721+
const snapFixtureFilePaths = snapDirStructure
722+
.map(snapRelativeDir => {
723+
const snapPath = path.join(__dirname, relativeFixtureDir, snapRelativeDir);
724+
return [
725+
path.join(snapPath, 'test.js.md'),
726+
path.join(snapPath, 'test.js.snap')
727+
];
728+
})
729+
.reduce((a, b) => a.concat(b), []);
730+
const removeExistingSnapFixtureFiles = snapPath => {
731+
try {
732+
fs.unlinkSync(snapPath);
733+
} catch (err) {
734+
if (err.code !== 'ENOENT') {
735+
throw err;
736+
}
737+
}
738+
};
739+
snapFixtureFilePaths.forEach(removeExistingSnapFixtureFiles);
740+
const verifySnapFixtureFiles = relFilePath => {
741+
t.true(fs.existsSync(relFilePath));
742+
};
743+
execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => {
744+
t.ifError(err);
745+
snapFixtureFilePaths.forEach(verifySnapFixtureFiles);
746+
t.match(stderr, /6 passed/);
747+
t.end();
748+
});
749+
});
750+
751+
test('snapshots resolved location from "snapshotDir" in AVA config', t => {
752+
t.plan(8);
753+
const relativeFixtureDir = 'fixture/snapshots/test-snapshot-location';
754+
const snapDir = 'snapshot-fixtures';
755+
const snapDirStructure = [
756+
'src',
757+
'src/feature',
758+
'src/feature/nested-feature'
759+
];
760+
const snapFixtureFilePaths = snapDirStructure
761+
.map(snapRelativeDir => {
762+
const snapPath = path.join(__dirname, relativeFixtureDir, snapDir, snapRelativeDir);
763+
return [
764+
path.join(snapPath, 'test.js.md'),
765+
path.join(snapPath, 'test.js.snap')
766+
];
767+
})
768+
.reduce((a, b) => a.concat(b), []);
769+
const removeExistingSnapFixtureFiles = snapPath => {
770+
try {
771+
fs.unlinkSync(snapPath);
772+
} catch (err) {
773+
if (err.code !== 'ENOENT') {
774+
throw err;
775+
}
776+
}
777+
};
778+
snapFixtureFilePaths.forEach(removeExistingSnapFixtureFiles);
779+
const verifySnapFixtureFiles = relFilePath => {
780+
t.true(fs.existsSync(relFilePath));
781+
};
782+
execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => {
783+
t.ifError(err);
784+
snapFixtureFilePaths.forEach(verifySnapFixtureFiles);
785+
t.match(stderr, /6 passed/);
786+
t.end();
787+
});
788+
});
789+
713790
test('--no-color disables formatting colors', t => {
714791
execCli(['--no-color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout, stderr) => {
715792
t.ok(err);
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"ava": {
3+
"files": [
4+
"src/**/*test.js"
5+
],
6+
"snapshotDir": "snapshot-fixtures"
7+
}
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../../../..';
2+
3+
test('test nested feature title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 42});
7+
});
8+
9+
test('another nested feature test', t => {
10+
t.snapshot(new Map());
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../../..';
2+
3+
test('test feature title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 42});
7+
});
8+
9+
test('another feature test', t => {
10+
t.snapshot(new Map());
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../..';
2+
3+
test('test title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 42});
7+
});
8+
9+
test('another test', t => {
10+
t.snapshot(new Map());
11+
});

test/fixture/snapshots/test-sourcemaps/build/feature/__tests__/test.js

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixture/snapshots/test-sourcemaps/build/feature/__tests__/test.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixture/snapshots/test-sourcemaps/build/test.js

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixture/snapshots/test-sourcemaps/build/test.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixture/snapshots/test-sourcemaps/build/test/test.js

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixture/snapshots/test-sourcemaps/build/test/test.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"ava": {
3+
"files": [
4+
"build/**/*test.js"
5+
]
6+
}
7+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../../../..'
2+
3+
test('feature test title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 40});
7+
});
8+
9+
test('another feature test', t => {
10+
t.snapshot(new Map());
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../..';
2+
3+
test('top level test title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 42});
7+
});
8+
9+
test('another top level test', t => {
10+
t.snapshot(new Map());
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import test from '../../../../../..';
2+
3+
test('test title', t => {
4+
t.snapshot({foo: 'bar'});
5+
6+
t.snapshot({answer: 43});
7+
});
8+
9+
test('another test', t => {
10+
t.snapshot(new Map());
11+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"compilerOptions": {
3+
"target": "es6",
4+
"module": "commonjs",
5+
"outDir": "build",
6+
"sourceMap": true
7+
},
8+
"include": [
9+
"src/**/*test.ts"
10+
]
11+
}

0 commit comments

Comments
 (0)