docs(07-04): complete state coverage plan — calendar.spec.ts + lists.spec.ts

This commit is contained in:
Lucas Berger
2026-06-11 02:13:34 -04:00
parent b074b4abb2
commit 10e570b21d
3 changed files with 124 additions and 14 deletions
@@ -0,0 +1,107 @@
---
phase: 07-mobile-test-harness
plan: "04"
subsystem: test-harness
tags: [playwright, e2e, calendar, lists, populated-state, error-state, empty-state, auth-bypass, service-worker]
dependency_graph:
requires:
- "apps/pwa/playwright.config.ts (07-01) — iphone/pixel project matrix, serviceWorkers: 'block', globalSetup path"
- "apps/pwa/e2e/global-setup.ts (07-02) — /health readiness gate + DB seed (calendar_id=10 'Seeded Test Event', 'E2E Grocery List' for user_id=1)"
- "apps/pwa/e2e/layout.spec.ts (07-03) — locator patterns and conventions mirrored"
provides:
- "apps/pwa/e2e/calendar.spec.ts — TEST-01 (populated + error) + TEST-02 (auth-bypass + SW precondition) assertions for /calendar"
- "apps/pwa/e2e/lists.spec.ts — TEST-01 (populated + network-simulated empty) assertions for /lists"
- "20 tests total (8 calendar + 12 lists, per profile) — all passing on iphone/WebKit and pixel/Chromium"
affects:
- "Phase 08 CI (both specs run as regression gates)"
tech_stack:
added: []
patterns:
- "page.route('/api/*', fulfill 500) registered BEFORE page.goto — error-state simulation (Pattern 5)"
- "page.unroute() immediately after assertion — route mocks scoped to single test (T-07-11)"
- "page.locator('.sx-react-calendar-wrapper') — CSS class fallback for widget wrapper with no semantic role"
- "getByRole('button', { name: 'Open list: E2E Grocery List' }) — aria-label stable anchor on ListCard"
- "page.route('/api/lists', fulfill 200 []) — network-simulated empty state without DB mutation (D-06)"
- "page.evaluate(() => navigator.serviceWorker.controller) — runtime SW controller assertion"
key_files:
created:
- apps/pwa/e2e/calendar.spec.ts
- apps/pwa/e2e/lists.spec.ts
modified: []
key_decisions:
- "D-04-SCHEDULE-X-LOCATOR: Asserted .sx-react-calendar-wrapper via CSS class (page.locator) since Schedule-X's React adapter emits no semantic role on the outer wrapper div — documented in index.css. No data-testid added to source; the CSS class is stable within @schedule-x/react."
- "D-04-POPULATED-NO-EVENT-CHIP: Populated calendar test asserts grid visible + empty-absent (NOT event chip text) — chip visibility depends on Schedule-X's default view and the seed event date relative to today; date-dependent assertions are exactly the drift the phase avoids (UI-SPEC Rule 6 rationale)."
- "D-04-EMPTY-STATE-NETWORK-SIM: Lists empty state simulated via page.route to 200 [] rather than DB mutation — preserves seeded populated state for concurrent test workers and satisfies D-06 deterministic seed / T-07-11 mock isolation."
- "D-04-LISTCARD-ARIA-LABEL: Lists populated test locates card by getByRole('button', { name: 'Open list: E2E Grocery List' }) — ListCard.tsx renders a <button> (not <a>) with that exact aria-label; no link role collision."
patterns-established:
- "Error-state simulation: register page.route BEFORE page.goto, assert heading+button, then page.unroute"
- "Empty-state simulation (no DB mutation): page.route to 200+empty-body BEFORE goto, assert empty UI, then page.unroute"
- "SW-block assertion: page.evaluate(() => navigator.serviceWorker?.controller) — null confirms no controlling SW"
- "Auth reach: getByRole('navigation', { name: 'Main navigation' }) visible + URL hostname check against external auth host"
requirements-completed: [TEST-01, TEST-02]
duration: 22min
completed: "2026-06-11"
---
# Phase 07 Plan 04: calendar.spec.ts + lists.spec.ts State Coverage Summary
**calendar.spec.ts and lists.spec.ts asserting populated/error/empty states on iPhone/WebKit and Pixel/Chromium, with TEST-02 DEV_AUTH_BYPASS and service-worker-block precondition assertions at runtime.**
## Performance
- **Duration:** 22 min
- **Started:** 2026-06-11T05:49:00Z
- **Completed:** 2026-06-11T06:11:47Z
- **Tasks:** 2
- **Files modified:** 2
## Accomplishments
- `apps/pwa/e2e/calendar.spec.ts` — 8 tests per profile (16 total) covering: TEST-02 auth-bypass reach + SW-controller null assertion; populated state (Schedule-X grid visible, EmptyState absent, no overflow); error state (mocked /api/events* 500, 'Couldn't load events' heading, Retry ≥44px, no overflow, mock unrouted)
- `apps/pwa/e2e/lists.spec.ts` — 6 tests per profile (12 total) covering: populated state (seeded 'E2E Grocery List' card by aria-label, listitem count ≥1, 'No lists yet' absent, no overflow); empty state (network-simulated via page.route to 200 [], 'No lists yet' + 'Tap + to create' visible, no overflow, mock unrouted)
- All 28 tests pass on both iphone (WebKit) and pixel (Chromium); `pnpm --filter @familysync/pwa typecheck` exits 0; no absolute URLs; seeded DB not mutated by any spec
## Task Commits
1. **Task 1: calendar.spec.ts** - `17b625b` (feat)
2. **Task 2: lists.spec.ts** - `b074b4a` (feat)
## Files Created/Modified
- `apps/pwa/e2e/calendar.spec.ts` — TEST-01 + TEST-02 assertions for /calendar (populated, error, auth-bypass, SW-block)
- `apps/pwa/e2e/lists.spec.ts` — TEST-01 assertions for /lists (populated and network-simulated empty)
## Decisions Made
- **D-04-SCHEDULE-X-LOCATOR:** `page.locator('.sx-react-calendar-wrapper')` used to assert calendar grid — the Schedule-X React adapter emits a div with this class but no semantic ARIA role. This is documented in `apps/pwa/src/styles/index.css` as the canonical outer wrapper class. No `data-testid` added to source code.
- **D-04-POPULATED-NO-CHIP:** Populated calendar test asserts grid visible + `'Nothing here'` absent rather than the seeded event chip text `'Seeded Test Event'`. Schedule-X renders only events in the current view window; the seed event is tomorrow UTC but the default view and timezone rendering makes chip visibility date-dependent. The plan explicitly flagged this risk.
- **D-04-EMPTY-NETWORK-SIM:** Lists empty state simulated with `page.route('/api/lists', fulfill 200 { lists: [] })` before `page.goto` rather than by deleting the seeded row. This preserves the deterministic seed for parallel test workers and avoids DB state mutation in specs (D-06 / T-07-11).
- **D-04-LISTCARD-BUTTON:** `ListCard.tsx` renders the card as `<button aria-label="Open list: ...">` (not `<a>`), so the locator uses `getByRole('button', { name: 'Open list: E2E Grocery List' })`.
## Deviations from Plan
None — plan executed exactly as written. All implementation choices were documented as decisions (listed above).
## Known Stubs
None — both spec files are complete implementations with no placeholders.
## Threat Surface Scan
No new network endpoints, auth paths, or schema changes. Both files are test-only.
Threat mitigations confirmed active:
- **T-07-09 (DEV_AUTH_BYPASS elevation):** TEST-02 precondition spec asserts the bypass reached the authed PWA — confirms the dev-only guard is working. The spec does not enable the bypass; it depends on the running dev stack.
- **T-07-10 (OIDC mock spoofing):** No storageState and no OIDC mock used — auth comes from the real DEV_AUTH_BYPASS middleware. Auth reach is asserted via nav landmark presence + URL hostname check (not a faked session).
- **T-07-11 (route mock leakage):** Every `page.route` call in calendar.spec.ts and lists.spec.ts is paired with `page.unroute` immediately after the assertion block. Mocks are page-scoped and do not persist across test contexts.
## Self-Check: PASSED
- `apps/pwa/e2e/calendar.spec.ts` — exists
- `apps/pwa/e2e/lists.spec.ts` — exists
- Task 1 commit `17b625b` — exists
- Task 2 commit `b074b4a` — exists
- 28 tests passing on both profiles — verified by final combined run
- `pnpm --filter @familysync/pwa typecheck` — exits 0
- No absolute URLs: `grep -cE "https?://localhost" e2e/calendar.spec.ts e2e/lists.spec.ts` → both 0