Skip to content

Commit

Permalink
feat(snapshot): support snapshotResolver and snapshotSerializers writ…
Browse files Browse the repository at this point in the history
…ten in ESM
  • Loading branch information
chentsulin committed Oct 30, 2021
1 parent 27b89ec commit 2cab732
Show file tree
Hide file tree
Showing 17 changed files with 329 additions and 31 deletions.
4 changes: 2 additions & 2 deletions e2e/__tests__/circusDeclarationErrors.test.ts
Expand Up @@ -12,8 +12,8 @@ import runJest from '../runJest';

skipSuiteOnJasmine();

it('defining tests and hooks asynchronously throws', () => {
const result = runJest('circus-declaration-errors', [
it('defining tests and hooks asynchronously throws', async () => {
const result = await runJest('circus-declaration-errors', [
'asyncDefinition.test.js',
]);

Expand Down
36 changes: 36 additions & 0 deletions e2e/__tests__/transform.test.ts
Expand Up @@ -347,4 +347,40 @@ onNodeVersions('>=12.17.0', () => {
expect(json.numPassedTests).toBe(1);
});
});

describe('transform-esm-snapshotResolver', () => {
const dir = path.resolve(
__dirname,
'..',
'transform/transform-esm-snapshotResolver',
);
const snapshotDir = path.resolve(dir, '__snapshots__');
const snapshotFile = path.resolve(snapshotDir, 'snapshot.test.js.snap');

const cleanupTest = () => {
if (fs.existsSync(snapshotFile)) {
fs.unlinkSync(snapshotFile);
}
if (fs.existsSync(snapshotDir)) {
fs.rmdirSync(snapshotDir);
}
};

beforeAll(() => {
runYarnInstall(dir);
});
beforeEach(cleanupTest);
afterAll(cleanupTest);

it('should transform the snapshotResolver', () => {
const result = runJest(dir, ['-w=1', '--no-cache', '--ci=false']);

expect(result.stderr).toMatch('1 snapshot written from 1 test suite');

const contents = require(snapshotFile);
expect(contents).toHaveProperty(
'snapshots are written to custom location 1',
);
});
});
});
110 changes: 110 additions & 0 deletions e2e/snapshot-serializers-esm/__tests__/snapshot.test.js
@@ -0,0 +1,110 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
'use strict';

describe('snapshot serializers', () => {
it('works with first plugin', () => {
const test = {
foo: 1,
};
expect(test).toMatchSnapshot();
});

it('works with second plugin', () => {
const test = {
bar: 2,
};
expect(test).toMatchSnapshot();
});

it('works with nested serializable objects', () => {
const test = {
foo: {
bar: 2,
},
};
expect(test).toMatchSnapshot();
});

it('works with default serializers', () => {
const test = {
$$typeof: Symbol.for('react.test.json'),
children: null,
props: {
id: 'foo',
},
type: 'div',
};
expect(test).toMatchSnapshot();
});

it('works with prepended plugins and default serializers', () => {
const test = {
$$typeof: Symbol.for('react.test.json'),
children: null,
props: {
aProp: {a: 6},
bProp: {foo: 8},
},
type: 'div',
};
expect(test).toMatchSnapshot();
});

it('works with prepended plugins from expect method called once', () => {
const test = {
$$typeof: Symbol.for('react.test.json'),
children: null,
props: {
aProp: {a: 6},
bProp: {foo: 8},
},
type: 'div',
};
// Add plugin that overrides foo specified by Jest config in package.json
expect.addSnapshotSerializer({
print: (val, serialize) => `Foo: ${serialize(val.foo)}`,
test: val => val && val.hasOwnProperty('foo'),
});
expect(test).toMatchSnapshot();
});

it('works with prepended plugins from expect method called twice', () => {
const test = {
$$typeof: Symbol.for('react.test.json'),
children: null,
props: {
aProp: {a: 6},
bProp: {foo: 8},
},
type: 'div',
};
// Add plugin that overrides preceding added plugin
expect.addSnapshotSerializer({
print: (val, serialize) => `FOO: ${serialize(val.foo)}`,
test: val => val && val.hasOwnProperty('foo'),
});
expect(test).toMatchSnapshot();
});

it('works with array of strings in property matcher', () => {
expect({
arrayOfStrings: ['stream'],
}).toMatchSnapshot({
arrayOfStrings: ['stream'],
});
});

it('works with expect.XXX within array in property matcher', () => {
expect({
arrayOfStrings: ['stream'],
}).toMatchSnapshot({
arrayOfStrings: [expect.any(String)],
});
});
});
12 changes: 12 additions & 0 deletions e2e/snapshot-serializers-esm/package.json
@@ -0,0 +1,12 @@
{
"jest": {
"testEnvironment": "node",
"transform": {
"\\.js$": "<rootDir>/transformer.js"
},
"snapshotSerializers": [
"./plugins/foo",
"<rootDir>/plugins/bar"
]
}
}
11 changes: 11 additions & 0 deletions e2e/snapshot-serializers-esm/plugins/bar.mjs
@@ -0,0 +1,11 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {createPlugin} from '../utils';

// We inject the call to "createPlugin('bar') through the transformer"
10 changes: 10 additions & 0 deletions e2e/snapshot-serializers-esm/plugins/foo/index.mjs
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import {createPlugin} from '../../utils';
export default createPlugin('foo');
17 changes: 17 additions & 0 deletions e2e/snapshot-serializers-esm/transformer.js
@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = {
process(src, filename) {
if (/bar.js$/.test(filename)) {
return `${src};\nmodule.exports = createPlugin('bar');`;
}
return src;
},
};
12 changes: 12 additions & 0 deletions e2e/snapshot-serializers-esm/utils.mjs
@@ -0,0 +1,12 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

export const createPlugin = prop => ({
print: (val, serialize) => `${prop} - ${serialize(val[prop])}`,
test: val => val && val.hasOwnProperty(prop),
});
@@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

test('snapshots are written to custom location', () => {
expect('foobar').toMatchSnapshot();
});
@@ -0,0 +1,22 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {SnapshotResolver} from 'jest-snapshot';

const snapshotResolver = {
resolveSnapshotPath: (testPath, snapshotExtension) =>
testPath.replace('__tests__', '__snapshots__') + snapshotExtension,

resolveTestPath: (snapshotFilePath, snapshotExtension) =>
snapshotFilePath
.replace('__snapshots__', '__tests__')
.slice(0, -(snapshotExtension || '').length),

testPathForConsistencyCheck: 'foo/__tests__/bar.test.js',
};

export default snapshotResolver;
6 changes: 6 additions & 0 deletions e2e/transform/transform-esm-snapshotResolver/package.json
@@ -0,0 +1,6 @@
{
"jest": {
"testEnvironment": "node",
"snapshotResolver": "<rootDir>/customSnapshotResolver.mjs"
}
}
28 changes: 13 additions & 15 deletions packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts
Expand Up @@ -27,11 +27,21 @@ const jestAdapter = async (
FRAMEWORK_INITIALIZER,
);

const localRequire = async <T = unknown>(path: string): Promise<T> => {
const esm = runtime.unstable_shouldLoadAsEsm(path);

if (esm) {
return (await runtime.unstable_importModule(path)) as any;
} else {
return runtime.requireModule(path);
}
};

const {globals, snapshotState} = await initialize({
config,
environment,
globalConfig,
localRequire: runtime.requireModule.bind(runtime),
localRequire,
parentProcess: process,
sendMessageToJest,
setGlobalsForRuntime: runtime.setGlobalsForRuntime.bind(runtime),
Expand Down Expand Up @@ -69,21 +79,9 @@ const jestAdapter = async (
});

for (const path of config.setupFilesAfterEnv) {
const esm = runtime.unstable_shouldLoadAsEsm(path);

if (esm) {
await runtime.unstable_importModule(path);
} else {
runtime.requireModule(path);
}
}
const esm = runtime.unstable_shouldLoadAsEsm(testPath);

if (esm) {
await runtime.unstable_importModule(testPath);
} else {
runtime.requireModule(testPath);
await localRequire(path);
}
await localRequire(testPath);

const results = await runAndTransformResultsToJestFormat({
config,
Expand Down
Expand Up @@ -55,7 +55,7 @@ export const initialize = async ({
config: Config.ProjectConfig;
environment: JestEnvironment;
globalConfig: Config.GlobalConfig;
localRequire: <T = unknown>(path: Config.Path) => T;
localRequire: <T = unknown>(path: Config.Path) => Promise<T>;
testPath: Config.Path;
parentProcess: Process;
sendMessageToJest?: TestFileEvent;
Expand Down Expand Up @@ -145,10 +145,10 @@ export const initialize = async ({

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

const {expand, updateSnapshot} = globalConfig;
const snapshotResolver = await buildSnapshotResolver(config, localRequire);
Expand Down
13 changes: 5 additions & 8 deletions packages/jest-jasmine2/src/setup_jest_globals.ts
Expand Up @@ -13,7 +13,6 @@ import {
addSerializer,
buildSnapshotResolver,
} from 'jest-snapshot';
import type {Plugin} from 'pretty-format';
import type {
Attributes,
default as JasmineSpec,
Expand All @@ -26,7 +25,7 @@ declare const global: Global.Global;
export type SetupOptions = {
config: Config.ProjectConfig;
globalConfig: Config.GlobalConfig;
localRequire: (moduleName: string) => Plugin;
localRequire: <T = unknown>(path: string) => Promise<T>;
testPath: Config.Path;
};

Expand Down Expand Up @@ -97,12 +96,10 @@ export default async ({
}: SetupOptions): Promise<SnapshotStateType> => {
// Jest tests snapshotSerializers in order preceding built-in serializers.
// Therefore, add in reverse because the last added is the first tested.
config.snapshotSerializers
.concat()
.reverse()
.forEach(path => {
addSerializer(localRequire(path));
});
const snapshotSerializers = config.snapshotSerializers.concat().reverse();
for (const path of snapshotSerializers) {
addSerializer(await localRequire(path));
}

patchJasmine();
const {expand, updateSnapshot} = globalConfig;
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-snapshot/src/SnapshotResolver.ts
Expand Up @@ -25,7 +25,7 @@ export const isSnapshotPath = (path: string): boolean =>

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

type LocalRequire = (module: string) => unknown;
type LocalRequire<T = unknown> = (path: string) => Promise<T>;

export const buildSnapshotResolver = async (
config: Config.ProjectConfig,
Expand Down

0 comments on commit 2cab732

Please sign in to comment.