Skip to content
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 @@ -3,6 +3,7 @@
### Features

- `[expect]` Have `Inverse` exportable ([#15704](https://github.com/jestjs/jest/pull/15704))
- `[jest-snapshot]` Handle line endings in snapshots ([#15708](https://github.com/jestjs/jest/pull/15708))

## 30.0.3

Expand Down
9 changes: 9 additions & 0 deletions e2e/__tests__/__snapshots__/snapshot-crlf.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`handles test names with different line endings in snapshots 1`] = `
"Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 3 passed, 3 total
Time: <<REPLACED>>
Ran all test suites."
`;
17 changes: 17 additions & 0 deletions e2e/__tests__/snapshot-crlf.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.
*/

import {extractSummary} from '../Utils';
import runJest from '../runJest';

test('handles test names with different line endings in snapshots', () => {
const result = runJest('snapshot-crlf');
const {summary} = extractSummary(result.stderr);

expect(result.exitCode).toBe(0);
expect(summary).toMatchSnapshot();
});
7 changes: 7 additions & 0 deletions e2e/snapshot-crlf/__tests__/__snapshots__/crlf.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`CR\\r 1`] = `1`;

exports[`CRLF\\r\\n 1`] = `2`;

exports[`LF\\n 1`] = `3`;
17 changes: 17 additions & 0 deletions e2e/snapshot-crlf/__tests__/crlf.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* 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.
*/
test('CR\r', () => {
expect(1).toMatchSnapshot();
});

test('CRLF\r\n', () => {
expect(2).toMatchSnapshot();
});

test('LF\n', () => {
expect(3).toMatchSnapshot();
});
5 changes: 5 additions & 0 deletions e2e/snapshot-crlf/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
20 changes: 4 additions & 16 deletions packages/expect/src/__tests__/__snapshots__/matchers.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,7 @@ Expected: <g>"cde"</>
Received: <r>"abc"</>
`;

exports[`.toBe() fails for: "four
4
line
string" and "3
line
string" 1`] = `
exports[`.toBe() fails for: "four\\n4\\nline\\nstring" and "3\\nline\\nstring" 1`] = `
<d>expect(</><r>received</><d>).</>toBe<d>(</><g>expected</><d>) // Object.is equality</>

<g>- Expected - 1</>
Expand All @@ -317,8 +312,7 @@ Expected: <g>"<i>delightful</i> JavaScript testing"</>
Received: <r>"<i>painless</i> JavaScript testing"</>
`;

exports[`.toBe() fails for: "with
trailing space" and "without trailing space" 1`] = `
exports[`.toBe() fails for: "with \\ntrailing space" and "without trailing space" 1`] = `
<d>expect(</><r>received</><d>).</>toBe<d>(</><g>expected</><d>) // Object.is equality</>

<g>- Expected - 1</>
Expand Down Expand Up @@ -2050,9 +2044,7 @@ Expected: <g>"apple"</>
Received: <r>"banana"</>
`;

exports[`.toEqual() {pass: false} expect("type TypeName<T> = T extends Function ? \\"function\\" : \\"object\\";").toEqual("type TypeName<T> = T extends Function
? \\"function\\"
: \\"object\\";") 1`] = `
exports[`.toEqual() {pass: false} expect("type TypeName<T> = T extends Function ? \\"function\\" : \\"object\\";").toEqual("type TypeName<T> = T extends Function\\n? \\"function\\"\\n: \\"object\\";") 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>

<g>- Expected - 3</>
Expand Down Expand Up @@ -3533,11 +3525,7 @@ Expected value: <g>"\\"That <i>cat </i>cartoon\\""</>
Received value: <r>"\\"That cartoon\\""</>
`;

exports[`.toHaveProperty() {pass: false} expect({"children": ["Roses are red.
Violets are blue.
Testing with Jest is good for you."], "props": null, "type": "pre"}).toHaveProperty('children,0', "Roses are red, violets are blue.
Testing with Jest
Is good for you.") 1`] = `
exports[`.toHaveProperty() {pass: false} expect({"children": ["Roses are red.\\nViolets are blue.\\nTesting with Jest is good for you."], "props": null, "type": "pre"}).toHaveProperty('children,0', "Roses are red, violets are blue.\\nTesting with Jest\\nIs good for you.") 1`] = `
<d>expect(</><r>received</><d>).</>toHaveProperty<d>(</><g>path</><d>, </><g>value</><d>)</>

Expected path: <g>["children", 0]</>
Expand Down
32 changes: 32 additions & 0 deletions packages/jest-snapshot-utils/src/__tests__/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import {
test('keyToTestName()', () => {
expect(keyToTestName('abc cde 12')).toBe('abc cde');
expect(keyToTestName('abc cde 12')).toBe('abc cde ');
expect(keyToTestName('test with\\r\\nCRLF 1')).toBe('test with\r\nCRLF');
expect(keyToTestName('test with\\rCR 1')).toBe('test with\rCR');
expect(keyToTestName('test with\\nLF 1')).toBe('test with\nLF');
expect(() => keyToTestName('abc cde')).toThrow(
'Snapshot keys must end with a number.',
);
Expand All @@ -36,6 +39,35 @@ test('testNameToKey', () => {
expect(testNameToKey('abc cde ', 12)).toBe('abc cde 12');
});

test('testNameToKey escapes line endings to prevent collisions', () => {
expect(testNameToKey('test with\r\nCRLF', 1)).toBe('test with\\r\\nCRLF 1');
expect(testNameToKey('test with\rCR', 1)).toBe('test with\\rCR 1');
expect(testNameToKey('test with\nLF', 1)).toBe('test with\\nLF 1');

expect(testNameToKey('test\r\n', 1)).not.toBe(testNameToKey('test\r', 1));
expect(testNameToKey('test\r\n', 1)).not.toBe(testNameToKey('test\n', 1));
expect(testNameToKey('test\r', 1)).not.toBe(testNameToKey('test\n', 1));
});

test('keyToTestName reverses testNameToKey transformation', () => {
const testCases = [
'simple test',
'test with\r\nCRLF',
'test with\rCR only',
'test with\nLF only',
'mixed\r\nline\rendings\n',
'test\r',
'test\r\n',
'test\n',
];

for (const testName of testCases) {
const key = testNameToKey(testName, 1);
const recovered = keyToTestName(key);
expect(recovered).toBe(testName);
}
});

test('saveSnapshotFile() works with \r\n', () => {
const filename = path.join(__dirname, 'remove-newlines.snap');
const data = {
Expand Down
34 changes: 31 additions & 3 deletions packages/jest-snapshot-utils/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,15 +75,43 @@
return null;
};

const normalizeTestNameForKey = (testName: string): string =>
testName.replaceAll(/\r\n|\r|\n/g, match => {
switch (match) {
case '\r\n':
return '\\r\\n';
case '\r':
return '\\r';
case '\n':
return '\\n';
default:
return match;

Check warning on line 88 in packages/jest-snapshot-utils/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-snapshot-utils/src/utils.ts#L87-L88

Added lines #L87 - L88 were not covered by tests
}
});

const denormalizeTestNameFromKey = (key: string): string =>
key.replaceAll(/\\r\\n|\\r|\\n/g, match => {
switch (match) {
case '\\r\\n':
return '\r\n';
case '\\r':
return '\r';
case '\\n':
return '\n';
default:
return match;

Check warning on line 102 in packages/jest-snapshot-utils/src/utils.ts

View check run for this annotation

Codecov / codecov/patch

packages/jest-snapshot-utils/src/utils.ts#L101-L102

Added lines #L101 - L102 were not covered by tests
}
});

export const testNameToKey = (testName: string, count: number): string =>
`${testName} ${count}`;
`${normalizeTestNameForKey(testName)} ${count}`;

export const keyToTestName = (key: string): string => {
if (!/ \d+$/.test(key)) {
throw new Error('Snapshot keys must end with a number.');
}

return key.replace(/ \d+$/, '');
const testNameWithoutCount = key.replace(/ \d+$/, '');
return denormalizeTestNameFromKey(testNameWithoutCount);
};

export const getSnapshotData = (
Expand Down
Loading