mirror of https://github.com/facebook/jest.git
Add resolver for custom snapshots paths (#6143)
* Add resolver for snapshot paths * Remove snapshotResolver from versioned doc * Simplify resolveSnapshotPath * Make error feedback more actionable * Assert test result before snapshot file Should help troubleshoot failing tests on CI * Run integration test with correct flags Same as the base snapshot.test.js * Add tests for malformed resolver module * Resolve paths in tests like implementation To avoid cross-platform mismatches * Rename integration-tests => e2e * Fix code review feedback * Add changelog entry * Fix review comments for e2e/__tests__/snapshot_resolver.test.js * Fix prettier error * Move changelog entry to correct place * Move up type import above normal imports * Add consistency check * Update snapshot_resolver.js
This commit is contained in:
parent
2b216e4d06
commit
8eefa9690e
|
@ -6,6 +6,7 @@
|
|||
- `[jest-haste-map]` Add `getFileIterator` to `HasteFS` for faster file iteration ([#7010](https://github.com/facebook/jest/pull/7010)).
|
||||
- `[jest-worker]` [**BREAKING**] Add functionality to call a `setup` method in the worker before the first call and a `teardown` method when ending the farm ([#7014](https://github.com/facebook/jest/pull/7014)).
|
||||
- `[jest-config]` [**BREAKING**] Set default `notifyMode` to `failure-change` ([#7024](https://github.com/facebook/jest/pull/7024))
|
||||
- `[jest-snapshot]` Enable configurable snapshot paths ([#6143](https://github.com/facebook/jest/pull/6143))
|
||||
|
||||
### Fixes
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = {
|
|||
setupTestFrameworkScriptFile: null,
|
||||
skipFilter: false,
|
||||
skipNodeResolution: false,
|
||||
snapshotResolver: null,
|
||||
snapshotSerializers: [],
|
||||
testEnvironment: 'node',
|
||||
testEnvironmentOptions: {},
|
||||
|
|
|
@ -642,6 +642,29 @@ If you want this path to be [relative to the root directory of your project](#ro
|
|||
|
||||
For example, Jest ships with several plug-ins to `jasmine` that work by monkey-patching the jasmine API. If you wanted to add even more jasmine plugins to the mix (or if you wanted some custom, project-wide matchers for example), you could do so in this module.
|
||||
|
||||
### `snapshotResolver` [string]
|
||||
|
||||
Default: `undefined`
|
||||
|
||||
The path to a module that can resolve test<->snapshot path. This config option lets you customize where Jest stores that snapshot files on disk.
|
||||
|
||||
Example snapshot resolver module:
|
||||
|
||||
```js
|
||||
// my-snapshot-resolver-module
|
||||
module.exports = {
|
||||
// resolves from test to snapshot path
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,
|
||||
|
||||
// resolves from snapshot to test path
|
||||
resolveTestPath: (snapshotFilePath, snapshotExtension) =>
|
||||
snapshotFilePath
|
||||
.replace('__snapshots__', '__tests__')
|
||||
.slice(0, -snapshotExtension.length),
|
||||
};
|
||||
```
|
||||
|
||||
### `snapshotSerializers` [array<string>]
|
||||
|
||||
Default: `[]`
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const runJest = require('../runJest');
|
||||
|
||||
const snapshotDir = path.resolve(
|
||||
__dirname,
|
||||
'../snapshot-resolver/__snapshots__',
|
||||
);
|
||||
const snapshotFile = path.resolve(snapshotDir, 'snapshot.test.js.snap');
|
||||
|
||||
describe('Custom snapshot resolver', () => {
|
||||
const cleanup = () => {
|
||||
if (fs.existsSync(snapshotFile)) {
|
||||
fs.unlinkSync(snapshotFile);
|
||||
}
|
||||
if (fs.existsSync(snapshotDir)) {
|
||||
fs.rmdirSync(snapshotDir);
|
||||
}
|
||||
};
|
||||
|
||||
beforeEach(cleanup);
|
||||
afterAll(cleanup);
|
||||
|
||||
it('Resolves snapshot files using custom resolver', () => {
|
||||
const result = runJest('snapshot-resolver', ['-w=1', '--ci=false']);
|
||||
|
||||
expect(result.stderr).toMatch('1 snapshot written from 1 test suite');
|
||||
|
||||
// $FlowFixMe dynamic require
|
||||
const content = require(snapshotFile);
|
||||
expect(content).toHaveProperty(
|
||||
'snapshots are written to custom location 1',
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
test('snapshots are written to custom location', () => {
|
||||
expect('foobar').toMatchSnapshot();
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,
|
||||
|
||||
resolveTestPath: (snapshotFilePath, snapshotExtension) =>
|
||||
snapshotFilePath
|
||||
.replace('__snapshots__', '__tests__')
|
||||
.slice(0, -snapshotExtension.length),
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"snapshotResolver": "<rootDir>/customSnapshotResolver.js"
|
||||
}
|
||||
}
|
|
@ -42,6 +42,7 @@ module.exports = {
|
|||
'/packages/jest-runtime/src/__tests__/module_dir/',
|
||||
'/packages/jest-runtime/src/__tests__/NODE_PATH_dir',
|
||||
'/packages/jest-snapshot/src/__tests__/plugins',
|
||||
'/packages/jest-snapshot/src/__tests__/fixtures/',
|
||||
'/packages/jest-validate/src/__tests__/fixtures/',
|
||||
'/packages/jest-worker/src/__performance_tests__',
|
||||
'/packages/pretty-format/perf/test.js',
|
||||
|
|
|
@ -13,7 +13,11 @@ import type {Event, TestEntry} from 'types/Circus';
|
|||
|
||||
import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
|
||||
import {formatExecError, formatResultsErrors} from 'jest-message-util';
|
||||
import {SnapshotState, addSerializer} from 'jest-snapshot';
|
||||
import {
|
||||
SnapshotState,
|
||||
addSerializer,
|
||||
buildSnapshotResolver,
|
||||
} from 'jest-snapshot';
|
||||
import {addEventHandler, dispatch, ROOT_DESCRIBE_BLOCK_NAME} from '../state';
|
||||
import {getTestID, getOriginalPromise} from '../utils';
|
||||
import run from '../run';
|
||||
|
@ -96,7 +100,9 @@ export const initialize = ({
|
|||
});
|
||||
|
||||
const {expand, updateSnapshot} = globalConfig;
|
||||
const snapshotState = new SnapshotState(testPath, {
|
||||
const snapshotResolver = buildSnapshotResolver(config);
|
||||
const snapshotPath = snapshotResolver.resolveSnapshotPath(testPath);
|
||||
const snapshotState = new SnapshotState(snapshotPath, {
|
||||
expand,
|
||||
getBabelTraverse,
|
||||
getPrettier,
|
||||
|
|
|
@ -18,6 +18,7 @@ import DependencyResolver from 'jest-resolve-dependencies';
|
|||
import testPathPatternToRegExp from './testPathPatternToRegexp';
|
||||
import {escapePathForRegex} from 'jest-regex-util';
|
||||
import {replaceRootDirInPath} from 'jest-config';
|
||||
import {buildSnapshotResolver} from 'jest-snapshot';
|
||||
|
||||
type SearchResult = {|
|
||||
noSCM?: boolean,
|
||||
|
@ -153,6 +154,7 @@ export default class SearchSource {
|
|||
const dependencyResolver = new DependencyResolver(
|
||||
this._context.resolver,
|
||||
this._context.hasteFS,
|
||||
buildSnapshotResolver(this._context.config),
|
||||
);
|
||||
|
||||
const tests = toTests(
|
||||
|
|
|
@ -161,6 +161,7 @@ export default class TestScheduler {
|
|||
const status = snapshot.cleanup(
|
||||
context.hasteFS,
|
||||
this._globalConfig.updateSnapshot,
|
||||
snapshot.buildSnapshotResolver(context.config),
|
||||
);
|
||||
|
||||
aggregatedResults.snapshot.filesRemoved += status.filesRemoved;
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
*/
|
||||
|
||||
import type {GlobalConfig, ProjectConfig} from 'types/Config';
|
||||
import Snapshot from 'jest-snapshot';
|
||||
import {isSnapshotPath} from 'jest-snapshot';
|
||||
|
||||
export default function isValidPath(
|
||||
globalConfig: GlobalConfig,
|
||||
|
@ -18,6 +18,6 @@ export default function isValidPath(
|
|||
return (
|
||||
!filePath.includes(globalConfig.coverageDirectory) &&
|
||||
!config.watchPathIgnorePatterns.some(pattern => filePath.match(pattern)) &&
|
||||
!filePath.endsWith(`.${Snapshot.EXTENSION}`)
|
||||
!isSnapshotPath(filePath)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -91,6 +91,7 @@ export default ({
|
|||
silent: true,
|
||||
skipFilter: false,
|
||||
skipNodeResolution: false,
|
||||
snapshotResolver: '<rootDir>/snapshotResolver.js',
|
||||
snapshotSerializers: ['my-serializer-module'],
|
||||
testEnvironment: 'jest-environment-jsdom',
|
||||
testEnvironmentOptions: {userAgent: 'Agent/007'},
|
||||
|
|
|
@ -185,6 +185,7 @@ const getConfigs = (
|
|||
setupTestFrameworkScriptFile: options.setupTestFrameworkScriptFile,
|
||||
skipFilter: options.skipFilter,
|
||||
skipNodeResolution: options.skipNodeResolution,
|
||||
snapshotResolver: options.snapshotResolver,
|
||||
snapshotSerializers: options.snapshotSerializers,
|
||||
testEnvironment: options.testEnvironment,
|
||||
testEnvironmentOptions: options.testEnvironmentOptions,
|
||||
|
|
|
@ -453,6 +453,7 @@ export default function normalize(options: InitialOptions, argv: Argv) {
|
|||
case 'moduleLoader':
|
||||
case 'runner':
|
||||
case 'setupTestFrameworkScriptFile':
|
||||
case 'snapshotResolver':
|
||||
case 'testResultsProcessor':
|
||||
case 'testRunner':
|
||||
case 'filter':
|
||||
|
|
|
@ -10,9 +10,11 @@
|
|||
|
||||
'use strict';
|
||||
|
||||
import type {ProjectConfig} from 'types/Config';
|
||||
|
||||
import traverse from 'babel-traverse';
|
||||
import {getASTfor} from './parsers/babylon_parser';
|
||||
import {utils} from 'jest-snapshot';
|
||||
import {buildSnapshotResolver, utils} from 'jest-snapshot';
|
||||
|
||||
type Node = any;
|
||||
|
||||
|
@ -95,11 +97,17 @@ const buildName: (
|
|||
export default class Snapshot {
|
||||
_parser: Function;
|
||||
_matchers: Array<string>;
|
||||
constructor(parser: any, customMatchers?: Array<string>) {
|
||||
_projectConfig: ?ProjectConfig;
|
||||
constructor(
|
||||
parser: any,
|
||||
customMatchers?: Array<string>,
|
||||
projectConfig?: ProjectConfig,
|
||||
) {
|
||||
this._parser = parser || getASTfor;
|
||||
this._matchers = ['toMatchSnapshot', 'toThrowErrorMatchingSnapshot'].concat(
|
||||
customMatchers || [],
|
||||
);
|
||||
this._projectConfig = projectConfig;
|
||||
}
|
||||
|
||||
getMetadata(filePath: string): Array<SnapshotMetadata> {
|
||||
|
@ -127,7 +135,9 @@ export default class Snapshot {
|
|||
},
|
||||
});
|
||||
|
||||
const snapshotPath = utils.getSnapshotPath(filePath);
|
||||
// NOTE if no projectConfig is given the default resolver will be used
|
||||
const snapshotResolver = buildSnapshotResolver(this._projectConfig || {});
|
||||
const snapshotPath = snapshotResolver.resolveSnapshotPath(filePath);
|
||||
const snapshots = utils.getSnapshotData(snapshotPath, 'none').data;
|
||||
let lastParent = null;
|
||||
let count = 1;
|
||||
|
|
|
@ -11,7 +11,11 @@ import type {GlobalConfig, Path, ProjectConfig} from 'types/Config';
|
|||
import type {Plugin} from 'types/PrettyFormat';
|
||||
|
||||
import {extractExpectedAssertionsErrors, getState, setState} from 'expect';
|
||||
import {SnapshotState, addSerializer} from 'jest-snapshot';
|
||||
import {
|
||||
buildSnapshotResolver,
|
||||
SnapshotState,
|
||||
addSerializer,
|
||||
} from 'jest-snapshot';
|
||||
|
||||
export type SetupOptions = {|
|
||||
config: ProjectConfig,
|
||||
|
@ -96,9 +100,12 @@ export default ({
|
|||
.forEach(path => {
|
||||
addSerializer(localRequire(path));
|
||||
});
|
||||
|
||||
patchJasmine();
|
||||
const {expand, updateSnapshot} = globalConfig;
|
||||
const snapshotState = new SnapshotState(testPath, {
|
||||
const snapshotResolver = buildSnapshotResolver(config);
|
||||
const snapshotPath = snapshotResolver.resolveSnapshotPath(testPath);
|
||||
const snapshotState = new SnapshotState(snapshotPath, {
|
||||
expand,
|
||||
getBabelTraverse: () => require('babel-traverse').default,
|
||||
getPrettier: () =>
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
const path = require('path');
|
||||
const {normalize} = require('jest-config');
|
||||
const {buildSnapshotResolver} = require('jest-snapshot');
|
||||
const DependencyResolver = require('../index');
|
||||
|
||||
const maxWorkers = 1;
|
||||
|
@ -34,6 +35,7 @@ beforeEach(() => {
|
|||
dependencyResolver = new DependencyResolver(
|
||||
hasteMap.resolver,
|
||||
hasteMap.hasteFS,
|
||||
buildSnapshotResolver(config),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,16 +10,8 @@
|
|||
import type {HasteFS} from 'types/HasteMap';
|
||||
import type {Path} from 'types/Config';
|
||||
import type {Resolver, ResolveModuleConfig} from 'types/Resolve';
|
||||
import Snapshot from 'jest-snapshot';
|
||||
|
||||
import {replacePathSepForRegex} from 'jest-regex-util';
|
||||
|
||||
const snapshotDirRegex = new RegExp(replacePathSepForRegex('/__snapshots__/'));
|
||||
const snapshotFileRegex = new RegExp(
|
||||
replacePathSepForRegex(`__snapshots__/(.*).${Snapshot.EXTENSION}`),
|
||||
);
|
||||
const isSnapshotPath = (path: string): boolean =>
|
||||
!!path.match(snapshotDirRegex);
|
||||
import type {SnapshotResolver} from 'types/SnapshotResolver';
|
||||
import {isSnapshotPath} from 'jest-snapshot';
|
||||
|
||||
/**
|
||||
* DependencyResolver is used to resolve the direct dependencies of a module or
|
||||
|
@ -28,10 +20,16 @@ const isSnapshotPath = (path: string): boolean =>
|
|||
class DependencyResolver {
|
||||
_hasteFS: HasteFS;
|
||||
_resolver: Resolver;
|
||||
_snapshotResolver: SnapshotResolver;
|
||||
|
||||
constructor(resolver: Resolver, hasteFS: HasteFS) {
|
||||
constructor(
|
||||
resolver: Resolver,
|
||||
hasteFS: HasteFS,
|
||||
snapshotResolver: SnapshotResolver,
|
||||
) {
|
||||
this._resolver = resolver;
|
||||
this._hasteFS = hasteFS;
|
||||
this._snapshotResolver = snapshotResolver;
|
||||
}
|
||||
|
||||
resolve(file: Path, options?: ResolveModuleConfig): Array<Path> {
|
||||
|
@ -89,10 +87,8 @@ class DependencyResolver {
|
|||
const changed = new Set();
|
||||
for (const path of paths) {
|
||||
if (this._hasteFS.exists(path)) {
|
||||
// /path/to/__snapshots__/test.js.snap is always adjacent to
|
||||
// /path/to/test.js
|
||||
const modulePath = isSnapshotPath(path)
|
||||
? path.replace(snapshotFileRegex, '$1')
|
||||
? this._snapshotResolver.resolveTestPath(path)
|
||||
: path;
|
||||
changed.add(modulePath);
|
||||
if (filter(modulePath)) {
|
||||
|
|
|
@ -14,7 +14,6 @@ import {getTopFrame, getStackTraceLines} from 'jest-message-util';
|
|||
import {
|
||||
saveSnapshotFile,
|
||||
getSnapshotData,
|
||||
getSnapshotPath,
|
||||
keyToTestName,
|
||||
serialize,
|
||||
testNameToKey,
|
||||
|
@ -26,7 +25,6 @@ export type SnapshotStateOptions = {|
|
|||
updateSnapshot: SnapshotUpdateState,
|
||||
getPrettier: () => null | any,
|
||||
getBabelTraverse: () => Function,
|
||||
snapshotPath?: string,
|
||||
expand?: boolean,
|
||||
|};
|
||||
|
||||
|
@ -55,8 +53,8 @@ export default class SnapshotState {
|
|||
unmatched: number;
|
||||
updated: number;
|
||||
|
||||
constructor(testPath: Path, options: SnapshotStateOptions) {
|
||||
this._snapshotPath = options.snapshotPath || getSnapshotPath(testPath);
|
||||
constructor(snapshotPath: Path, options: SnapshotStateOptions) {
|
||||
this._snapshotPath = snapshotPath;
|
||||
const {data, dirty} = getSnapshotData(
|
||||
this._snapshotPath,
|
||||
options.updateSnapshot,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`malformed custom resolver in project config inconsistent functions throws 1`] = `"<bold>Custom snapshot resolver functions must transform paths consistently, i.e. expects resolveTestPath(resolveSnapshotPath('some-path/__tests__/snapshot_resolver.test.js')) === some-path/__SPECS__/snapshot_resolver.test.js</>"`;
|
||||
|
||||
exports[`malformed custom resolver in project config missing resolveSnapshotPath throws 1`] = `
|
||||
"<bold>Custom snapshot resolver must implement a \`resolveSnapshotPath\` function.</>
|
||||
Documentation: https://facebook.github.io/jest/docs/en/configuration.html#snapshotResolver"
|
||||
`;
|
||||
|
||||
exports[`malformed custom resolver in project config missing resolveTestPath throws 1`] = `
|
||||
"<bold>Custom snapshot resolver must implement a \`resolveTestPath\` function.</>
|
||||
Documentation: https://facebook.github.io/jest/docs/en/configuration.html#snapshotResolver"
|
||||
`;
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,
|
||||
|
||||
resolveTestPath: (snapshotFilePath, snapshotExtension) =>
|
||||
snapshotFilePath
|
||||
.replace('__snapshots__', '__SPECS__')
|
||||
.slice(0, -snapshotExtension.length),
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
resolveTestPath: () => {},
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
resolveSnapshotPath: () => {},
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
module.exports = {
|
||||
resolveSnapshotPath: (testPath, snapshotExtension) =>
|
||||
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,
|
||||
|
||||
resolveTestPath: (snapshotFilePath, snapshotExtension) =>
|
||||
snapshotFilePath
|
||||
.replace('__snapshots__', '__tests__')
|
||||
.slice(0, -snapshotExtension.length),
|
||||
};
|
|
@ -0,0 +1,108 @@
|
|||
const path = require('path');
|
||||
const {buildSnapshotResolver} = require('../snapshot_resolver');
|
||||
|
||||
describe('defaults', () => {
|
||||
let snapshotResolver;
|
||||
const projectConfig = {
|
||||
rootDir: 'default',
|
||||
// snapshotResolver: null,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
snapshotResolver = buildSnapshotResolver(projectConfig);
|
||||
});
|
||||
|
||||
it('returns cached object if called multiple times', () => {
|
||||
expect(buildSnapshotResolver(projectConfig)).toBe(snapshotResolver);
|
||||
});
|
||||
|
||||
it('resolveSnapshotPath()', () => {
|
||||
expect(snapshotResolver.resolveSnapshotPath('/abc/cde/a.test.js')).toBe(
|
||||
path.join('/abc', 'cde', '__snapshots__', 'a.test.js.snap'),
|
||||
);
|
||||
});
|
||||
|
||||
it('resolveTestPath()', () => {
|
||||
expect(
|
||||
snapshotResolver.resolveTestPath('/abc/cde/__snapshots__/a.test.js.snap'),
|
||||
).toBe(path.resolve('/abc/cde/a.test.js'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('custom resolver in project config', () => {
|
||||
let snapshotResolver;
|
||||
const customSnapshotResolverFile = path.join(
|
||||
__dirname,
|
||||
'fixtures',
|
||||
'customSnapshotResolver.js',
|
||||
);
|
||||
const projectConfig = {
|
||||
rootDir: 'custom1',
|
||||
snapshotResolver: customSnapshotResolverFile,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
snapshotResolver = buildSnapshotResolver(projectConfig);
|
||||
});
|
||||
|
||||
it('returns cached object if called multiple times', () => {
|
||||
expect(buildSnapshotResolver(projectConfig)).toBe(snapshotResolver);
|
||||
});
|
||||
|
||||
it('resolveSnapshotPath()', () => {
|
||||
expect(
|
||||
snapshotResolver.resolveSnapshotPath(
|
||||
path.resolve('/abc/cde/__tests__/a.test.js'),
|
||||
),
|
||||
).toBe(path.resolve('/abc/cde/__snapshots__/a.test.js.snap'));
|
||||
});
|
||||
|
||||
it('resolveTestPath()', () => {
|
||||
expect(
|
||||
snapshotResolver.resolveTestPath(
|
||||
path.resolve('/abc', 'cde', '__snapshots__', 'a.test.js.snap'),
|
||||
),
|
||||
).toBe(path.resolve('/abc/cde/__tests__/a.test.js'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('malformed custom resolver in project config', () => {
|
||||
const newProjectConfig = (filename: string) => {
|
||||
const customSnapshotResolverFile = path.join(
|
||||
__dirname,
|
||||
'fixtures',
|
||||
filename,
|
||||
);
|
||||
return {
|
||||
rootDir: 'missing-resolveSnapshotPath',
|
||||
snapshotResolver: customSnapshotResolverFile,
|
||||
};
|
||||
};
|
||||
|
||||
it('missing resolveSnapshotPath throws ', () => {
|
||||
const projectConfig = newProjectConfig(
|
||||
'customSnapshotResolver-missing-resolveSnapshotPath.js',
|
||||
);
|
||||
expect(() => {
|
||||
buildSnapshotResolver(projectConfig);
|
||||
}).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('missing resolveTestPath throws ', () => {
|
||||
const projectConfig = newProjectConfig(
|
||||
'customSnapshotResolver-missing-resolveTestPath.js',
|
||||
);
|
||||
expect(() => {
|
||||
buildSnapshotResolver(projectConfig);
|
||||
}).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
|
||||
it('inconsistent functions throws ', () => {
|
||||
const projectConfig = newProjectConfig(
|
||||
'customSnapshotResolver-inconsistent-fns.js',
|
||||
);
|
||||
expect(() => {
|
||||
buildSnapshotResolver(projectConfig);
|
||||
}).toThrowErrorMatchingSnapshot();
|
||||
});
|
||||
});
|
|
@ -13,7 +13,6 @@ const chalk = require('chalk');
|
|||
|
||||
const {
|
||||
getSnapshotData,
|
||||
getSnapshotPath,
|
||||
keyToTestName,
|
||||
saveSnapshotFile,
|
||||
serialize,
|
||||
|
@ -51,12 +50,6 @@ test('testNameToKey', () => {
|
|||
expect(testNameToKey('abc cde ', 12)).toBe('abc cde 12');
|
||||
});
|
||||
|
||||
test('getSnapshotPath()', () => {
|
||||
expect(getSnapshotPath('/abc/cde/a.test.js')).toBe(
|
||||
path.join('/abc', 'cde', '__snapshots__', 'a.test.js.snap'),
|
||||
);
|
||||
});
|
||||
|
||||
test('saveSnapshotFile() works with \r\n', () => {
|
||||
const filename = path.join(__dirname, 'remove-newlines.snap');
|
||||
const data = {
|
||||
|
|
|
@ -10,11 +10,16 @@
|
|||
import type {HasteFS} from 'types/HasteMap';
|
||||
import type {MatcherState} from 'types/Matchers';
|
||||
import type {Path, SnapshotUpdateState} from 'types/Config';
|
||||
import type {SnapshotResolver} from 'types/SnapshotResolver';
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import diff from 'jest-diff';
|
||||
import {EXPECTED_COLOR, matcherHint, RECEIVED_COLOR} from 'jest-matcher-utils';
|
||||
import {
|
||||
buildSnapshotResolver,
|
||||
isSnapshotPath,
|
||||
EXTENSION,
|
||||
} from './snapshot_resolver';
|
||||
import SnapshotState from './State';
|
||||
import {addSerializer, getSerializers} from './plugins';
|
||||
import * as utils from './utils';
|
||||
|
@ -22,20 +27,17 @@ import * as utils from './utils';
|
|||
const fileExists = (filePath: Path, hasteFS: HasteFS): boolean =>
|
||||
hasteFS.exists(filePath) || fs.existsSync(filePath);
|
||||
|
||||
const cleanup = (hasteFS: HasteFS, update: SnapshotUpdateState) => {
|
||||
const pattern = '\\.' + utils.SNAPSHOT_EXTENSION + '$';
|
||||
const cleanup = (
|
||||
hasteFS: HasteFS,
|
||||
update: SnapshotUpdateState,
|
||||
snapshotResolver: SnapshotResolver,
|
||||
) => {
|
||||
const pattern = '\\.' + EXTENSION + '$';
|
||||
const files = hasteFS.matchFiles(pattern);
|
||||
const filesRemoved = files
|
||||
.filter(
|
||||
snapshotFile =>
|
||||
!fileExists(
|
||||
path.resolve(
|
||||
path.dirname(snapshotFile),
|
||||
'..',
|
||||
path.basename(snapshotFile, '.' + utils.SNAPSHOT_EXTENSION),
|
||||
),
|
||||
hasteFS,
|
||||
),
|
||||
!fileExists(snapshotResolver.resolveTestPath(snapshotFile), hasteFS),
|
||||
)
|
||||
.map(snapshotFile => {
|
||||
if (update === 'all') {
|
||||
|
@ -290,11 +292,13 @@ const _toThrowErrorMatchingSnapshot = ({
|
|||
};
|
||||
|
||||
module.exports = {
|
||||
EXTENSION: utils.SNAPSHOT_EXTENSION,
|
||||
EXTENSION,
|
||||
SnapshotState,
|
||||
addSerializer,
|
||||
buildSnapshotResolver,
|
||||
cleanup,
|
||||
getSerializers,
|
||||
isSnapshotPath,
|
||||
toMatchInlineSnapshot,
|
||||
toMatchSnapshot,
|
||||
toThrowErrorMatchingInlineSnapshot,
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
import type {ProjectConfig, Path} from 'types/Config';
|
||||
import type {SnapshotResolver} from 'types/SnapshotResolver';
|
||||
import chalk from 'chalk';
|
||||
import path from 'path';
|
||||
|
||||
export const EXTENSION = 'snap';
|
||||
export const DOT_EXTENSION = '.' + EXTENSION;
|
||||
|
||||
export const isSnapshotPath = (path: string): boolean =>
|
||||
path.endsWith(DOT_EXTENSION);
|
||||
|
||||
const cache: Map<Path, SnapshotResolver> = new Map();
|
||||
export const buildSnapshotResolver = (
|
||||
config: ProjectConfig,
|
||||
): SnapshotResolver => {
|
||||
const key = config.rootDir;
|
||||
if (!cache.has(key)) {
|
||||
cache.set(key, createSnapshotResolver(config.snapshotResolver));
|
||||
}
|
||||
return cache.get(key);
|
||||
};
|
||||
|
||||
function createSnapshotResolver(snapshotResolverPath: ?Path): SnapshotResolver {
|
||||
return typeof snapshotResolverPath === 'string'
|
||||
? createCustomSnapshotResolver(snapshotResolverPath)
|
||||
: {
|
||||
resolveSnapshotPath: (testPath: Path) =>
|
||||
path.join(
|
||||
path.join(path.dirname(testPath), '__snapshots__'),
|
||||
path.basename(testPath) + DOT_EXTENSION,
|
||||
),
|
||||
|
||||
resolveTestPath: (snapshotPath: Path) =>
|
||||
path.resolve(
|
||||
path.dirname(snapshotPath),
|
||||
'..',
|
||||
path.basename(snapshotPath, DOT_EXTENSION),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function createCustomSnapshotResolver(
|
||||
snapshotResolverPath: Path,
|
||||
): SnapshotResolver {
|
||||
const custom = (require(snapshotResolverPath): SnapshotResolver);
|
||||
|
||||
if (typeof custom.resolveSnapshotPath !== 'function') {
|
||||
throw new TypeError(mustImplement('resolveSnapshotPath'));
|
||||
}
|
||||
if (typeof custom.resolveTestPath !== 'function') {
|
||||
throw new TypeError(mustImplement('resolveTestPath'));
|
||||
}
|
||||
|
||||
const customResolver = {
|
||||
resolveSnapshotPath: testPath =>
|
||||
custom.resolveSnapshotPath(testPath, DOT_EXTENSION),
|
||||
resolveTestPath: snapshotPath =>
|
||||
custom.resolveTestPath(snapshotPath, DOT_EXTENSION),
|
||||
};
|
||||
|
||||
verifyConsistentTransformations(customResolver);
|
||||
|
||||
return customResolver;
|
||||
}
|
||||
|
||||
function mustImplement(functionName: string) {
|
||||
return (
|
||||
chalk.bold(
|
||||
`Custom snapshot resolver must implement a \`${functionName}\` function.`,
|
||||
) +
|
||||
'\nDocumentation: https://facebook.github.io/jest/docs/en/configuration.html#snapshotResolver'
|
||||
);
|
||||
}
|
||||
|
||||
function verifyConsistentTransformations(custom: SnapshotResolver) {
|
||||
const fakeTestPath = path.posix.join(
|
||||
'some-path',
|
||||
'__tests__',
|
||||
'snapshot_resolver.test.js',
|
||||
);
|
||||
const transformedPath = custom.resolveTestPath(
|
||||
custom.resolveSnapshotPath(fakeTestPath),
|
||||
);
|
||||
if (transformedPath !== fakeTestPath) {
|
||||
throw new Error(
|
||||
chalk.bold(
|
||||
`Custom snapshot resolver functions must transform paths consistently, i.e. expects resolveTestPath(resolveSnapshotPath('${fakeTestPath}')) === ${transformedPath}`,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,7 +17,6 @@ import naturalCompare from 'natural-compare';
|
|||
import path from 'path';
|
||||
import prettyFormat from 'pretty-format';
|
||||
|
||||
export const SNAPSHOT_EXTENSION = 'snap';
|
||||
export const SNAPSHOT_VERSION = '1';
|
||||
const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
|
||||
export const SNAPSHOT_GUIDE_LINK = 'https://goo.gl/fbAQLP';
|
||||
|
@ -92,12 +91,6 @@ export const keyToTestName = (key: string) => {
|
|||
return key.replace(/ \d+$/, '');
|
||||
};
|
||||
|
||||
export const getSnapshotPath = (testPath: Path) =>
|
||||
path.join(
|
||||
path.join(path.dirname(testPath), '__snapshots__'),
|
||||
path.basename(testPath) + '.' + SNAPSHOT_EXTENSION,
|
||||
);
|
||||
|
||||
export const getSnapshotData = (
|
||||
snapshotPath: Path,
|
||||
update: SnapshotUpdateState,
|
||||
|
|
|
@ -153,6 +153,7 @@ export type InitialOptions = {
|
|||
silent?: boolean,
|
||||
skipFilter?: boolean,
|
||||
skipNodeResolution?: boolean,
|
||||
snapshotResolver?: Path,
|
||||
snapshotSerializers?: Array<Path>,
|
||||
errorOnDeprecated?: boolean,
|
||||
testEnvironment?: string,
|
||||
|
@ -273,6 +274,7 @@ export type ProjectConfig = {|
|
|||
setupTestFrameworkScriptFile: ?Path,
|
||||
skipFilter: boolean,
|
||||
skipNodeResolution: boolean,
|
||||
snapshotResolver: ?Path,
|
||||
snapshotSerializers: Array<Path>,
|
||||
testEnvironment: string,
|
||||
testEnvironmentOptions: Object,
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
import type {Path} from './Config';
|
||||
|
||||
export type SnapshotResolver = {|
|
||||
resolveSnapshotPath(testPath: Path): Path,
|
||||
resoveTestPath(snapshotPath: Path): Path,
|
||||
|};
|
Loading…
Reference in New Issue