Skip to content

Commit

Permalink
feat: improve each template literal inference (#14920)
Browse files Browse the repository at this point in the history
  • Loading branch information
MathieuFedrigo authored Mar 17, 2024
1 parent 375b3ee commit ca363c8
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
- `[jest-runtime]` Support `import.meta.resolve` ([#14930](https://github.com/jestjs/jest/pull/14930))
- `[@jest/schemas]` Upgrade `@sinclair/typebox` to v0.31 ([#14072](https://github.com/jestjs/jest/pull/14072))
- `[@jest/types]` `test.each()`: Accept a readonly (`as const`) table properly ([#14565](https://github.com/jestjs/jest/pull/14565))
- `[@jest/types]` Improve argument type inference passed to `test` and `describe` callback functions from `each` tables ([#14920](https://github.com/jestjs/jest/pull/14920))
- `[jest-snapshot]` [**BREAKING**] Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in snapshots ([#13965](https://github.com/facebook/jest/pull/13965))
- `[jest-snapshot]` Support Prettier 3 ([#14566](https://github.com/facebook/jest/pull/14566))
- `[pretty-format]` [**BREAKING**] Do not render empty string children (`''`) in React plugin ([#14470](https://github.com/facebook/jest/pull/14470))
Expand Down
45 changes: 40 additions & 5 deletions docs/GlobalAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -1050,7 +1050,7 @@ test.each(table)('table as a variable example', (a, b, expected, extra) => {

#### Template literal

If all values are of the same type, the template literal API will type the arguments correctly:
If all input values are of the same type, the template literal API will type the arguments correctly:

```ts
import {test} from '@jest/globals';
Expand All @@ -1060,12 +1060,27 @@ test.each`
${1} | ${2} | ${3}
${3} | ${4} | ${7}
${5} | ${6} | ${11}
`('template literal example', ({a, b, expected}) => {
// all arguments are of type `number`
`('template literal example same type', ({a, b, expected}) => {
// all arguments are of type `number` because all inputs (a, b, expected) are of type `number`
});
```

Otherwise it will require a generic type argument:
If the inputs have different types, the arguments will be typed as a union of all the input types (i.e. type of the variables inside the template literal):

```ts
import {test} from '@jest/globals';

test.each`
a | b | expected
${1} | ${2} | ${'three'}
${3} | ${4} | ${'seven'}
${5} | ${6} | ${'eleven'}
`('template literal example different types', ({a, b, expected}) => {
// all arguments are of type `number | string` because some inputs (a, b) are of type `number` and some others (expected) are of type `string`
});
```

Otherwise, if you want each argument to have the right type, you have to explicitly provide the generic type argument:

```ts
import {test} from '@jest/globals';
Expand All @@ -1076,6 +1091,26 @@ test.each<{a: number; b: number; expected: string; extra?: boolean}>`
${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`
// all arguments are typed as expected, e.g. `a: number`, `expected: string`, `extra: boolean | undefined`
});
```

:::caution

Keep in mind the variables inside the template literal are not type checked, so you have to ensure that their types are correct.

```ts
import {test} from '@jest/globals';

test.each<{a: number; expected: string}>`
a | expected
${1} | ${'one'}
${'will not raise TS error'} | ${'two'}
${3} | ${'three'}
`('template literal with wrongly typed input', ({a, expected}) => {
// all arguments are typed as stated in the generic: `a: number`, `expected: string`
// WARNING: `a` is of type `number` but will be a string in the 2nd test case.
});
```

:::
16 changes: 8 additions & 8 deletions packages/jest-types/__typetests__/each.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ expectType<void>(
${'a'} | ${true}
${'b'} | ${false}
`('some test', ({item, expected}) => {
expectType<unknown>(item);
expectType<unknown>(expected);
expectType<string | boolean>(item);
expectType<string | boolean>(expected);
}),
);
expectType<void>(
Expand Down Expand Up @@ -257,8 +257,8 @@ expectType<void>(
`(
'some test',
({item, expected}) => {
expectType<unknown>(item);
expectType<unknown>(expected);
expectType<string | boolean>(item);
expectType<string | boolean>(expected);
},
1000,
),
Expand Down Expand Up @@ -393,8 +393,8 @@ expectType<void>(
${'a'} | ${true}
${'b'} | ${false}
`('some test', async ({item, expected}) => {
expectType<unknown>(item);
expectType<unknown>(expected);
expectType<string | boolean>(item);
expectType<string | boolean>(expected);
}),
);
expectType<void>(
Expand Down Expand Up @@ -432,8 +432,8 @@ expectType<void>(
`(
'some test',
({item, expected}) => {
expectType<unknown>(item);
expectType<unknown>(expected);
expectType<string | boolean>(item);
expectType<string | boolean>(expected);
},
1000,
),
Expand Down
6 changes: 3 additions & 3 deletions packages/jest-types/src/Global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,12 @@ interface Each<EachFn extends TestFn | BlockFn> {
) => void;

// when the table is a template literal
<T = unknown>(
<T extends Array<unknown>>(
strings: TemplateStringsArray,
...expressions: Array<T>
...expressions: T
): (
name: string | NameLike,
fn: (arg: Record<string, T>, done: DoneFn) => ReturnType<EachFn>,
fn: (arg: Record<string, T[number]>, done: DoneFn) => ReturnType<EachFn>,
timeout?: number,
) => void;

Expand Down

0 comments on commit ca363c8

Please sign in to comment.