Files
RenewCMS/features/seo_sites_articles_integration.qa.md
T

14 KiB
Raw Blame History

QA: SEO round, slice 4 (Sites defaults, Articles search status, Integration, suggestion dismissal)

SPA slice only. No Go change: the API of features/seo_data_backend.feature and features/seo_output_backend.feature already exists, so this slice has no httptest part. 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: GET/POST/PATCH /v1/site/..., GET /v1/article/ (items with seo_status and seo_suggestions, counts, total), PATCH /v1/article/{id}, POST /v1/article/{id}/seo/check, and the media routes of the shared picker. Mount SitesView, ArticleView, IntegrationView and EditorView inside DashboardShell with a resolved session and a test router. Mock matchMedia for desktop and mobile. Use fake timers for the search debounce. Do not freeze the system date. The rendering of "Updated …" is not asserted. Report the web tests as run or NOT RUN, never as passing when not run. One spec file per numbered group, under web/src/views/__tests__/ (SeoSites*.spec.ts, SeoArticles*.spec.ts, SeoDismiss*.spec.ts, SeoIntegration*.spec.ts).

Regression: every existing Sites, Articles (ArticlesPagination, ArticlesPageSizeMemory, ArticlesSitesAuthors), Integration and EditorSeo* spec now sees the toolbar count request, the new fields and the dismiss buttons. Give them default mocks (count request answers total 3 on the request carrying seo=suggestions; sites with the three SEO fields) rather than loosening unmocked-call checks, and show that they still pass.

  1. Sites form fields: order, copy of states 1 and 2 (edit and add), placeholders, helpers, the counter, no "(optional)" on Site name, the "How articles use these defaults" block and its placement under the preview block.
  2. Live example: the Examples table (site name, name fallback, placeholder "Renew" fallback, %title% only, two %site%), updated on each keystroke, no request sent; global replace, not first occurrence.
  3. Template validation: the four invalid values (just text, %site%, %TITLE%, empty) on blur and on submit, nothing sent, focus on the first invalid field, example hidden, error cleared on a valid value, 400 invalid_title_template from the server shows the same message.
  4. Limits: maxlength 100, 200 and 320; the counter at 0, 45 and 320.
  5. Save payloads: PATCH holds only changed fields (assert exact JSON) for template only and for name + description + robots; POST omits the three fields when untouched and sends them when changed; the discard dialog on a changed SEO field; a site without the fields opens with defaults and "Save changes" sends nothing.
  6. Save failure and sessions: 500 and network failure keep the panel and the notice; 403 owner_only gives the existing toast; 401 goes to /login?redirect=/settings/sites&reason=expired; admin and verified users get the 403 page and no form.
  7. SeoStatusChip: the status table (ready, suggestions 1 and 2, hidden with and without a count), placement in the Status cell and in the card meta row, tooltip/accessible name (singular and plural), the unusable items (no seo_status, suggestions with 0, unknown status), the row action that keeps the chip.
  8. Toggle and URL: parse and write seo (parseArticleQuery, toRouteQuery, sameArticleQuery), page reset, q/status/sort/site/size kept, other changes keep seo, reset-to-1 table, opened with ?seo=suggestions&page=2, unknown values dropped silently via replace (the six values), the request carries seo=suggestions only when on, tab counts unchanged.
  9. Count: total 3, total 0, pending, failure (no count, no notice, no URL change); the extra request (seo=suggestions&page=1&size=5, with q, status and site) is made only while off, only when q/tab/site change, and the main response total is used while on.
  10. Empty states: "Nothing needs attention" with its action (clears seo only), the not-in-design combinations (search, Draft, Online, a site), paginator hidden, Retry notice still works.
  11. Single-site install: the Site column and filter are absent and everything above behaves the same.
  12. Dismiss ×, rendering: one button per row of the panel list, none in the compact section list; the footnote; accessible name with the message interpolated; Tab order (after "Go to …"); Enter and Space; visible focus class.
  13. Dismiss ×, behaviour: success sends exactly one PATCH with seo_dismissed equal to the saved list plus the code (assert exact JSON, no other field, no draft field); the button is busy and the row stays until 200; the row leaves; one new check follows; summary line and "All good" follow the check; the draft, the panel values and the editor status are untouched; focus table (first, second, last, and the last remaining row going to "Show dismissed"); 500 and network failure keep the row and show the toast, send no check; 401 mirrors the draft and redirects; 404 shows "Not found"; the three roles; Save and Publish stay enabled; "Show dismissed" sends seo_dismissed: [] then a check.
  14. Integration group: position after Articles, group description, 2 routes, both cards "Public" and "API key OK", expanded content (fields, query rows, tabs, error rows with the real strings), no duplicate route in Articles or Sites, POST …/seo/check still in Articles, index and Jump to order, deep link #seo, counts 8/2/10/6/1 and "1 route" for Sites, the 403 note, no /v1 request.
  15. Catalogue spec: update the group list, the per-group counts, the Sites description, the "Public" assertions (they currently list GET /v1/article/{id}/seo and the sitemap with the Articles and Sites groups) and the 403 note.
  16. i18n: every new t() key has a French entry (extend the i18n spec), the French plurals, one French walk through the four screens.

