6.3 KiB
QA: Creating a new article from NewArticleModal
Implementation note for coder (informational, not a QA assertion)
Only HomeView.vue is wired as a real created consumer in this slice: it listens for
@created="article => { router.push(...); useToast().add(...) }". The Articles-page trigger and
the /article/new route stay pointed at the current ArticleView.vue placeholder until that page
exists; don't wire a created handler there yet.
The modal is title-only: the First paragraph field is removed and the POST body is {"title": "..."}
with no body key (see features/article_body_unlimited.feature).
The confirm button's label is the design's "Create draft".
The Created toast uses useToast().add({ id: 'article-created-{id}', color: 'success', icon: 'i-lucide-circle-check', title: 'Draft created', description: '"{title}" is saved as a draft.' }),
per docs/design/components.md's toast-id convention (action + article id) and the Toast rules
section (success/info close after 5 s).
Automated (Vitest + Vue Test Utils, task test:web)
Mock useApi's request for POST /v1/article/ per case; no real backend, no httptest server.
Mount HomePage (which owns NewArticleModal) inside DashboardShell with a resolved verified session,
and a test router at /home. Sample data throughout: title "Choosing tiles for a small bathroom",
created article id 42, is_online false.
- Default state on open — click "New article"; assert the modal shows title "New article", the intro text, an empty focused Title field with counter "0 / 200" and help "3 to 200 characters.", "Cancel" and "Create draft", and no First paragraph field.
- Blur validation — type "Ti" into Title and blur; assert the error "Use at least 3
characters." replaces the help text and the counter "2 / 200" is in its error style; assert no
requestcall. - Submit with an invalid title — Title "Ti", click "Create draft"; assert the error shows and
requestis never called. - Submitting state — fill Title validly, click "Create draft"; assert
requestis called once withPOST /v1/article/and the JSON body exactly{"title": "Choosing tiles for a small bathroom"}; assert Title and Cancel become disabled, the confirm button reads "Creating…"; assert pressing Escape and clicking the backdrop do not close the modal while unresolved. - Successful create hands off — from Submitting, resolve
request201 with the fixture article (id 42); assert the modal closes; assert the router navigated to/article/42/edit; assert a toast was raised with id "article-created-42", color success, icon "i-lucide-circle-check", title "Draft created", and description ""Choosing tiles for a small bathroom" is saved as a draft." - Title rejected (400) — from Submitting, resolve
requestrejecting withApiError(400); assert the title is kept, the Title field shows "Check this title and try again.", and an alert reads "Something in the form isn't valid. Check the highlighted fields." - Error, Examples-driven — from Submitting, (a) resolve
requestrejecting withApiError(500), (b) resolve it rejecting withNetworkError; in each, assert the title is kept, the alert reads "Couldn't create the article. Your text is still here. Try again in a moment.", and the field shows no error. - Retry after Error succeeds — from the Error state (500), click "Create draft" again; assert
requestis called a second time with the same JSON body; resolve 201 with the fixture; assert the modal closes, the router navigated to/article/42/edit, and the same "Draft created" toast (id "article-created-42") is raised. - Cancel discards — fill Title, click "Cancel"; assert the modal closes immediately with no
confirmation UI, and
requestwas never called.
Visual (manual verification — desktop and mobile, light and dark)
Reference: docs/design/screens/new-article.md and docs/design/source/RenewCMS Screens Back-office.dc.html, group "New article". Run task visual:check -- new-article for each state
below and compare against the board frame; add each state's mock setup to web/visual/states.ts
first, reusing this slice's sample data (title "Choosing tiles for a small bathroom"). This screen
has no time-dependent content, so no fixed now is needed. The board frames may still draw a body
line in Submitting, Title rejected and Error; the design .md files are authoritative for the
title-only modal and the boards are a known follow-up.
- Default: empty, focused Title with counter "0 / 200", help text, "Cancel" / "Create draft".
- Validation: Title "Ti" with counter "2 / 200" in error and "Use at least 3 characters."
- Submitting: Title filled and disabled, confirm reads "Creating…" with its loading state.
- Title rejected (400): Title filled, error "Check this title and try again.", alert "Something in the form isn't valid. Check the highlighted fields."
- Error: Title filled, alert "Couldn't create the article. Your text is still here. Try again in a moment.", no field-level error.
- Created: no board frame; verified by Vitest (toast copy + navigation).
- Keyboard/focus: Tab reaches Title (already focused on open) → Cancel → Create draft; Esc closes the modal in every dismissible state and is blocked while Submitting; the counter's live region announces only when its error threshold is crossed, not on every keystroke, per CharacterCounter's a11y spec.
- Motion: modal open/close follows the standard 240 ms timing and stops under reduced motion.
Manual verification
- Real POST /v1/article/ round trip against a running server — Vitest only mocks
useApi; confirm the real 201/400/500 responses, that a title-only create returns 201 with an empty body, and that the created article is retrievable at/article/{id}/editagainsttask devwith a live SQLite file, since automated tests never exercise the real Go handler orCreateArticleUseCase. - Toast placement and stacking on a live app — confirm the "Draft created" toast's real
position, duration and stacking behavior (per docs/design/components.md's Toast rules) against
a running app, since Vitest only asserts that
useToast().addwas called with the right arguments, not the rendered/animated toast region.