From 55d8909134ff69c03561b6dee076edc55b8dfa93 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Sat, 26 Sep 2026 13:04:43 +0200 Subject: [PATCH] plan: user flows are tested in the browser, W3 keeps server rules HTTP tests that imitate htmx keep passing when an htmx upgrade breaks the pages. W6 becomes the specification of every user flow: it absorbs W3's form and action flows (accounts, writing, connections, settings, feeds), adds reliability rules (assert outcomes not htmx mechanisms, no sleeps, no retries, repeated runs before acceptance), an htmx inventory every item of which a test must exercise, and library mutation checks (json-enc, head-support, the error handler). W3 shrinks to four tasks: visibility, guards on every mutating route, one-shot links, and the API/RSS/uploads, with no HX-* assertions. W6.B0 starts first. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016UQyTX1ww97bDaWMEBtmuE --- .claude/skills/frontend-htmx/SKILL.md | 6 +- .claude/skills/wave-run/SKILL.md | 3 +- docs/implementation-plan.md | 18 ++++-- docs/plan/r2.md | 6 +- docs/plan/w3.md | 53 ++++++++++------ docs/plan/w6.md | 91 ++++++++++++++++++++++----- docs/plan/wb.md | 2 +- 7 files changed, 131 insertions(+), 48 deletions(-) diff --git a/.claude/skills/frontend-htmx/SKILL.md b/.claude/skills/frontend-htmx/SKILL.md index 27b7127..8c8103f 100644 --- a/.claude/skills/frontend-htmx/SKILL.md +++ b/.claude/skills/frontend-htmx/SKILL.md @@ -71,8 +71,10 @@ violations on every page, so an inline script or a style without the nonce shows - On a failure, the report prints a trace and a screenshot path. `make ui-trace F=` opens the trace (DOM snapshot per step, network, console). Look at the screenshot before reading any HTML. - A change to a controller, a template's interactive parts or an htmx attribute comes with a browser test in - the matching `e2e/browser/_test.go`. Server-only behavior (statuses, headers, DB state) belongs in - the cheaper `e2e/` HTTP tests instead. + the matching `e2e/browser/_test.go`. Assert what the user sees and the DB state, never htmx + internals (events, `hx-*` attributes, request headers), so the test survives an htmx upgrade and catches + one that breaks the page. Only server rules that don't need the page's JavaScript (access control, + guards, API, RSS, headers) belong in the cheaper `e2e/` HTTP tests, which don't imitate htmx. - Locate by role, label and text (`page.GetByRole("button", …{Name: "Publish"})`). Where that's ambiguous, add a `data-testid` to the template. That is fine in frontend work, but not in test waves, which don't change templates. diff --git a/.claude/skills/wave-run/SKILL.md b/.claude/skills/wave-run/SKILL.md index bf90319..faca2f0 100644 --- a/.claude/skills/wave-run/SKILL.md +++ b/.claude/skills/wave-run/SKILL.md @@ -86,7 +86,8 @@ no logs. If you need a detail, ask with `SendMessage`, which keeps the subagent' Variations by wave: -- **W6 (browser):** step 1 is `make test-ui RUN=`. The mutation check breaks a Stimulus +- **W6 (browser):** step 1 is `make test-ui RUN=`, then again with `COUNT=3` (a flaky + test is sent back, not accepted). The mutation check breaks a Stimulus controller or an htmx attribute the task covers. - **R-waves and RS (refactors):** replace step 1 with `make test-q PKG=` plus `git diff --stat -- e2e/`, which must be empty. Also check that the task shrank the `pkg/arch` allowlist diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index d5f535e..5385a99 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -20,7 +20,8 @@ The end state: repository (`pkg/repo`), every business rule and authorization check in a service (`pkg/service/`), and handlers and CLI subcommands only translate to and from service calls. An architecture test enforces it; -- a browser test suite, so frontend changes can be iterated on safely; +- a browser test suite that specifies every user flow, so frontend changes and + frontend dependency upgrades (htmx, Stimulus) are checked in one command; - a docker-compose development stack (Postgres, and [tommy](https://github.com/can3p/tommy) as the mail sink and S3-compatible object store) with every build tool in a container; @@ -47,10 +48,10 @@ Related documents: | W0 | Test foundation | — (gogo `v0.0.2` released) | done | `test/w0-foundation` | | W1 | Unit tests, no database | W0 | not started | `test/w1-unit` | | W2 | Package tests against Postgres | W0 | not started | `test/w2-db` | -| W3 | End-to-end HTTP tests | W0 | not started | `test/w3-e2e` | +| W3 | End-to-end HTTP tests: server rules | W0 | not started | `test/w3-e2e` | | W4 | Local stack (Postgres, tommy for mail and S3), dev tooling container, app in compose, seed | W0 | not started | `test/w4-local-stack` | | W5 | Coverage ratchet | W1–W4 | not started | `test/w5-ratchet` | -| W6 | Browser tests (playwright-go) | W0 | not started | `test/w6-browser` | +| W6 | Browser tests (playwright-go): user flows | W0 | not started | `test/w6-browser` | | WB | Bug-fix wave (#108–#117, #119–#122) | W1–W3 | not started | `fix/wb-survey-bugs` | | R1 | Router decomposition (move handlers) | W3, W6, WB | planned | `refactor/r1-router` | | RS | Repositories and services, thin handlers | R1 | planned | `refactor/rs-layers` | @@ -63,9 +64,9 @@ Related documents: ``` ┌── W1 (12 tasks) ──┐ ├── W2 (9 tasks) ──┤ -W0 ─────────┼── W3 (6 tasks) ──┼── W5 ── WB ── R1 ── RS ── R2 ── R4 +W0 ─────────┼── W3 (4 tasks) ──┼── W5 ── WB ── R1 ── RS ── R2 ── R4 (1 session) ├── W4 (5 tasks) ──┘ │ │ - └── W6 (7 tasks) ──────────────────┘ └─ R5 (also after R3) + └── W6 (8 tasks) ──────────────────┘ └─ R5 (also after R3) R3: after W5 R6: any time ``` @@ -76,6 +77,11 @@ can run at the same time: about 40 tasks in total, each owning disjoint files. Each of those waves branches from `test/w0-foundation` (or `master` once W0 has merged), not from each other. +**Start W6.B0 first.** User flows are tested in the browser, not over plain +HTTP: an HTTP test that imitates htmx keeps passing when an htmx upgrade +breaks the pages. So the browser suite is the main safety net for R1 and RS, +and W3 covers only the server rules that don't depend on the frontend. + **Layering is two waves on purpose.** R1 moves handlers verbatim, so its diff is reviewable as a move. RS then extracts repositories and services area by area, reusing R1's per-area files. R5 comes after RS, so the ORM swap touches @@ -126,7 +132,7 @@ cost, and opens the PR. | Tier | `model:` value for the Agent tool | Use for | |---|---|---| -| strong | `opus` | W0; wave coordination; visibility/permission tests (W2.D2a, W3.E1); W6.B0 browser harness; R1 skeleton; RS contracts (step 0) and the visibility service (L1); R5 planning | +| strong | `opus` | W0; wave coordination; visibility/permission tests (W2.D2a, W3.E1, W3.E2); W6.B0 browser harness; R1 skeleton; RS contracts (step 0) and the visibility service (L1); R5 planning | | mid | `sonnet` | business-logic tests (connections, forms, feed composition), E2E and browser scenarios, WB fixes, RS area extractions | | cheap | `haiku` | pure-function unit tests, golden tests, factory-driven CRUD checks, docs, CI config | diff --git a/docs/plan/r2.md b/docs/plan/r2.md index f3db67e..573a57a 100644 --- a/docs/plan/r2.md +++ b/docs/plan/r2.md @@ -90,12 +90,12 @@ Replace the switch with explicit settings, such as `--secure-cookies`, tommy rejects. - **The E2E harness** starts the tommy container (mail and S3, with the bucket configured), and passes the endpoint, bucket and path-style settings to the - binary. It adds `app.S3Objects(t)`. The upload tests from W3 (E5 - `upload_media`, E6 `PUT /api/v1/image`) gain assertions that the object + binary. It adds `app.S3Objects(t)`. The upload tests (W6.B5's upload + through the settings page, W3.E4's `PUT /api/v1/image`) gain assertions that the object landed in the bucket with the right content type. They also check that requesting a resized class stores the cached variant (the caching media server writes it back to storage). The "upload, then GET the returned - `/user-media` URL" assertions that W3 already made pass unchanged; they + `/user-media` URL" assertions that W3 and W6 already made pass unchanged; they prove the switch from local files to S3 kept serving intact. - Unit tests keep using `fakestorage`. diff --git a/docs/plan/w3.md b/docs/plan/w3.md index ec1afbe..83970c8 100644 --- a/docs/plan/w3.md +++ b/docs/plan/w3.md @@ -1,20 +1,35 @@ -## W3 — End-to-end HTTP tests +## W3 — End-to-end HTTP tests: server rules -**6 parallel tasks**, all on the W0 harness. Each owns one file, -`e2e/_test.go`. They assert status codes, redirects, htmx headers, key -HTML (via goquery) and resulting DB state (via factory readers). Together they -are the specification R1 must preserve: **every route in `cmd/web/main.go`, -`actions.go` and `api.go` gets at least one test**. The coordinator checks this -against a route list extracted from the source. +**4 parallel tasks**, all on the W0 harness. Each owns one file, +`e2e/_test.go`. -| Task | Routes | Tier | +**Scope.** W3 pins the rules the server enforces regardless of the frontend: +who may see what, which requests are refused, what the API and RSS return, and +which security headers are sent. User flows (submitting a form, clicking an +action button, what the page shows afterwards) are **not** W3's: they need the +page's JavaScript and belong to the browser suite (W6). An HTTP test that +imitates htmx keeps passing when an htmx upgrade breaks every page, so W3: + +- sends plain requests (the harness client doesn't set `HX-Request`, and the + server never reads it); +- never asserts on `HX-*` response headers (the harness has no accessors for + them); +- asserts statuses, `Location` redirects, key HTML via goquery, and database + state via factory readers. For a refused mutation it also asserts that the + database is unchanged. + +Together with W6, W3 is the specification R1 must preserve. **Every route in +`cmd/web/main.go`, `actions.go` and `api.go` is covered by W3 or W6**: GET +routes and every guard on mutating routes here, and every mutating route's +successful use in W6. The coordinator checks the union against a route list +extracted from the source. + +| Task | Covers | Tier | |---|---|---| -| E1 | Public and visibility: `/`, `/articles/:id` (valid, unknown, bad name), `/users/:username` and `/rss/public/:username` for each profile visibility, `/users/:u/user_styles` (referer enforcement, `@scope` wrapping for non-Firefox UAs), `/posts/:id` (the D2a matrix, at HTTP level, one case per row), `/shared/:id`, `/explore` anon vs logged-in, `/user-media/robots.txt`, `/user-media/favicon.ico`, `/static/*`, CSP and nosniff headers present | **strong** | -| E2 | Auth: `/login` (logged-in redirect, `return_url` signature kept or dropped), `POST /form/login` (CSRF required, bad credentials, success redirect, signed return), `POST /controls/action/logout`, `/signup` and `POST /form/signup` with registration open and closed, `/confirm_signup/:id`, `/invite/:id` and `POST /form/accept_invite/:id` (full flow, reused invite → 404), `/confirm_waiting_list/:id`, `POST /form/signup_waiting_list` (always 404 today), `EnforceAuth` redirects on every `/controls` and `/write` route. #109 as skipped tests. | mid | -| E3 | Posts: `/write` (with a `?prompt=`), `POST /controls/form/edit_post` (new draft, autosave, publish, make_draft, delete, someone else's post → 404), `/posts/:id/edit` (not the author → 403), `/posts/:id/md`, `/posts/:id/zip` (#110 skipped), `POST /controls/form/new_comment` (reply, permissions, emails queued), `/controls/action/delete_draft` | mid | -| E4 | Connections: whitelist form, `remove_from_whitelist`, `create_connection`, `drop_connection`, `request_mediation`, `revoke_mediation_request`, `sign_mediation`, `dismiss_mediation`, `accept_connection`, `reject_connection`, and `/controls` rendering each state. A full three-user story: A and B connected, B and C connected, A requests C, B signs, C accepts, and A sees C's direct-only post. | mid | -| E5 | Settings and misc: `/controls/settings`, save_settings, save_user_styles, change_password (then log in with the new one), generate_api_key, send_invite (email queued), prompt_post and dismiss_prompt, create_share and delete_share (then `/shared/:id` works and stops working), add_user_feed (feed at a test `httptest.Server`), remove_rss_subscription, `dissmiss_rss_item` (sic), upload_media (a PNG fixture; see "Uploads" below), `settings/export` and `settings/import` round-trip | cheap | -| E6 | API v1 and private RSS: missing, malformed and unknown bearer (400/403), `GET /api/v1/posts` pagination, `POST /api/v1/posts` (new, edit), `DELETE /api/v1/posts/:id` (own post → 200, foreign post → 404; fixed in #118), `PUT /api/v1/image` (see "Uploads"), `/rss/private/:key` (valid, and unknown → #115 skipped) | cheap | +| E1 | Visibility and read access: `/`, `/articles/:id` (valid, unknown, bad name), `/users/:username` and `/rss/public/:username` for each profile visibility, `/users/:u/user_styles` (referer enforcement, `@scope` wrapping for non-Firefox UAs), `/posts/:id` (the D2a matrix at HTTP level, one case per row), `/posts/:id/md`, `/posts/:id/zip` (#110 skipped), `/posts/:id/edit` (not the author → 403), `/shared/:id` (valid, deleted share), `/explore` anonymous vs logged in, `/user-media/robots.txt`, `/user-media/favicon.ico`, `/static/*`, and CSP and `nosniff` headers present | **strong** | +| E2 | Guards on every mutating route, driven by a table built from the route list: anonymous → the `EnforceAuth` redirect to `/login` on every `/controls` and `/write` route; missing and wrong CSRF token → 403 on every `/form` and `/controls` POST (header and `header_csrf` field); object-level authorization: acting on someone else's post, draft, comment, share, API key, feed subscription or connection is refused, with the database unchanged. Also `/login` while logged in, `return_url` signature kept or dropped, and bad credentials. #109 as skipped tests. | **strong** | +| E3 | One-shot links and account GETs: `/confirm_signup/:id` (valid, unknown, already confirmed), `/invite/:id` (valid, used → 404), `/confirm_waiting_list/:id`, `/signup` with registration open and closed, `POST /form/signup_waiting_list` (always 404 today) | cheap | +| E4 | API v1, private RSS and uploads through the API: missing, malformed and unknown bearer (400/403), `GET /api/v1/posts` pagination, `POST /api/v1/posts` (new, edit), `DELETE /api/v1/posts/:id` (own post → 200, foreign post → 404; fixed in #118), `PUT /api/v1/image` (see "Uploads"), `/rss/private/:key` (valid; unknown → #115 skipped) | cheap | **Uploads are tested black-box, upload then serve,** so the tests survive R2 switching storage from local files to S3. After an upload, `GET` the returned @@ -23,8 +38,10 @@ as an image. Then `GET` it with a resize class: 200, and the image is no wider than that class. An unknown name returns 404. Before R2 the binary writes to local storage inside the harness's temp working directory. From R2 on it writes to tommy's S3, and R2 adds bucket-level assertions next to these -without editing them. +without editing them. The upload through the settings page's file input is +W6.B5's. -**Done when:** every route is covered, `make cover` shows the `cmd/web` -statement coverage coming from the binary, and the E2E suite runs in under two -minutes. +**Done when:** every GET route and every guard is covered (the union with W6 +covers every route), no test sets `HX-Request` or reads an `HX-*` header, +`make cover` shows the `cmd/web` statement coverage coming from the binary, +and the E2E suite runs in under two minutes. diff --git a/docs/plan/w6.md b/docs/plan/w6.md index cbe91dc..a304a9f 100644 --- a/docs/plan/w6.md +++ b/docs/plan/w6.md @@ -1,14 +1,20 @@ ## W6 — Browser tests -**Goal:** a browser suite that drives the real app with real assets, so later -frontend work (templates, Stimulus controllers, htmx behavior, SCSS) can be -changed and checked in one command. W3 checks what the server sends; W6 -checks what the user sees and clicks: JavaScript running, htmx swaps landing -in the right place, confirmations, toasts, dark mode, and no console or CSP -errors. +**Goal:** a browser suite that drives the real app with real assets and is +**the specification of everything a user does in a page**. Every flow that +needs the page's JavaScript is tested here, not over plain HTTP: forms, +action buttons, htmx swaps and redirects, Stimulus controllers, +confirmations, toasts, dark mode, and no console or CSP errors. Later frontend +work (templates, controllers, SCSS, and upgrades of htmx, Stimulus or any +other frontend dependency) is checked with one command, and an upgrade that +breaks a page fails a test. + +W3 pins the server rules that don't depend on the frontend (visibility, +guards, API, RSS). The successful use of every mutating route is W6's; the +coordinators check that W3 and W6 together cover every route. W6 depends only on W0 (the E2E harness and the factories). It runs in parallel -with W1–W4 and is part of the safety net for R1 and RS. It is a test wave: +with W1–W4 and is the main safety net for R1 and RS; run B0 first after W0. It is a test wave: the W0–W6 ground rules in `docs/testing.md` apply. **Templates, JS and SCSS don't change in W6.** Locators use roles, labels, text and the existing CSS classes. Adding `data-testid` attributes is a change for later frontend work, @@ -57,7 +63,7 @@ This task defines the contract for B1–B6. both paths in one line. Open the trace with `make ui-trace F=`. CI uploads the directory as an artifact. - **Make targets:** `make ui-deps` installs Chromium, once. `make test-ui` runs - the suite headless (`RUN=` narrows it). `HEADED=1 SLOWMO=250` watches + the suite headless (`RUN=` narrows it, `COUNT=` repeats it). `HEADED=1 SLOWMO=250` watches it run locally. The quiet targets gain a `TAGS=` pass-through, so that `make vet-q PKG=./e2e/browser/... TAGS=browser` compiles the suite. - **CI:** a `browser` job that runs `yarn install --frozen-lockfile`, @@ -70,6 +76,53 @@ This task defines the contract for B1–B6. - Add a "Browser tests" section to `docs/testing.md` with one worked example. Update the `frontend-htmx` and `test-failure` skills to point at it. +### Reliability rules (part of B0's contract) + +A browser suite is only useful if a red run means a real regression. + +- **Assert outcomes, not mechanisms.** Tests assert what the user sees + (text, roles, visibility, URL, title) and the resulting database state. + They don't wait for htmx events, read `hx-*` attributes, inspect request + headers or call `window.htmx`. That keeps them valid across htmx versions + and across replacing htmx altogether. +- **Wait by assertion only.** Playwright's auto-waiting locator assertions, + never sleeps and never `WaitForTimeout`. A "nothing happened" check asserts + on a state that the action would have changed (for example, the row count + after the swap), not on elapsed time. +- **Isolation.** A fresh database (`e2e.Start`) and browser context per test, + no shared fixtures, so tests may use `t.Parallel()` and run in any order. +- **No retries.** CI doesn't re-run failed browser tests. A flaky test is a + bug: it is fixed or skipped with an issue number, never retried into green. + B0's smoke test must pass `-count=10` in a row, and every scenario task's + tests `-count=3`, before the task is accepted. +- **Library mutation checks.** Besides a broken controller, B0 proves the + suite catches frontend-library breakage. With each of these changes to + `cmd/web/client/js/index.js` the suite must fail, and then it is reverted: + the `json-enc` extension removed (action buttons stop working), the + `head-support` import removed (the title no longer changes on boosted + navigation), and the `htmx:responseError` handler removed (no error + toast). A test that stays green through these doesn't cover htmx. +- **Dependency updates run the suite.** The `browser` CI job runs on every + PR, including Dependabot's npm updates, and is required for merging. + +### htmx and frontend inventory + +The coordinator greps the frontend and checks that each item below is +exercised by at least one test whose assertion is user-visible, the same way +it checks the Stimulus controllers: + +- every `hx-*` attribute in use (`hx-boost`, `hx-post`, `hx-swap` in each + mode used, `hx-target`, `hx-trigger`, `hx-disabled-elt`, `hx-encoding`, + `hx-headers`) and every extension (`head-support`, `json-enc`); +- the `htmx.config` choices in `index.js` (script tags not executed, CSP + nonces for inline scripts and styles), checked through the CSP guard; +- the error handlers (`htmx:responseError`, `htmx:sendError`): a server error + and a dropped connection (`page.Route` aborting the request) each show the + error toast; +- every `HX-*` response header the server sends (`HX-Redirect`, + `HX-Refresh`, `HX-Retarget`, `HX-Trigger` and so on), through its effect: + the new URL, the reloaded page, the error rendered in place, the toast. + ### Scenarios (parallel after B0) Each task owns one file, `e2e/browser/_test.go`. Tests are written @@ -78,14 +131,18 @@ itself. | Task | Covers | Tier | |---|---|---| -| B1 | Navigation and chrome: boosted navigation between every top-level page (title and head merge, back and forward), flashes and toasts appear and auto-dismiss, the collapse, toggle and spoiler controllers, dark mode under `prefers-color-scheme: dark` emulation (the `data-bs-theme` and a computed background from `_dark-mode.scss`), the mobile menu at a 390px viewport | mid | -| B2 | Writing: `/write` with the markdown editor controller (type, toolbar buttons, preview if present), autosave of a draft, publish, the rendered post (headings, code highlighting, gallery, lite-youtube embed element, lazy images), edit an existing post, delete a draft via the confirm dialog (`page.OnDialog`) | mid | -| B3 | Comments: comment on a post, reply inline via the comment-form controller, the thread updates without a full reload, nesting and collapse, and a user who may not comment doesn't get the form | mid | -| B4 | Actions via the generic action controller: the connection story (request, mediator signs, accept) clicked through the `/controls` buttons, including `skipReload` swaps and `hx-swap="delete"`; create a share and copy its link (grant clipboard permission and read it back); dismiss a prompt; dismiss an RSS item | mid | -| B5 | Settings and forms: validation errors rendered in place (htmx retarget), save general settings, change password and log in again, user styles applied on the profile page, generate an API key, send an invite, upload an image through the file input and see it render (`naturalWidth > 0`, served from `/user-media`) | mid | +| B1 | Navigation and chrome: boosted navigation between every top-level page (title and head merge, back and forward), flashes and toasts appear and auto-dismiss, the collapse, toggle and spoiler controllers, dark mode under `prefers-color-scheme: dark` emulation (the `data-bs-theme` and a computed background from `_dark-mode.scss`), the mobile menu at a 390px viewport, and the error toast on a server error and on a dropped connection (`page.Route`) | mid | +| B2 | Writing: `/write` (also with `?prompt=`) and the markdown editor controller (type, toolbar buttons, preview if present), autosave of a draft, publish, make a published post a draft again, delete through the editor, the rendered post (headings, code highlighting, gallery, lite-youtube embed element, lazy images), edit an existing post, delete a draft via the confirm dialog (`page.OnDialog`) | mid | +| B3 | Comments: comment on a post, reply inline via the comment-form controller, the thread updates without a full reload, nesting and collapse, the author and participant notifications queued (factory readers), and a user who may not comment doesn't get the form | mid | +| B4 | Actions via the generic action controller, clicked through the `/controls` buttons, including `skipReload` swaps and `hx-swap="delete"`: the three-user connection story (A and B connected, B and C connected, A requests C, B signs, C accepts, and A then sees C's direct-only post), and each other connection action once (whitelist and remove from whitelist, create and drop a connection, revoke and dismiss a mediation request, reject a connection); create a share and copy its link (grant clipboard permission and read it back), open it anonymously, delete it and see it gone; ask for a post (prompt) and dismiss a prompt | mid | +| B5 | Settings and forms: validation errors rendered in place (htmx retarget), save general settings, change password and log in again, user styles applied on the profile page, generate an API key, send an invite (email queued), add a feed (pointing at an `httptest.Server` the test owns), see its items, dismiss one and unsubscribe, export settings and import them back, upload an image through the file input and see it render (`naturalWidth > 0`, served from `/user-media`) | mid | | B6 | Layout sweep: every page the W4.S5 crawl visits, logged in and anonymous, at 1280px and 390px: no horizontal overflow (`scrollWidth <= innerWidth`), no guard violations, and every image loaded | cheap | +| B7 | Accounts: log in through the form (bad credentials show the error in place; a signed `return_url` lands on that page), log out, sign up while registration is open, accept an invitation (the new account is logged in and connected to the inviter), and follow a confirmation link from the queued email | mid | -**Done when:** the suite passes locally and in CI in under three minutes; -each Stimulus controller under `cmd/web/client/js/controllers/` is exercised by -at least one test (the coordinator checks this against the directory listing); -and one deliberately broken controller (the mutation check) fails a test. +**Done when:** the suite passes locally and in CI in under five minutes, and +passes `-count=3` in CI without a failure; each Stimulus controller under +`cmd/web/client/js/controllers/` and each item of the htmx inventory is +exercised by at least one test (the coordinator checks both against a grep); +every mutating route is used successfully by a test (with W3, every route is +covered); and one deliberately broken controller plus the three library +mutation checks each fail a test. diff --git a/docs/plan/wb.md b/docs/plan/wb.md index a85f22a..92fcc5b 100644 --- a/docs/plan/wb.md +++ b/docs/plan/wb.md @@ -24,7 +24,7 @@ as well. Each fix removes the matching `t.Skip`, and that test is the proof. **Future, not part of WB:** #123 (re-enable signups with bot protection; signups are off on purpose) and #124 (feed and explore pagination). Both come -after R1, because they touch routes R1 moves. #124 needs W3's feed E2E tests +after R1, because they touch routes R1 moves. #124 needs W6.B5's feed browser tests in place first. ### Known bugs (for de-duplication)