Visual (task visual:check -- <screen>, desktop and mobile, light and dark)

Setups go in web/visual/states.ts, one per state, with mocked /v1 and the board's own sample data. Hard gate: every capture within 10 %, no per-state exception. Reference frames: DirectoryFrame.dc.html (Sites form, SuggestionList), AppFrame.dc.html (Articles) and IntegrationPage.dc.html.

  • Sites: SEO defaults (5): Edit: SEO defaults (Renew, f.sname, template %title% | %site%, example "How we price a bathroom renovation | Renew"), Add: defaults, Template: custom (site name Journal, template %title% – %site%), Template: not valid, Save failed. The existing Sites form frames (7) now contain the three fields and the new block: update their setups with the board values.
  • Articles: search status (3): Status chips (rows: 2 suggestions, hidden, 3 suggestions, 1 suggestion), Needs attention on, Needs attention: none. Every existing Articles and pagination frame now shows the toggle with the count 3 and the chips of the board rows, so each setup mocks the count request with total 3. States whose "Updated" column or text is clock-sensitive follow what the existing setups already do (skip: clock-sensitive), and state 8 "Published" stays skipped.
  • Integration (8 existing states): the index and the mobile select now contain "Search and sharing", and the page has the new group (collapsed). Mock nothing new. The Navigation state's open select must list the new option. If the 403 note makes a capture exceed 10 %, report it to the user; do not add an exception.
  • Editor: the dismiss column is now implemented. Remove the five withMobileException(..., dismissColumnReason) wrappers (and the dismissColumnReason constant, withMobileException if nothing else uses it) in web/visual/states.ts, covering 'Panel: defaults', 'Panel: outside the soft ranges', 'Panel: cover without alt', 'Panel: choose cover' and the fifth state at line ~5828. Those states must pass task visual:check -- editor at the default 10 % in both viewports. If one still fails, report it; do not restore an exception.
  • Re-run Settings, Users, Profile and Home: report any capture above 10 %. Any failure needs the user's decision (an approvedBy exception or a design update).

Final pass: slice 3 leftovers (required in the qa report)

  1. Grep web/src (excluding __tests__) for text in templates not passed through t(...), and for title="...", placeholder="..." and aria-label="..." attributes with English literals, in the files of slice 3 (SeoPanel.vue, SearchSharingSection.vue, SuggestionList.vue, ApiPreviewDialog.vue, SearchPreview.vue, SocialCardPreview.vue, SeoDisclosure.vue, EditorView.vue, seoCopy.ts, seoApiPreview.ts, seoPreview.ts).
  2. Extract every t('…') key used by those files and by the files of this slice, and confirm each has an entry in web/src/i18n/messages.ts (the i18n spec must cover them). Spot check on the current tree: no missing entry found among the editor strings sampled; the new ones for this slice are still to add.
  3. Report findings by file and line. Fix those that belong to this slice. Anything else is reported to the user, not fixed silently.

