Files
RenewCMS/features/pending_screen.qa.md
T

6.1 KiB

QA: Pending registration screen

Automated (Vitest + Vue Test Utils, task test:web)

Mock useApi (DELETE /v1/auth/register, POST /v1/auth/logout) and useToast/useOverlay per case; no real backend, no httptest server. Mount PendingPage inside AuthShell with a test router and a test Pinia session store. Seed/clear sessionStorage["renewcms:pendingEmail"] per case.

  1. Default state — set sessionStorage["renewcms:pendingEmail"] = "marta@renew.example"; mount at /register/pending; assert a ResultPanel with tone "success", icon "i-lucide-mail-check", title "Check your email", text "We sent a verification link to marta@renew.example. It expires in 2 hours.", the note paragraph rendered via the default slot, and two secondary actions "Log out" and "Cancel registration".
  2. Email unknown state — clear sessionStorage["renewcms:pendingEmail"]; mount at /register/pending; assert the same ResultPanel shape with text "We sent a verification link to the email you registered with. It expires in 2 hours." and the same note and actions.
  3. Logging out — mount with an unresolved POST /v1/auth/logout mock; click "Log out"; assert both actions are disabled; resolve the mock 200; assert the session store's clear() was called and the router's current path is /login.
  4. Confirm cancel opens the dialog — click "Cancel registration"; assert a ConfirmDialog renders with title "Cancel your registration?", description "Your account will be deleted. You can register again with the same or a different email.", buttons "Keep my account" and "Cancel registration" (tone danger), role="alertdialog", and initial focus on "Keep my account".
  5. Cancelling state — with the dialog open, mock DELETE /v1/auth/register with an unresolved promise, click "Cancel registration" inside the dialog; assert both dialog buttons are disabled, the confirm button reads "Cancelling…", aria-busy="true" on the dialog, and the mocked call was made with { onForbidden: 'passthrough' }.
  6. Keep my account — with the dialog open, click "Keep my account"; assert the dialog closes, DELETE /v1/auth/register was never called, and the underlying ResultPanel is unchanged.
  7. Cancelled state — confirm the dialog; mock DELETE /v1/auth/register resolving 200; assert the dialog closes, the router's current path is /register, and a toast was raised with color "success", icon "i-lucide-circle-check", title "Registration cancelled", description "You can create the owner account again."
  8. Already verified state — seed the session store with { verified: false }, status:'ready'; confirm the dialog; mock DELETE /v1/auth/register resolving 403; mock the session store's retry() to resolve and set session.session = { verified: true }; assert retry() was awaited before the navigation; assert the "You're verified" ResultPanel ("Taking you to your dashboard…", no actions) is shown and a toast was raised with color "info", icon "i-lucide-info", title "Your account is already verified", description "There's nothing to cancel." while the path is not yet /home; then assert the router reaches /home after 800 ms with no second toast.
  9. Cancel failed state — two sub-cases: (a) a 500 response, (b) a rejected fetch. In each, confirm the dialog, resolve/reject the mock accordingly, and assert the dialog stays open, an inline failure indicator is shown inside it (not a toast — assert no toast was raised), and both dialog buttons are re-enabled.
  10. useApi onForbidden option (unit-level, alongside the existing suite in api_client_global_error_handling's spec file) — seed session = { verified: false }; mock fetch resolving 403; call useApi().request('/v1/auth/register', { method: 'DELETE', onForbidden: 'passthrough' }); assert the promise rejects with ApiError status 403; assert the router's current route did not change (no redirect to /register/pending); assert the session store is unchanged. This regresses nothing in the existing onForbidden-absent (default 'redirect') scenarios already covered by features/api_client_global_error_handling.feature.

Visual (manual verification — desktop and mobile, light and dark)

Reference: docs/design/source/RenewCMS Screens Auth.dc.html, group "Pending". Run task visual:check -- pending for each state and compare against the board frame.

  • Default state: icon tile, title, text with the interpolated email, note paragraph, "Log out" and "Cancel registration" secondary buttons — matches the board exactly.
  • Email unknown state: same layout with the generic text.
  • Confirm cancel state: ConfirmDialog layout, button order (Keep my account left, Cancel registration right, danger color), matches components.md's button-order rule.
  • Cancelling state: disabled buttons, "Cancelling…" label, spinner/aria-busy styling.
  • Cancelled state (transient, capture via visual test hook or a frozen mock): toast appearance over the /register board.
  • Already verified state: the "You're verified" ResultPanel with the info toast over it, captured before the 800 ms redirect (the visual setup freezes timers with paused: true).
  • Keyboard/focus: focus starts on "Keep my account" in the dialog and Enter does not confirm; while cancelling, focus cannot leave the dialog; on the ResultPanel states, focus lands on the title on mount; toasts are announced without stealing focus.

Manual verification

  • Real DELETE /v1/auth/register and POST /v1/auth/logout against a running server — Vitest only mocks useApi; confirm the real 200/403 round-trips against task dev with a live SQLite file. The endpoint's own status/body/cookie contract is already covered by features/cancel_pending_registration.qa.md; this slice only re-verifies the screen consumes it correctly.
  • Router-guard staleness fix in a real browser — confirm that after the "Already verified" 403, a hard reload of /home does not bounce back to /register/pending, since Vitest's fake session store can't fully reproduce the router's afterEach re-check timing.