Files
RenewCMS/features/article_list_localization.qa.md
T

6.6 KiB
Raw Blame History

QA: Editorial article list by story and language

Order of work is part of the procedure: the repository query and its real-database tests come first (items 1 to 4), then the use case and the HTTP contract (5 to 8), then the SPA (9 onward). Backend items are Go tests in test/, black-box.

Automated, repository (SQLite file, task test)

  1. Seed the "Journal" dataset (8 stories, 17 versions, bylines, updated_at as in the feature) and the "hidden-version" variant through the real ArticleRepository on a temp SQLite file with database.Migrate. Drive the new grouped query through article.Repository with the new filter fields (group, lang, tab, author) and assert, table-driven over the feature's Examples: story ids per page, total, representative per story, coverage order, missing_langs, story_updated_at, story_authors, counts.
  2. Access filter inside the query: for a caller with scope own, all-online and online only, assert the hidden-version dataset yields the same ids, totals and counts as a database where the hidden articles do not exist. Also check page boundaries (a hidden version must not shift a page: 5 per page of readable stories).
  3. Filter semantics: the "single readable version satisfies all version-level filters" cases (status+author, status+q, author+seo), the "story_authors is the union" case, the "missing" cases (no readable version, site not enabling the language, disabled language, unassigned articles) and accent-insensitive q in grouped, language and not-in modes.
  4. Sort whitelist and stability: each sort of the feature, ties by story_id descending, no overlap or gap between pages 1 to 3 on a 12-story set.

Automated, real databases (task test:mysql, task test:postgres)

Repeat items 1 to 4 against MySQL 8.4, MariaDB 11 and PostgreSQL. Compare ids, totals and counts with SQLite, not only pass/fail. Also assert no query returns a different id set under a different collation (title sort aside). Record NOT RUN with the reason if the daemon is down, or use the podman shim (see memory). Inspect the executed SQL once (GORM debug) to confirm no dialect-only construct, no per-row query for coverage (coverage and authors are loaded in a bounded number of queries per page), and that request text is never concatenated.

Automated, use case (task test)

  1. ListArticlesUseCase over fakes: grouped command, role grants for owner, reader and restricted writer, fake site and localization repositories. Assert representative choice, missing_langs only when create_translation is held, the counts rules, and that a hidden version never reaches the result. Assert sentinel errors with errors.Is. Existing use case tests stay green.

Automated, HTTP (task test)

  1. article.NewHandler(...) over the real use cases and fakes, a fresh chi.NewRouter() mirroring NewArticleRouter in api/router.go (GET / under jwtauth.Verifier only), validator.InitValidator() first, httptest, jwt cookies signed with a test jwtauth.New("HS256", ...) secret (owner, reader, restricted writer, unverified user, expired, none).
  2. "Public lists are unchanged": bare request and a public request keep the flat keys exactly (compare the key set and values with a golden produced before the slice); lang=fr and author narrow the flat list; group=story for the three public callers and every invalid query in the Examples answer 400 with apperr.BodyErrorMessage and the fake repository records no call.
  3. Grouped response shape: decode and assert items[] keys (flat item keys plus story_updated_at, story_authors, coverage, missing_langs, capabilities), total, page, size, counts keys (all, online, draft, suggestions, missing), items [] not null, snake_case, errors plain text. Repository failure and count failure answer 500 "Failed to retrieve articles". Docs: docs/ gains the grouped contract (parameters, shape, counts, errors) so the SPA mocks /v1 from it.

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

  1. Mock GET /v1/article/ per state with the Journal responses of the feature (and Press, Atelier and the reader). One test per design state of group L7 except loc.l7.review: loc.l7.all, page2, fr, frMissing, emptyMissing, noResults, frLoading, listFail, limited, author, langFilter, rowMenu, filters, tabsScroll, long. Assert: the request query (group=story, lang, tab, author, page, size; defaults omitted), the URL after a successful response, the unit label text, the tab labels and counts, "–" counts while loading, the retained filters on failure, the chip order and collapse (+N, accessible names), dashed chips only from missing_langs, and the identical row menu items at mobile and desktop widths.
  2. URL state: invalid lang, tab=review, author=abc, page=abc fall back and are replaced; every filter change drops page; the tab and filter chips match the URL; "Clear all" empties all filters.
  3. Regression of the amended screens: ArticlesPage still passes the sort, pagination, row action, delete and toast cases of articles_list and articles_pagination_screen, now on grouped rows.

Visual (task visual:check -- localization-list or the L7 group)

Compare each state with its board frame at desktop (1440), 1024 where the board draws it, and mobile (390), light and dark. The gate is hard: every capture within 10 %. Mock /v1 from web/visual/states.ts with the board's own data (docs/design/source/locale-fixtures.js: Journal 8 stories, 17 versions, page size 5; Press; Atelier; the limited scope). Titles, bylines (Marta Alves, Camille Roche, Luis Ferrer) and the language names come from the fixture. All states use relative text ("2 h ago", "Yesterday", "3 days ago"): list them as skip: clock-sensitive only if the SPA renders them from the current time; otherwise freeze nothing and use the board's absolute dates. loc.l7.review is out of scope (release 2). loc.l7.tabsScroll is captured at 1440 and 390 only.

Manual

  • Keyboard and focus: tab order through search, filters, tabs (arrow keys), rows, row menu (Enter or Down opens, Esc returns to the button), chips, pager; focus ring on the dashed chip buttons; the filter sheet traps focus and returns it to its trigger.
  • Screen reader: the unit label announces politely on every filter change; chips announce "French: draft" and "3 more languages: ...".
  • Hover and motion: tab fade and chevrons, skeleton pulse, sheet open and close, reduced motion.
  • Real databases through a running server: open the list against a real SQLite file with several languages, a restricted account, and the same data on MySQL and PostgreSQL; compare counts with the Languages page.
  • Real SMTP: not applicable.