From f2498de30d085fc175bcb2ad75bee4f3e6cdaaac Mon Sep 17 00:00:00 2001 From: Mark1626 Date: Sun, 19 Jul 2020 21:57:16 +0530 Subject: [PATCH] feat: Support concurrent in each --- CHANGELOG.md | 1 + e2e/__tests__/jasmineAsync.test.ts | 18 +++ .../__tests__/concurrent-each.test.js | 20 +++ .../__tests__/concurrent-only-each.test.js | 22 ++++ .../jestAdapterInit.ts | 12 +- .../__snapshots__/array.test.ts.snap | 36 ++++++ .../__snapshots__/template.test.ts.snap | 117 ++++++++++++++++++ .../jest-each/src/__tests__/array.test.ts | 9 ++ .../jest-each/src/__tests__/index.test.ts | 15 +++ .../jest-each/src/__tests__/template.test.ts | 9 ++ packages/jest-each/src/bind.ts | 24 ++-- packages/jest-each/src/index.ts | 27 +++- .../src/__tests__/concurrent.test.ts | 17 +++ packages/jest-jasmine2/src/each.ts | 12 ++ .../jest-jasmine2/src/jasmineAsyncInstall.ts | 8 +- packages/jest-types/src/Global.ts | 21 ++-- 16 files changed, 342 insertions(+), 26 deletions(-) create mode 100644 e2e/jasmine-async/__tests__/concurrent-each.test.js create mode 100644 e2e/jasmine-async/__tests__/concurrent-only-each.test.js create mode 100644 packages/jest-jasmine2/src/__tests__/concurrent.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bc2c8d7bb91..3bbffd8ff1d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features - `[pretty-format]` Added support for serializing custom elements (web components) ([#10217](https://github.com/facebook/jest/pull/10237)) +- `[jest-each, jest-jasmine, jest-circus]` Add support for .concurrent.each ([#9326](https://github.com/facebook/jest/pull/9326)) ### Fixes diff --git a/e2e/__tests__/jasmineAsync.test.ts b/e2e/__tests__/jasmineAsync.test.ts index 6eaaa864e956..3006a04b3810 100644 --- a/e2e/__tests__/jasmineAsync.test.ts +++ b/e2e/__tests__/jasmineAsync.test.ts @@ -128,6 +128,24 @@ describe('async jasmine', () => { expect(json.testResults[0].message).toMatch(/concurrent test fails/); }); + it('works with concurrent.each', () => { + const {json} = runWithJson('jasmine-async', ['concurrent-each.test.js']); + expect(json.numTotalTests).toBe(4); + expect(json.numPassedTests).toBe(2); + expect(json.numFailedTests).toBe(0); + expect(json.numPendingTests).toBe(2); + }); + + it('works with concurrent.only.each', () => { + const {json} = runWithJson('jasmine-async', [ + 'concurrent-only-each.test.js', + ]); + expect(json.numTotalTests).toBe(4); + expect(json.numPassedTests).toBe(2); + expect(json.numFailedTests).toBe(0); + expect(json.numPendingTests).toBe(2); + }); + it("doesn't execute more than 5 tests simultaneously", () => { const {json} = runWithJson('jasmine-async', ['concurrent-many.test.js']); expect(json.numTotalTests).toBe(10); diff --git a/e2e/jasmine-async/__tests__/concurrent-each.test.js b/e2e/jasmine-async/__tests__/concurrent-each.test.js new file mode 100644 index 000000000000..1e358d9db85c --- /dev/null +++ b/e2e/jasmine-async/__tests__/concurrent-each.test.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +it.concurrent.each([ + [1, 2], + [2, 3], +])('adds one to number', async (a, b) => { + expect(a + 1).toBe(b); +}); + +it.concurrent.skip.each([ + [1, 2], + [2, 3], +])('should skip this test', Promise.resolve()); diff --git a/e2e/jasmine-async/__tests__/concurrent-only-each.test.js b/e2e/jasmine-async/__tests__/concurrent-only-each.test.js new file mode 100644 index 000000000000..c818e1de3d53 --- /dev/null +++ b/e2e/jasmine-async/__tests__/concurrent-only-each.test.js @@ -0,0 +1,22 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +'use strict'; + +it.concurrent.only.each([ + [1, 2], + [2, 3], +])('adds one to number', async (a, b) => { + expect(a + 1).toBe(b); +}); + +it.concurrent.each([ + [1, 2], + [2, 3], +])('adds one to number', async (a, b) => { + expect(a + 1).toBe(b); +}); diff --git a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts index eb0595455c4e..840ab5776acf 100644 --- a/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts +++ b/packages/jest-circus/src/legacy-code-todo-rewrite/jestAdapterInit.ts @@ -21,6 +21,7 @@ import { addSerializer, buildSnapshotResolver, } from 'jest-snapshot'; +import {bind} from 'jest-each'; import throat from 'throat'; import { ROOT_DESCRIBE_BLOCK_NAME, @@ -73,7 +74,7 @@ export const initialize = async ({ nodeGlobal.test.concurrent = (test => { const concurrent = ( testName: string, - testFn: () => Promise, + testFn: () => Promise, timeout?: number, ) => { // For concurrent tests we first run the function that returns promise, and then register a @@ -86,9 +87,9 @@ export const initialize = async ({ nodeGlobal.test(testName, () => promise, timeout); }; - concurrent.only = ( + const only = ( testName: string, - testFn: () => Promise, + testFn: () => Promise, timeout?: number, ) => { const promise = mutex(() => testFn()); @@ -96,8 +97,13 @@ export const initialize = async ({ test.only(testName, () => promise, timeout); }; + concurrent.only = only; concurrent.skip = test.skip; + concurrent.each = bind(test, false); + concurrent.skip.each = bind(test.skip, false); + only.each = bind(test.only, false); + return concurrent; })(nodeGlobal.test); diff --git a/packages/jest-each/src/__tests__/__snapshots__/array.test.ts.snap b/packages/jest-each/src/__tests__/__snapshots__/array.test.ts.snap index d779a5e5df50..389742a74938 100644 --- a/packages/jest-each/src/__tests__/__snapshots__/array.test.ts.snap +++ b/packages/jest-each/src/__tests__/__snapshots__/array.test.ts.snap @@ -84,6 +84,42 @@ Instead was called with: undefined " `; +exports[`jest-each .test.concurrent throws an error when called with an empty array 1`] = ` +"Error: \`.each\` called with an empty Array of table data. +" +`; + +exports[`jest-each .test.concurrent throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template Literal. + +Instead was called with: undefined +" +`; + +exports[`jest-each .test.concurrent.only throws an error when called with an empty array 1`] = ` +"Error: \`.each\` called with an empty Array of table data. +" +`; + +exports[`jest-each .test.concurrent.only throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template Literal. + +Instead was called with: undefined +" +`; + +exports[`jest-each .test.concurrent.skip throws an error when called with an empty array 1`] = ` +"Error: \`.each\` called with an empty Array of table data. +" +`; + +exports[`jest-each .test.concurrent.skip throws an error when not called with an array 1`] = ` +"\`.each\` must be called with an Array or Tagged Template Literal. + +Instead was called with: undefined +" +`; + exports[`jest-each .test.only throws an error when called with an empty array 1`] = ` "Error: \`.each\` called with an empty Array of table data. " diff --git a/packages/jest-each/src/__tests__/__snapshots__/template.test.ts.snap b/packages/jest-each/src/__tests__/__snapshots__/template.test.ts.snap index f5097aa6218d..a7f2fabc0be5 100644 --- a/packages/jest-each/src/__tests__/__snapshots__/template.test.ts.snap +++ b/packages/jest-each/src/__tests__/__snapshots__/template.test.ts.snap @@ -273,6 +273,123 @@ exports[`jest-each .test throws error when there are no arguments for given head " `; +exports[`jest-each .test.concurrent throws an error when called with an empty string 1`] = ` +"Error: \`.each\` called with an empty Tagged Template Literal of table data. +" +`; + +exports[`jest-each .test.concurrent throws error when there are fewer arguments than headings over multiple rows 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, + 1, + 1, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent throws error when there are fewer arguments than headings when given one row 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent throws error when there are no arguments for given headings 1`] = ` +"Error: \`.each\` called with a Tagged Template Literal with no data, remember to interpolate with \${expression} syntax. +" +`; + +exports[`jest-each .test.concurrent.only throws an error when called with an empty string 1`] = ` +"Error: \`.each\` called with an empty Tagged Template Literal of table data. +" +`; + +exports[`jest-each .test.concurrent.only throws error when there are fewer arguments than headings over multiple rows 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, + 1, + 1, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent.only throws error when there are fewer arguments than headings when given one row 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent.only throws error when there are no arguments for given headings 1`] = ` +"Error: \`.each\` called with a Tagged Template Literal with no data, remember to interpolate with \${expression} syntax. +" +`; + +exports[`jest-each .test.concurrent.skip throws an error when called with an empty string 1`] = ` +"Error: \`.each\` called with an empty Tagged Template Literal of table data. +" +`; + +exports[`jest-each .test.concurrent.skip throws error when there are fewer arguments than headings over multiple rows 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, + 1, + 1, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent.skip throws error when there are fewer arguments than headings when given one row 1`] = ` +"Not enough arguments supplied for given headings: +a | b | expected + +Received: +Array [ + 0, + 1, +] + +Missing 2 arguments" +`; + +exports[`jest-each .test.concurrent.skip throws error when there are no arguments for given headings 1`] = ` +"Error: \`.each\` called with a Tagged Template Literal with no data, remember to interpolate with \${expression} syntax. +" +`; + exports[`jest-each .test.only throws an error when called with an empty string 1`] = ` "Error: \`.each\` called with an empty Tagged Template Literal of table data. " diff --git a/packages/jest-each/src/__tests__/array.test.ts b/packages/jest-each/src/__tests__/array.test.ts index 666e7aece2ec..19136ae65f81 100644 --- a/packages/jest-each/src/__tests__/array.test.ts +++ b/packages/jest-each/src/__tests__/array.test.ts @@ -28,6 +28,9 @@ const getGlobalTestMocks = () => { }; globals.test.only = jest.fn(); globals.test.skip = jest.fn(); + globals.test.concurrent = jest.fn(); + globals.test.concurrent.only = jest.fn(); + globals.test.concurrent.skip = jest.fn(); globals.it.only = jest.fn(); globals.it.skip = jest.fn(); globals.describe.only = jest.fn(); @@ -38,6 +41,9 @@ const getGlobalTestMocks = () => { describe('jest-each', () => { [ ['test'], + ['test', 'concurrent'], + ['test', 'concurrent', 'only'], + ['test', 'concurrent', 'skip'], ['test', 'only'], ['it'], ['fit'], @@ -289,6 +295,8 @@ describe('jest-each', () => { test.each([ [['test']], [['test', 'only']], + [['test', 'concurrent']], + [['test', 'concurrent', 'only']], [['it']], [['fit']], [['it', 'only']], @@ -327,6 +335,7 @@ describe('jest-each', () => { [ ['xtest'], ['test', 'skip'], + ['test', 'concurrent', 'skip'], ['xit'], ['it', 'skip'], ['xdescribe'], diff --git a/packages/jest-each/src/__tests__/index.test.ts b/packages/jest-each/src/__tests__/index.test.ts index 40ba0c391700..b2d5c7bec183 100644 --- a/packages/jest-each/src/__tests__/index.test.ts +++ b/packages/jest-each/src/__tests__/index.test.ts @@ -20,6 +20,21 @@ describe('array', () => { }); }); +describe('concurrent', () => { + describe('.add', () => { + each([ + [0, 0, 0], + [0, 1, 1], + [1, 1, 2], + ]).test.concurrent( + 'returns the result of adding %s to %s', + async (a, b, expected) => { + expect(a + b).toBe(expected); + }, + ); + }); +}); + describe('template', () => { describe('.add', () => { each` diff --git a/packages/jest-each/src/__tests__/template.test.ts b/packages/jest-each/src/__tests__/template.test.ts index 7952d90fcc5f..fe5d67217d0b 100644 --- a/packages/jest-each/src/__tests__/template.test.ts +++ b/packages/jest-each/src/__tests__/template.test.ts @@ -27,6 +27,9 @@ const getGlobalTestMocks = () => { }; globals.test.only = jest.fn(); globals.test.skip = jest.fn(); + globals.test.concurrent = jest.fn(); + globals.test.concurrent.only = jest.fn(); + globals.test.concurrent.skip = jest.fn(); globals.it.only = jest.fn(); globals.it.skip = jest.fn(); globals.describe.only = jest.fn(); @@ -37,6 +40,9 @@ const getGlobalTestMocks = () => { describe('jest-each', () => { [ ['test'], + ['test', 'concurrent'], + ['test', 'concurrent', 'only'], + ['test', 'concurrent', 'skip'], ['test', 'only'], ['it'], ['fit'], @@ -315,6 +321,7 @@ describe('jest-each', () => { test.each([ [['test']], [['test', 'only']], + [['test', 'concurrent', 'only']], [['it']], [['fit']], [['it', 'only']], @@ -361,6 +368,8 @@ describe('jest-each', () => { [ ['xtest'], ['test', 'skip'], + ['test', 'concurrent'], + ['test', 'concurrent', 'skip'], ['xit'], ['it', 'skip'], ['xdescribe'], diff --git a/packages/jest-each/src/bind.ts b/packages/jest-each/src/bind.ts index 773f722a4c6d..93998266f281 100644 --- a/packages/jest-each/src/bind.ts +++ b/packages/jest-each/src/bind.ts @@ -18,16 +18,20 @@ export type EachTests = Array<{ arguments: Array; }>; -type TestFn = (done?: Global.DoneFn) => Promise | void | undefined; -type GlobalCallback = (testName: string, fn: TestFn, timeout?: number) => void; +// type TestFn = (done?: Global.DoneFn) => Promise | void | undefined; +type GlobalCallback = ( + testName: string, + fn: Global.ConcurrentTestFn, + timeout?: number, +) => void; -export default (cb: GlobalCallback, supportsDone: boolean = true) => ( - table: Global.EachTable, - ...taggedTemplateData: Global.TemplateData -) => +export default ( + cb: GlobalCallback, + supportsDone: boolean = true, +) => (table: Global.EachTable, ...taggedTemplateData: Global.TemplateData) => function eachBind( title: string, - test: Global.EachTestFn, + test: Global.EachTestFn, timeout?: number, ): void { try { @@ -70,11 +74,11 @@ const buildTemplateTests = ( const getHeadingKeys = (headings: string): Array => headings.replace(/\s/g, '').split('|'); -const applyArguments = ( +const applyArguments = ( supportsDone: boolean, params: Array, - test: Global.EachTestFn, -): Global.EachTestFn => + test: Global.EachTestFn, +): Global.EachTestFn => supportsDone && params.length < test.length ? (done: Global.DoneFn) => test(...params, done) : () => test(...params); diff --git a/packages/jest-each/src/index.ts b/packages/jest-each/src/index.ts index b22d4e305ef6..b18cfd546271 100644 --- a/packages/jest-each/src/index.ts +++ b/packages/jest-each/src/index.ts @@ -22,15 +22,32 @@ const install = ( '`.each` must only be called with an Array or Tagged Template Literal.', ); } - const test = (title: string, test: Global.EachTestFn, timeout?: number) => - bind(g.test)(table, ...data)(title, test, timeout); + const test = ( + title: string, + test: Global.EachTestFn, + timeout?: number, + ) => bind(g.test)(table, ...data)(title, test, timeout); test.skip = bind(g.test.skip)(table, ...data); test.only = bind(g.test.only)(table, ...data); - const it = (title: string, test: Global.EachTestFn, timeout?: number) => - bind(g.it)(table, ...data)(title, test, timeout); + const testConcurrent = ( + title: string, + test: Global.EachTestFn, + timeout?: number, + ) => bind(g.test.concurrent)(table, ...data)(title, test, timeout); + + test.concurrent = testConcurrent; + testConcurrent.only = bind(g.test.concurrent.only)(table, ...data); + testConcurrent.skip = bind(g.test.concurrent.skip)(table, ...data); + + const it = ( + title: string, + test: Global.EachTestFn, + timeout?: number, + ) => bind(g.it)(table, ...data)(title, test, timeout); it.skip = bind(g.it.skip)(table, ...data); it.only = bind(g.it.only)(table, ...data); + it.concurrent = testConcurrent; const xit = bind(g.xit)(table, ...data); const fit = bind(g.fit)(table, ...data); @@ -38,7 +55,7 @@ const install = ( const describe = ( title: string, - suite: Global.EachTestFn, + suite: Global.EachTestFn, timeout?: number, ) => bind(g.describe, false)(table, ...data)(title, suite, timeout); describe.skip = bind(g.describe.skip, false)(table, ...data); diff --git a/packages/jest-jasmine2/src/__tests__/concurrent.test.ts b/packages/jest-jasmine2/src/__tests__/concurrent.test.ts new file mode 100644 index 000000000000..9e65fc57a0d2 --- /dev/null +++ b/packages/jest-jasmine2/src/__tests__/concurrent.test.ts @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +describe('concurrent', () => { + test.concurrent.each([ + [1, 2], + [2, 3], + [3, 4], + ])('should add 1 to number', async (a, sum) => { + expect(a + 1).toEqual(sum); + }); +}); diff --git a/packages/jest-jasmine2/src/each.ts b/packages/jest-jasmine2/src/each.ts index 054754e1b2f6..1b022929cf9a 100644 --- a/packages/jest-jasmine2/src/each.ts +++ b/packages/jest-jasmine2/src/each.ts @@ -24,4 +24,16 @@ export default (environment: JestEnvironment): void => { environment.global.fdescribe, false, ); + environment.global.it.concurrent.each = bindEach( + environment.global.it.concurrent, + false, + ); + environment.global.it.concurrent.only.each = bindEach( + environment.global.it.concurrent.only, + false, + ); + environment.global.it.concurrent.skip.each = bindEach( + environment.global.it.concurrent.skip, + false, + ); }; diff --git a/packages/jest-jasmine2/src/jasmineAsyncInstall.ts b/packages/jest-jasmine2/src/jasmineAsyncInstall.ts index b33e399cc304..7e9a862efb64 100644 --- a/packages/jest-jasmine2/src/jasmineAsyncInstall.ts +++ b/packages/jest-jasmine2/src/jasmineAsyncInstall.ts @@ -163,7 +163,11 @@ function makeConcurrent( env: Jasmine['currentEnv_'], mutex: ReturnType, ): Global.ItConcurrentBase { - return function (specName, fn, timeout) { + const concurrentFn = function ( + specName: string, + fn: Global.TestFn, + timeout?: number, + ) { let promise: Promise = Promise.resolve(); const spec = originalFn.call(env, specName, () => promise, timeout); @@ -187,6 +191,8 @@ function makeConcurrent( return spec; }; + concurrentFn.each = () => {}; + return concurrentFn; } export default function jasmineAsyncInstall( diff --git a/packages/jest-types/src/Global.ts b/packages/jest-types/src/Global.ts index df96271c126a..d57cd45a6bbb 100644 --- a/packages/jest-types/src/Global.ts +++ b/packages/jest-types/src/Global.ts @@ -12,6 +12,9 @@ export type TestName = string; export type TestFn = ( done?: DoneFn, ) => Promise | void | undefined; +export type ConcurrentTestFn = ( + done?: DoneFn, +) => Promise; export type BlockFn = () => void; export type BlockName = string; export type HookFn = TestFn; @@ -23,21 +26,24 @@ export type ArrayTable = Table | Row; export type TemplateTable = TemplateStringsArray; export type TemplateData = Array; export type EachTable = ArrayTable | TemplateTable; -export type EachTestFn = ( + +export type TestCallback = BlockFn | TestFn | ConcurrentTestFn; + +export type EachTestFn = ( ...args: Array -) => Promise | void | undefined; +) => ReturnType; // TODO: Get rid of this at some point type Jasmine = {_DEFAULT_TIMEOUT_INTERVAL?: number; addMatchers: Function}; -type Each = ( +type Each = (( table: EachTable, ...taggedTemplateData: Array -) => (title: string, test: EachTestFn, timeout?: number) => void; +) => (title: string, test: EachTestFn, timeout?: number) => void) | (() => void); export interface ItBase { (testName: TestName, fn: TestFn, timeout?: number): void; - each: Each; + each: Each; } export interface It extends ItBase { @@ -47,7 +53,8 @@ export interface It extends ItBase { } export interface ItConcurrentBase { - (testName: string, testFn: () => Promise, timeout?: number): void; + (testName: string, testFn: ConcurrentTestFn, timeout?: number): void; + each: Each; } export interface ItConcurrentExtended extends ItConcurrentBase { @@ -61,7 +68,7 @@ export interface ItConcurrent extends It { export interface DescribeBase { (blockName: BlockName, blockFn: BlockFn): void; - each: Each; + each: Each; } export interface Describe extends DescribeBase {