docs: migrate README to API-Doc-Convention (S2-cascade.libs test-lib)
Restructured per ADR-0038 with Coverage-Statement-Block per Fault- Injection-Audit-Convention. Topology auto-populated. Pre-commit hook installed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
13
.githooks/pre-commit
Normal file
13
.githooks/pre-commit
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Sporel API-Doc-Convention pre-commit hook.
|
||||||
|
SPOREL_EXE="${SPOREL_EXE:-$(command -v Sporel.exe 2>/dev/null || command -v sporel 2>/dev/null)}"
|
||||||
|
if [ -z "$SPOREL_EXE" ]; then
|
||||||
|
echo "INFO: Sporel.exe not on PATH. Skipping lint." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
"$SPOREL_EXE" --lint="$(pwd)" --fix
|
||||||
|
RC=$?
|
||||||
|
if git diff --cached --name-only | grep -qx 'README.md'; then
|
||||||
|
git add README.md
|
||||||
|
fi
|
||||||
|
exit $RC
|
||||||
106
README.md
106
README.md
@@ -1,15 +1,76 @@
|
|||||||
# sporel-lib-core.input-test
|
# lib-core.input-test
|
||||||
|
|
||||||
P.3.3 test-lib — validates `lib-core.input` v0.4.0 API contracts (BIND_MAP,
|
Test-lib for `lib-core.input` (Tests-as-Libs per ADR-0037).
|
||||||
`was_action_released`, engine mouse-surface) via the `engine.test.*` assertion surface.
|
|
||||||
17 assertions total.
|
|
||||||
|
|
||||||
- Lib-ID: `lib-core.input-test`
|
**Version:** 0.1.0
|
||||||
- Version: `0.1.0`
|
**Lib-ID:** lib-core.input-test
|
||||||
- Kind: `test-lib` (headless; M.run_tests(ctx) invoked by engine test-runner)
|
**Requires:** lib-core.input v>=0.4.0
|
||||||
- Spec: `meta/docs/superpowers/specs/2026-05-12-p3-3-mouse-input-design.md`
|
**Tags:** test, input
|
||||||
|
|
||||||
## Run
|
## Topology
|
||||||
|
|
||||||
|
<!-- topology:start (auto-generated; do not edit) -->
|
||||||
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
this["lib-core.input-test"]
|
||||||
|
lib_core_input["lib-core.input"]
|
||||||
|
this --> lib_core_input
|
||||||
|
engine["engine.*"]
|
||||||
|
this --> engine
|
||||||
|
```
|
||||||
|
<!-- topology:end -->
|
||||||
|
|
||||||
|
## Coverage Statement
|
||||||
|
|
||||||
|
Per Fault-Injection-Audit-Convention (2026-05-16):
|
||||||
|
|
||||||
|
| Section | Assertion-Count | Coverage |
|
||||||
|
|---|---|---|
|
||||||
|
| Engine surface presence | 7 | `MOUSE_LEFT/RIGHT/MIDDLE` constants, `is_mouse_down`, `was_mouse_pressed`, `was_released`, `was_mouse_released` |
|
||||||
|
| `bind` mouse-strings | 1 | `action_count == 1` after `bind("click", {"mouse_left"})` |
|
||||||
|
| `bind` mixed key+mouse | 1 | `action_count == 2` after mixed bind `{"e", "mouse_left"}` |
|
||||||
|
| `bind` rejects unknown mouse-name | 3 | Lua error raised, message mentions bad string, no partial state mutation |
|
||||||
|
| Headless query stubs | 4 | `is_action_down`, `was_action_pressed`, `was_action_released` return false; `was_action_released` for unknown returns false |
|
||||||
|
| Key-only regression | 1 | `bind("walk_left", {"a","left"})` still works after BIND_MAP refactor |
|
||||||
|
| **TOTAL** | **17** | |
|
||||||
|
|
||||||
|
### Tested-Surface
|
||||||
|
- `input.bind` (mouse-string acceptance, mixed key+mouse, unknown-name rejection, atomicity)
|
||||||
|
- `input.action_count`
|
||||||
|
- `input.is_action_down` (bound action headless = false)
|
||||||
|
- `input.was_action_pressed`
|
||||||
|
- `input.was_action_released` (bound action headless = false; unknown action = false)
|
||||||
|
- Engine surface: `MOUSE_LEFT/RIGHT/MIDDLE`, `is_mouse_down`, `was_mouse_pressed`, `was_released`, `was_mouse_released` function presence
|
||||||
|
|
||||||
|
### Fault-Injection Audit (2026-05-16)
|
||||||
|
|
||||||
|
Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation.
|
||||||
|
|
||||||
|
#### Caught Mutations
|
||||||
|
|
||||||
|
| # | Mutation | Caught by |
|
||||||
|
|---|---|---|
|
||||||
|
| M1 | `function M.was_action_released` renamed (API-break) | Runtime error "attempt to call a nil value (field 'was_action_released')" — test aborts after assertion 14 |
|
||||||
|
| M2 | `if not entries then return false end` changed to `return nil` | `was_action_released for unknown action returns false` fails (`not ok`, got nil) |
|
||||||
|
|
||||||
|
#### Uncaught Mutations (Gaps)
|
||||||
|
|
||||||
|
| # | Mutation | Why uncaught | Severity |
|
||||||
|
|---|---|---|---|
|
||||||
|
| M3 | Dispatch-swap in `was_action_released` for-loop (key-kind ↔ mouse-kind) | Headless: all input queries return false regardless of swapped dispatch — no real events differentiate the paths | Med |
|
||||||
|
|
||||||
|
**Resolution:** Headless-unreachable. Requires real key/mouse events. Future-Slice B: integration test with simulated input events. No cheap headless fill possible.
|
||||||
|
|
||||||
|
### Structurally Untestable
|
||||||
|
- Real keyboard/mouse event delivery — headless mode never produces input events.
|
||||||
|
- `was_action_released` dispatch correctness for key-kind vs mouse-kind binding — see Gap M3.
|
||||||
|
|
||||||
|
### Deferred
|
||||||
|
- Integration test with simulated input events (Future-Slice B).
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Via `--test-libs=lib-core.input-test` (Smoke P0-S-batch Stage):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
Sporel.exe --libs-dir=<libs-path> --test-libs=lib-core.input-test
|
Sporel.exe --libs-dir=<libs-path> --test-libs=lib-core.input-test
|
||||||
@@ -17,26 +78,9 @@ Sporel.exe --libs-dir=<libs-path> --test-libs=lib-core.input-test
|
|||||||
|
|
||||||
Output is TAP version 13 on stdout; exit-code = failure-count (0 = all pass).
|
Output is TAP version 13 on stdout; exit-code = failure-count (0 = all pass).
|
||||||
|
|
||||||
## Coverage Statements (2026-05-16 Fault-Injection Audit)
|
## References
|
||||||
|
|
||||||
Verified via deliberate-mutation in fixture-mirror; mutations reverted after observation.
|
- ADR-0037 (Tests-as-Libs Convention)
|
||||||
|
- ADR-0038 (API-Doc-Convention)
|
||||||
### Caught Mutations
|
- Production-Lib: `~/Projects/Sporel/sporel-libs/lib-core/input/README.md`
|
||||||
|
- Spec: `meta/docs/superpowers/specs/2026-05-12-p3-3-mouse-input-design.md`
|
||||||
| # | Mutation | Caught by |
|
|
||||||
|---|---|---|
|
|
||||||
| M1 | `function M.was_action_released` renamed to `M._was_action_released_disabled` (API-break) | Runtime error "attempt to call a nil value (field 'was_action_released')" — test aborts after assertion 14 |
|
|
||||||
| M2 | `if not entries then return false end` changed to `return nil` (wrong return type for unknown action) | Assertion "was_action_released for unknown action returns false" (ok 16) — `not ok`, got nil |
|
|
||||||
|
|
||||||
### Uncaught Mutations (Gaps)
|
|
||||||
|
|
||||||
| # | Mutation | Why uncaught | Severity |
|
|
||||||
|---|---|---|---|
|
|
||||||
| M3 | Dispatch-swap in `was_action_released` for-loop: key-kind entries call `was_mouse_released`, mouse-kind call `was_released` | Headless mode: all input queries return false regardless of correct vs swapped dispatch — no real events to differentiate the paths | Med |
|
|
||||||
|
|
||||||
**Resolution per Gap:**
|
|
||||||
- Gap M3: Headless-unreachable — requires real key/mouse events to distinguish `was_released` from `was_mouse_released`. Future-Slice B: integration test with simulated input events. No cheap headless fill possible.
|
|
||||||
|
|
||||||
### Tested Production-Lib Surface
|
|
||||||
|
|
||||||
`lib-core.input` v0.4.0 — Tests cover behavior of: `input.bind` (mouse-string acceptance, mixed key+mouse, unknown-name rejection, atomicity), `input.action_count`, `input.is_action_down`, `input.was_action_pressed`, `input.was_action_released` (bound action headless=false; unknown action=false). Engine surface assertions: `MOUSE_LEFT/RIGHT/MIDDLE` constants, `is_mouse_down`, `was_mouse_pressed`, `was_released`, `was_mouse_released` function presence.
|
|
||||||
|
|||||||
3
scripts/install-hooks.sh
Normal file
3
scripts/install-hooks.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
echo "Hooks activated."
|
||||||
Reference in New Issue
Block a user