-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Snapshot location #1489
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Snapshot location #1489
Changes from 6 commits
f2471c8
2c344fa
5c65c9e
9e98219
548c6b6
d239882
12f1ff8
902f0d3
3ec0217
e3fc508
6282999
de453b3
22838a9
b18db03
61af5da
982a0a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -144,6 +144,7 @@ exports.run = () => { | |
timeout: conf.timeout, | ||
concurrency: conf.concurrency ? parseInt(conf.concurrency, 10) : 0, | ||
updateSnapshots: conf.updateSnapshots, | ||
snapshotLocation: conf.snapshotLocation, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps resolve the absolute path here: Total bikeshed, but how do you feel about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure no preference here from me projectDir, I think I saw Dir used somewhere else :) |
||
color: conf.color | ||
}); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,6 +52,7 @@ class Runner extends EventEmitter { | |
this.projectDir = options.projectDir; | ||
this.serial = options.serial; | ||
this.updateSnapshots = options.updateSnapshots; | ||
this.snapshotLocation = options.snapshotLocation; | ||
|
||
this.hasStarted = false; | ||
this.results = []; | ||
|
@@ -187,7 +188,8 @@ class Runner extends EventEmitter { | |
projectDir: this.projectDir, | ||
relFile: path.relative(this.projectDir, this.file), | ||
testDir: path.dirname(this.file), | ||
updating: this.updateSnapshots | ||
updating: this.updateSnapshots, | ||
location: this.snapshotLocation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes I like it. |
||
}); | ||
this.emit('dependency', this.snapshots.snapPath); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,6 +81,18 @@ function tryRead(file) { | |
} | ||
} | ||
|
||
function tryReadSourceMap(file) { | ||
const content = tryRead(file); | ||
if (content) { | ||
try { | ||
return JSON.parse(content.toString()); | ||
} catch (err) { | ||
console.error('Parse error on snapshot sourcemap', file); | ||
throw err; | ||
} | ||
} | ||
} | ||
|
||
function withoutLineEndings(buffer) { | ||
let newLength = buffer.byteLength - 1; | ||
while (buffer[newLength] === 0x0A || buffer[newLength] === 0x0D) { | ||
|
@@ -355,7 +367,16 @@ class Manager { | |
} | ||
} | ||
|
||
function determineSnapshotDir(projectDir, testDir) { | ||
function determineSnapshotDir(options) { | ||
const projectDir = options.projectDir; | ||
const testDir = determineSourceMappedDir(options); | ||
if (options.location) { | ||
// "snapshotLocation" in ava package.json config, snapshots derive a location | ||
// relative from the source testDir not the preset "__snapshots__" folder names | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, I guess the comment highlighted some of the ambiguitiy, please checkout the latest revision. |
||
const resolvedLocation = path.resolve(options.projectDir, options.location); | ||
const relativeTestLocation = options.testDir.replace(options.projectDir, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this refer to Also, you should be able to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, please checkout the latest revision. |
||
return path.join(resolvedLocation, relativeTestLocation); | ||
} | ||
const parts = new Set(path.relative(projectDir, testDir).split(path.sep)); | ||
if (parts.has('__tests__')) { | ||
return path.join(testDir, '__snapshots__'); | ||
|
@@ -365,8 +386,31 @@ function determineSnapshotDir(projectDir, testDir) { | |
return testDir; | ||
} | ||
|
||
function determineSourceMappedDir(options) { | ||
const sourceMapFilePath = path.join(options.testDir, `${options.name}.map`); | ||
const sourceMapContent = tryReadSourceMap(sourceMapFilePath); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use https://www.npmjs.com/package/convert-source-map, like in nyc: https://github.com/istanbuljs/nyc/blob/fb3ab927796963379edfddd4d2385002fa236f65/lib/source-maps.js#L16 This will support inline source maps as well as source map references. It does presume that these references are in the source file, but I think that's a fair assumption. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed thanks a bunch I am new to that library very cool. Seems to work well with the inline version in my testing https://github.com/impaler/ava-snapshot-location/tree/master/inline-sourcemaps |
||
|
||
if (sourceMapContent) { | ||
const targetFilename = path.basename(sourceMapContent.file, 'js'); | ||
const testFileSource = sourceMapContent.sources.find(file => { | ||
const fileName = file.replace(path.extname(targetFilename), ''); | ||
return fileName.indexOf(targetFilename) > -1; | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic isn't quite right. According to the source map "spec" I couldn't quite find a library which exports the correct source resolution. I think this will be sufficient for our use case (though I haven't tested it): // Assume there is but a single source. Multiple sources imply multiple possible
// snapshot locations, which won't work.
const firstSource = sourceMapContent.sources[0];
const rootedSource = sourceMapContent.sourceRoot ?
sourceMapContent.sourceRoot + firstSource :
firstSource;
// `convert-source-map` doesn't provide the path to the source map file, if the
// source map came from such a file. Assuming that if it did, it is stored right
// next to the test file. Or, alternatively, if it is stored elsewhere,
// resolving `rootedSource` relative to the test file results in a path that
// is the same as if it had been resolved relative to the source map file.
// Note that `options.relFile` is relative to `options.projectDir`.
const sourceFile = path.resolve(path.join(options.projectDir, options.relFile), rootedSource);
return path.dirname(sourceFile); I'm not sure how much we need to guard against malformed source maps (e.g. no or empty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks I learned a few things here. I guess we wont be able to answer how much more to guard without trying more than just typescript. With the way things are layed out it shouldn't be too hard to expand on this. |
||
if (testFileSource) { | ||
if (options.location) { | ||
const basePath = path.join(options.projectDir, options.location); | ||
const sourceTestFilePath = path.dirname(path.resolve(options.testDir, testFileSource)); | ||
const relativePath = sourceTestFilePath.replace(options.projectDir, ''); | ||
return path.join(basePath, relativePath); | ||
} | ||
return path.dirname(path.resolve(options.testDir, testFileSource)); | ||
} | ||
} | ||
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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1041,6 +1041,20 @@ You can then check your code. If the change was intentional you can use the `--u | |
$ ava --update-snapshots | ||
``` | ||
|
||
You can also set a custom location for the generated snapshot fixtures. This can be configured in the `package.json#ava` configuration for every test run. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
```json | ||
{ | ||
"ava": { | ||
"snapshotLocation": "custom-directory" | ||
} | ||
} | ||
``` | ||
|
||
Snapshots fixtures will be then saved in a directory structure that reflects the test sourcecode. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
If you are transpiling the tests with sourcemaps, ava will derive the test location from the `*.js.map` files, see more in the [TypeScript](docs/recipes/typescript.md) recipe. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
### 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. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -680,6 +680,49 @@ test('legacy snapshot files are reported to the console', t => { | |
}); | ||
}); | ||
|
||
test('snapshots infer their location from sourcemaps', t => { | ||
t.plan(8); | ||
const dirname = path.join('fixture/snapshots/test-sourcemaps'); | ||
const testLocationTypes = [ | ||
'src', | ||
'src/test/snapshots', | ||
'src/feature/__tests__/__snapshots__' | ||
]; | ||
const removeExists = relFilePath => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't remove anything though? You should remove these files at least prior to running the test, otherwise on local setups you won't be able to tell if the code has broken if they're persisted from previous runs. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes this is wrong, I have updated this be a little more dry 12f1ff8#diff-583d616f4b01bf1fa153e71c0cef7de7R704. If there are more integration tests on projects like this I can think of some better ways to do it, like maybe the project should be first duplicated to a /tmp location and so on. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not too concerned about how DRY this is. The other test looks good, just need to take that same approach here. |
||
const snapshotFolderPath = path.join(__dirname, dirname, relFilePath); | ||
t.true(fs.existsSync(path.join(snapshotFolderPath, 'test.js.md'))); | ||
t.true(fs.existsSync(path.join(snapshotFolderPath, 'test.js.snap'))); | ||
}; | ||
execCli([], {dirname}, (err, stdout, stderr) => { | ||
t.ifError(err); | ||
testLocationTypes.forEach(removeExists); | ||
t.match(stderr, /6 passed/); | ||
t.end(); | ||
}); | ||
}); | ||
|
||
test('snapshots resolved location from "snapshotLocation" in ava config', t => { | ||
t.plan(8); | ||
const dirname = path.join('fixture/snapshots/test-snapshot-location'); | ||
const snapshotLocation = 'snapshot-fixtures'; | ||
const testLocationTypes = [ | ||
'src', | ||
'src/feature', | ||
'src/feature/nested-feature' | ||
]; | ||
const removeExists = relFilePath => { | ||
const snapshotFolderPath = path.join(__dirname, dirname, snapshotLocation, relFilePath); | ||
t.true(fs.existsSync(path.join(snapshotFolderPath, 'test.js.md'))); | ||
t.true(fs.existsSync(path.join(snapshotFolderPath, 'test.js.snap'))); | ||
}; | ||
execCli([], {dirname}, (err, stdout, stderr) => { | ||
t.ifError(err); | ||
testLocationTypes.forEach(removeExists); | ||
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); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"ava": { | ||
"files": [ | ||
"src/**/*test.js" | ||
], | ||
"snapshotLocation": "snapshot-fixtures" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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.
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.
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.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"scripts": { | ||
"build": "tsc" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this assume (Also, going by the GitHub output here, the indentation seems off in this file and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it does, I don't think there will be any need to run build here for the tests anyway. So I fixed the indentation and removed "scripts". |
||
}, | ||
"ava": { | ||
"files": [ | ||
"build/**/*test.js" | ||
] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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()); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "es6", | ||
"module": "commonjs", | ||
"outDir": "build", | ||
"sourceMap": true | ||
}, | ||
"include": [ | ||
"src/**/*test.ts" | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than this paragraph, update the sample
tsconfig.json
to enable source maps. Honestly it should always be enabled, since it helps AVA show correct stack traces and code snippets when assertions fail.