Skip to content

Latest commit

 

History

History
41 lines (33 loc) · 3.66 KB

testing.md

File metadata and controls

41 lines (33 loc) · 3.66 KB

Testing

Rules for writing tests, and for asking for them in review. AGENTS.md carries the same writing rules under Verifying; change both together.

What to test

  • Test meaningful logic or observable behavior. Do not render components to static markup to assert props or attributes, or add tests that merely assert callback wiring or mirror the implementation.
  • In review, ask for a missing test only when it would catch a real regression. Do not ask for one assertion per constant, label, or switch case.
  • Backend behavior changes ship with focused tests for that behavior.
  • Proof for a bug fix depends on the bug. Logic, data, state, and protocol bugs get a failing test first (the fewest that fail on main for that reason, in the module's existing test file), then the fix. Purely visual bugs (spacing, colour, alignment, a layout shift) get before/after captures as their proof and no test: do not add UI tests for nitpicks, and do not ask for one in review when a capture already shows it. A server-only fix with no visible surface ships the test plus a short runtime transcript.

Waiting for work

  • The server is event-sourced, and side effects run after the command commits. In tests, drain the effect worker (OrchestrationEffectWorkerV2.drain) or await the specific persisted event or Deferred that marks the milestone. Never wait on sleeps or polling. A test that needs a timeout to pass is wrong. Concretely:
    • it.effect runs on a TestClock; Effect.sleep only advances through TestClock.adjust. it.live is wall-clock and only for subprocess or network fixtures.
    • No yieldNow or sleep polling loops. Await the Deferred, take from the Queue, or drain the worker.
    • No per-test timeout argument or retry. The project timeout is the ceiling, and flaky tests are fixed, not retried.
    • No performance.now() or Date.now() deltas. Assert on events and counts, not elapsed time.
    • Browser tests assert through expect.element or vi.waitFor, never requestAnimationFrame or transitionend.
    • vi.mock factories must not spread a real module that pulls in Shiki or the diff workers.
    • it.skip and friends need a tracking issue on the line above: // quarantine: #1234 <why>.
    • Lint enforces this: t3code/no-test-timeout-or-unreasoned-skip, t3code/no-frame-waits-in-browser-tests, t3code/no-wall-clock-in-tests, t3code/no-real-time-wait-in-tests, t3code/no-polling-loop-in-tests.

Web browser tests

  • Web browser tests are *.browser.test.tsx or *.browser.test.ts and render with await using screen = await render(...) from apps/web/test/render.tsx. t3code/require-disposable-render errors on the old patterns.

    • Acquire what the test drives after mounting (fake timers it advances, spies it sets up after mounting) after the render, so it disposes before the unmount. Anything the first render reads comes before it.
    • A helper that renders and then waits or clicks puts the screen in an AsyncDisposableStack and move()s it out only on success, so a failed wait still unmounts.
    • rerender needs a fresh element on every call.
    • No lifecycle hooks and no module-level mutable state. Shared mock state that vi.mock factories read is reset by a setup() call at the top of the test.
    • router is on by default. Pass router: false only with a one-line comment saying why.
    it("saves on Enter", async () => {
      const onSave = vi.fn();
      using _now = vi.spyOn(Date, "now").mockReturnValue(0);
      await using screen = await render(<TitleEditor onSave={onSave} />, { router: "/threads/1" });
      await userEvent.type(screen.getByRole("textbox"), "Title{Enter}");
      expect(onSave).toHaveBeenCalledWith("Title");
    });