Skip to content

Commit

Permalink
Improve where snapshots are stored (#1489)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
impaler authored and novemberborn committed Sep 2, 2017
1 parent de5e8b9 commit 7fadc34
Show file tree
Hide file tree
Showing 23 changed files with 247 additions and 7 deletions.
3 changes: 2 additions & 1 deletion docs/recipes/typescript.md
Expand Up @@ -18,7 +18,8 @@ Create a [`tsconfig.json`](https://github.com/Microsoft/TypeScript/wiki/tsconfig
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015"
"target": "es2015",
"sourceMaps": true
}
}
```
Expand Down
1 change: 1 addition & 0 deletions lib/cli.js
Expand Up @@ -149,6 +149,7 @@ exports.run = () => {
timeout: conf.timeout,
concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0,
updateSnapshots: conf.updateSnapshots,
snapshotDir: conf.snapshotDir ? path.resolve(projectDir, conf.snapshotDir) : null,
color: conf.color
});

Expand Down
3 changes: 2 additions & 1 deletion lib/main.js
Expand Up @@ -13,7 +13,8 @@ const runner = new Runner({
match: opts.match,
projectDir: opts.projectDir,
serial: opts.serial,
updateSnapshots: opts.updateSnapshots
updateSnapshots: opts.updateSnapshots,
snapshotDir: opts.snapshotDir
});

worker.setRunner(runner);
Expand Down
3 changes: 3 additions & 0 deletions lib/runner.js
Expand Up @@ -52,6 +52,7 @@ class Runner extends EventEmitter {
this.projectDir = options.projectDir;
this.serial = options.serial;
this.updateSnapshots = options.updateSnapshots;
this.snapshotDir = options.snapshotDir;

this.hasStarted = false;
this.results = [];
Expand Down Expand Up @@ -184,6 +185,8 @@ class Runner extends EventEmitter {
compareTestSnapshot(options) {
if (!this.snapshots) {
this.snapshots = snapshotManager.load({
file: this.file,
fixedLocation: this.snapshotDir,
name: path.basename(this.file),
projectDir: this.projectDir,
relFile: path.relative(this.projectDir, this.file),
Expand Down
25 changes: 22 additions & 3 deletions lib/snapshot-manager.js
Expand Up @@ -11,6 +11,7 @@ const indentString = require('indent-string');
const makeDir = require('make-dir');
const md5Hex = require('md5-hex');
const Buffer = require('safe-buffer').Buffer;
const convertSourceMap = require('convert-source-map');

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

Expand Down Expand Up @@ -355,8 +356,13 @@ class Manager {
}
}

function determineSnapshotDir(projectDir, testDir) {
const parts = new Set(path.relative(projectDir, testDir).split(path.sep));
function determineSnapshotDir(options) {
const testDir = determineSourceMappedDir(options);
if (options.fixedLocation) {
const relativeTestLocation = path.relative(options.projectDir, testDir);
return path.join(options.fixedLocation, relativeTestLocation);
}
const parts = new Set(path.relative(options.projectDir, testDir).split(path.sep));
if (parts.has('__tests__')) {
return path.join(testDir, '__snapshots__');
} else if (parts.has('test') || parts.has('tests')) { // Accept tests, even though it's not in the default test patterns
Expand All @@ -365,8 +371,21 @@ function determineSnapshotDir(projectDir, testDir) {
return testDir;
}

function determineSourceMappedDir(options) {
const source = tryRead(options.file).toString();
const converter = convertSourceMap.fromSource(source) || convertSourceMap.fromMapFileSource(source, options.testDir);
if (converter) {
const map = converter.toObject();
const firstSource = `${map.sourceRoot || ''}${map.sources[0]}`;
const sourceFile = path.resolve(options.testDir, firstSource);
return path.dirname(sourceFile);
}

return options.testDir;
}

function load(options) {
const dir = determineSnapshotDir(options.projectDir, options.testDir);
const dir = determineSnapshotDir(options);
const reportFile = `${options.name}.md`;
const snapFile = `${options.name}.snap`;
const snapPath = path.join(dir, snapFile);
Expand Down
14 changes: 14 additions & 0 deletions readme.md
Expand Up @@ -1045,6 +1045,20 @@ You can then check your code. If the change was intentional you can use the `--u
$ ava --update-snapshots
```

You can specify a fixed location for storing the snapshot files in AVA's [`package.json` configuration](#configuration):

```json
{
"ava": {
"snapshotLocation": "custom-directory"
}
}
```

The snapshot files will be saved in a directory structure that mirrors that of your test files.

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)).

### Skipping assertions

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.
Expand Down
6 changes: 4 additions & 2 deletions test/assert.js
Expand Up @@ -755,10 +755,12 @@ test('.snapshot()', t => {

const projectDir = path.join(__dirname, 'fixture');
const manager = snapshotManager.load({
projectDir,
testDir: projectDir,
file: __filename,
name: 'assert.js',
projectDir,
relFile: 'test/assert.js',
fixedLocation: null,
testDir: projectDir,
updating
});
const setup = title => {
Expand Down
77 changes: 77 additions & 0 deletions test/cli.js
Expand Up @@ -710,6 +710,83 @@ test('legacy snapshot files are reported to the console', t => {
});
});

test('snapshots infer their location from sourcemaps', t => {
t.plan(8);
const relativeFixtureDir = path.join('fixture/snapshots/test-sourcemaps');
const snapDirStructure = [
'src',
'src/test/snapshots',
'src/feature/__tests__/__snapshots__'
];
const snapFixtureFilePaths = snapDirStructure
.map(snapRelativeDir => {
const snapPath = path.join(__dirname, relativeFixtureDir, snapRelativeDir);
return [
path.join(snapPath, 'test.js.md'),
path.join(snapPath, 'test.js.snap')
];
})
.reduce((a, b) => a.concat(b), []);
const removeExistingSnapFixtureFiles = snapPath => {
try {
fs.unlinkSync(snapPath);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
};
snapFixtureFilePaths.forEach(removeExistingSnapFixtureFiles);
const verifySnapFixtureFiles = relFilePath => {
t.true(fs.existsSync(relFilePath));
};
execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => {
t.ifError(err);
snapFixtureFilePaths.forEach(verifySnapFixtureFiles);
t.match(stderr, /6 passed/);
t.end();
});
});

test('snapshots resolved location from "snapshotDir" in AVA config', t => {
t.plan(8);
const relativeFixtureDir = 'fixture/snapshots/test-snapshot-location';
const snapDir = 'snapshot-fixtures';
const snapDirStructure = [
'src',
'src/feature',
'src/feature/nested-feature'
];
const snapFixtureFilePaths = snapDirStructure
.map(snapRelativeDir => {
const snapPath = path.join(__dirname, relativeFixtureDir, snapDir, snapRelativeDir);
return [
path.join(snapPath, 'test.js.md'),
path.join(snapPath, 'test.js.snap')
];
})
.reduce((a, b) => a.concat(b), []);
const removeExistingSnapFixtureFiles = snapPath => {
try {
fs.unlinkSync(snapPath);
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
};
snapFixtureFilePaths.forEach(removeExistingSnapFixtureFiles);
const verifySnapFixtureFiles = relFilePath => {
t.true(fs.existsSync(relFilePath));
};
execCli([], {dirname: relativeFixtureDir}, (err, stdout, stderr) => {
t.ifError(err);
snapFixtureFilePaths.forEach(verifySnapFixtureFiles);
t.match(stderr, /6 passed/);
t.end();
});
});

test('--no-color disables formatting colors', t => {
execCli(['--no-color', '--verbose', 'formatting-color.js'], {dirname: 'fixture'}, (err, stdout, stderr) => {
t.ok(err);
Expand Down
8 changes: 8 additions & 0 deletions test/fixture/snapshots/test-snapshot-location/package.json
@@ -0,0 +1,8 @@
{
"ava": {
"files": [
"src/**/*test.js"
],
"snapshotDir": "snapshot-fixtures"
}
}
@@ -0,0 +1,11 @@
import test from '../../../../../../..';

test('test nested feature title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 42});
});

test('another nested feature test', t => {
t.snapshot(new Map());
});
11 changes: 11 additions & 0 deletions test/fixture/snapshots/test-snapshot-location/src/feature/test.js
@@ -0,0 +1,11 @@
import test from '../../../../../..';

test('test feature title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 42});
});

test('another feature test', t => {
t.snapshot(new Map());
});
11 changes: 11 additions & 0 deletions test/fixture/snapshots/test-snapshot-location/src/test.js
@@ -0,0 +1,11 @@
import test from '../../../../..';

test('test title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 42});
});

test('another test', t => {
t.snapshot(new Map());
});

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/build/test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions test/fixture/snapshots/test-sourcemaps/build/test.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/build/test/test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/package.json
@@ -0,0 +1,7 @@
{
"ava": {
"files": [
"build/**/*test.js"
]
}
}
@@ -0,0 +1,11 @@
import test from '../../../../../../..'

test('feature test title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 40});
});

test('another feature test', t => {
t.snapshot(new Map());
});
11 changes: 11 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/src/test.ts
@@ -0,0 +1,11 @@
import test from '../../../../..';

test('top level test title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 42});
});

test('another top level test', t => {
t.snapshot(new Map());
});
11 changes: 11 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/src/test/test.ts
@@ -0,0 +1,11 @@
import test from '../../../../../..';

test('test title', t => {
t.snapshot({foo: 'bar'});

t.snapshot({answer: 43});
});

test('another test', t => {
t.snapshot(new Map());
});
11 changes: 11 additions & 0 deletions test/fixture/snapshots/test-sourcemaps/tsconfig.json
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "build",
"sourceMap": true
},
"include": [
"src/**/*test.ts"
]
}

0 comments on commit 7fadc34

Please sign in to comment.