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.
74 lines
2.9 KiB
Markdown
74 lines
2.9 KiB
Markdown
# 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](../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)](../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](../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](./tooling.md#source-imports-use-ts-extensions)).
|
|
|
|
## AAA ordering
|
|
|
|
The rule is in
|
|
[CONTRIBUTING.md § Testing discipline (type-driven)](../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](./tooling.md#oxlint-disable-directives-live-next-to-the-code)).
|