Skip to content

Commit f8aa171

Browse files
committed
feat(snapshot): support snapshotResolver and snapshotSerializers written in ESM
1 parent 27b89ec commit f8aa171

File tree

16 files changed

+327
-29
lines changed

16 files changed

+327
-29
lines changed

e2e/__tests__/transform.test.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,40 @@ onNodeVersions('>=12.17.0', () => {
347347
expect(json.numPassedTests).toBe(1);
348348
});
349349
});
350+
351+
describe('transform-esm-snapshotResolver', () => {
352+
const dir = path.resolve(
353+
__dirname,
354+
'..',
355+
'transform/transform-esm-snapshotResolver',
356+
);
357+
const snapshotDir = path.resolve(dir, '__snapshots__');
358+
const snapshotFile = path.resolve(snapshotDir, 'snapshot.test.js.snap');
359+
360+
const cleanupTest = () => {
361+
if (fs.existsSync(snapshotFile)) {
362+
fs.unlinkSync(snapshotFile);
363+
}
364+
if (fs.existsSync(snapshotDir)) {
365+
fs.rmdirSync(snapshotDir);
366+
}
367+
};
368+
369+
beforeAll(() => {
370+
runYarnInstall(dir);
371+
});
372+
beforeEach(cleanupTest);
373+
afterAll(cleanupTest);
374+
375+
it('should transform the snapshotResolver', () => {
376+
const result = runJest(dir, ['-w=1', '--no-cache', '--ci=false']);
377+
378+
expect(result.stderr).toMatch('1 snapshot written from 1 test suite');
379+
380+
const contents = require(snapshotFile);
381+
expect(contents).toHaveProperty(
382+
'snapshots are written to custom location 1',
383+
);
384+
});
385+
});
350386
});
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
'use strict';
9+
10+
describe('snapshot serializers', () => {
11+
it('works with first plugin', () => {
12+
const test = {
13+
foo: 1,
14+
};
15+
expect(test).toMatchSnapshot();
16+
});
17+
18+
it('works with second plugin', () => {
19+
const test = {
20+
bar: 2,
21+
};
22+
expect(test).toMatchSnapshot();
23+
});
24+
25+
it('works with nested serializable objects', () => {
26+
const test = {
27+
foo: {
28+
bar: 2,
29+
},
30+
};
31+
expect(test).toMatchSnapshot();
32+
});
33+
34+
it('works with default serializers', () => {
35+
const test = {
36+
$$typeof: Symbol.for('react.test.json'),
37+
children: null,
38+
props: {
39+
id: 'foo',
40+
},
41+
type: 'div',
42+
};
43+
expect(test).toMatchSnapshot();
44+
});
45+
46+
it('works with prepended plugins and default serializers', () => {
47+
const test = {
48+
$$typeof: Symbol.for('react.test.json'),
49+
children: null,
50+
props: {
51+
aProp: {a: 6},
52+
bProp: {foo: 8},
53+
},
54+
type: 'div',
55+
};
56+
expect(test).toMatchSnapshot();
57+
});
58+
59+
it('works with prepended plugins from expect method called once', () => {
60+
const test = {
61+
$$typeof: Symbol.for('react.test.json'),
62+
children: null,
63+
props: {
64+
aProp: {a: 6},
65+
bProp: {foo: 8},
66+
},
67+
type: 'div',
68+
};
69+
// Add plugin that overrides foo specified by Jest config in package.json
70+
expect.addSnapshotSerializer({
71+
print: (val, serialize) => `Foo: ${serialize(val.foo)}`,
72+
test: val => val && val.hasOwnProperty('foo'),
73+
});
74+
expect(test).toMatchSnapshot();
75+
});
76+
77+
it('works with prepended plugins from expect method called twice', () => {
78+
const test = {
79+
$$typeof: Symbol.for('react.test.json'),
80+
children: null,
81+
props: {
82+
aProp: {a: 6},
83+
bProp: {foo: 8},
84+
},
85+
type: 'div',
86+
};
87+
// Add plugin that overrides preceding added plugin
88+
expect.addSnapshotSerializer({
89+
print: (val, serialize) => `FOO: ${serialize(val.foo)}`,
90+
test: val => val && val.hasOwnProperty('foo'),
91+
});
92+
expect(test).toMatchSnapshot();
93+
});
94+
95+
it('works with array of strings in property matcher', () => {
96+
expect({
97+
arrayOfStrings: ['stream'],
98+
}).toMatchSnapshot({
99+
arrayOfStrings: ['stream'],
100+
});
101+
});
102+
103+
it('works with expect.XXX within array in property matcher', () => {
104+
expect({
105+
arrayOfStrings: ['stream'],
106+
}).toMatchSnapshot({
107+
arrayOfStrings: [expect.any(String)],
108+
});
109+
});
110+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"jest": {
3+
"testEnvironment": "node",
4+
"transform": {
5+
"\\.js$": "<rootDir>/transformer.js"
6+
},
7+
"snapshotSerializers": [
8+
"./plugins/foo",
9+
"<rootDir>/plugins/bar"
10+
]
11+
}
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import {createPlugin} from '../utils';
10+
11+
// We inject the call to "createPlugin('bar') through the transformer"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
import {createPlugin} from '../../utils';
10+
export default createPlugin('foo');
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
'use strict';
9+
10+
module.exports = {
11+
process(src, filename) {
12+
if (/bar.js$/.test(filename)) {
13+
return `${src};\nmodule.exports = createPlugin('bar');`;
14+
}
15+
return src;
16+
},
17+
};
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
*/
8+
9+
export const createPlugin = prop => ({
10+
print: (val, serialize) => `${prop} - ${serialize(val[prop])}`,
11+
test: val => val && val.hasOwnProperty(prop),
12+
});
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
test('snapshots are written to custom location', () => {
9+
expect('foobar').toMatchSnapshot();
10+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {SnapshotResolver} from 'jest-snapshot';
9+
10+
const snapshotResolver = {
11+
resolveSnapshotPath: (testPath, snapshotExtension) =>
12+
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,
13+
14+
resolveTestPath: (snapshotFilePath, snapshotExtension) =>
15+
snapshotFilePath
16+
.replace('__snapshots__', '__tests__')
17+
.slice(0, -(snapshotExtension || '').length),
18+
19+
testPathForConsistencyCheck: 'foo/__tests__/bar.test.js',
20+
};
21+
22+
export default snapshotResolver;
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"jest": {
3+
"testEnvironment": "node",
4+
"snapshotResolver": "<rootDir>/customSnapshotResolver.mjs"
5+
}
6+
}

packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,21 @@ const jestAdapter = async (
2727
FRAMEWORK_INITIALIZER,
2828
);
2929

30+
const localRequire = async <T = unknown>(path: string): Promise<T> => {
31+
const esm = runtime.unstable_shouldLoadAsEsm(path);
32+
33+
if (esm) {
34+
return (await runtime.unstable_importModule(path)) as any;
35+
} else {
36+
return runtime.requireModule(path);
37+
}
38+
};
39+
3040
const {globals, snapshotState} = await initialize({
3141
config,
3242
environment,
3343
globalConfig,
34-
localRequire: runtime.requireModule.bind(runtime),
44+
localRequire,
3545
parentProcess: process,
3646
sendMessageToJest,
3747
setGlobalsForRuntime: runtime.setGlobalsForRuntime.bind(runtime),
@@ -69,21 +79,9 @@ const jestAdapter = async (
6979
});
7080

7181
for (const path of config.setupFilesAfterEnv) {
72-
const esm = runtime.unstable_shouldLoadAsEsm(path);
73-
74-
if (esm) {
75-
await runtime.unstable_importModule(path);
76-
} else {
77-
runtime.requireModule(path);
78-
}
79-
}
80-
const esm = runtime.unstable_shouldLoadAsEsm(testPath);
81-
82-
if (esm) {
83-
await runtime.unstable_importModule(testPath);
84-
} else {
85-
runtime.requireModule(testPath);
82+
await localRequire(path);
8683
}
84+
await localRequire(testPath);
8785

8886
const results = await runAndTransformResultsToJestFormat({
8987
config,

packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const initialize = async ({
5555
config: Config.ProjectConfig;
5656
environment: JestEnvironment;
5757
globalConfig: Config.GlobalConfig;
58-
localRequire: <T = unknown>(path: Config.Path) => T;
58+
localRequire: <T = unknown>(path: Config.Path) => Promise<T>;
5959
testPath: Config.Path;
6060
parentProcess: Process;
6161
sendMessageToJest?: TestFileEvent;
@@ -145,10 +145,10 @@ export const initialize = async ({
145145

146146
// Jest tests snapshotSerializers in order preceding built-in serializers.
147147
// Therefore, add in reverse because the last added is the first tested.
148-
config.snapshotSerializers
149-
.concat()
150-
.reverse()
151-
.forEach(path => addSerializer(localRequire(path)));
148+
const snapshotSerializers = config.snapshotSerializers.concat().reverse();
149+
for (const path of snapshotSerializers) {
150+
addSerializer(await localRequire(path));
151+
}
152152

153153
const {expand, updateSnapshot} = globalConfig;
154154
const snapshotResolver = await buildSnapshotResolver(config, localRequire);

packages/jest-jasmine2/src/setup_jest_globals.ts

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
addSerializer,
1414
buildSnapshotResolver,
1515
} from 'jest-snapshot';
16-
import type {Plugin} from 'pretty-format';
1716
import type {
1817
Attributes,
1918
default as JasmineSpec,
@@ -26,7 +25,7 @@ declare const global: Global.Global;
2625
export type SetupOptions = {
2726
config: Config.ProjectConfig;
2827
globalConfig: Config.GlobalConfig;
29-
localRequire: (moduleName: string) => Plugin;
28+
localRequire: <T = unknown>(path: string) => Promise<T>;
3029
testPath: Config.Path;
3130
};
3231

@@ -97,12 +96,10 @@ export default async ({
9796
}: SetupOptions): Promise<SnapshotStateType> => {
9897
// Jest tests snapshotSerializers in order preceding built-in serializers.
9998
// Therefore, add in reverse because the last added is the first tested.
100-
config.snapshotSerializers
101-
.concat()
102-
.reverse()
103-
.forEach(path => {
104-
addSerializer(localRequire(path));
105-
});
99+
const snapshotSerializers = config.snapshotSerializers.concat().reverse();
100+
for (const path of snapshotSerializers) {
101+
addSerializer(await localRequire(path));
102+
}
106103

107104
patchJasmine();
108105
const {expand, updateSnapshot} = globalConfig;

packages/jest-snapshot/src/SnapshotResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export const isSnapshotPath = (path: string): boolean =>
2525

2626
const cache = new Map<Config.Path, SnapshotResolver>();
2727

28-
type LocalRequire = (module: string) => unknown;
28+
type LocalRequire<T = unknown> = (path: string) => Promise<T>;
2929

3030
export const buildSnapshotResolver = async (
3131
config: Config.ProjectConfig,

0 commit comments

Comments
 (0)