[feature] `.toEqualUnorderedTypeOf`
I have a use case where the order of items in the construction of a tuple type is non-deterministic. To test I currently do:
const tuple = expectTypeOf<TupleType>();
tuple.toMatchTypeOf<[any, any, any]>(); // has length 3
tuple.items.exclude<A | B>().toEqualTypeOf<C>(); // one of the items is C
tuple.items.exclude<A | C>().toEqualTypeOf<B>(); // one of the items is B
tuple.items.exclude<B | C>().toEqualTypeOf<A>(); // one of the items is A
But this only works when tuple items are indeed not union types. There is no way to test if one of the items type is a certain union type.
I propose a .toEqualUnorderedTypeOf:
expectTypeOf<TestType>().toEqualUnorderedTypeOf<[A, B, A]>();
/* any of these would pass, and fail otherwise
* [A, B, A]
* [B, A, A]
* [A, A, B]
*/
Other matchers/combinators along this line are
-
.toContainTypeOf// passes if at least one item has equal type -
.toContainItemsTypeOf// passes if tuple items is a superset of provided tuple -
.excludeItem// returns tuple with items of equal type removed, or fail if not found -
.excludeItems// returns tuple minus (in set sense) the provided tuple, or fail if can't
These seems a little niche and probably complex to implement - could you say more about the use case? Maybe there's a way it could be made deterministic, it seems a bit strange for types to be non-deterministic.
There is this trick to implement transforming union types to tuples of constituents:
// must condition on T
type Contra<T> = T extends infer I ? (arg: I) => void : never;
// must wrap in tuple
type InferContra<F> = [F] extends [(arg: infer T) => void] ? T : never;
/*
* Which one is picked is nondeteriminstic.
* Recall that "a" | "b" == "b" | "a".
*/
type PickOne<T> = InferContra<InferContra<<Contra<<Contra<T>>>>;
type _Union2Tuple<ACC extends any[], T> = PickOne<T> extends infer U
? Exclude<T, U> extends never
? [T, ...ACC]
: _Union2Tuple<[U, ...ACC], Exclude<T, U>>
: never;
type Union2Tuple<T> = _Union2Tuple<[], T>;
/*
* Because the order of constructing the tuple type is nondeterministic,
* we can not assert with a tuple type. We can assert the length of the tuple
* by _matching_ with tuples such as `[any, any]` to assert a length of 2.
* Then exclude all but except one type from the tuple items type in all
* combinations.
*/
{
const tuple = expectTypeOf<Union2Tuple<"a" | 1>>();
tuple.toMatchTypeOf<[any, any]>();
tuple.items.exclude<"a">().toEqualTypeOf<1>();
tuple.items.exclude<1>().toEqualTypeOf<"a">();
}
@mmkal I'm willing to hack on how one would be implemented. Names are open for bike-shedding.
@gomain this is an old issue, but v0.20.0 added a UnionToTuple type for some internal stuff. And you are right, I have observed the ordering of the tuple does seem to be non-deterministic. I assumed it would just be unstable between typescript versions, but it can change between times you hover on a type in the IDE.
If you still want to try it out, and can find a not-too-complicated way to implement, I'd accept a PR.