Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: split some "jest-snapshot" utility functions to its own package "@jest/snapshot-utils" #15095

Merged
merged 6 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading