@types/jest package and had problems.Definitions by: in index.d.ts) so they can respond.The jest-each liberally uses any which effectively drops typing:
interface Each {
(cases: any[]): (name: string, fn: (...args: any[]) => any) => void;
(strings: TemplateStringsArray, ...placeholders: any[]): (
name: string,
fn: (arg: any) => any
) => void;
}
For example:
// Type of letter is any.
test.each(['a', 'b', 'c'])('%# test %p', (letter) => {
expect(letter).toBeTruthy()
})
// Unless manually typed.
test.each(['a', 'b', 'c'])('%# test %p', (letter: string) => {
expect(letter).toBeTruthy()
})
What should the typing be though? This works for the above example:
interface Each {
<T>(cases: ReadonlyArray<T>): (
name: string,
fn: (...args: T[]) => any
) => void
(strings: TemplateStringsArray, ...placeholders: any[]): (
name: string,
fn: (arg: any) => any
) => void
}
// Type of letter is now string \o/
test.each(['a', 'b', 'c'])('%# test %p', (letter) => {
expect(letter).toBeTruthy()
})
But breaks when multidimensional tuples are destructured. E.g.:
/**
* [
* diagram: string,
* lhs: Rect,
* rhs: Rect,
* intersection: Rect,
* intersects: boolean,
* union: Rect,
* flip: boolean
* ]
*/
const tests = [
[
`
0 โ Overlapping Square
โโโโโฑโโ
โโโผโโLโRโผ
โโโโโนโโ
โ
`, // diagram
{x: -1, y: -1, w: 2, h: 2}, // lhs
{x: 0, y: -1, w: 2, h: 2}, // rhs
{x: 0, y: -1, w: 1, h: 2}, // intersection
true, // intersects
{x: -1, y: -1, w: 3, h: 2}, // union
false // flip
], // ...
]
describe('intersection()', () => {
// Broken: Type '(string | boolean | Rect)[]' is not assignable to type 'string'. :[
test.each(tests)(
'%#) %s (%p, %p) => %p %p, %p, %p',
(
_diagram: string,
lhs: Rect,
rhs: Rect,
intersection: Rect,
_intersects: boolean,
_union: Rect,
flip: boolean
) =>
expect(
rect.intersection(flip ? rhs : lhs, flip ? lhs : rhs)
).toStrictEqual(intersection)
)
// ...
})
This task tracks identifying the proper template parameterization and adding it to the Jest types.
Can't find another way than:
```typescript
interface Each {
...
// default case
// for primitive array
(strings: TemplateStringsArray, ...placeholders: any[]): (
name: string,
fn: (arg: any) => any
) => void;
}
OP almost had it.
On the first overload, the <T> should be <T extends any[]>, and the fn should be fn: (...args: T) => any. This might require TS 3.1 for proper behavior, especially if there is length or type mismatches or if the compiler is forced to infer an union type; I'm not entirely sure.
For the primitive value special case, there should be another overload with <T extends string | boolean | number | null | undefined>. The annoying thing is, jest checks it at runtime with Array.isArray, so we should be able to allow objects too, but we have no way to say "not any[]" to exclude arrays from this... yet. (Could try using an unbound <T> and any[] extends T ? never : T to force this overload to turn into never[] if you mix in an array.)
It might be possible to support mixing them with (Turns out you can't -- or, you can, but it will not DWIM).<T extends string | boolean | number | null | undefined | any[]>(cases: ReadonlyArray<T | [Exclude<T, any[]>]>), but writing the type of the test function then becomes way complicated, there might be odd interactions with never, and it might just break inference. It sounds possible, though, just annoying.
Lastly, we'll probably need an "escape hatch" overload with just (cases: ReadonlyArray<ReadonlyArray<any>>) to handle extreme corner cases. It'll produce better error messages if it's not generic anyway, and conforming to it _should_ automatically conform to the <T extends any[]> overload.
Note that the very last argument (the first argument past the argument you put in the each table for the current iteration) will actually be a done callback for nodeback-style tests, but I'm not sure there's any way we can declare it. It'll only be present if the .length of the function is long enough, and typescript doesn't generate literal .length types. Even if you need to test nodebacks it's easier to just write the test as an async function anyway and use util.promisify.
The template string overload, though, is not really possible to do with anything other than any.
@antoinebrault, @Jessidhia, thank you for the prompt and helpful responses. I'm afraid my typing isn't so good as yours. Does this look correct to you?
interface Each {
// Exclusively arrays.
<T extends any[]>(cases: ReadonlyArray<T>): (
name: string,
fn: (...args: T) => any
) => void
// Not arrays.
<T extends object | symbol | string | number | boolean | null | undefined>(
cases: ReadonlyArray<T>
): (name: string, fn: (...args: T[]) => any) => void
// Unsupported.
(cases: ReadonlyArray<ReadonlyArray<any>>): (
name: string,
fn: (...args: any[]) => any
) => void
(strings: TemplateStringsArray, ...placeholders: any[]): (
name: string,
fn: (arg: any) => any
) => void
}
In the second overload, I wasn't able to limit T to primitives via any[] extends T ? never : T. Maybe you will have more luck. As it is written, I don't know that T extends object | symbol | string | number | boolean | null | undefined really buys us anything and suggest reducing to:
interface Each {
// Exclusively arrays.
<T extends any[]>(cases: ReadonlyArray<T>): (
name: string,
fn: (...args: T) => any
) => void
// Not arrays.
<T>(cases: ReadonlyArray<T>): (
name: string,
fn: (...args: T[]) => any
) => void
// Unsupported.
(cases: ReadonlyArray<ReadonlyArray<any>>): (
name: string,
fn: (...args: any[]) => any
) => void
(strings: TemplateStringsArray, ...placeholders: any[]): (
name: string,
fn: (arg: any) => any
) => void
}
Note that the very last argument (the first argument past the argument you put in the each table for the current iteration) will actually be a
donecallback for nodeback-style tests, but I'm not sure there's any way we can declare it. It'll only be present if the.lengthof the function is long enough, and typescript doesn't generate literal.lengthtypes. Even if you need to test nodebacks it's easier to just write the test as anasyncfunction anyway and useutil.promisify.
I am only guessing but, for a tuple, perhaps it would be possible to type with something like T extends [any]? I don't think we could know a dynamic array's length.
Just to confirm the expected behaviour.
With "@types/jest": "^24.0.11" and "typescript": "^3.3.3333" there are still problems with automatic type inference.
For example, below:


...the type of test cases gets inferred as string | number instead of [string, number], causing the compilation to fail.
Adding an explicit cast of test case array or using a generic call it.each<[string, boolean]> helps.
Do you think we'll have any luck with automatic type inference for that purpose?
If not at the moment, what would be the TS language features that we lack and can request to tackle this issue?
I'm unsure but would like to know too. I have this same issue with Array.reduce():
type Case = [string, boolean, number]
const cases: ReadonlyArray<Case> = [
['foo', false],
['bar', true]
].reduce(
(
sum: ReadonlyArray<Case>,
[str, bool] // Actually (string | boolean)[] instead of [string, boolean] tuple.
) => ([...sum, <Case>([str, bool, 1])]), // Cast required.
[]
)
I have the same error with Each and tuple test cases.
Same here.
As I said earlier, the only possible way I found was to hardcode each tuple.
interface Each { <T extends [A], A>(cases: ReadonlyArray<T>): (name: string, fn: (...args: T) => any, timeout?: number) => void; <T extends [A, B], A, B>(cases: ReadonlyArray<T>): (name: string, fn: (...args: T) => any, timeout?: number) => void; ... }
For me passing the tuples through could resolve correctly my test-cases, or am I missing something?
interface Each {
// Exclusively arrays.
<T extends any, K extends any>(cases: ReadonlyArray<[T, K]>): (name: string, fn: (...args: [T, K]) => any, timeout?: number) => void;
}
Most helpful comment
Just to confirm the expected behaviour.
With
"@types/jest": "^24.0.11"and"typescript": "^3.3.3333"there are still problems with automatic type inference.For example, below:
...the type of test cases gets inferred as
string | numberinstead of[string, number], causing the compilation to fail.Adding an explicit cast of test case array or using a generic call
it.each<[string, boolean]>helps.Do you think we'll have any luck with automatic type inference for that purpose?
If not at the moment, what would be the TS language features that we lack and can request to tackle this issue?