Exit conditions

  • All Articles, Sites and Integration frames (existing and the 8 new ones) pass task visual:check, plus Editor, Settings, Users, Profile and Home re-run with no regression above 10 %.
  • The slice 3 dismiss-column exceptions are gone from web/visual/states.ts and grep -n dismissColumnReason web/visual/states.ts finds nothing; the Editor panel frames pass without exception.
  • Vitest specs reported honestly, run or NOT RUN.
  • bun lint clean. No comment added. No Go file changed.

Divergences between the design and the real API (feed back to docs/design)

  • Sitemap errors: the design says 404 "Site not found" and 400 "Invalid page or size."; the API answers 404 site_not_found and 400 with the generic body-mismatch message (apperr.BodyErrorMessage). The Integration cards use the real strings.
  • Article list: the design (routes.md) describes an array body with X-Total-Count; the code reads {items, total, page, size, counts}. Unchanged by this slice.
  • The 403 note under the Errors table has no layout in the design.
  • The 403 note under the Errors table is a design gap: it pushes the following rows down, so Integration "6. Errors" carries an approved exception (16 % desktop, 22 % mobile).
  • Hidden chip: the board wraps the "Hidden" chip under the Online badge in the 110 px Status column; the feature says it never wraps, and the app follows the feature.
  • Add: defaults: the board's Example row always falls back to "Renew"; the app falls back to the Name, then "Renew", as the feature requires.
  • Articles row 5: the design text says row 5 has 1 suggestion, but the board data draws no chip on it; the visual setup follows the board data.
  • Picker debt: on mobile the shared media picker sheet is taller and shows more tiles than the board, so Editor "Panel: choose cover" carries an approved mobile exception (29 %). The picker was not changed in this slice.

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. Source of the "Needs attention" count (extra request, total).
  2. Zero count shown as "0", no count while pending or on failure.
  3. Empty-state precedence when seo=suggestions is combined with a search, a tab or a site.
  4. Chips refresh only with the next list load after a row action.
  5. Items without a usable status show no chip.
  6. Example fallback to the name, then to "Renew" as site name.
  7. Accessible name of the chip on touch.
  8. The 403 row note on the Errors table.
  9. Dismiss ×: accessible name "Dismiss suggestion: ", the failure toast "Couldn't dismiss the suggestion" / "Try again in a moment.", the busy state, the focus rules, and "Show dismissed" restoring all codes.

Proposed French (for review, in web/src/i18n/messages.ts)

English key French
Needs attention À traiter
Hidden Masqué
Hidden from search engines Masqué des moteurs de recherche
Nothing needs attention Rien à traiter
Every article has a clean check. Suggestions are optional, so this is just a bonus. Tous les articles passent la vérification. Les suggestions sont facultatives, c'est un petit plus.
Show all articles Afficher tous les articles
Site name Nom du site
Title template Modèle de titre
Use %title% for the article title and %site% for the site name. Utilisez %title% pour le titre de l'article et %site% pour le nom du site.
The template must contain %title%. Le modèle doit contenir %title%.
Example Exemple
Default description Description par défaut
How articles use these defaults Comment les articles utilisent ces valeurs par défaut
Dismiss suggestion: {0} Ignorer la suggestion : {0}
Couldn't dismiss the suggestion Impossible d'ignorer la suggestion
Show dismissed Afficher les suggestions ignorées
Search and sharing Recherche et partage (already present)

Manual checks screenshots cannot cover

  • Keyboard: tab order through the Sites form (the new fields sit between the image and the robots policy), Space and Enter on "Needs attention", focus on the first invalid field, the dismiss button order and focus after a row leaves, the index and the Jump to select (Enter, Escape).
  • Hover and touch: the chip tooltips on desktop, the chip text read by a screen reader, the toggle's pressed state.
  • Real task dev: the owner sets a template and sees the title change in "What the frontend gets"; dismiss a suggestion, reload, and see it stay hidden and the article's chip count drop; the Articles filter agrees with curl '…/v1/article/?seo=suggestions'; the sitemap card matches curl on /v1/site/renew/sitemap and its X-Total-Count; read the screens in French.