6.6 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 confirm button's label changes from the placeholder's "Create article" to the design's "Create draft" — this is a deliberate copy change, not a leftover.
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",
first paragraph "Small rooms show every grout line, so we start with the floor.", 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.", an empty First paragraph field with its placeholder and help, "Cancel" and "Create draft".
- Blur validation, Title — 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. - Blur validation, First paragraph — with a valid Title, leave First paragraph empty and
blur; assert "Add a first paragraph. It can be a single sentence." appears; assert no
requestcall. - Submit with both fields invalid — Title "Ti", empty First paragraph, click "Create draft";
assert both errors show and
requestis never called. - Submitting state — fill both fields validly, click "Create draft"; assert
requestis called once withPOST /v1/article/and the exact JSON body; assert both fields 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 both field values are kept, the Title field shows "Check this title and try again.", an alert reads "Something in the form isn't valid. Check the highlighted fields.", and the First paragraph field shows no error. - Error, Examples-driven — from Submitting, (a) resolve
requestrejecting withApiError(500), (b) resolve it rejecting withNetworkError; in each, assert both field values are kept, the alert reads "Couldn't create the article. Your text is still here. Try again in a moment.", and neither field shows an 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 both fields, 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", first
paragraph "Small rooms show every grout line, so we start with the floor."). This screen has no
time-dependent content, so no fixed now is needed.
- Default: empty, focused Title with counter "0 / 200", empty First paragraph, help text on both fields, "Cancel" / "Create draft".
- Validation: Title "Ti" with counter "2 / 200" in error and "Use at least 3 characters."; First paragraph error "Add a first paragraph. It can be a single sentence."
- Submitting: both fields filled and disabled, confirm reads "Creating…" with its loading state.
- Title rejected (400): both fields filled, Title error "Check this title and try again.", alert "Something in the form isn't valid. Check the highlighted fields."
- Error: both fields 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) → First paragraph → 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/warn 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 and that a genuinely-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.