Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(@jest/types): infer argument types passed to test and describe functions from each tables #12885

Merged
merged 2 commits into from May 31, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -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

Expand Down
107 changes: 106 additions & 1 deletion docs/GlobalAPI.md
Expand Up @@ -9,7 +9,7 @@ In your test files, Jest puts each of these methods and objects into the global

import TOCInline from '@theme/TOCInline';

<TOCInline toc={toc.slice(2)} />
<TOCInline toc={toc.slice(1)} />

---

Expand Down Expand Up @@ -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`
});
```