Files

9.8 KiB

QA: Editor, search and sharing

SPA slice only. No Go change: the API of features/seo_data_backend.feature and features/seo_output_backend.feature already exists. A wrong contract is reported as a backend defect.

Roles: the domain defines only owner and admin. "verified" is a verified account without either role.

Automated SPA (Vitest + Vue Test Utils, task test:web)

Mock useApi's request per case for GET and PATCH /v1/article/{id}, POST /v1/article/{id}/seo/check, GET /v1/article/{id}/seo, GET /v1/site/ and the media routes of the picker. Mount EditorView inside DashboardShell with a resolved session and a test router. Mock matchMedia for desktop and mobile. Use fakes for localStorage and sessionStorage. Use Vitest fake timers for the debounce delay and the 2 s "Copied", and do not freeze the system date. The clipboard is a stub on navigator.clipboard. Report the web tests as run or NOT RUN, never as passing when not run. Names below are suggestions; one spec file per numbered group, under web/src/views/__tests__/ (EditorSeo*.spec.ts).

Regression: every existing Editor spec (EditorView, EditorMetaSection, EditorAuthorList, EditorAddAuthor, EditorImageUpload, EditorMediaPicker, EditorVariants, ArticleEditor*, EditorStatusBar) now sees a POST /v1/article/{id}/seo/check on load. Give them a default mock ({status: "ready", suggestions: [], slug: {available: true, suggestion: null}}) rather than loosening unmocked-call checks, and show they still pass.

  1. Section: position under "Site and authors", collapsed default, summary Examples (pending, ready, 1 and 2 suggestions, hidden) with the colour class, Loading skeleton summary, no section on 404, mobile sheet placement.
  2. Memory, with the storage fallback (see "Red/green proof"): own key distinct from renewcms-meta-section-open, shared by sidebar and sheet, wins over nothing else, both getItem and setItem throwing SecurityError, no API call. Opening via "Edit search and sharing", "What the frontend gets" or a suggestion link does not change the stored choice.
  3. Open section: address from the site's base_url host plus the slug, other site, ready block, up to three suggestions in response order with "Go to " links, "+N more", checking text, hidden line, check failure with "Try again", 401 and 404 on the check.
  4. Suggestion copy: each of the 11 codes renders its message from params (the four drawn in the design with the exact copy, seven @not-in-design) and has a French entry; the link label Examples and their focus targets (seo_title, seo_description, cover_image_alt, cover_image_id, slug, body, author_ids).
  5. Check scheduling: exactly one check on load with {}; only changed fields in the body; unsaved body included; debounce Examples with fake timers (rapid typing sends nothing, a pause sends one with the latest value) for every text field; discrete changes check immediately; a late answer never overwrites a newer one (resolve out of order) and the stale request is aborted; no PATCH from any check; no client-side rule (a test that feeds a 74-character title and a check answer without title_long still shows no title_long message).
  6. Panel: opening on desktop and mobile with the right container, defaults with every chip, other chips (@not-in-design), filled in, soft counter Examples at 29, 30, 60, 61 (title) and 69, 70, 160, 161 (description) with the amber class and no error colour, maxlength at the API limits, excerpt 300 vs 301 with Save changes disabled, slug Auto/pencil/Reset (sends ""), cover picker with "Use image", alt text appearing with a cover, cover removal sending null, social customised, canonical Examples (on blur and the 422 invalid_url reopening Advanced), noindex switch, preview truncation Examples (60 and 160 desktop, 70 and 120 mobile, template included), previews follow typing and fallbacks, Escape/Cancel discards, focus return.
  7. Slug availability: check on blur with {"slug": …}, taken state and Use <slug>-2, the suggestion applied and re-checked, 409 slug_taken on save keeps the panel open and offers the suggestion through a follow-up check, nothing applied, an owned slug shows no error.
  8. Save integration: "Save changes" sends one PATCH with seo_title only; body pending is sent along; unchanged fields are not sent (assert the exact JSON); Saving disables fields; Saved closes the panel, refreshes the check and shows the toast; 500 and network failure keep the panel, the notice and the values; editor Save, Ctrl/Cmd S and "Save and publish" after a failed panel save; the leave dialog with an unsaved SEO value; the 401 mirror to sessionStorage and the restored draft with the panel fields; sessionStorage throwing; suggestions never disable Save or Publish; Cancel sends nothing; offline; the three roles.
  9. ApiPreviewDialog: one GET per opening from both openers, desktop dialog and mobile sheet, Formatted keys and the chip Examples, Raw JSON equals the response, Copy JSON writes the pretty-printed raw object, "Copied" for 2 s (fake timers) and the live-region announcement, a rejected or missing clipboard, draft note when is_online is false, loading with Copy disabled, error with "Try again" for 500 and a network error, read-only (no PATCH, no POST), Escape/Close focus return.
  10. i18n: every new t() key has a French entry (extend the i18n spec); one French walk through the section, the panel and the dialog.
  11. API types: Article carries the SEO fields and Site carries site_name, title_template, default_description and default_robots; useArticleDraft mirrors the SEO fields in sessionStorage and sends only changed ones (unit test of savePayload through save()).

