A type test asks whether a caller is accepted or rejected, not whether the runtime implements a rule. Include positive usage so a contract that rejects everything cannot pass by accident. Negative usage needs a targeted diagnostic, not a broad ignore comment.
@ts-expect-error requires the next line to contain a diagnostic. If the public type becomes too permissive, the directive becomes unused and checking fails. It does not verify a particular error code: keep each invalid usage minimal so another unrelated error cannot hide the intended regression.
In the download, consumer.ts imports through the package’s public export and its built .d.ts files. It checks valid branded ids and payloads, plus invalid raw ids, missing title, wrong callback fields, async listeners, readonly assignment and an unknown channel. It is never a runtime entry point.
The browser error lab below shows one misuse without suppression so you can inspect the actual compiler message. The local negative tests then assert that this kind of misuse remains rejected during future edits.
Predict, check, and run
Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.
type Listener<T> = (value: T) => undefined;
const listener: Listener<{ title: string }> = book => {
console.log(book.title);
};
listener({ title: "Types" });Enable JavaScript to edit and type-check this example.
Check your prediction
Types Read the compiler error
Predict the rejected relationship before checking. Read the real diagnostic and its source location; fix the contract or the value instead of hiding it with an assertion.
type Listener<T> = (value: T) => undefined;
const callback: Listener<number> = async value => {
console.log(value);
};Enable JavaScript to edit and type-check this example.
Check your diagnosis
The Promise return is unsupported. A compile-only negative test can protect that rejection without running the callback.
Keep each negative caller focused
An expected-error directive confirms that some diagnostic exists on the following line. It does not confirm which diagnostic. A wrong payload with both a missing brand and a missing title could keep failing for the brand after the title contract was accidentally weakened.
Establish every unrelated precondition first, then violate one relationship. Include valid calls through the actual public export too. This combination prevents both false confidence from unrelated errors and an unusably strict API that rejects everyone.
type Item = {
title: string;
pages: number;
};
function label(item: Item): string {
return item.title;
}
function negative(): void {
// @ts-expect-error only the required title is missing
label({ pages: 120 });
}
console.log(label({ title: "Types", pages: 120 }));Enable JavaScript to edit and type-check this example.
Check your prediction
Types The negative line keeps pages valid. Weakening the title requirement can now make this specific directive unused instead of leaving another unrelated error in its place.
type Listener<T> = (value: T) => undefined;
function negative(): void {
// @ts-expect-error the return policy rejects Promise-returning listeners
const listener: Listener<number> = async value => {};
}
const sync: Listener<number> = value => {};
console.log(sync(0) === undefined);Enable JavaScript to edit and type-check this example.
Check your prediction
true The valid callback checks usability; the negative callback checks the async restriction. Widening undefined to void should break the negative test rather than change a runtime fixture.
Try the rule on new inputs
Inspect each real consumer directive and name the one relationship it violates. Then weaken that relationship and require an unused-directive failure. Restore declarations by rebuilding from source.
Build it yourself
Public Type Tests: practise
Write a Listener<{count:number}> and a valid callback printing 0. In a never-called function, add one @ts-expect-error immediately before an async callback assignment. Print type contract checked after the valid call. Then weaken Listener to void and confirm the directive becomes unused.
// Start from the stated contract. Add a valid call and an edge case.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The negative example should have only the return-type incompatibility.
Hint 2: a first step
Keep intentional invalid usage inside a function you never invoke; checking still visits it.
Show one solution
type Listener<T> = (value: T) => undefined;
const listener: Listener<{ count: number }> = event => {
console.log(event.count);
};
function negativeContracts(): void {
// @ts-expect-error an async function returns a Promise, not undefined
const wrong: Listener<{ count: number }> = async () => {};
}
listener({ count: 0 });
console.log("type contract checked");Enable JavaScript to edit and type-check this example.
0
type contract checkedTrace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.
Change it and test it
Remove the directive and read the real error. Restore it, then change undefined to void and confirm the unused-directive diagnostic. This mutation demonstrates what the negative case protects.
Completion is saved in this browser only. It does not measure mastery.