📝 Add development/ docs for decisions and known issues
Category files under development/ replace the decision prose that was scattered through README.md and CONTRIBUTING.md. Each decision is a block with Decision (YYYY-MM) / Why / Rejected / Known issue; the new library.md records the public API contract and its limitations. AGENTS.md and backlog.tasks now point at the new home.
This commit is contained in:
1 parent
97dfe9e7b4
commit
dc7ce22fc5
9 files changed
+1049
-5
No files matched your search
@@ -0,0 +1,76 @@
|
||||
# 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
|
||||
|
||||
New behavior follows **type-driven development** (in Edwin Brady's sense):
|
||||
_treat the type as the plan for a program, and use the compiler and type checker
|
||||
as your assistant, guiding you to a complete program that satisfies the type_
|
||||
([idris-lang.org](https://www.idris-lang.org/)). Here that plan is the
|
||||
`expectTypeOf` assertion, written first. The loop is **type → red → green →
|
||||
refactor**:
|
||||
|
||||
1. **Type** — write the compile-time expectation first
|
||||
(`expectTypeOf(...).toEqualTypeOf<…>()`) and let `npm run check:tsc` fail on
|
||||
the _type_. The type error is the spec you want to hit before the runtime
|
||||
logic exists.
|
||||
2. **Red** — add the matching runtime assertion (`assert.*`) so
|
||||
`npm run test:unit` now fails on behavior.
|
||||
3. **Green** — implement in `src/*.ts` until both the type check and the test
|
||||
pass.
|
||||
4. **Refactor** — with the type system and the tests as the safety net, then
|
||||
`npm run verify` as the definition-of-done gate.
|
||||
|
||||
This is why every test in the suite pairs an `expectTypeOf(...)` with an
|
||||
`assert.*` — keep them together.
|
||||
|
||||
#### 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.
|
||||
|
||||
## Test tiers
|
||||
|
||||
- `npm run test:unit` — the test runner alone, for the fast local loop.
|
||||
- `npm test` — `check:tsc` + the unit suite; this is what pre-push and the
|
||||
baseline check run.
|
||||
- `npm run test:ci` — adds c8 coverage; used by CI. `c8` uses V8 coverage, so
|
||||
the `--strip-types` source is instrumented without a build step.
|
||||
|
||||
The runner is `node --test --strip-types "src/**/*.test.ts"`. It relies on the
|
||||
`.ts` import-extension convention (see [tooling.md](./tooling.md#source-imports-use-ts-extensions)).
|
||||
|
||||
#### Known issue
|
||||
|
||||
- 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)).
|
||||
Reference in new issue
Block a user