Table of contents:
npm install asserttt
asserttt
has no non-dev dependencies!
- Testing types
- Documenting TypeScript with code examples (e.g. via Markcheck)
- Support for TypeScript exercises, such as the ones provided by
Type<Challenge[]>
. - Testing that related types are consistent in normal code
If a file contains type tests, it’s not enough to run it, we must also type-check it:
-
tsx (TypeScript Execute) is a tool that type-checks files before running them.
- It works well with the Mocha test runner: example setup
-
ts-expect-error performs two tasks:
- Checking if each
@ts-expect-error
annotation prevents the right kind of error - Optional: reporting errors detected by TypeScript
- Checking if each
- Markcheck tests Markdown code blocks.
import { type Assert, assertType, type Assignable, type Equal, type Extends, type Includes, type Not } from 'asserttt';
//========== Asserting types: Assert<B> ==========
{
type Pair<X> = [X, X];
type _1 = Assert<Equal<
Pair<'a'>, ['a', 'a']
>>;
type _2 = Assert<Not<Equal<
Pair<'a'>, ['x', 'x']
>>>;
}
{
type _ = [
Assert<Assignable<number, 123>>,
Assert<Extends<Array<string>, Object>>,
Assert<Not<Extends<Array<string>, RegExp>>>,
Assert<Includes<'a'|'b', 'a'>>,
Assert<Includes<'a'|'b'|'c', 'a'|'c'>>,
];
}
//========== Asserting types of values: assertType<T>(v) ==========
const n = 3 + 1;
assertType<number>(n);
Assert<B>
assertType<T>(value)
Equality:
Equal<X, Y>
MutuallyAssignable<X, Y>
PedanticEqual<X, Y>
Comparing/detecting types:
Extends<Sub, Super>
Assignable<Target, Source>
Includes<Superset, Subset>
IsAny<T>
Boolean operations:
Not<B>
export type MutuallyAssignable<X, Y> =
[X, Y] extends [Y, X] ? true : false
;
- The brackets on the left-hand side of
extends
prevent distributivity. - Almost what we want for checking equality, but
any
is equal to all types – which is problematic when testing types.
This Equal
predicate works well for many use cases:
type Equal<X, Y> =
[IsAny<X>, IsAny<Y>] extends [true, true] ? true
: [IsAny<X>, IsAny<Y>] extends [false, false] ? MutuallyAssignable<X, Y>
: false
;
type IsAny<T> = 0 extends (1 & T) ? true : false;
type PedanticEqual<X, Y> =
(<T>() => T extends X ? 1 : 2) extends // (A)
(<T>() => T extends Y ? 1 : 2) ? true : false // (B)
;
It was suggested by Matt McCutchen (source). How does it work (source)?
In order to check whether the function type in line A extends the function type in line B, TypeScript has to compare the following two conditional types:
T extends X ? 1 : 2
T extends Y ? 1 : 2
Since T
does not have a value, both conditional types are deferred. Assignability of two deferred conditional types is computed via the internal function isTypeIdenticalTo()
and only true
if:
- Both have the same constraint.
- Their “then” branches have the same type and their “else” branches have the same type.
Thanks to #1, X
and Y
are compared precisely.
This hack has several downsides: See test/pedantic-equal_test.ts
for more information.
type Assert<_T extends true> = void;
Alas, we can’t conditionally produce errors at the type level. That’s why we need to resort to a type parameter whose extends
constraint requires it to be assignable to true
.
(Idea by Blaine Bublitz)
-
Package ts-expect inspired this package. It’s very similar. This package uses different names and has a utility type
Assert
(which doesn’t produce runtime code):type _ = Assert<Equal<X,Y>>; // asserttt expectType<TypeEqual<X, Y>>(true); // ts-expect
-
The type-challenges repository has a module with utility types for exercises. How is asserttt different?
- Smaller API
- Different names
- Implements boolean NOT via a helper type
Not
(vs. two versions of the same utility type).
-
eslint-plugin-expect-type supports an elegant notation but requires a special tool (eslint) for checking.