Skip to content

Commit

Permalink
feat: split some jest-snapshot utility functions to its own package…
Browse files Browse the repository at this point in the history
… `@jest/snapshot-utils` (#15095)
  • Loading branch information
connectdotz authored May 29, 2024
1 parent b7ae0b8 commit 7a2ccc2
Show file tree
Hide file tree
Showing 19 changed files with 445 additions and 344 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- `[@jest/types]` Improve argument type inference passed to `test` and `describe` callback functions from `each` tables ([#14920](https://github.com/jestjs/jest/pull/14920))
- `[jest-snapshot]` [**BREAKING**] Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in snapshots ([#13965](https://github.com/facebook/jest/pull/13965))
- `[jest-snapshot]` Support Prettier 3 ([#14566](https://github.com/facebook/jest/pull/14566))
- `[@jest/util-snapshot]` Extract utils used by tooling from `jest-snapshot` into its own package ([#15095](https://github.com/facebook/jest/pull/15095))
- `[pretty-format]` [**BREAKING**] Do not render empty string children (`''`) in React plugin ([#14470](https://github.com/facebook/jest/pull/14470))

### Fixes
Expand Down
12 changes: 6 additions & 6 deletions e2e/__tests__/findRelatedFiles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ afterEach(() => cleanup(DIR));
describe('--findRelatedTests flag', () => {
test('runs tests related to filename', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/test.test.js': `
const a = require('../a');
test('a', () => {});
Expand All @@ -44,7 +44,7 @@ describe('--findRelatedTests flag', () => {
}

writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/test.test.js': `
const a = require('../a');
test('a', () => {});
Expand All @@ -65,7 +65,7 @@ describe('--findRelatedTests flag', () => {

test('runs tests related to filename with a custom dependency extractor', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/test-skip-deps.test.js': `
const dynamicImport = path => Promise.resolve(require(path));
test('a', () => dynamicImport('../a').then(a => {
Expand Down Expand Up @@ -118,7 +118,7 @@ describe('--findRelatedTests flag', () => {

test('runs tests related to filename with a custom dependency extractor written in ESM', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/test-skip-deps.test.js': `
const dynamicImport = path => Promise.resolve(require(path));
test('a', () => dynamicImport('../a').then(a => {
Expand Down Expand Up @@ -168,7 +168,7 @@ describe('--findRelatedTests flag', () => {

test('generates coverage report for filename', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/a.test.js': `
require('../a');
require('../b');
Expand Down Expand Up @@ -219,7 +219,7 @@ describe('--findRelatedTests flag', () => {

test('coverage configuration is applied correctly', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'__tests__/a.test.js': `
require('../a');
test('a', () => expect(1).toBe(1));
Expand Down
14 changes: 7 additions & 7 deletions e2e/__tests__/multiProjectRunner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ afterEach(() => cleanup(DIR));

test("--listTests doesn't duplicate the test files", () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'/project1.js': "module.exports = {rootDir: './', displayName: 'BACKEND'}",
'/project2.js': "module.exports = {rootDir: './', displayName: 'BACKEND'}",
'__tests__/inBothProjectsTest.js': "test('test', () => {});",
Expand All @@ -35,7 +35,7 @@ test("--listTests doesn't duplicate the test files", () => {

test('can pass projects or global config', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'base_config.js': `
module.exports = {
haste: {
Expand Down Expand Up @@ -132,7 +132,7 @@ test('can pass projects or global config', () => {

test('"No tests found" message for projects', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'package.json': '{}',
'project1/__tests__/file1.test.js': `
const file1 = require('../file1');
Expand Down Expand Up @@ -336,7 +336,7 @@ test('allows a single project', () => {

test('resolves projects and their <rootDir> properly', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'package.json': JSON.stringify({
jest: {
projects: [
Expand Down Expand Up @@ -436,7 +436,7 @@ test('resolves projects and their <rootDir> properly', () => {

test('Does transform files with the corresponding project transformer', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'file.js': SAMPLE_FILE_CONTENT,
'package.json': '{}',
'project1/__tests__/project1.test.js': `
Expand Down Expand Up @@ -487,7 +487,7 @@ test('Does transform files with the corresponding project transformer', () => {
describe("doesn't bleed module file extensions resolution with multiple workers", () => {
test('external config files', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'file.js': 'module.exports = "file1"',
'file.p2.js': 'module.exports = "file2"',
'package.json': '{}',
Expand Down Expand Up @@ -537,7 +537,7 @@ describe("doesn't bleed module file extensions resolution with multiple workers"

test('inline config files', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'file.js': 'module.exports = "file1"',
'file.p2.js': 'module.exports = "file2"',
'package.json': JSON.stringify({
Expand Down
6 changes: 3 additions & 3 deletions e2e/__tests__/unexpectedToken.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ afterEach(() => cleanup(DIR));

test('triggers unexpected token error message for non-JS assets', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'asset.css': '.style {}',
'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}),
});
Expand All @@ -37,7 +37,7 @@ test('triggers unexpected token error message for non-JS assets', () => {

test('triggers unexpected token error message for untranspiled node_modules', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'node_modules/untranspiled-module': 'import {module} from "some-module"',
'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}),
});
Expand All @@ -59,7 +59,7 @@ test('triggers unexpected token error message for untranspiled node_modules', ()

test('does not trigger unexpected token error message for regular syntax errors', () => {
writeFiles(DIR, {
'.watchmanconfig': '',
'.watchmanconfig': '{}',
'faulty.js': 'import {module from "some-module"',
'faulty2.js': 'const name = {first: "Name" second: "Second"}',
'package.json': JSON.stringify({jest: {testEnvironment: 'node'}}),
Expand Down
37 changes: 37 additions & 0 deletions packages/jest-snapshot-utils/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "@jest/snapshot-utils",
"version": "30.0.0-alpha.4",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-snapshot-utils"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"require": "./build/index.js",
"import": "./build/index.mjs",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@jest/types": "workspace:*",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"natural-compare": "^1.4.0"
},
"devDependencies": {
"@types/graceful-fs": "^4.1.3",
"@types/natural-compare": "^1.4.0"
},
"engines": {
"node": "^16.10.0 || ^18.12.0 || >=20.0.0"
},
"publishConfig": {
"access": "public"
}
}
179 changes: 179 additions & 0 deletions packages/jest-snapshot-utils/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

jest.mock('graceful-fs', () => ({
...jest.createMockFromModule<typeof import('fs')>('fs'),
existsSync: jest.fn().mockReturnValue(true),
}));

import * as path from 'path';
import chalk = require('chalk');
import * as fs from 'graceful-fs';
import {
SNAPSHOT_GUIDE_LINK,
SNAPSHOT_VERSION,
SNAPSHOT_VERSION_WARNING,
getSnapshotData,
keyToTestName,
saveSnapshotFile,
testNameToKey,
} from '../utils';

test('keyToTestName()', () => {
expect(keyToTestName('abc cde 12')).toBe('abc cde');
expect(keyToTestName('abc cde 12')).toBe('abc cde ');
expect(() => keyToTestName('abc cde')).toThrow(
'Snapshot keys must end with a number.',
);
});

test('testNameToKey', () => {
expect(testNameToKey('abc cde', 1)).toBe('abc cde 1');
expect(testNameToKey('abc cde ', 12)).toBe('abc cde 12');
});

test('saveSnapshotFile() works with \r\n', () => {
const filename = path.join(__dirname, 'remove-newlines.snap');
const data = {
myKey: '<div>\r\n</div>',
};

saveSnapshotFile(data, filename);
expect(fs.writeFileSync).toHaveBeenCalledWith(
filename,
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`myKey`] = `<div>\n</div>`;\n',
);
});

test('saveSnapshotFile() works with \r', () => {
const filename = path.join(__dirname, 'remove-newlines.snap');
const data = {
myKey: '<div>\r</div>',
};

saveSnapshotFile(data, filename);
expect(fs.writeFileSync).toHaveBeenCalledWith(
filename,
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`myKey`] = `<div>\n</div>`;\n',
);
});

test('getSnapshotData() throws when no snapshot version', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
const update = 'none';

expect(() => getSnapshotData(filename, update)).toThrow(
chalk.red(
`${chalk.bold('Outdated snapshot')}: No snapshot header found. ` +
'Jest 19 introduced versioned snapshots to ensure all developers on ' +
'a project are using the same version of Jest. ' +
'Please update all snapshots during this upgrade of Jest.\n\n',
) + SNAPSHOT_VERSION_WARNING,
);
});

test('getSnapshotData() throws for older snapshot version', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue(
`// Jest Snapshot v0.99, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`myKey`] = `<div>\n</div>`;\n',
);
const update = 'none';

expect(() => getSnapshotData(filename, update)).toThrow(
`${chalk.red(
`${chalk.red.bold('Outdated snapshot')}: The version of the snapshot ` +
'file associated with this test is outdated. The snapshot file ' +
'version ensures that all developers on a project are using ' +
'the same version of Jest. ' +
'Please update all snapshots during this upgrade of Jest.',
)}\n\nExpected: v${SNAPSHOT_VERSION}\n` +
`Received: v0.99\n\n${SNAPSHOT_VERSION_WARNING}`,
);
});

test('getSnapshotData() throws for newer snapshot version', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue(
`// Jest Snapshot v2, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`myKey`] = `<div>\n</div>`;\n',
);
const update = 'none';

expect(() => getSnapshotData(filename, update)).toThrow(
`${chalk.red(
`${chalk.red.bold('Outdated Jest version')}: The version of this ` +
'snapshot file indicates that this project is meant to be used ' +
'with a newer version of Jest. ' +
'The snapshot file version ensures that all developers on a project ' +
'are using the same version of Jest. ' +
'Please update your version of Jest and re-run the tests.',
)}\n\nExpected: v${SNAPSHOT_VERSION}\nReceived: v2`,
);
});

test('getSnapshotData() does not throw for when updating', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
const update = 'all';

expect(() => getSnapshotData(filename, update)).not.toThrow();
});

test('getSnapshotData() marks invalid snapshot dirty when updating', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
const update = 'all';

expect(getSnapshotData(filename, update)).toMatchObject({dirty: true});
});

test('getSnapshotData() marks valid snapshot not dirty when updating', () => {
const filename = path.join(__dirname, 'old-snapshot.snap');
jest
.mocked(fs.readFileSync)
.mockReturnValue(
`// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`myKey`] = `<div>\n</div>`;\n',
);
const update = 'all';

expect(getSnapshotData(filename, update)).toMatchObject({dirty: false});
});

test('escaping', () => {
const filename = path.join(__dirname, 'escaping.snap');
const data = '"\'\\';
const writeFileSync = jest.mocked(fs.writeFileSync);

writeFileSync.mockReset();
saveSnapshotFile({key: data}, filename);
const writtenData = writeFileSync.mock.calls[0][1];
expect(writtenData).toBe(
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
'exports[`key`] = `"\'\\\\`;\n',
);

// eslint-disable-next-line no-eval
const readData = eval(`var exports = {}; ${writtenData} exports`);
expect(readData).toEqual({key: data});
const snapshotData = readData.key;
expect(data).toEqual(snapshotData);
});
9 changes: 9 additions & 0 deletions packages/jest-snapshot-utils/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export * from './utils';
export * from './types';
8 changes: 8 additions & 0 deletions packages/jest-snapshot-utils/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

export type SnapshotData = Record<string, string>;
Loading

0 comments on commit 7a2ccc2

Please sign in to comment.