diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4cff3ea6efa0..d14ba70e1299 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
- `[jest]` Expose `Config` type ([#12848](https://github.com/facebook/jest/pull/12848))
- `[@jest/reporters]` Improve `GitHubActionsReporter`s annotation format ([#12826](https://github.com/facebook/jest/pull/12826))
+- `[@jest/types]` Infer argument types passed to `test` and `describe` callback functions from `each` tables ([#12885](https://github.com/facebook/jest/pull/12885))
### Fixes
diff --git a/docs/GlobalAPI.md b/docs/GlobalAPI.md
index 42cdd9f9c872..ad68f1389c15 100644
--- a/docs/GlobalAPI.md
+++ b/docs/GlobalAPI.md
@@ -9,7 +9,7 @@ In your test files, Jest puts each of these methods and objects into the global
import TOCInline from '@theme/TOCInline';
-
+
---
@@ -934,3 +934,108 @@ const add = (a, b) => a + b;
test.todo('add should be associative');
```
+
+## TypeScript Usage
+
+:::info
+
+These TypeScript usage tips and caveats are only applicable if you import from `'@jest/globals'`:
+
+```ts
+import {describe, test} from '@jest/globals';
+```
+
+:::
+
+### `.each`
+
+The `.each` modifier offers few different ways to define a table of the test cases. Some of the APIs have caveats related with the type inference of the arguments which are passed to `describe` or `test` callback functions. Let's take a look at each of them.
+
+:::note
+
+For simplicity `test.each` is picked for the examples, but the type inference is identical in all cases where `.each` modifier can be used: `describe.each`, `test.concurrent.only.each`, `test.skip.each`, etc.
+
+:::
+
+#### Array of objects
+
+The array of objects API is most verbose, but it makes the type inference a painless task. A `table` can be inlined:
+
+```ts
+test.each([
+ {name: 'a', path: 'path/to/a', count: 1, write: true},
+ {name: 'b', path: 'path/to/b', count: 3},
+])('inline table', ({name, path, count, write}) => {
+ // arguments are typed as expected, e.g. `write: boolean | undefined`
+});
+```
+
+Or declared separately as a variable:
+
+```ts
+const table = [
+ {a: 1, b: 2, expected: 'three', extra: true},
+ {a: 3, b: 4, expected: 'seven', extra: false},
+ {a: 5, b: 6, expected: 'eleven'},
+];
+
+test.each(table)('table as a variable', ({a, b, expected, extra}) => {
+ // again everything is typed as expected, e.g. `extra: boolean | undefined`
+});
+```
+
+#### Array of arrays
+
+The array of arrays style will work smoothly with inlined tables:
+
+```ts
+test.each([
+ [1, 2, 'three', true],
+ [3, 4, 'seven', false],
+ [5, 6, 'eleven'],
+])('inline table example', (a, b, expected, extra) => {
+ // arguments are typed as expected, e.g. `extra: boolean | undefined`
+});
+```
+
+However, if a table is declared as a separate variable, it must be typed as an array of tuples for correct type inference (this is not needed only if all elements of a row are of the same type):
+
+```ts
+const table: Array<[number, number, string, boolean?]> = [
+ [1, 2, 'three', true],
+ [3, 4, 'seven', false],
+ [5, 6, 'eleven'],
+];
+
+test.each(table)('table as a variable example', (a, b, expected, extra) => {
+ // without the annotation types are incorrect, e.g. `a: number | string | boolean`
+});
+```
+
+#### Template literal
+
+If all values are of the same type, the template literal API will type the arguments correctly:
+
+```ts
+test.each`
+ a | b | expected
+ ${1} | ${2} | ${3}
+ ${3} | ${4} | ${7}
+ ${5} | ${6} | ${11}
+`('template literal example', ({a, b, expected}) => {
+ // all arguments are of type `number`
+});
+```
+
+Otherwise it will require a generic type argument:
+
+```ts
+test.each<{a: number; b: number; expected: string; extra?: boolean}>`
+ a | b | expected | extra
+ ${1} | ${2} | ${'three'} | ${true}
+ ${3} | ${4} | ${'seven'} | ${false}
+ ${5} | ${6} | ${'eleven'}
+`('template literal example', ({a, b, expected, extra}) => {
+ // without the generic argument in this case types would default to `unknown`
+});
+```
diff --git a/packages/jest-types/__typetests__/each.test.ts b/packages/jest-types/__typetests__/each.test.ts
new file mode 100644
index 000000000000..147153b3b914
--- /dev/null
+++ b/packages/jest-types/__typetests__/each.test.ts
@@ -0,0 +1,667 @@
+/**
+ * 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.
+ */
+
+import {expectError, expectType} from 'tsd-lite';
+import {describe, test} from '@jest/globals';
+
+const list = [1, 2, 3];
+const tupleList: [number, number, string] = [1, 2, 'three'];
+const table = [
+ [1, 2, 'three'],
+ [3, 4, 'seven'],
+];
+const tupleTable: Array<[number, number, string, boolean?]> = [
+ [1, 2, 'three', true],
+ [3, 4, 'seven', false],
+ [5, 6, 'eleven'],
+];
+const objectTable = [
+ {a: 1, b: 2, expected: 'three', extra: true},
+ {a: 3, b: 4, expected: 'seven', extra: false},
+ {a: 5, b: 6, expected: 'eleven'},
+];
+
+// test.each
+
+expectType(
+ test.each(list)('some test', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each(list)(
+ 'some test',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each(tupleList)('some test', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each(tupleList)(
+ 'some test',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each([3, 4, 'seven'])('some test', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each([3, 4, 'seven'])(
+ 'some test',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each(table)('some test', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each(table)(
+ 'some test',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each(tupleTable)('some test', (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ }),
+);
+expectType(
+ test.each(tupleTable)(
+ 'some test',
+ (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each([
+ [1, 2, 'three'],
+ [3, 4, 'seven'],
+ ])('some test', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each([
+ [1, 2, 'three'],
+ [3, 4, 'seven'],
+ ])(
+ 'some test',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each(objectTable)('some test', ({a, b, expected, extra}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ }),
+);
+expectType(
+ test.each([
+ {a: 1, b: 2, expected: 'three', extra: true},
+ {a: 3, b: 4, expected: 'seven', extra: false},
+ {a: 5, b: 6, expected: 'eleven'},
+ ])(
+ 'some test',
+ ({a, b, expected, extra}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `('some test', ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `('some test', ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each<{item: string; expected: boolean}>`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `('some test', ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `(
+ 'some test',
+ ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+expectType(
+ test.each`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `(
+ 'some test',
+ ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+expectType(
+ test.each<{item: string; expected: boolean}>`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `(
+ 'some test',
+ ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectError(test.each());
+expectError(test.each('abc'));
+expectError(test.each(() => {}));
+
+expectType(test.only.each);
+expectType(test.skip.each);
+
+// test.concurrent.each
+
+expectType(
+ test.concurrent.each(list)('some test', async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each(list)(
+ 'some test',
+ async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.concurrent.each(tupleList)('some test', async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each(tupleList)(
+ 'some test',
+ async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.concurrent.each([3, 4, 'seven'])('some test', async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each([3, 4, 'seven'])(
+ 'some test',
+ async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.concurrent.each(table)('some test', async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each(table)(
+ 'some test',
+ async (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.concurrent.each(tupleTable)(
+ 'some test',
+ async (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ ),
+);
+expectType(
+ test.concurrent.each(tupleTable)(
+ 'some test',
+ async (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.concurrent.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `('some test', async ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `('some test', async ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each<{item: string; expected: boolean}>`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `('some test', async ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ }),
+);
+expectType(
+ test.concurrent.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `(
+ 'some test',
+ async ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ test.each`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `(
+ 'some test',
+ ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+expectType(
+ test.each<{item: string; expected: boolean}>`
+ item | expected
+ ${'a'} | ${true}
+ ${'b'} | ${false}
+ `(
+ 'some test',
+ ({item, expected}) => {
+ expectType(item);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectError(test.concurrent.each());
+expectError(test.concurrent.each('abc'));
+expectError(test.concurrent.each(() => {}));
+
+expectType(test.concurrent.only.each);
+expectType(test.concurrent.skip.each);
+
+// describe.each
+
+expectType(
+ describe.each(list)('describe each', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each(list)(
+ 'describe each',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each(tupleList)('describe each', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each(tupleList)(
+ 'describe each',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each([3, 4, 'seven'])('describe each', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each([3, 4, 'seven'])(
+ 'describe each',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each(table)('describe each', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each(table)(
+ 'describe each',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each(tupleTable)('describe each', (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ }),
+);
+expectType(
+ describe.each(tupleTable)(
+ 'describe each',
+ (a, b, expected, extra) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each([
+ [1, 2, 'three'],
+ [3, 4, 'seven'],
+ ])('describe each', (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each([
+ [1, 2, 'three'],
+ [3, 4, 'seven'],
+ ])(
+ 'describe each',
+ (a, b, expected) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each(objectTable)('describe each', ({a, b, expected, extra}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ }),
+);
+expectType(
+ describe.each([
+ {a: 1, b: 2, expected: 'three', extra: true},
+ {a: 3, b: 4, expected: 'seven', extra: false},
+ {a: 5, b: 6, expected: 'eleven'},
+ ])(
+ 'describe each',
+ ({a, b, expected, extra}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ expectType(extra);
+ },
+ 1000,
+ ),
+);
+
+expectType(
+ describe.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `('describe each', ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each<{
+ a: number;
+ b: number;
+ expected: string;
+ }>`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `('describe each', ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ }),
+);
+expectType(
+ describe.each`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `(
+ 'describe each',
+ ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+expectType(
+ describe.each<{
+ a: number;
+ b: number;
+ expected: string;
+ }>`
+ a | b | expected
+ ${1} | ${1} | ${2}
+ ${1} | ${2} | ${3}
+ ${2} | ${1} | ${3}
+ `(
+ 'describe each',
+ ({a, b, expected}) => {
+ expectType(a);
+ expectType(b);
+ expectType(expected);
+ },
+ 1000,
+ ),
+);
+
+expectError(describe.each());
+expectError(describe.each('abc'));
+expectError(describe.each(() => {}));
+
+expectType(describe.only.each);
+expectType(describe.skip.each);
diff --git a/packages/jest-types/__typetests__/globals.test.ts b/packages/jest-types/__typetests__/globals.test.ts
index e4bbd6fb6c6b..24fd9db72efb 100644
--- a/packages/jest-types/__typetests__/globals.test.ts
+++ b/packages/jest-types/__typetests__/globals.test.ts
@@ -17,55 +17,198 @@ import {
import type {Global} from '@jest/types';
const fn = () => {};
-const doneFn: Global.DoneTakingTestFn = done => {
- done();
-};
const asyncFn = async () => {};
const genFn = function* () {};
-const timeout = 5;
-const testName = 'Test name';
-const list = [1, 2, 3];
const table = [
- [1, 2],
- [3, 4],
+ [1, 2, 3],
+ [4, 5, 6],
];
-const readonlyTable = [[1, 2], 'one'] as const;
-// https://jestjs.io/docs/api#methods
-expectType(afterAll(fn));
-expectType(afterAll(asyncFn));
-expectType(afterAll(genFn));
-expectType(afterAll(fn, timeout));
-expectType(afterAll(asyncFn, timeout));
-expectType(afterAll(genFn, timeout));
-expectType(afterEach(fn));
-expectType(afterEach(asyncFn));
-expectType(afterEach(genFn));
-expectType(afterEach(fn, timeout));
-expectType(afterEach(asyncFn, timeout));
-expectType(afterEach(genFn, timeout));
+const testName = 'Test name';
+const timeout = 5;
+
+// done
+
+test(testName, done => {
+ done();
+});
+
+test(testName, done => {
+ done('error message');
+});
+
+test(testName, done => {
+ done(new Error('message'));
+});
+
+test(testName, done => {
+ expectType(done);
+});
+
+test(testName, done => {
+ expectError(done(123));
+});
+
+// beforeAll
+
expectType(beforeAll(fn));
expectType(beforeAll(asyncFn));
expectType(beforeAll(genFn));
+expectType(
+ beforeAll(done => {
+ expectType(done);
+ }),
+);
+
expectType(beforeAll(fn, timeout));
expectType(beforeAll(asyncFn, timeout));
expectType(beforeAll(genFn, timeout));
+expectType(
+ beforeAll(done => {
+ expectType(done);
+ }, timeout),
+);
+
+expectError(beforeAll());
+expectError(beforeAll('abc'));
+
+expectError(
+ beforeAll(async done => {
+ done();
+ }),
+);
+expectError(
+ beforeAll(function* (done) {
+ done();
+ }),
+);
+
+// beforeEach
+
expectType(beforeEach(fn));
expectType(beforeEach(asyncFn));
expectType(beforeEach(genFn));
+expectType(
+ beforeEach(done => {
+ expectType(done);
+ }),
+);
+
expectType(beforeEach(fn, timeout));
expectType(beforeEach(asyncFn, timeout));
expectType