feat: register T0

This commit is contained in:
fsylvain committed 2026-09-26 02:48:16 +02:00
1 parent a5873f8886
commit 3c3f7c6194
9 files changed
+920 -4

No files matched your search

+112
View File
@@ -0,0 +1,112 @@
Feature: Register screen
As the first person to set up a fresh RenewCMS install
I want a /register screen that creates the single owner account via POST /v1/auth/register,
validates the same rules the API enforces before submitting, and tells me plainly when
someone already owns this site or the server failed
So that I can create my account once, land on the verification-pending page, and never see a
confusing or silent failure
Background:
Given /register is a public route (meta.auth "public") rendered as RegisterPage inside
AuthShell, per docs/design/routes.md
And RegisterPage only calls POST /v1/auth/register; it never calls GET /v1/auth/registration
And the form has a Username field (placeholder "e.g. marta", a live "N / 20" character
counter, help text "Letters and numbers, 3 to 20 characters. You'll use it to log in."), an
Email field (placeholder "you@example.com", help text "We'll send a verification link
here."), and a Password field (a password input with a show/hide toggle and a live
checklist item "At least 8 characters" that is unmet/error until the rule is satisfied, then
turns met/green)
And client-side validation runs on blur of each field and again on submit, before any request
is sent, using the same rules as the API: Username must be 3 to 20 characters (the API has
no character-class constraint beyond length, so the client checks length only, matching the
help text's length claim but not attempting to enforce "letters and numbers" client-side),
Email must be a well-formed email address, and Password must be at least 8 characters
And a failing Username shows "Use 3 to 20 characters."; a failing Email shows "Enter a full
email address, like name@example.com."; a failing Password leaves the checklist item in its
unmet/error state
And a valid submit calls POST /v1/auth/register with { username, email, password }
And on a 200 response, the jwt cookie is set by the server and the app navigates to
/register/pending without re-resolving the session first
And a 403 response means someone already verified an owner account (user.ErrRegistrationClosed
on the backend) and the whole form is replaced by a ResultPanel
And a 500 response, or a request that never reaches the server, is surfaced as a server/network
error; the fields keep whatever the user typed and the form re-enables
And a 400 response is not expected once client-side validation passes, since the client checks
the same rules as the API; it is out of scope for this slice
Scenario: Default — a fresh, empty form
When the user navigates to /register
Then the heading is "Create the owner account"
And the description is "This creates the single admin account for this site. Once it's
verified, registration closes."
And the Username field shows placeholder "e.g. marta" with no value and counter "0 / 20"
And the Email field shows placeholder "you@example.com" with no value
And the Password field shows no value, with a show/hide toggle and the checklist item
"At least 8 characters" in its unmet state
And the submit button reads "Create account"
And the footer reads "Already set up?" with a "Log in" link to /login
Scenario: Validation errors — checked on blur and on submit
Given the user is on /register
When the user types "ma" into Username and blurs the field
Then the Username counter reads "2 / 20" in its error state and the error "Use 3 to 20
characters." is shown
When the user types "marta@renew" into Email and blurs the field
Then the Email field shows the error "Enter a full email address, like name@example.com."
When the user types "lime" into Password and blurs the field
Then the checklist item "At least 8 characters" stays in its error state
When the user submits with these same values
Then POST /v1/auth/register is never called
And all three errors from above are still shown
Scenario: Filled, valid — every rule is met
Given the user is on /register
When the user types "marta" into Username, "marta@renew.example" into Email, and
"limewash2026" into Password, and reveals the password with the show/hide toggle
Then the Username counter reads "5 / 20" with no error, and the help text "Letters and
numbers, 3 to 20 characters. You'll use it to log in." is shown
And the Email field shows no error, with the help text "We'll send a verification link here."
And the Password field shows "limewash2026" as plain text and the checklist item "At least 8
characters" turns to its met/green state
And the submit button reads "Create account" and is enabled
Scenario: Submitting — fields and button are disabled while the request is in flight
Given the user is on /register with Username "marta", Email "marta@renew.example", and
Password "limewash2026"
When the user submits and POST /v1/auth/register has not yet resolved
Then the Username, Email and Password fields are disabled
And the submit button reads "Creating account…" and shows a loading state
Scenario: Registration closed — the form is replaced by a ResultPanel
Given the user is on /register with Username "marta", Email "marta@renew.example", and
Password "limewash2026"
When the user submits and POST /v1/auth/register resolves 403
Then the form, its fields and the footer are no longer shown
And a ResultPanel with tone "info" and icon "user-x" is shown instead, with title "This site
already has an owner" and text "Log in instead."
And its single action is a primary button "Go to log in"
When the user activates "Go to log in"
Then the app navigates to /login
Scenario Outline: Server or network error — the form is kept and re-enabled
Given the user is on /register with Username "marta", Email "marta@renew.example", and
Password "limewash2026"
When the user submits and POST /v1/auth/register <failure>
Then an error alert reads "Couldn't create the account" with description "Something went
wrong on the server. Try again in a moment."
And the Username field still shows "marta", the Email field still shows "marta@renew.example",
and the Password field still shows "limewash2026"
And the Username, Email and Password fields are re-enabled
And the submit button reads "Create account" again, no longer loading
Examples:
| failure |
| fails with a 500 response |
| never reaches the server |
Scenario: A successful registration goes straight to the pending page
Given the user is on /register with Username "marta", Email "marta@renew.example", and
Password "limewash2026"
When the user submits and POST /v1/auth/register resolves 200 with a jwt cookie
Then the app navigates to /register/pending
+73
View File
@@ -0,0 +1,73 @@
# QA: Register screen
## Automated (Vitest + Vue Test Utils, `task test:web`)
Mock `useApi` (`POST /v1/auth/register` only — RegisterPage never calls
`GET /v1/auth/registration`); no real backend, no httptest server. Mount RegisterPage inside
AuthShell with a test router.
1. **Default state** — mount at /register; assert heading "Create the owner account", description
"This creates the single admin account for this site. Once it's verified, registration
closes.", Username placeholder "e.g. marta" with no value and counter "0 / 20", Email
placeholder "you@example.com" with no value, Password with no value and the checklist item
"At least 8 characters" in its unmet state, submit button "Create account", and the footer
"Already set up? Log in" linking to /login.
2. **Validation errors state** — mount at /register; set Username to "ma" and blur; assert counter
"2 / 20" in error and the error "Use 3 to 20 characters."; set Email to "marta@renew" and blur;
assert the error "Enter a full email address, like name@example.com."; set Password to "lime"
and blur; assert the checklist item stays in its error state; submit; assert the mocked
`POST /v1/auth/register` was never called and all three errors are still shown.
3. **Filled, valid state** — set Username "marta", Email "marta@renew.example", Password
"limewash2026"; toggle password visibility on; assert counter "5 / 20" with no error and the
help text restored, no Email error, Password rendered as plain text, the checklist item in its
met/green state, and the submit button enabled and reading "Create account".
4. **Submitting state** — set Username "marta", Email "marta@renew.example", Password
"limewash2026"; mock `POST /v1/auth/register` with an unresolved promise; submit; assert
Username, Email and Password are disabled and the submit button reads "Creating account…" with
a loading indicator.
5. **Registration closed state** — set the same valid values; mock `POST /v1/auth/register`
resolving 403; submit; assert the form and footer are no longer rendered; assert a ResultPanel
with title "This site already has an owner", text "Log in instead.", and a single primary
action "Go to log in"; click it and assert the router's current path is /login.
6. **Server or network error state** — two sub-cases: (a) a 500 response, (b) a rejected fetch
(network error). In each, set Username "marta", Email "marta@renew.example", Password
"limewash2026", submit, and assert an error alert with title "Couldn't create the account" and
description "Something went wrong on the server. Try again in a moment."; assert Username,
Email and Password still show their values; assert the three fields are re-enabled; assert the
submit button reads "Create account" and is no longer loading.
7. **Successful registration navigates to pending** — mock `POST /v1/auth/register` resolving 200;
set the same valid values; submit; assert the router's current path is /register/pending, and
that no `GET /v1/auth/me` call was made by RegisterPage itself.
## Visual (manual verification — desktop and mobile, light and dark)
Reference: `docs/design/source/RenewCMS Screens Auth.dc.html`, group "Register". Run
`task visual:check -- register` for each state and compare against the board frame.
- [ ] Default state: heading, description, Username with counter and help text, Email with help
text, Password with show/hide toggle and the unmet checklist item, "Create account" button,
"Already set up? Log in" footer — matches the board exactly.
- [ ] Validation errors state: Username counter and error, Email error, and the Password checklist
item's error styling all render inline per components.md.
- [ ] Filled, valid state: the checklist item's met/green transition and the revealed password
text render correctly.
- [ ] Submitting state: disabled field styling and the loading spinner in the submit button.
- [ ] Registration closed state: the ResultPanel (info tone, user-x icon tile, title, text, single
primary "Go to log in" action) fully replaces the form and footer.
- [ ] Server error state: error alert (circle-x icon, no close button per the Log in screen's
precedent) above the heading, fields retaining their values, form re-enabled.
- [ ] Keyboard/focus: Tab order is Username → Email → Password → show/hide toggle → submit; when
the Registration closed ResultPanel appears, focus moves to its title and it is
`role="status"`; the server error alert is `role="alert"` and does not steal focus from
wherever the user was; the checklist item's live state change is not visually jarring
(no layout shift) as it flips from unmet to met.
## Manual verification
- **Real POST /v1/auth/register against a running server** — Vitest only mocks `useApi`; confirm
the real 403 (`user.ErrRegistrationClosed`) and a real 200 round-trip with the actual jwt cookie
against `task dev` with a live SQLite file, since this slice's automated tests never exercise the
actual Go handler, the `CreateUser` use case, or cookie issuance.
- **Real navigation to /register/pending after a live registration** — confirm the route guard
reads the freshly set cookie correctly, since Vitest never resolves a real session against
GET /v1/auth/me on that navigation.