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 d19c3af
Show file tree
Hide file tree
Showing 18 changed files with 372 additions and 42 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
38 changes: 38 additions & 0 deletions e2e/__tests__/transform.test.ts
Expand Up @@ -347,4 +347,42 @@ 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'], {
nodeOptions: '--experimental-vm-modules --no-warnings',
});

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.mjs$/.test(filename)) {
return `${src};\nexport default 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,18 @@
/**
* 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 default {
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',
};
6 changes: 6 additions & 0 deletions e2e/transform/transform-esm-snapshotResolver/package.json
@@ -0,0 +1,6 @@
{
"jest": {
"testEnvironment": "node",
"snapshotResolver": "<rootDir>/customSnapshotResolver.mjs"
}
}
37 changes: 21 additions & 16 deletions packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapter.ts
Expand Up @@ -10,7 +10,7 @@ import type {TestFileEvent, TestResult} from '@jest/test-result';
import type {Config} from '@jest/types';
import type Runtime from 'jest-runtime';
import type {SnapshotStateType} from 'jest-snapshot';
import {deepCyclicCopy} from 'jest-util';
import {deepCyclicCopy, interopRequireDefault} from 'jest-util';

const FRAMEWORK_INITIALIZER = require.resolve('./jestAdapterInit');

Expand All @@ -27,11 +27,28 @@ const jestAdapter = async (
FRAMEWORK_INITIALIZER,
);

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

if (esm) {
return runtime.unstable_importModule(path) as any;
} else {
const requiredModule = runtime.requireModule<T>(path);
if (!applyInteropRequireDefault) {
return requiredModule;
}
return interopRequireDefault(requiredModule).default;
}
};

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 +86,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,10 @@ 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,
applyInteropRequireDefault?: boolean,
) => Promise<T>;
testPath: Config.Path;
parentProcess: Process;
sendMessageToJest?: TestFileEvent;
Expand Down Expand Up @@ -145,10 +148,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
20 changes: 19 additions & 1 deletion packages/jest-jasmine2/src/index.ts
Expand Up @@ -12,6 +12,7 @@ import type {AssertionResult, TestResult} from '@jest/test-result';
import type {Config, Global} from '@jest/types';
import type Runtime from 'jest-runtime';
import type {SnapshotStateType} from 'jest-snapshot';
import {interopRequireDefault} from 'jest-util';
import installEach from './each';
import {installErrorOnPrivate} from './errorOnPrivate';
import type Spec from './jasmine/Spec';
Expand Down Expand Up @@ -138,14 +139,31 @@ export default async function jasmine2(
});
}

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

if (esm) {
return runtime.unstable_importModule(path) as any;
} else {
const requiredModule = runtime.requireModule<T>(path);
if (!applyInteropRequireDefault) {
return requiredModule;
}
return interopRequireDefault(requiredModule).default;
}
};

const snapshotState: SnapshotStateType = await runtime
.requireInternalModule<typeof import('./setup_jest_globals')>(
path.resolve(__dirname, './setup_jest_globals.js'),
)
.default({
config,
globalConfig,
localRequire: runtime.requireModule.bind(runtime),
localRequire,
testPath,
});

Expand Down

0 comments on commit d19c3af

Please sign in to comment.