# QA procedure: Roles sync pass 6, final copy and role activity events Spec: features/roles_sync_copy_activity.feature. Vitest + Vue Test Utils under `task test:web`, /v1 mocked with the existing kits (useApi mock of views/__tests__, access bodies from web/src/test/access.ts, activityFixtures.ts). The backend Rule adds a Go test. No live server, no real SMTP. ## Honesty rules 0. First run `task test:web` and `task test` on the unmodified tree and record exit codes and failing specs (known Go failure from slice 7: TestRouterRoutesAreInTheCatalogue). 1. If a runner cannot execute, every item of that side is NOT RUN, never passing. 2. Report each new or edited spec and which already failed in the baseline. 3. Assert only copy the design gives. FR is asserted only where the design table gives it. The fallback sentences of the feature are a design gap and are asserted as the coder writes them, flagged in the report. 4. No spec freezes the system date; fixed `created_at` strings; no relative-date assertion. ## Automated (Vitest) | Spec | Cases | |---|---| | lib/__tests__/activitySentence.spec.ts (extend) | the six success sentences EN/FR, role.reassign with moved 3 and 1 and to_name, the six failed sentences EN/FR, the three fallback rows (no "undefined"), unknown action | | lib/__tests__/activityCategories.spec.ts (extend) | category list and order, "Users and invites", "Roles" requests `category=roles`, "Users & invites" absent | | views/__tests__/ActivityRoles.spec.ts (extend) | list with the six events, the six failed rows with the Failed badge | | views/__tests__/ActivityFilters.spec.ts (extend) | the select options EN/FR | | components/__tests__/ChangeRoleDialog.spec.ts (extend) | success toast EN/FR with member and role names, "Couldn't change the role" alert for 500 and network, choice kept, button enabled | | views/__tests__/RoleEditorView.spec.ts (extend) | save failed (500, network) for POST and PUT: alert, "Try again", edits kept, Save enabled | | components/__tests__/DeleteRoleDialog.spec.ts (extend) | 409 target_role_not_found: alert, picker cleared, action off; 400 invalid_reassign keeps the existing refusal; role_has_members with the live count; 500 on reassign keeps the choice and sends no DELETE | | components/__tests__/InviteUserModal.spec.ts (extend) | the four invite errors placement (under Email, under Role, alert), email and role kept | | i18n/roleMessages.spec.ts (extend) | every key of the "Final copy" table exists in EN and FR, placeholders match | | i18n/neutralWording.spec.ts (new) | no EN value in the roles, users, profile, api-key and activity keys matches \b(he\|she\|his\|her\|him)\b | Mocks to reuse: Ines Duarte, Jonas Weber, Marta Alves; roles Owner, Admin, Writer, Contributor, Content lead, Intern (1 member). ## Automated (Go, httptest, in-memory fakes) in test/ Build the role handler with `NewHandler(...)` over the use cases and the in-memory fakes of role.Repository, user.Repository and the audit recorder of the existing roles API tests. Mount on a fresh `chi.NewRouter()` mirroring api/router.go with `jwtauth.Verifier` + `Authenticator` and a test `jwtauth.New("HS256", …)`. Call `validator.InitValidator()`. No SQLite file, no SMTP. New fake names must be unique across the package. | Test | Steps and assertions | |---|---| | TestReassignRecordsDestinationName | POST /v1/role/{Contributor}/reassign {target_role_id: Writer} with 4 members: the recorded event params are from, to, moved 4 and to_name "Writer" | | TestReassignZeroMovedRecordsNothing | 0 members: 200 `{"moved":0}`, no event (unchanged) | | TestReassignRefusalRecordsNothing | a refusal (400, 403, 404, 409) records no event | Extend, do not duplicate, test/roles_api_usecases_test.go line ~735 (the existing params assertion). Mutation scope for the touched use case: `MUTATE_FILES=ReassignRoleMembers.go MUTATE_RUN='TestReassign' task mutate -- ./internal/infrastructure/useCases`. docs/api.md gains one line for `to_name`. `task lint:go` and `gofmt -l` apply. ## Visual (task visual:check) Screens `roles` and `activity`; states from docs/design/screens/roles.md groups 8, 12 and 13. Sample data is the board's: Ines Duarte, Jonas Weber, Marta Alves, Contributor, Intern, Writer, Content lead, 3 members moved. Gate: every capture within 10 % in light and dark, desktop and mobile. `skip: clock-sensitive` for any activity row whose rendering depends on relative time; the role rows use fixed timestamps from the frame and are checked. States: Role changed toast, Role editor save failed, Change role failed, Delete role target deleted / role gained members / move failed, Invite invalid email / account exists / role above yours / send failed, Activity role events list, failed role events, category filter open. ## Manual checks 1. Toast durations (6 s; warnings stay until dismissed or Reload), polite announcement. 2. Keyboard: focus lands on "Try again", and on the field with the error in the invite dialog. 3. Real browser, FR: read every message of the table once. 4. Real flow: move members of a role to another and read the event in /activity with the destination name. 5. `task lint:web` (eslint and vue-tsc) green; `task test:web` and `task test` exit 0. ## Design gaps and notes - G1 FR for strings the design gives in EN only (Read = Own banners and empty states, Profile card tags, Integration copy) is proposed by the coder and not asserted. - G2 The activity reasons ("Why it failed") are out of this slice by decision. - G3 Fallback wordings for role.reassign events without to_name or moved ("Moved 3 members from Intern to another role", "Moved the members of the role Intern", "Failed to move the members of the role Intern") are not in the design. - G4 Singular "Failed to move 1 member" is derived from the success sentence; the design gives the failed sentence for 3 only. A failed reassign does not record `moved`, so the failed full sentence appears only if the event carries it. - G5 The user.remove sentences are in features/roles_sync_remove_user.feature. - Backend change: the additive `to_name` param only.