Skip to content

test(e2e): full Playwright coverage for v0.2 + RHF discard fix#14

Merged
mayrang merged 1 commit into
mainfrom
feat/v0.2-e2e-coverage
May 27, 2026
Merged

test(e2e): full Playwright coverage for v0.2 + RHF discard fix#14
mayrang merged 1 commit into
mainfrom
feat/v0.2-e2e-coverage

Conversation

@mayrang
Copy link
Copy Markdown
Owner

@mayrang mayrang commented May 27, 2026

Summary

Adds Playwright coverage for every public surface (both pre-existing v0.1 features that had only unit-test coverage and the new v0.2 additions), and ships one real bug fix uncovered during the e2e pass.

Test deltas:

  • Unit: 201 → 202 (RHF discard regression test)
  • E2E: 21 → 84 (28 scenarios × Chromium / Firefox / WebKit)
  • Bundle: 5.42 KB / 8 KB unchanged

Example app

Restructured to a tiny hash router. The existing SignupWizard stays at / (so the original 7 headline scenarios are untouched), each new feature gets its own page under #/<route>. Lazy-loaded so each route only ships what it needs.

New demo pages:

Route Demonstrates
#/conflict multiTab: 'warn' + both <ConflictDialog> and <ConflictResolver>
#/auto-storage autoAdapter with a 5,000-char threshold for fast migration
#/external-control getFormDraft from a sibling + useFormDraftStatus sibling reader
#/heartbeat createHeartbeatDetector against an intercepted /__heartbeat__ route
#/rhf #/formik #/tanstack Each adapter wired against zod + localStorage
#/session-storage #/indexeddb Non-default storage backends

E2E specs (7 new files)

  • conflict.spec.ts (4 tests): tab-A-saves-B-sees-conflict-dialog-merges-to-correct-result, Esc keeps local, headless resolver pick-all-remote, aria-modal=true + focus contract
  • auto-storage.spec.ts (2 tests): small → localStorage, bloat → IndexedDB migration with onMigration callback; discard clears both
  • external-control.spec.ts (4 tests): useFormDraftStatus reactivity, external getValues / save / discard
  • heartbeat.spec.ts (1 test): online → offline → online transitions
  • adapters.spec.ts (6 tests): persist+restore+discard per RHF / Formik / TanStack
  • storage-adapters.spec.ts (4 tests): sessionStorage + IndexedDB persist + don't-leak-to-localStorage

Bug found and fixed: RHF discard

The RHF adapter exposed discard: draft.discard raw, while Formik and TanStack adapters wrapped it to also reset the underlying form. Result:

  1. discard() clears localStorage and resets draft.values to defaults
  2. The RHF input still shows the stale text (RHF doesn't subscribe to draft.values after mount)
  3. Worse: when I patched the adapter to also call form.reset(defaults), RHF's watch subscription fired on the reset, called draft.patch(emptyDefaults), and re-persisted the empty form back into the just-cleared draft, silently undoing the discard

Fix: added an ignoreNextWatchRef flag (same pattern as Formik's ignoreNextFormChangeRef). The watch subscriber consumes-and-clears it, skipping the patch when the reset is library-initiated (both restore and discard paths). New unit test locks the regression in.

Notable test infrastructure

  • Heartbeat e2e uses page.route('**/__heartbeat__', ...) toggling instead of context.setOffline(true). Playwright's setOffline doesn't reliably intercept same-origin loopback connections to the dev server, so the route handler is the deterministic way to flip reachability.
  • Conflict focus assertion brings tab B to the front + re-focuses the first dialog button before asserting document.activeElement is inside the dialog. In multi-page contexts, only one tab has window focus and programmatic .focus() on a non-active tab silently no-ops in headed Chromium.

README v0.2.0

  • Banner now reflects v0.2.0 (was v0.1.0-rc.2)
  • Status block: 202 unit + 84 e2e, 5.42 KB / 8 KB
  • FAQ entry for "TanStack / Formik" now points at the dedicated adapters
  • Contributing list drops Formik / TanStack / heartbeat (all shipped)

Test plan

  • npm run typecheck — clean
  • npm run lint — clean
  • npm test — 202 passing
  • npx playwright test — 84 passing across Chromium / Firefox / WebKit
  • npm run build clean, npm run size 5.42 KB / 8 KB

Adds Playwright coverage for every public surface, both v0.1 features
that previously had only unit-test coverage and the v0.2 additions.

Example app:
- Hash-based router with a landing index and one page per demo
- WizardPage (existing SignupWizard, unchanged behavior)
- ConflictPage exercises both <ConflictDialog> and <ConflictResolver>
- AutoStoragePage exercises autoAdapter migration logic with a low
  5,000-char threshold so the e2e can bloat past it in one click
- ExternalControlPage drives a draft via getFormDraft() from one
  sibling and reads status via useFormDraftStatus from another
- HeartbeatPage runs a 1s interval probe against a route-intercepted
  /__heartbeat__ endpoint
- RhfPage / FormikPage / TanstackPage one-input demos per adapter
- SessionStoragePage / IndexedDBPage exercise non-default storage

E2E specs (28 scenarios × 3 engines = 84 tests):
- tests/e2e/conflict.spec.ts: cross-tab conflict, dialog merge, Esc
  dismiss, headless resolver pick-all-remote, dialog aria-modal +
  focus contract
- tests/e2e/auto-storage.spec.ts: small → localStorage, bloat →
  IndexedDB migration, discard clears both
- tests/e2e/external-control.spec.ts: status sibling reactivity,
  external getValues / save / discard
- tests/e2e/heartbeat.spec.ts: online → offline → online transitions
  via page.route() interception (context.setOffline doesn't always
  intercept same-origin loopback)
- tests/e2e/adapters.spec.ts: persist+restore+discard for RHF /
  Formik / TanStack
- tests/e2e/storage-adapters.spec.ts: sessionStorage and IndexedDB
  persist + don't-leak

Bug fix:
- useFormDraftRHF.discard() now wraps draft.discard() to also call
  form.reset(defaultValues, { keepDefaultValues: true }), mirroring
  the Formik / TanStack adapters. Previously discard cleared storage
  but the RHF input still showed the stale text. Worse, RHF's watch
  subscription then fired on the reset and re-patched the empty
  defaults back into the just-cleared draft, silently un-discarding.
- Added ignoreNextWatchRef to swallow the library-initiated reset
  events on both restore and discard paths.
- New unit test locks the regression in.

Also bumps package.json to 0.2.0, updates README badges + status
section (202 unit tests + 84 Playwright e2e, 5.42 KB / 8 KB), and
removes Formik/TanStack adapter from the open contribution list.
@mayrang mayrang merged commit 3ce90aa into main May 27, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant