mirror of
https://github.com/hanzoai/jest.git
synced 2026-09-24 16:59:57 +00:00
feat: handle line endings in snapshot (#15708)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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."
|
||||
`;
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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`;
|
||||
@@ -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();
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
@@ -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</>
|
||||
@@ -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</>
|
||||
@@ -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</>
|
||||
@@ -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]</>
|
||||
|
||||
@@ -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.',
|
||||
);
|
||||
@@ -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 = {
|
||||
|
||||
@@ -75,15 +75,43 @@ const validateSnapshotVersion = (snapshotContents: string) => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
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 = (
|
||||
|
||||
Reference in New Issue
Block a user