development/ had restated the actionables that CONTRIBUTING.md owns: the branching step list, the script prefix list, the feedback-tier rule of thumb, the commit convention and the type-driven test loop. Those now live only in CONTRIBUTING.md; development/ keeps the decision blocks and links to the rule. development/README.md, CONTRIBUTING.md and AGENTS.md state the 'write each fact once' principle explicitly.
56 lines
2.4 KiB
Markdown
56 lines
2.4 KiB
Markdown
# Testing
|
|
|
|
For this library the types _are_ the feature — narrowing, `exhaustive()`
|
|
returns, the `Matcher<T>` contract — so a runtime-only test loop would verify
|
|
the wrong thing. The contributor-facing 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 the loop is type-driven and what was rejected.
|
|
|
|
#### Decision (2026-09)
|
|
|
|
Test-driven development is _type_-driven here: 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, and a type-level library
|
|
would then ship a broken feature that 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 wiring, `exhaustive()`
|
|
throwing, or the `otherwise` fallback (see `src/index.test.ts`).
|
|
|
|
## Enforcement
|
|
|
|
Type-first is also enforced structurally: `npm test` runs `check:tsc` before the
|
|
test runner, so a wrong type can never be papered over by a passing assertion.
|
|
Per [AGENTS.md § Never do](../AGENTS.md#never-do), reach green honestly — fix the
|
|
types so both the type check and the runtime assertion pass, never suppress the
|
|
ones you can't make pass.
|
|
|
|
The runner is `node --test --strip-types "src/**/*.test.ts"` and the tiers are
|
|
listed in
|
|
[CONTRIBUTING.md § Development commands](../CONTRIBUTING.md#development-commands).
|
|
`c8` uses V8 coverage, so the `--strip-types` source is instrumented without a
|
|
build step. The runner relies on the `.ts` import-extension convention (see
|
|
[tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
|
|
|
## Known issues
|
|
|
|
- The type-aware linter misidentifies `expectTypeOf()` as a floating promise,
|
|
so `src/index.test.ts` carries a file-level `oxlint-disable
|
|
typescript/no-floating-promises` with an explanatory comment. This is a known
|
|
false positive, not a rule we want off project-wide (see
|
|
[tooling.md § oxlint-disable directives live next to the code](./tooling.md#oxlint-disable-directives-live-next-to-the-code)).
|