feat: UsersTable and UserCardList get Email, Display name (dash when empty), Role (new RoleBadge), Invite status and Created
This commit is contained in:
1 parent
153fd8719a
commit
1ece778caf
19 files changed
+485
-204
No files matched your search
+47
-14
@@ -16,20 +16,27 @@ The list items are keyed by `kind` and `id`.
|
||||
## Automated (Vitest + Vue Test Utils, `task test:web`)
|
||||
|
||||
Mock `useApi`'s `request` for `GET /v1/user/` and `POST /v1/user/invite` per case; no real backend.
|
||||
Mount UsersPage inside DashboardShell with a resolved verified session (id 1, username "marta") and
|
||||
a test router at /users. The Default fixture is the Background's 5 items; the invite fixture is
|
||||
Mount UsersPage inside DashboardShell with a resolved verified session (id 1, username "marta",
|
||||
role "owner") and a test router at /users; cases 6 to 17 use that owner session. The Default
|
||||
fixture is the Background's 5 items; the invite fixture is
|
||||
id 3, email "lea.martin@renew.example". No test freezes the system date. The specs only assert on
|
||||
the fixture's absolute dates; the sent row's "Today" label is not asserted anywhere (see the Visual
|
||||
part). Run each layout case at a desktop and a below-md width (mock `matchMedia`).
|
||||
|
||||
1. **Default** — assert breadcrumb "Users", title, subtitle, "Invite user", rows in fixture order,
|
||||
"Inès Duarte" with "ines" beside it, the "you" tag only on the "marta" row, badge words and
|
||||
the fixture's absolute dates.
|
||||
1. **Default** — assert breadcrumb "Users", title, subtitle, "Invite user", the table header
|
||||
labels (Email, Display name, Role, Invite status, Created), rows in fixture order, per row the
|
||||
email, the display name or "—", the RoleBadge word and class (owner filled, admin outlined),
|
||||
the badge word and the absolute date, "Paul Huber" on the invite row, the "you" tag only on
|
||||
"marta@renew.example", no username text in any row, and the below-md cards (display name line
|
||||
only when set).
|
||||
2. **Navigation** — assert the sidebar and the drawer list Home, Articles, Users, Integration,
|
||||
Settings in that order, and "Users" carries aria-current="page" on /users.
|
||||
3. **Loading** — unresolved `GET`; with fake timers assert nothing is drawn at 149 ms; at 150 ms
|
||||
assert 5 skeleton rows and `aria-busy="true"`; resolve and assert the Default rows.
|
||||
4. **Only you** — a single "marta" item; assert the row, the hint text and "Invite user".
|
||||
assert 5 skeleton rows and `aria-busy="true"`, and at desktop that each row has a RoleBadge
|
||||
and a status badge placeholder (below md the cards keep the two-bar shape); resolve and
|
||||
assert the Default rows.
|
||||
4. **Only you** — a single "marta" item (email, role "owner"); assert the row, the hint text and
|
||||
"Invite user".
|
||||
5. **Error, Examples-driven** — reject with `ApiError(500)`, then `NetworkError`; assert the alert
|
||||
copy, the "Try again" action, no close button and no rows; click "Try again"; assert `request`
|
||||
is called again, then resolve the Default fixture and assert its rows.
|
||||
@@ -45,8 +52,8 @@ part). Run each layout case at a desktop and a below-md width (mock `matchMedia`
|
||||
was not called a second time, the list has 6 rows with the new Pending row first and the
|
||||
others unchanged, the first row has the `--ui-bg-elevated` background class, and the toast
|
||||
was raised with id "invite-sent-3", color success, icon "i-lucide-circle-check", title
|
||||
"Invite sent" and description "lea.martin@renew.example has 3 days to accept." Assert nothing
|
||||
about the "Today" label.
|
||||
"Invite sent" and description "lea.martin@renew.example has 3 days to accept." Assert the new
|
||||
first row shows "—" and "admin", and nothing about the "Today" label.
|
||||
10. **Replacement of an expired invite** — invite "p.huber@gmail.com", resolve 201; assert exactly
|
||||
one row for that address, first and Pending.
|
||||
11. **Invite sent into Only you** — assert the new row above "marta" and the hint gone.
|
||||
@@ -65,6 +72,14 @@ part). Run each layout case at a desktop and a below-md width (mock `matchMedia`
|
||||
the same body; resolve 201 and assert the closed modal, the row and the toast.
|
||||
17. **Cancel discards** — type, click "Cancel"; assert immediate close, no `request`, an empty
|
||||
field on reopening, and focus back on the opening button.
|
||||
18. **Display names in the Users list** — the Default fixture with user 1 named "Marta Alves"
|
||||
(session `/v1/auth/me` too); assert the "marta@renew.example" row shows "Marta Alves", "owner"
|
||||
and Accepted, the "ines.duarte@renew.example" row "Inès Duarte" and "admin", the
|
||||
"jonas@renew.example" row "—" and "admin", and the sidebar chip "Marta Alves".
|
||||
19. **Non-owners, Examples-driven** — session role "admin", at desktop and below md; assert no
|
||||
"Invite user" button (header or pinned), the list still renders, no reserved space for the
|
||||
pinned button, and the hint absent for a list of only "marta" and "jonas". Also a session with
|
||||
no role (no button) and an owner control (button present in both layouts).
|
||||
|
||||
## Visual (task visual:check -- users, desktop and mobile, light and dark)
|
||||
|
||||
@@ -73,11 +88,24 @@ Screens Back-office.dc.html`, exact values in `docs/design/source/AppFrame.dc.ht
|
||||
state's mock to `web/visual/states.ts` with the board's own sample data. The gate is hard: every
|
||||
capture of every non-skipped state must be within 10 %, for both viewports and both themes.
|
||||
|
||||
- [ ] Default: the 5 board rows (`sam.oliveira@renew.example`, `p.huber@gmail.com`, `ines` with
|
||||
"Inès Duarte", `jonas`, `marta` with "you"), dates 27 Sep 2026, 3 Aug 2026, 18 Jun 2026,
|
||||
2 Apr 2026, 12 Mar 2026; session user `marta`.
|
||||
- [ ] Loading: 5 skeleton rows; the setup must give skeletons time to show (150 ms) before the capture.
|
||||
- [ ] Only you: the single `marta` row plus the hint.
|
||||
- [ ] Fixtures: `usersFixture` in `web/visual/states.ts` follows the board data: invite 2
|
||||
`sam.oliveira@renew.example` pending, no name; invite 1 `p.huber@gmail.com` with
|
||||
`display_name: 'Paul Huber'` expired; user 3 `ines.duarte@renew.example` "Inès Duarte" admin;
|
||||
user 2 `jonas@renew.example` admin, empty name; user 1 `marta@renew.example` owner, empty
|
||||
name. `verifiedSession` carries `role: 'owner'`. `profileSession` and `usersWithDisplayNames`
|
||||
are updated so "Display names in the Users list" shows "Marta Alves", owner.
|
||||
- [ ] Default: the 5 board rows by email with display names, roles and status badges, dates
|
||||
27 Sep 2026, 3 Aug 2026, 18 Jun 2026, 2 Apr 2026, 12 Mar 2026; session user `marta`. The
|
||||
board also draws Cancel/Renew buttons in the sixth column, which this slice does not
|
||||
render; if the gate fails on them, stop and ask the user (no exception without approval).
|
||||
- [ ] Loading: 5 skeleton rows, with role and status placeholders on desktop and the board's
|
||||
two-bar cards on mobile; the setup must give skeletons time to show (150 ms) before the
|
||||
capture.
|
||||
- [ ] Only you: the single `marta@renew.example` row (owner) plus the hint.
|
||||
- [ ] Profile "Display names in the Users list" (`task visual:check -- profile "Display names in
|
||||
the Users list"`): Marta Alves / Inès Duarte / dash.
|
||||
- [ ] Not drawn, no visual state: non-owner rendering (no "Invite user", no hint). Covered by
|
||||
Vitest only (case 19); do not add it to `web/visual/states.ts`.
|
||||
- [ ] Error: the alert and "Try again", no table or cards; the header button is still drawn.
|
||||
- [ ] Invite: default: modal over the Default list, empty focused Email; the mobile pinned button hidden.
|
||||
- [ ] Invite: validation: value `sam.oliveira@renew`, error copy.
|
||||
@@ -91,6 +119,9 @@ capture of every non-skipped state must be within 10 %, for both viewports and b
|
||||
- [ ] Layout, taken from the board frame (`AppFrame.dc.html`), not `components.md`: desktop header
|
||||
row is title block left and the 32 px "Invite user" button right, aligned to the bottom of
|
||||
the row; on mobile the button is 16 px from left, right and bottom, 48 px high, 18 px icon.
|
||||
- [ ] Layout, from `AppFrame.dc.html`: 36 px table header, 48 px rows, sixth 150 px track empty;
|
||||
RoleBadge 20 px high, 7 px side padding, IBM Plex Mono 11 px medium; cards show the email
|
||||
line, then the display name, then the badges line.
|
||||
- [ ] Not covered by screenshots: keyboard and focus order (Tab reaches "Invite user", the modal
|
||||
focus starts on Email, then Cancel, then "Send invite", focus is trapped, Esc closes it in
|
||||
every dismissible state and is blocked while Sending, focus returns to the opening button);
|
||||
@@ -106,6 +137,8 @@ capture of every non-skipped state must be within 10 %, for both viewports and b
|
||||
- **Real invitation email** — needs real or sandboxed SMTP (`SMTP_HOST` and the related env
|
||||
vars): the mail arrives and its link `{PUBLIC_URL}/invite/accept?c=...` carries the code. The
|
||||
link will not resolve in the SPA, because the accept screen is out of scope.
|
||||
- **Admin session** — sign in once as an admin against `task dev` and confirm there is no
|
||||
"Invite user" button and no "Only you" hint.
|
||||
- **Toast placement and stacking on a live app** — bottom right on desktop and above the pinned
|
||||
button on mobile.
|
||||
- **"Today" and the elevated row on a live app** — confirm the new row and the "Today" label
|
||||
|
||||
Reference in new issue
Block a user