5.6 KiB
5.6 KiB
QA procedure: SPA Roles screens (slice 6)
Spec: features/roles_spa_screens.feature. Vitest + Vue Test Utils under task test:web, /v1 mocked through the existing kits, access bodies built with web/src/test/access.ts. No live server, no real SMTP. Visual checks are out of this slice by user decision.
Honesty rules
- First run
task test:webon the unmodified tree and record the baseline (exit code, failing specs). - If the runner cannot execute, every SPA item is NOT RUN, never passing.
- Report each new or edited spec and which already failed in the baseline.
- Spec only the copy the design gives. Items marked TBD in the feature (reset, review differences, duplicate note, types without schema.view, templates beyond the examples, role_not_found) get behaviour specs that don't assert text, and are listed as design gaps.
- Date and clock: fixed
updated_atstrings, a pinned time zone or injected formatter for "11:02"; no spec freezes the system date.
Automated (Vitest)
| Spec | Cases |
|---|---|
| router/tests/rolesRoutes.spec.ts (new) | the route table of the feature; /settings/permissions redirect with no 403 flash; typed guard with the session checking |
| components/tests/DashboardShell.spec.ts (extend) | Access > Roles entry with roles.view in sidebar and drawer, absent without it, no Permissions entry |
| composables/tests/useRoles.spec.ts (new) | list, get, create, update, delete, reassign, reset calls; error mapping of every refusal code; role_conflict re-read of updated_by |
| lib/tests/rolePreview.spec.ts (new) | every sentence of the design, grouping of types, Nothing granted, Changed tagging, read-only wording, members line |
| lib/tests/roleForm.spec.ts (new) | form from role, grants to payload (wildcard plus typed, none omitted, owner-only never sent), Reset of a Custom row, presets, unsaved counter, discard |
| lib/tests/grantLocks.spec.ts (new) | escalation locks via scopeOf (all vs own, typed none), owner-only items, picker disabling |
| views/tests/RolesListView.spec.ts (new) | Default, Loading (150 ms with fake timers), Only built-in, Error with retry, No permission to edit, row menus, mobile cards |
| views/tests/RoleEditorView.spec.ts (new) | New role, Editing, Built-in as owner / admin, Owner locked, own role, view-only, Unsaved, Saving, Saved, name required / taken, Conflict, 403 lowered, escalation and role_locked answers, create then route replace, duplicate prefill |
| components/tests/PermissionGrid.spec.ts (covers PresetPills and OnOffCell), ScopeControl.spec.ts, PermissionGroup.spec.ts (new) | roles/names ("Events: Update"), keyboard (arrows, Home, End, Space, roving tabindex), live region text, aria-disabled and reason, changed marker, "x of y enabled", expand all |
| components/tests/RolePreview.spec.ts (new) | card on desktop, 92% sheet on mobile, Close |
| components/tests/DeleteRoleDialog.spec.ts (covers RolePicker through pickerKit.ts) (new) | Blocked, picker disabled reasons, Move and delete sequence and each failure, Confirm no members, built-in dialog, role_has_members live count |
| lib/tests/grantKey.spec.ts (new) | parseGrantKey and formatGrantKey split on the first colon, type key containing a colon through form, locks and names |
| i18n/roleMessages.spec.ts (new) | new keys exist in EN and FR, placeholders match |
Manual checks
- Keyboard: tab order across the grid (one stop per cell), arrows, Home and End; disabled items reach focus and announce their reason; focus visible.
- Screen reader: live region announces "Events, Update, set to Own."; sticky bar and sheet focus return.
- Two browsers: owner edits Contributor while an admin has it open, then the admin's Save gets the Conflict state with the real name and time.
- Lower a role while its editor is open in another tab: the next refused save shows "Your access changed".
- Real flow in a real browser: create Contributor with Write own, assign it from Users, delete Intern, delete Contributor with reassign.
- Mobile 390 px: card grid, sticky bar and sheet; touch targets 44 px. French copy. Dark mode.
- Direct link to /settings/permissions after build: the embedded SPA redirects (a Go 301 is not implemented).
task lint:web(eslint and vue-tsc) green;task test:webexit 0 on the final tree.
Design gaps and backend notes
- G1 presets semantics and the "7 unsaved changes" count: a preset touches only the content grid and the count is the number of changed (key, scope) entries, name and description counting 1 each.
- G2 "All content types" is the wildcard
content.<action>; a type row is Custom with a Reset link when it holds a typed grant, including:none. - G3 type list without schema.view: the grid shows Articles plus the types named in the role's typed grants, with a note; copy TBD.
- G4 reset-to-default button, confirm dialog and toast copy TBD.
- G5 "Review differences" read-only Added/Removed/Changed list; copy TBD.
- G6 Duplicate at
/settings/roles/new?from=<id>, query stripped after load; copy TBD. - G7 preview templates beyond the Contributor, Nothing granted and Schema/Media/Authors examples; copy TBD.
- G8
role_not_foundpage-level state with "Back to Roles"; copy TBD.invalid_role_namemaps to "Give the role a name."; other 400 codes show a generic error toast. - G9 the SPA cannot send an HTTP 301: a vue-router client redirect to
/settings/roles. - G10 the built-in delete dialog is unreachable: component-level spec only.
- G11 no leave guard: leaving drops the edits.
- G12 after create the route is replaced with
/settings/roles/:idand the "Role created" toast shows. - No backend change is needed: all routes exist (including POST /v1/role/{id}/reset and updated_by).