Happier Testing And TDD
Use this skill for behavior-changing work in this repository, especially when changes touch shared runtime contracts, CLI/server/UI flows, or any lane that historically accumulates stale fixtures.
Goal
Apply strict RED-GREEN-REFACTOR while following Happier-specific lane, fixture, and rerun rules so changes do not silently drift until a late pipeline sweep.
Workflow
- Inventory first
- Search for existing tests by symbol, route, command, feature id, config key, component name, or error code.
- Map the affected lane(s) and any shared/package-local harnesses the change can invalidate before editing code.
- Name the observable contract or material risk the test must distinguish before writing it.
- For user-visible or environment-dependent work, define the composed live recipe before implementation: exact entry point, provider/account/state, actions, expected outcome, recovery path, and build/bundle/runtime identity that will prove the result.
- Update the most relevant existing test first when possible.
- Consolidate overlapping tests instead of stacking new ones on top.
- Classify failures correctly
production bug: runtime behavior is wrongtest drift: assertions/fixtures assume an obsolete contractharness drift: helpers/mocks/testkit no longer match real runtime wiringinfra/resource issue: disk, Docker, stale child processes, or similar environment failures
- RED
- Write or update the smallest relevant test first.
- Run only the smallest relevant slice and confirm it fails because the intended behavior is missing or wrong, not because of setup, fixtures, mocks, wording, syntax, or an unrelated error.
- GREEN
- Implement the smallest fix that satisfies the failing behavior.
- Keep internal behavior real; mock only system boundaries.
- REFACTOR
- Extract shared helpers only when there is repeated real duplication or repeated stale drift.
- Keep file responsibilities focused.
- Broaden validation
- After a targeted green run in a shared area, rerun one broader related lane.
- Before handoff, rerun the touched package typecheck/build-enforcing lane and the relevant repo lanes.
Test Value Gate
- Scope-preserving solution economy never caps evidence. Test and QA depth follow materially distinct behavior, reachable failure modes, and risk; implementation size, line count, or a desire for one runnable check cannot justify dropping a required contract, edge/failure/recovery case, compatibility direction, platform path, or live gate.
- TDD proves an observable contract; it does not require a new test for every changed function, branch, helper, or file.
- Prefer strengthening or consolidating the canonical owner-level test over adding overlapping coverage.
- One discriminating test is more valuable than many shallow permutations. Add cases only for materially different contracts, boundaries, or failure modes.
- A useful test distinguishes the intended implementation from at least one plausible incorrect implementation. If it would pass both, strengthen or remove it.
- Do not add runtime tests that merely restate TypeScript types, mirror implementation structure, assert pass-through wiring or incidental call counts, or police wording, formatting, raw styles, or example values.
- Exercise real internal behavior through the canonical/public owner boundary whenever practical.
- Remove or consolidate redundant tests introduced or exposed by the change.
Compatibility Contract Gate
- Use
skills/happier-compatibility when a behavior change affects wire/semantic contracts, persistence, schemas/migrations, feature negotiation, installer/service state, mixed versions, upgrades, or rollback. - Name the exact released/predecessor producer and consumer plus the direction the test proves. Prefer the real historical serializer/client/artifact or a provenance-pinned golden vector; do not reconstruct “old” behavior from current types or a new mock.
- Add one discriminating contract/vector test per material reachable direction, then only the risk-selected end-to-end flows. Do not multiply UI × CLI × daemon × server permutations when the changed seam does not couple them.
- Inventory and consolidate existing compatibility fixtures and harnesses before adding another family; a compatibility test must not create a second implementation of the protocol it is meant to verify.
Happier Lane Map
Canonical top-level lanes:
yarn testyarn test:integrationyarn test:e2e:core:fastyarn test:e2e:core:slowyarn test:e2e:uiyarn test:providersyarn test:db-contract:docker
CLI lane rule:
apps/cli unit tests must not force a full CLI dist build.- Use the lane-specific global setup files:
src/test-setup.unit.tssrc/test-setup.integration.tssrc/test-setup.slow.ts
Fixture And Mock Policy
- Do not partially mock central shared modules such as
@/sync/domains/state/storage. - Prefer package-local shared factories/testkits for repeated boundary mocks.
- Keep cross-repo primitives in
packages/tests/src/testkit. - Before adding a new helper or mock family, inspect the codebase for the existing canonical testkit/helper for that boundary.
- Prefer reusing, extending, generalizing, or extracting from canonical helpers over introducing similar-but-different variants.
- When a new canonical helper replaces older local variants, migrate or remove the overlapping variants instead of leaving parallel helper families behind.
- Be careful with repeat-offender boundaries: prefer canonical helpers over fresh inline mocks for UI boundaries such as
expo-router, @/text, @/modal, react-native, and react-native-unistyles; prefer existing server route/DB harnesses over direct storage mocks when available. - For
apps/ui tests, treat apps/ui/sources/dev/testkit/** as the default surface. Read apps/ui/sources/dev/testkit/README.md first and prefer imports from @/dev/testkit for mocks, fixtures, render helpers, hook helpers, and harnesses. - Do not add new inline
vi.mock(...) families for expo-router, @/text, @/modal, react-native, react-native-unistyles, or @/sync/domains/state/storage when the UI testkit already owns that boundary. If a needed case is missing, extend the canonical UI testkit helper in the same change instead of inventing a file-local mock family. - If a one-off local UI override is truly unavoidable, keep it minimal, base it on the canonical factory where possible, and leave a short justification comment rather than turning it into a new reusable pattern.
- Prefer typed fixtures/builders from the owning testkit over repeated inline object literals whenever the same state/session/theme/config shape is reused across tests.
- Keep package-specific fixtures near the owning package:
- UI helpers in
apps/ui - CLI helpers in
apps/cli - server helpers in
apps/server
UI E2E Rules
- Use stable
testID selectors, not visible copy, as the primary selector contract. - Click the real submit/confirm button after waiting for it to be enabled.
- Do not rely on Enter-to-send or similar settings-sensitive shortcuts unless the test explicitly configures the setting first.
- When a UI flow changes, update the corresponding Playwright spec in the same change.
Anti-Flake Process Rules
- Keep only one active rerun per spec/lane.
- If a runner hangs or is killed, inspect whether the failure is repo-owned, harness-owned, or environmental before retrying blindly.
- When shared process helpers change, rerun a broader lane that can reveal leaked handles or child-process cleanup regressions.
Live Validation Gates
Host-test green alone is not shippable for user-visible behavior; this skill owns the lane-level live-validation rules.
- Treat live gates (managed-stack browser QA, argent device QA) as ship gates for UI-visible changes; write or extend host tests from what the live loop taught, afterwards.
- For daemon/session/provider/API behavior that depends on real process, transport, authentication, persistence, or provider semantics, run the named composed CLI/API/daemon recipe when the authorized environment is available. Corridor tests do not replace this gate.
- If a defect family escapes host tests twice, stop adding host tests and switch to live-in-the-loop: fix → load and attest the updated build/bundle/module → replay the exact failing recipe → verify live, closing each defect with a live PASS against that attested basis in the same session.
- When a full-suite result is used as a release/ship gate, or shared-state leakage/order dependence is a material risk, run it twice back-to-back before calling it deterministic.
- If a documented memory-heavy UI host suite OOMs at the default heap, rerun with
NODE_OPTIONS=--max-old-space-size=8192 instead of silently narrowing the lane. - Device QA must pin bundle identity when stale Metro state could invalidate the result: full Metro reload, Fast Refresh off, and a module probe.
Do not mark a validation step complete merely because wiring is registered, a command reached a compiler/test runner, or a background process remains running. Record the terminal exit/result and decisive product evidence. If the live recipe cannot run, mark it BLOCKED with the missing prerequisite and next action; do not substitute more host tests and call the behavior shipped.
Output Expectations
When reporting testing work, summarize:
- failing area and classification
- root cause
- targeted RED/GREEN evidence
- broader lane rerun performed
- residual risk, if any