fix: visuals reference matching overhaul and better workflow

This commit is contained in:
fsylvain committed 2026-09-26 21:04:51 +02:00
1 parent 930b836241
commit d493a181e1
25 files changed
+461 -241

No files matched your search

+3
View File
@@ -74,8 +74,11 @@ Feature: Log in screen
"For your security, wait a moment before trying again."
And the Username field still shows "marta" and the Password field still shows "limewash"
And the submit button reads "Try again in 0:42" and is disabled
And the Username and Password fields are disabled while the countdown runs, as the board draws
them
When 42 seconds elapse
Then the submit button re-enables and reads "Log in"
And the Username and Password fields are editable again
Scenario: Session expired — redirected from a 401 elsewhere
Given the user is redirected to "/login?redirect=/article/12/edit&reason=expired"
+9 -8
View File
@@ -28,8 +28,9 @@ route starts at the path each case needs.
`POST /v1/auth/login` resolving 429 with a `Retry-After: 42` header; submit; assert an info alert
with title "Too many attempts" and description "For your security, wait a moment before trying
again."; assert Username still reads "marta" and Password still reads "limewash"; assert the
submit button reads "Try again in 0:42" and is disabled; advance fake timers by 42 seconds;
assert the button re-enables and reads "Log in".
submit button reads "Try again in 0:42" and is disabled; assert both fields are disabled;
advance fake timers by 42 seconds; assert the button re-enables and reads "Log in" and both fields
are editable again.
7. **Session expired state** — mount at
"/login?redirect=/article/12/edit&reason=expired"; assert an info alert with title "Your session
expired" and description "Log in again to pick up where you left off. Unsaved changes are kept on
@@ -71,12 +72,12 @@ Reference: `docs/design/source/RenewCMS Screens Auth.dc.html`, group "Log in".
and mobile, light and dark.
- [ ] Wrong credentials state: error alert (circle-x icon, `role="alert"`) above the heading,
cleared/focused Password field, desktop and mobile, light and dark.
- [ ] Too many attempts state: info alert (timer icon), disabled button with the live "Try again in
m:ss" countdown text, desktop and mobile, light and dark.
- [ ] Session expired state: info alert (clock icon), focused Password field, desktop and mobile,
light and dark.
- [ ] Password changed state: success alert (circle-check icon, dismissible close button per
components.md), desktop and mobile, light and dark.
- [ ] Too many attempts state: info alert (timer icon), disabled fields and a disabled button with
the live "Try again in m:ss" countdown text, desktop and mobile, light and dark.
- [ ] Session expired state: info alert (clock icon, no close button), focused Password field,
desktop and mobile, light and dark.
- [ ] Password changed state: success alert (circle-check icon, no close button, as the board draws
it), desktop and mobile, light and dark.
- [ ] Server or network error state: error alert (circle-x icon, no close button), fields retain
their values, desktop and mobile, light and dark.
- [ ] Keyboard/focus: Tab order is Username → Password → show/hide toggle → "Forgot password?" →
+12 -11
View File
@@ -53,12 +53,12 @@ Feature: Pending registration screen
verified on another device while this tab still holds a stale unverified session), the
action first awaits session.retry() so the session store's cached verified flag is refreshed
to true — without this, the router guard's stale local state would immediately bounce a
navigation to /home straight back to /register/pending — then the dialog closes, the app
navigates to /home, and a toast (color "info", icon "i-lucide-info", title "Your account is
already verified", description "There's nothing to cancel.") is shown; PendingPage does not
render a ResultPanel for this case, since the ResultPanel drawn in this state on the design
board is Verify's own Success panel, reused purely to depict the destination page behind the
toast
navigation to /home straight back to /register/pending — then the dialog closes,
PendingPage replaces its panel with a ResultPanel (tone "success", icon
"i-lucide-shield-check", title "You're verified", text "Taking you to your dashboard…", no
actions) and shows a toast (color "info", icon "i-lucide-info", title "Your account is already
verified", description "There's nothing to cancel.") over it, exactly as the design board
draws this state, then navigates to /home after the same 800 ms Verify's Success uses
And on a 500 response or a request that never reaches the server, the action rejects, the
dialog stays open, and it shows the failure inline (per ConfirmDialog's general contract:
"the action runs inside the dialog, so errors show there instead of a toast"), re-enabling
@@ -126,17 +126,18 @@ Feature: Pending registration screen
And a toast is shown with color "success", icon "i-lucide-circle-check", title "Registration
cancelled", description "You can create the owner account again."
Scenario: Already verified — 403 refreshes the session and navigates to /home with a toast
Scenario: Already verified — 403 shows the verified panel and toast, then navigates to /home
Given the ConfirmDialog's action is in flight after the user confirmed
And the session store currently holds { verified: false }
When DELETE /v1/auth/register resolves 403 with the apperr.AlreadyVerifiedErrorMessage body
Then session.retry() is awaited before any navigation happens
And once it resolves the session store holds { verified: true }
And the dialog closes
And the app navigates to /home
And a toast is shown with color "info", icon "i-lucide-info", title "Your account is already
verified", description "There's nothing to cancel."
And no ResultPanel is rendered by PendingPage for this transition
And a ResultPanel is shown with tone "success", icon "i-lucide-shield-check", title "You're
verified", text "Taking you to your dashboard…", and no actions
And a toast is shown over it with color "info", icon "i-lucide-info", title "Your account is
already verified", description "There's nothing to cancel."
And after 800 ms the app navigates to /home
Scenario Outline: Cancel failed — the dialog stays open and shows the failure inline
Given the ConfirmDialog's action is in flight after the user confirmed
+6 -6
View File
@@ -34,10 +34,10 @@ a test Pinia session store. Seed/clear `sessionStorage["renewcms:pendingEmail"]`
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 router's current path is /home; assert a toast was raised with
color "info", icon "i-lucide-info", title "Your account is already verified", description
"There's nothing to cancel."; assert no ResultPanel assertions target this transition (Pending
unmounts as the route changes).
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
@@ -63,8 +63,8 @@ Reference: `docs/design/source/RenewCMS Screens Auth.dc.html`, group "Pending".
- [ ] 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: toast appearance over the /home board (per the design board's
illustrative use of Verify's Success ResultPanel as the backdrop).
- [ ] 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.
+19 -13
View File
@@ -23,14 +23,16 @@ Feature: Verify account screen
log-in state instead of the global redirect to /login, and a 403 renders the Invalid-or-
expired state instead of the global "unverified session" redirect
And Verifying and No-session-retrying both render the same full-card spinner: a centered
"i-lucide-loader-2" icon with a spin animation and the text "Verifying your account…",
"i-lucide-loader-2" icon with a spin animation, the title "Verifying your account…" and the
line "This only takes a moment.", as the design board's AuthCard spinner draws it,
role="status", replacing AuthShell's whole card body with no title header shown
And on a 200, VerifyPage shows a ResultPanel with tone "success", icon "i-lucide-shield-check",
title "Your account is verified", text "Taking you to your dashboard…", and no actions, while
concurrently awaiting session.retry() (needed so /home's route guard sees the now-verified
session instead of whatever stale state this tab last cached) and an 800 ms timer; once both
settle, the app navigates to /home and a toast (color "success", icon "i-lucide-circle-check",
title "Your account is verified", description "Welcome to RenewCMS.") is shown
a toast (color "success", icon "i-lucide-circle-check", title "Your account is verified",
description "Welcome to RenewCMS.") is shown immediately over that panel, as the design board
draws it, while concurrently awaiting session.retry() (needed so /home's route guard sees the
now-verified session instead of whatever stale state this tab last cached) and an 800 ms
timer; once both settle, the app navigates to /home
And on a 403 (user.ErrInvalidVerificationCode or user.ErrVerificationCodeExpired), VerifyPage
shows a ResultPanel with tone "error", icon "i-lucide-link-2-off", title "This verification
link doesn't work", text "It may have expired (links last 2 hours) or already been used.",
@@ -61,8 +63,10 @@ Feature: Verify account screen
username") and a Password field (password input with a show/hide toggle), and a submit button
"Log in and verify"; there is no "Forgot password?" hint and no registration-open footer,
unlike LoginView
And the inline form's submit is disabled until both fields are non-empty; on submit it calls
POST /v1/auth/login with { username, password }
And the inline form's submit button is enabled, as the design board draws it; on submit,
LoginView's own validation runs first (Username under 3 characters shows "Use at least 3
characters.", an empty Password shows "Enter your password.", and nothing is sent), and a
valid form calls POST /v1/auth/login with { username, password }
And a 200 from that login call transitions to the No-session-retrying spinner and immediately
retries POST /v1/auth/verify with the same code and the same passthrough options, which then
follows the 200/403 handling above
@@ -85,10 +89,10 @@ Feature: Verify account screen
When POST /v1/auth/verify resolves 200
Then a ResultPanel is shown with tone "success", icon "i-lucide-shield-check", title "Your
account is verified", text "Taking you to your dashboard…", and no actions
And a toast is shown over it with color "success", icon "i-lucide-circle-check", title "Your
account is verified", description "Welcome to RenewCMS."
And session.retry() is awaited
And after 800 ms and once session.retry() has resolved, the app navigates to /home
And a toast is shown with color "success", icon "i-lucide-circle-check", title "Your account is
verified", description "Welcome to RenewCMS."
Scenario: Invalid or expired — 403 offers a way to start over
Given the user is on "/register/validate?c=abc123" in the Verifying state
@@ -142,11 +146,12 @@ Feature: Verify account screen
toggle, and a submit button "Log in and verify"
And there is no "Forgot password?" link and no registration-open footer
Scenario: No session — submit is disabled until both fields are filled
Scenario: No session — empty fields are caught on submit, as on Log in
Given the inline log-in form from "No session" is shown
Then the submit button is disabled
When the user types "marta" into Username and "limewash2026" into Password
Then the submit button is enabled
When the user submits with both fields empty
Then Username shows "Use at least 3 characters." and Password shows "Enter your password."
And POST /v1/auth/login is not called
Scenario: No session — successful login retries verification with the same code
Given the inline log-in form from "No session" is shown on "/register/validate?c=abc123"
@@ -171,7 +176,8 @@ Feature: Verify account screen
And POST /v1/auth/login resolves 429 with Retry-After 30
Then an info alert is shown with title "Too many attempts" and description "For your security,
wait a moment before trying again."
And the submit button reads "Try again in 0:30" and is disabled until the countdown ends
And the submit button reads "Try again in 0:30" and it and both fields are disabled until the
countdown ends, mirroring LoginView
Scenario Outline: No session — server or network error during the inline login
Given the inline log-in form from "No session" is shown
+10 -7
View File
@@ -7,14 +7,15 @@ Mock `useApi` (`POST /v1/auth/verify`, `POST /v1/auth/login`, `DELETE /v1/auth/r
VerifyPage inside AuthShell with a test router whose current route starts at the "c" query the case
needs; use fake timers for the 800 ms success delay and the 429 countdown.
1. **Verifying state** — mount at "/register/validate?c=abc123"; assert the full-card spinner text
1. **Verifying state** — mount at "/register/validate?c=abc123"; assert the full-card spinner title
"Verifying your account…" and that `POST /v1/auth/verify` was called exactly once with
`{ code: 'abc123' }` and `{ onUnauthorized: 'passthrough', onForbidden: 'passthrough' }`.
2. **Success state** — mock `POST /v1/auth/verify` resolving 200; mock the session store's
`retry()` to resolve; mount and let it settle; assert the ResultPanel (success, shield-check,
"Your account is verified", "Taking you to your dashboard…", no actions); advance fake timers by
800 ms; assert `retry()` was called; assert the router's current path is /home and a toast was
raised with title "Your account is verified", description "Welcome to RenewCMS."
"Your account is verified", "Taking you to your dashboard…", no actions); assert a toast was
already raised with title "Your account is verified", description "Welcome to RenewCMS." while
the path is still /register/validate; advance by 800 ms; assert `retry()` was called and the
router's current path is /home, with no second toast.
3. **Invalid or expired state** — mock `POST /v1/auth/verify` resolving 403; assert the ResultPanel
(error, link-2-off, "This verification link doesn't work", the text and note above) with actions
"Cancel registration and start over" (primary) and "Log out" (secondary).
@@ -38,8 +39,9 @@ needs; use fake timers for the 800 ms success delay and the 429 countdown.
to finish verifying", description, Username placeholder "Your username", Password with show/hide,
submit "Log in and verify") and assert no "Forgot password?" link and no registration footer are
rendered.
8. **No session — submit disabled until filled** — from state 7, assert the submit button is
disabled; fill both fields; assert it becomes enabled.
8. **No session — empty fields are caught on submit** — from state 7, assert the submit button is
enabled; submit with both fields empty; assert "Use at least 3 characters." and "Enter your
password." are shown and `POST /v1/auth/login` was not called.
9. **No session — successful login retries** — from state 7, fill valid credentials; mock
`POST /v1/auth/login` resolving 200; mock a second `POST /v1/auth/verify` call; submit; assert the
spinner "Verifying your account…" reappears and the second `POST /v1/auth/verify` call was made
@@ -67,7 +69,8 @@ Reference: `docs/design/source/RenewCMS Screens Auth.dc.html`, group "Verify". R
- [ ] Verifying state: the full-card spinner (the only one in the product) matches the board's
layout exactly, with no title header shown.
- [ ] Success state: ResultPanel with no actions, transient toast over the /home board.
- [ ] Success state: ResultPanel with no actions and the success toast over it, captured before
the 800 ms redirect (the visual setup freezes timers with `paused: true`).
- [ ] Invalid or expired state: ResultPanel with note and both actions, button order (primary
"Cancel registration and start over" first per components.md's safe/primary-on-top rule for
stacked mobile buttons).