Red/green proof for the storage fallback (required in the qa report)

metaSectionMemory.ts already wraps its storage calls in try/catch. For the new key, the spec "A storage that throws never breaks the editor" (both getItem and setItem) must be shown failing without the fix:

  1. Write the spec with a localStorage whose getItem and setItem throw SecurityError.
  2. Temporarily remove the try/catch from the new read/write helpers (or call localStorage directly), run task test:web -- EditorSeoMemory, and record the red run (the editor throws or the toggle does nothing).
  3. Restore the helpers, run again, and record the green run.
  4. Report both outputs. The temporary revert is not committed.

Visual (task visual:check -- editor, desktop and mobile, light and dark)

Setups go in web/visual/states.ts, one setup per state, with mocked /v1 and the board's own sample data (article 12 "How we price a bathroom renovation", Renew, "Inès Duarte", media "north-wall.jpg" and "kitchen-after.webp", values from the frames). Hard gate: every capture within 10 %, with no per-state exception. Reference frames: AppFrame.dc.html (sidebar section), DirectoryFrame.dc.html (SeoPanel, SlugField, MediaField, picker) and the board RenewCMS Screens Back-office.dc.html.

  • Existing Editor frames with the sidebar: the collapsed section with "Ready" under "Site and authors". Their setups need a default POST check mock. Loading shows the skeleton summary. Not found has none.
  • Editor: search and sharing (24): Collapsed ready, suggestions, hidden; Open ready, suggestions (4 codes: title_long, cover_alt, no_h, short), checking, hidden, other site (site: 1 = Journal); Panel defaults (codes: cover_none), filled in, outside the soft ranges, slug edited, slug taken, cover without alt, choose cover, social sharing open (with scrollY 560 on desktop), social customised, advanced with invalid canonical (scrollY 640), hidden from search (scrollY 700), mobile search preview, checking, saving, save failed; Saved (toast).
  • Editor: what the frontend gets (6): Formatted, Raw JSON, Copied, Draft article, Loading, Error.
  • States that wait on a timer (the debounce, the 2 s "Copied") are set up by their end state; none depends on the date or time, so none is skip: clock-sensitive.
  • Re-run Articles, Sites, Authors, Profile, Settings and Home: report any capture that moves above 10 %. Any failure needs the user's decision (an approvedBy exception or a design update).

Exit conditions

  • All existing Editor frames and the 30 new frames pass task visual:check -- editor with no per-state exception.
  • Vitest specs reported honestly, run or NOT RUN, with the red/green proof attached.
  • task lint and bun lint clean; no comment added.

Copy and behaviour not in the design (feed back to docs/design later)

Each has a French entry in web/src/i18n/messages.ts.

  1. The codes now use the board copy: title_empty, desc_long, cover_none, slug_long, no_au, body_alt, short.
  2. The other fallback chips (excerpt, site default description, no image) and the chips of the dialog.
  3. The check failure text "Couldn't check this article." with "Try again" (no state in the design).
  4. The "Go to authors" and "Go to URL slug" labels, and where "Go to body" and "Go to authors" move focus.
  5. The clipboard-refused behaviour (no copy, no state).
  6. The excerpt-over-300 inline error text, and the "Save changes" disabled rule.

Manual checks screenshots cannot cover

  • Keyboard: tab order through the section, the panel (fields, Advanced, switch, previews, buttons) and the dialog; Escape; focus returns to the opener; focus lands on the field named by "Go to …"; "Copied" read by a screen reader.
  • Hover, focus ring and motion: slide-over and sheet transitions, the amber counter, the chip styles.
  • Real task dev: edit a title, description and cover, watch the check update after a pause, take a slug used by another article (409 flow and "Use …-2"), Save, then open "What the frontend gets" and compare it with curl on GET /v1/article/{id}/seo; hide from search and see "Hidden from search"; clipboard on a real browser over http://localhost.
  • Storage: block site data in the browser and check that the editor still works and the section still toggles.