My current challenge involves implementing a function similar to Jest's test.each
iterator:
// with "as const"
forEach([
[ 1, 2, 3 ],
[ "a", "b", "c" ],
] as const, (first, second, third) => {
// ...
});
// without "as const"
forEach([
[ 1, 2, 3 ],
[ "a", "b", "c" ],
], (first, second, third) => {
// ...
});
The main objective here is to ensure that the arguments first
, second
, and third
are strongly typed: without as const
, they should all be of type string | number
; with as const
, they should be respectively 1 | "a"
, 2 | "b"
, and 3 | "c"
. The actual functionality of this function may not be relevant and could even be nonsensical based on its name.
I have made some progress towards achieving the desired typing effect (view in the Playground):
// implementation is not required
declare function forEach<
Lists extends ReadonlyArray<ReadonlyArray<unknown>>,
>(
lists: Lists,
iterator: (...args: Lists[number]) => void,
): void;
Although I considered adopting Jest's typings, their approach seemed messy and fragile, so I decided against it.
The arguments are correctly typed, but there are still compilation errors in both scenarios:
with
as const
:The type
readonly [1, 2, 3]
is 'readonly' and cannot be assigned to the mutable type[first: 1 | "a", second: 2 | "b", third: 3 | "c"]
without
as const
:Type
number[] | string[]
is not assignable to type
. Target requires 3 element(s) but source may have fewer.[first: string | number, second: string | number, third: string | number]
Is there a way to define the forEach
function to satisfy both use cases?