mirror of https://github.com/facebook/jest.git
feat: add support for snapshot matchers in concurrent tests (#14139)
This commit is contained in:
parent
372d6c53bf
commit
4ecf91ccb2
|
@ -2,6 +2,7 @@
|
|||
|
||||
### Features
|
||||
|
||||
- `[jest-circus, jest-snapshot]` Add support for snapshot matchers in concurrent tests ([#14139](https://github.com/jestjs/jest/pull/14139))
|
||||
- `[jest-cli]` Include type definitions to generated config files ([#14078](https://github.com/facebook/jest/pull/14078))
|
||||
- `[jest-snapshot]` Support arrays as property matchers ([#14025](https://github.com/facebook/jest/pull/14025))
|
||||
- `[jest-core, jest-circus, jest-reporter, jest-runner]` Added support for reporting about start individual test cases using jest-circus ([#14174](https://github.com/jestjs/jest/pull/14174))
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* 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 {skipSuiteOnJasmine} from '@jest/test-utils';
|
||||
import runJest from '../runJest';
|
||||
|
||||
skipSuiteOnJasmine();
|
||||
|
||||
test('Snapshots get correct names in concurrent tests', () => {
|
||||
const result = runJest('snapshot-concurrent', ['--ci']);
|
||||
expect(result.exitCode).toBe(0);
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`A a 1`] = `"Aa1"`;
|
||||
|
||||
exports[`A a 2`] = `"Aa2"`;
|
||||
|
||||
exports[`A b 1`] = `"Ab1"`;
|
||||
|
||||
exports[`A b 2`] = `"Ab2"`;
|
||||
|
||||
exports[`A c 1`] = `"Ac1"`;
|
||||
|
||||
exports[`A c 2`] = `"Ac2"`;
|
||||
|
||||
exports[`A d 1`] = `"Ad1"`;
|
||||
|
||||
exports[`A d 2`] = `"Ad2"`;
|
||||
|
||||
exports[`B 1`] = `"B1"`;
|
||||
|
||||
exports[`B 2`] = `"B2"`;
|
||||
|
||||
exports[`C 1`] = `"C1"`;
|
||||
|
||||
exports[`C 2`] = `"C2"`;
|
||||
|
||||
exports[`D 1`] = `"D1"`;
|
||||
|
||||
exports[`D 2`] = `"D2"`;
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
describe('A', () => {
|
||||
it.concurrent('a', async () => {
|
||||
await sleep(100);
|
||||
expect('Aa1').toMatchSnapshot();
|
||||
expect('Aa2').toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.concurrent('b', async () => {
|
||||
await sleep(10);
|
||||
expect('Ab1').toMatchSnapshot();
|
||||
expect('Ab2').toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.concurrent('c', async () => {
|
||||
expect('Ac1').toMatchSnapshot();
|
||||
expect('Ac2').toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('d', () => {
|
||||
expect('Ad1').toMatchSnapshot();
|
||||
expect('Ad2').toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
it.concurrent('B', async () => {
|
||||
await sleep(10);
|
||||
expect('B1').toMatchSnapshot();
|
||||
expect('B2').toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('C', () => {
|
||||
expect('C1').toMatchSnapshot();
|
||||
expect('C2').toMatchSnapshot();
|
||||
});
|
||||
|
||||
it.concurrent('D', async () => {
|
||||
expect('D1').toMatchSnapshot();
|
||||
expect('D2').toMatchSnapshot();
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"jest": {
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@jest/expect-utils": "workspace:^",
|
||||
"@types/node": "*",
|
||||
"jest-get-type": "workspace:^",
|
||||
"jest-matcher-utils": "workspace:^",
|
||||
"jest-message-util": "workspace:^",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import type {AsyncLocalStorage} from 'async_hooks';
|
||||
import type {EqualsFunction, Tester} from '@jest/expect-utils';
|
||||
import type * as jestMatcherUtils from 'jest-matcher-utils';
|
||||
import {INTERNAL_MATCHER_FLAG} from './jestMatchersObject';
|
||||
|
@ -57,6 +58,7 @@ export interface MatcherUtils {
|
|||
|
||||
export interface MatcherState {
|
||||
assertionCalls: number;
|
||||
currentConcurrentTestName?: AsyncLocalStorage<string>;
|
||||
currentTestName?: string;
|
||||
error?: Error;
|
||||
expand?: boolean;
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import {AsyncLocalStorage} from 'async_hooks';
|
||||
import pLimit = require('p-limit');
|
||||
import {jestExpect} from '@jest/expect';
|
||||
import type {Circus} from '@jest/types';
|
||||
import shuffleArray, {RandomNumberGenerator, rngBuilder} from './shuffleArray';
|
||||
import {dispatch, getState} from './state';
|
||||
|
@ -19,6 +21,10 @@ import {
|
|||
makeRunResult,
|
||||
} from './utils';
|
||||
|
||||
type ConcurrentTestEntry = Omit<Circus.TestEntry, 'fn'> & {
|
||||
fn: Circus.ConcurrentTestFn;
|
||||
};
|
||||
|
||||
const run = async (): Promise<Circus.RunResult> => {
|
||||
const {rootDescribeBlock, seed, randomize} = getState();
|
||||
const rng = randomize ? rngBuilder(seed) : undefined;
|
||||
|
@ -49,20 +55,8 @@ const _runTestsForDescribeBlock = async (
|
|||
|
||||
if (isRootBlock) {
|
||||
const concurrentTests = collectConcurrentTests(describeBlock);
|
||||
const mutex = pLimit(getState().maxConcurrency);
|
||||
for (const test of concurrentTests) {
|
||||
try {
|
||||
const promise = mutex(test.fn);
|
||||
// Avoid triggering the uncaught promise rejection handler in case the
|
||||
// test errors before being awaited on.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
promise.catch(() => {});
|
||||
test.fn = () => promise;
|
||||
} catch (err) {
|
||||
test.fn = () => {
|
||||
throw err;
|
||||
};
|
||||
}
|
||||
if (concurrentTests.length > 0) {
|
||||
startTestsConcurrently(concurrentTests);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -120,7 +114,7 @@ const _runTestsForDescribeBlock = async (
|
|||
|
||||
function collectConcurrentTests(
|
||||
describeBlock: Circus.DescribeBlock,
|
||||
): Array<Omit<Circus.TestEntry, 'fn'> & {fn: Circus.ConcurrentTestFn}> {
|
||||
): Array<ConcurrentTestEntry> {
|
||||
if (describeBlock.mode === 'skip') {
|
||||
return [];
|
||||
}
|
||||
|
@ -135,13 +129,33 @@ function collectConcurrentTests(
|
|||
child.mode === 'skip' ||
|
||||
(hasFocusedTests && child.mode !== 'only') ||
|
||||
(testNamePattern && !testNamePattern.test(getTestID(child)));
|
||||
return skip
|
||||
? []
|
||||
: [child as Circus.TestEntry & {fn: Circus.ConcurrentTestFn}];
|
||||
return skip ? [] : [child as ConcurrentTestEntry];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function startTestsConcurrently(concurrentTests: Array<ConcurrentTestEntry>) {
|
||||
const mutex = pLimit(getState().maxConcurrency);
|
||||
const testNameStorage = new AsyncLocalStorage<string>();
|
||||
jestExpect.setState({currentConcurrentTestName: testNameStorage});
|
||||
for (const test of concurrentTests) {
|
||||
try {
|
||||
const promise = testNameStorage.run(getTestID(test), () =>
|
||||
mutex(test.fn),
|
||||
);
|
||||
// Avoid triggering the uncaught promise rejection handler in case the
|
||||
// test fails before being awaited on.
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
promise.catch(() => {});
|
||||
test.fn = () => promise;
|
||||
} catch (err) {
|
||||
test.fn = () => {
|
||||
throw err;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const _runTest = async (
|
||||
test: Circus.TestEntry,
|
||||
parentSkipped: boolean,
|
||||
|
|
|
@ -279,7 +279,9 @@ const _toMatchSnapshot = (config: MatchSnapshotConfig) => {
|
|||
|
||||
context.dontThrow && context.dontThrow();
|
||||
|
||||
const {currentTestName, isNot, snapshotState} = context;
|
||||
const {currentConcurrentTestName, isNot, snapshotState} = context;
|
||||
const currentTestName =
|
||||
currentConcurrentTestName?.getStore() ?? context.currentTestName;
|
||||
|
||||
if (isNot) {
|
||||
throw new Error(
|
||||
|
|
Loading…
Reference in New Issue