6.6 KiB
QA procedure: relation field in the SPA
Two parts. Part 1 is automated (Vitest component tests, task test:web). Part 2 is the visual gate (task visual:check). Part 3 is manual. The Go side is in relation_field_type.qa.md. Until task test:web exists, SPA tests are reported NOT RUN, never passing.
Part 1: automated (Vitest + Vue Test Utils)
Mock /v1 per test with the shapes below; no live server. A field is {"key","label","type":"relation","required","unique":false,"default":null,"min":null,"max":null,"options":[],"relation":{"content_type","kind"}} and every other field carries "relation": null.
Registry
lib/fieldTypes: the relation entry has the properties of the registry outline (label, iconlucide:link, controlRelationField, no unique/limits/default/choices, empty value per kind, sample null or []). An unknown type still falls back to text; a relation never does.- Client validation table (single and many, required/optional, 0, "3", [3], duplicates, 51 ids) and the message map including the "not produced" rules.
Builder (one test per state)
3. Type gallery: Relation is the last tile; locked on a saved field with the other tiles dimmed.
4. New relation: the related-type select lists the other types (mock GET /v1/content-type), the help text, the three cards with the exact sentences, default "One to many" (proposed); choosing a type updates the sentences.
5. Preview outline: the kind change updates both sentences and the line · shown on Venue as the field "Events"; the example cards "Event" and "Venue" show the board's chips (Rue Verte hall, Open day, Lime plaster workshop, +1). Nothing about the inverse is in any request.
6. Save body: relation sent as {"content_type","kind"}, others "relation": null; a loaded field round-trips unchanged; no related type chosen blocks Save with the proposed message.
7. Saved relation: key, tiles, related type and cards are disabled; Label and Required editable; the proposed lock note shows.
8. Review: required relation + entries > 0 shows the flagged row and disabled "Apply changes" with no PUT; optional shows the clean row; required with 0 entries sends at once; making an optional relation required shows "Entries may not fit" with Apply enabled.
9. Server answers: 409 schema_incompatible and 422 invalid_field map to the existing flows.
10. Delete refused: 409 content_type_referenced shows the proposed copy and the type stays listed.
11. A non-owner/admin sees no builder.
Entry form (one test per state)
12. Single control: type "rue" -> only GET /v1/content-type/venues/entry?q=rue&page=1&size=... is sent (debounced, trimmed, stale answers ignored); rows show title and second line; pick sets the value to 3, one chip, "Unsaved changes", live message.
13. Many control: chips for [11, 12], "Search to add", the help line; add keeps order; x and Backspace remove; picked ids are not offered again; the 51st is blocked with the proposed message.
14. States: loading (skeleton after 150 ms), no match with "Clear search", empty type, 500, network error, 429 countdown, 401 redirect, 404 (all with the proposed copy where tagged), "Try again" re-sends only the search.
15. Missing target (optional and required), saving, keyboard (arrows, Enter, Esc, Tab, Backspace), required-empty error and focus order (first invalid field only).
16. Outline "What the control shows and sends" (all 7 rows) and the create payload with venue: null, speakers: [] and with a picked number.
17. Dirty tracking: back to the original value gives "Saved"; reordered many ids count as changed.
18. Server answers table: required, type, unique, unknown_field (reload alert), each with the right copy, chip and focus.
19. Layout: a relation after boolean/number/date never pairs.
List
20. Cell outline (one chip, two chips, two chips + "+2", dash for null, dash for [], missing chip) from each item's relations with no extra request; relation columns are not sortable (no sort icon, no aria-sort).
21. Desktop grid for items 42/41/40 matches the board texts; the mobile card shows the labelled lines; the Columns menu lists Venue and Speakers with the link icon and the cap of 6.
22. Plain text of a relation cell never shows the numeric id; a first-field relation keeps the link and the "Entry N" title.
Accessibility 23. Combobox/listbox roles and attributes, chip group label, "Remove X from Y" button names, required/invalid/describedby wiring, the live region texts.
Part 2: visual (task visual:check -- <screen>)
Hard gate: each capture within 10 % at 1440x900 and 390x844, light and dark. Add setups in web/visual/states.ts with the board's own sample data; no clock-sensitive state exists in this group.
| Screen | State | Board sample data the mock must reuse |
|---|---|---|
| schema | Relation in the builder | Events with Venue (label), key venue, related type Venue, "One to many" selected, preview sentences, example cards with chips "Rue Verte hall", "Open day", "Lime plaster workshop", "+1" |
| entry (content/events/42) | Relation in the entry form | Title "Lime plaster workshop" (21 / 80); Venue search "rue" with rows "Rue Verte hall / Lyon · 120 seats", "Rue Verte yard / Lyon · outdoor", "Rue du Port studio / Nantes · 24 seats"; Speakers chips "Ines Duarte", "Jonas Pereira"; help "An event can have many speakers." |
| content (events) | Relation in the list | Items 42 "Lime plaster workshop" 21 Nov 2026 (Rue Verte hall; Ines Duarte, Jonas Pereira, +2), 41 "Heat pumps: what the numbers say" 3 Nov 2026 (–; Jonas Pereira), 40 "Open day at the Rue Verte site" 12 Nov 2026 (Rue Verte yard; –) |
Notes:
- The board's states are drawn with a "Later" badge; the SPA does not render it. Expect a pixel difference at the badge. If the 10 % gate fails because of it, the user decides on a documented
exception(approvedBy); the QA never adds one. - The board's builder shows a four-tile gallery plus Relation; the SPA shows the full gallery. The mismatch is reported to the user, not worked around.
- States tagged "(proposed copy)" (loading, no match, error, missing chip, locked inspector, review refusal, delete refusal) are not on the board and get Vitest tests only, no visual capture.
Part 3: manual (screenshots can't cover)
- Keyboard and focus order across the combobox, chips and list; screen reader announcements of the live region.
- Hover and motion of the popover and of the chips' x; the mobile bottom-sheet versus under-input choice.
- Real SQLite end to end with the built binary: build the type pair, pick, save, reload, delete a target and see the missing chip.
bunx playwright install chromiumthe first time on a machine.