- Tests are located in
**/__tests__/*. - Testing guidelines are described in
docs/testing.md. Be sure to read this file in full before writing tests. - Run linter with
yarn lint. - Run unit tests with
yarn test. In a fresh worktree or after dependency changes, runyarn build:stylesandyarn build:packagesfirst — mass import-resolution failures mean stale generated output, not broken code. The full suite takes minutes: run it once to survey, then iterate file-scoped withnpx vitest run --project unit <path>, and save a second full run for the end. - Run Puppeteer tests with
yarn test:puppeteer. - Do not merge the base branch into a pull request to get CI to re-run against a newer base. Test, Puppeteer, Lint, and TDD run on
refs/pull/<n>/merge, so every run already tests the head merged withmainas of that run — andmainmoving triggers no run, so the merge would only buy a fresh result at the cost of a full extra suite. Merge it in when the pull request conflicts — GitHub builds no merge ref for a conflicting pull request, so those four report no run at all until it is resolved — or when the base has just recovered from a breakage the pull request's last run was measured against. See merging the base branch. - Ensure linter, unit tests, and puppeteer tests all pass before requesting a review.
- Cover each behavior at exactly one level. If a unit, store, or JSDOM test already proves the behavior, do not add a redundant Puppeteer or iOS test for it; higher-level tests are slow and are justified only by a distinct risk the lower-level test cannot cover, such as real browser or device behavior, input mapping, or a visual regression. See test levels.
- Compose tests from the existing helpers before writing raw Redux dispatches, browser APIs, or DOM queries:
src/test-helpers/for unit, store, and JSDOM tests (e.g.importToContext,setCursorFirstMatch,addMulticursorAtFirstMatch),src/e2e/puppeteer/helpers/for Puppeteer tests, andsrc/e2e/iOS/helpers/for WebdriverIO tests. Search the helper directory for the user intent before hand-rolling it. Add a new helper only when none expresses that intent, name it after the intent, and put it in the helpers directory. See compose helpers and test helpers. - Preserve production behavior: do not add CI- or test-only application branches that change product semantics. Control external dependencies from test code through dependency injection or a named, arrange-only helper. Any unavoidable environment adaptation must be narrow, explicit, irrelevant to the assertion, and documented by the production-parity and sanctioned-backdoor policies. Do not mock the subject under test or mutate the DOM inline to create alternate behavior.
- Fail hard when a required target or precondition is missing. For an asynchronous transition, use an existing named waiter or add one that waits for the specific condition, and let that condition be one the user can see (
waitForCursor,waitForEditable,waitForSelector,waitForAlert) rather than a proxy:exportThoughtsand theexportContextbackdoor it wraps are the assertion, called once against an exact expected outline, never the body of apage.waitForFunction,waitUntil, orvi.waitFor. Do not use conditional logic, optional fallbacks, ortry/catchto suppress an unexpected state and let the test continue or pass. See helper contracts and false-positive prevention. - Wait in the page, not from node.
page.waitForFunctionruns the predicate in the browser on every animation frame;expect.pollandvi.waitForre-read the page from node on an interval and are banned by an eslint rule. In store and JSDOM tests, whereinitStoreandcreateTestAppenable fake timers, flush withawait vi.runAllTimersAsync()(wrapped inactwhen it causes React updates) and then assert, rather than polling. When the wait is the assertion, catch its timeout, read the value once, and throw a message naming what was actually rendered — and always throw, never swallow. See waiting. - Select elements by meaning: prefer accessible role and name, a visible label or domain value, then
aria-label, then a purpose-builtdata-testid. Do not couple tests to styling classes, DOM ancestry, index, or render order. Add an accessible attribute or test id only when no stronger semantic locator exists. See selector guidance. - Inline the user's steps. A repeated sequence such as
importText+setCursor+toggleNoteis concrete, readable, and imperative; the repetition is inherent in the test case, not duplication to hide behind a local setup wrapper. A helper performs one user action or queries one condition — it never bundles several steps. See concrete tests and helper contracts.