Runtime tests execute JavaScript after type information is erased. A compile-time contract test instead asks whether valid usage is accepted and known invalid usage is rejected. These tests protect the public type surface when implementations change.
TypeScript’s @ts-expect-error comment expects a diagnostic on the next line. If no error remains, the directive itself becomes an unused-directive error. That makes it useful for a deliberately invalid call in a compile-only test file. @ts-ignore does not provide the same unused-directive signal.
Do not use expected-error comments to patch application code or to make a lesson’s real error disappear. The project’s contracts.ts contains only intentional misuse and is checked by tsc, never executed as a program. The ordinary playground error lab below remains unsuppressed so learners can read the actual diagnostic.
The project does not enable a special framework for type tests. Its normal strict compiler includes contracts.ts. Runtime tests still verify validation, normalization and request ordering; type tests cannot replace them.
Predict, run, and explain
Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.
type Book = { readonly title: string };
function label(book: Book): string {
return book.title;
}
console.log(label({ title: "Types" }));Enable JavaScript to edit and type-check this example.
Check your prediction
Types Read the compiler error
This is a deliberate negative test without suppression, so the real compiler error stays visible.
type Book = { readonly title: string };
const book: Book = { title: "Types" };
book.title = "Changed";Enable JavaScript to edit and type-check this example.
Check your diagnosis
Readonly prevents assignment through this typed reference. An application should create a changed copy; a compile-only test can retain the invalid line under an expected-error directive.
Protect caller relationships without executing misuse
A negative type test expects a diagnostic on a minimal invalid line. Keep unrelated mistakes off that line so the intended error cannot disappear while another error continues satisfying the directive. Positive calls are equally important: a signature rejecting every caller is not a useful success.
Compile-only invalid code must not become part of the runtime entry. A never-called function in a small playground can demonstrate checking, while the real project uses a separate contract file. A type test cannot prove trimming, error paths or request order.
type Book = { readonly title: string };
function label(book: Book): string {
return book.title;
}
function negative(): void {
// @ts-expect-error the caller must provide a string title
label({ title: 25 });
}
console.log(label({ title: "Types" }));Enable JavaScript to edit and type-check this example.
Check your prediction
Types The invalid call is checked but never executed. If label becomes too permissive, the directive must become unused and fail the checker.
type Book = { readonly title: string };
function negative(book: Book): void {
// @ts-expect-error readonly is part of the caller contract
book.title = "Changed";
}
const book: Book = { title: "Types" };
console.log(book.title);Enable JavaScript to edit and type-check this example.
Check your prediction
Types The example protects assignment permissions separately from runtime freezing. No invalid mutation is executed just to demonstrate the compiler constraint.
Try the rule on new inputs
Temporarily remove readonly and confirm the unused-directive error. Separately remove runtime normalization and confirm only the behavior suite catches it. Explain why neither test layer replaces the other.
Your turn
Compile-Time Contract Tests: practise
Create a readonly Book and a label function. Print Types for a valid call. In a local compile-only test file, add a deliberately missing title call preceded by @ts-expect-error. Then weaken the function parameter and confirm the directive becomes unused.
// Keep the runnable example valid. Negative test files are checked, not executed.Enable JavaScript to edit and type-check this example.
Hint 1: the idea
The negative test should fail if the public signature accidentally accepts an invalid call.
Hint 2: a first step
Use the actual invalid line in the compile-only file, not an unrelated error elsewhere.
Show one solution
type Book = { readonly title: string };
function label(book: Book): string {
return book.title;
}
console.log(label({ title: "Types" }));Enable JavaScript to edit and type-check this example.
TypesExplain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?
Change it and test it
Inspect the downloaded contracts.ts. Identify its four expected failures. Remove a readonly modifier temporarily and confirm checking fails because its expected error disappeared, then restore it. Never run dist/contracts.js: its invalid operations are not application examples.
Completion is saved in this browser only. It does not measure mastery.