Files
tiny-pattern-ts/development/testing.md
T
tmu ce3d618757 🔥 Remove the example index spec
The spec only exercised the template's match/P example API. Removing it
first lets the modules and exports it imports go next without leaving a
dangling reference.
2026-09-16 21:57:42 +00:00

2.9 KiB

Testing

For this library the types are the feature, so a runtime-only test loop would verify the wrong thing. The commands are in CONTRIBUTING.md; this file records why the loop is shaped the way it is.

Type-driven development

The rules — the loop and the pairing rule — are in CONTRIBUTING.md § Testing discipline (type-driven). What follows is why and what was rejected.

Decision (2026-09)

The compile-time expectation is written before the runtime assertion, and both before the implementation.

Why

  • A runtime-only test can pass while the type is wrong, so a type-level library would ship a broken feature its tests bless.
  • The type error is a more precise spec than a failing assertion, because it states the exact expected type before the logic exists.

Rejected

  • Runtime-first (classic red/green): it verifies the value, not the contract, and the contract is the product.
  • Testing the type only: it would not catch handler dispatch or the _ fallback (see src/primitive.test.ts).

The runner is node --test --strip-types "src/**/*.test.ts" and the tiers are in CONTRIBUTING.md § Development commands. c8 uses V8 coverage, so the --strip-types source is instrumented without a build step, and the runner relies on the .ts import-extension convention (see tooling.md).

AAA ordering

The rule is in CONTRIBUTING.md § Testing discipline (type-driven).

Decision (2026-09)

Test bodies read arrange → act → assert: inputs (the factory) set up first, the subject exercised once from them, all checks last — types then runtime. The blocks are labeled with // Arrange / // Act / // Assert comments and separated by a blank line; an empty block drops its label.

Why

  • Interleaved setup/checks hide what runs vs. what is observed; the eye re-reads the block to find the seams.
  • A factory built mid-test invites a second throwaway call of the subject; arranging it once makes the positive construction and the negative Parameters<…> check share one source of truth.
  • Labels make the seams explicit, not inferred — grep-able and reviewable without reading the statements.

Rejected

  • Unlabeled ordering (bare blank lines): the seams still have to be found by reading; the labels cost nothing.

Known issues

  • The type-aware linter misidentifies expectTypeOf() as a floating promise, so test files that use it (src/primitive.test.ts) carry a file-level oxlint-disable typescript/no-floating-promises with an explanatory comment. It is a known false positive, not a rule worth disabling project-wide (see tooling.md § oxlint-disable directives live next to the code).