Files
RenewCMS/features/auth_shell_login_conformance.qa.md
T

6.4 KiB

QA: AuthShell and log-in screen conform to the design spec

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

No /v1 calls are relevant to these assertions; mount AuthShell and LoginView directly (LoginView already needs useApi/GET /v1/auth/registration mocked per features/login_screen.qa.md's setup, reused here only as scaffolding, not as the thing under test).

  1. AppLogo renders inline SVG with no edge in either mode — mount AuthShell with title "Log in"; assert an inline <svg> (not <img>) renders as UPageCard's first child element, left-aligned (not centered) above the title/description block, sharing the card's 24px gap, at 32px, with role="img" aria-label="RenewCMS", and is not inside an <a>; assert its outer square has no stroke in light mode; switch to dark mode (toggle the .dark class the way nuxt_ui_shell_foundation's color-mode tests do) and assert the outer square still has no stroke, per docs/design/source/AuthCard.dc.html's literal, non-theme-branched edge: 'transparent'.
  2. UPageCard title is an h1 with tabindex="-1" — mount AuthShell with title "Log in" and description "Welcome back. Log in to manage your articles."; assert wrapper.find('h1').attributes('tabindex') is "-1" and its text is "Log in"; assert the description text "Welcome back. Log in to manage your articles." renders via UPageCard's description prop (not as a sibling <p> hand-authored in AuthShell's template). This replaces the older assertion in AuthShell.spec.ts that only checked for any <h1>; keep that assertion passing (h1 text still equals the title) while adding the tabindex check.
  3. Muted background at sm and up — mount AuthShell at a >= 640px viewport (mock useBreakpoints/matchMedia the way other shell tests do); assert <main>'s class list includes the muted background utility (e.g. bg-muted); assert the card renders with a border and is capped at 400px width, offset 12vh from the top.
  4. Default background, borderless card, 20px padding, xl controls below sm — mount AuthShell at a < 640px viewport; assert <main>'s class list includes the default background utility (bg-default) and not the muted one; assert the card has no border class; assert the card's padding is 20px; assert the Username/Password inputs and submit button render at size="xl".
  5. Login form is a UForm — mount LoginView at /login; assert the form wrapping the fields is a UForm component instance, not a bare <form> element with only @submit.prevent wired by hand.
  6. Forgot password in the #hint slot — mount LoginView at /login; assert the Password UFormField is passed content (or renders content) in its hint slot containing a "Forgot password?" link to /register/reset/request; assert the field's rendered label text is exactly "Password" with no link inside it.
  7. Inputs default to neutral, then highlight red on error — mount LoginView at /login; assert the Username and Password UInput components render with highlight falsy (or absent) and the theme's default ring class (ring-(--ui-border-accented)); then trigger the Validation errors state (type "ma" into Username, submit with Password empty); assert both UInputs now receive :highlight="true" (or the field's error is otherwise truthy) so the edge renders in --ui-error, alongside "Use at least 3 characters." and "Enter your password." respectively.

Implementation finding for coder

web/src/views/LoginView.vue's Username and Password UInputs currently receive no :highlight prop at all — UFormField gets :error="usernameError" / :error="passwordError", but that value is never forwarded to the inner UInput. Today the edge never turns red in the Validation errors state; this isn't a stale/inverted binding, it's a missing one. Wiring :highlight="!!usernameError" / :highlight="!!passwordError" (or equivalent) onto each UInput is the fix scenario 7 above pins down.

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

Reference: docs/design/source/RenewCMS Screens Auth.dc.html, group "Log in", plus docs/design/source/RenewCMS Components Product.dc.html's AuthShell drawing and docs/design/source/RenewCMS Components Primitives.dc.html's Input drawing.

  • AppLogo renders inside UPageCard as its first element, left-aligned above the title (not centered above the card), sharing the card's 24px gap; the mark matches the favicon.svg drawing exactly (white R on a black square), desktop and mobile; no edge stroke is visible around the square in either light or dark mode.
  • UPageCard title ("Log in") renders at the card-title size/weight from the AuthShell drawing and receives focus (visible focus ring) on route change; description text matches "Welcome back. Log in to manage your articles." styling, desktop and mobile, light and dark.
  • At sm and up: 400px card with its border, 12vh top offset, on the muted page canvas, light and dark.
  • Below sm: card fills the screen borderless with 20px padding on the default (not muted) background; fields and the submit button render at the touch (xl, 44px) size, light and dark.
  • The Password field's "Forgot password?" link sits right-aligned on the label row (the hint position), matching the Auth board, desktop and mobile, light and dark.
  • Username and Password fields show the neutral control-edge border by default; in the Validation errors state (short username, empty password) both fields' edges turn to the error color alongside their messages, in both light and dark.
  • Keyboard/focus: Tab order remains Username → Password → show/hide toggle → "Forgot password?" → submit; UPageCard's h1 is reachable/focusable via tabindex="-1" on route change, matching AuthShell's existing a11y spec.

Manual verification

  • AppLogo reused in DashboardShell — out of scope for this slice (AuthShell/login only). DashboardShell.vue's sidebar header is currently a plain <span class="font-semibold"> RenewCMS</span> (not AppLogo), and its footer is a bare <span>Log out</span> instead of the UserMenu component components.md specifies. Both need their own "Built from" conformance pass in a later DashboardShell slice; AppLogo (built here with a size prop) will be ready to drop in at that point without changes.