| name | test-style |
| description | Conventions for writing tests in silkdown so they double as documentation and produce explicit, easy-to-pinpoint failure messages. Auto-trigger when creating or editing any file matching packages/*/test/**/*.test.{ts,tsx} or e2e/**/*.spec.ts, when adding new vitest or Playwright tests, when reviewing existing tests for clarity, or when triaging a failing assertion that "doesn't tell you which thing broke". Also fires on phrases like "name this test", "test description", "split this test", "expect message", "assertion message", "expect.soft", "tests as documentation", "explicit failure", "vitest assert", "playwright test name", "what's a good test name". |
Test style — silkdown
Tests in this repo are read at least as often as they are run. They serve as
the executable specification for the public API: a contributor (or AI
agent) should be able to grep for the behaviour they're about to change, read
the matching test names, and understand both what the system promises and
why. When a test fails in CI, the message must point at the specific
behaviour that broke — not at "expected true to be false".
Layered on top of tdd (which covers the loop) and react-best-practices
(component testing rules). When in doubt, prefer integration / "social unit"
tests that mount the real EditorView over isolated mocks.
Principles
- Test names describe behaviour, in full sentences —
<does X> when <Y>.
Read-aloud check: a contributor with no context can predict the assertions
from the title alone. → references/naming.md
- One behaviour per test — split compound names (
"wraps and unwraps")
into two tests. The cost is 5 lines; the gain is that CI tells you which
half broke. → references/naming.md
describe blocks name the behaviour area, not the file —
describe("inline reveal — bold (StrongEmphasis)"), not
describe("tests"). The describe name is half the test sentence.
→ references/naming.md
- Every non-self-explanatory assertion gets a second-arg message —
expect(value, "what should be true").toBe(...). Required when multiple
assertions in one test could fail ambiguously, when the literal compared
value (null, true, a number) doesn't communicate intent, or when the
diff would be a long DOM dump. → references/assertions.md
assert(value, "msg") over if (!value) return; — the if-return
pattern silently passes when fixtures are wrong. Use vitest's assert to
fail loudly with a fixture-pointing message and narrow the type for the
rest of the test. → references/assertions.md
- Don't drill through optional chains in the assertion target —
expect(link?.getAttribute("...")) hides the root cause. Assert link
first, then drill in directly. → references/assertions.md
- Name magic offsets — pull them from the doc string itself or hoist to
a named constant. A future reader sees
cursorOnLine2 and knows the
scenario. → references/structure.md
- Always destroy mounted views and restore stubbed globals —
afterEach
calls view.destroy(), restores window.open, runs cleanup() for
testing-library. Leaks corrupt the next test.
→ references/structure.md
- e2e: assertion at every meaningful intermediate state — multi-press
sequences must report which press broke things, not just the final state.
→ references/structure.md
- Don't mock
@codemirror/* or @silkdown/core collaborators — repo
tests are integration-style. Mocks are reserved for browser APIs
(window.open) and external IO. → references/structure.md
Forbidden patterns (review-stoppers)
it("should ..."), it("works"), it("renders correctly"), it("test ...").
- Compound names joined with
and/,: "wraps and unwraps".
if (!fixture) return; — replaced with assert.
expect(maybeNull?.something).toBe(...) — assert maybeNull first.
expect(true).toBe(true) placeholder assertions.
- Mocking
@codemirror/* or @silkdown/core collaborators.
Pre-commit checklist
Examples
Four shapes cover ~95% of tests in this repo. See
references/examples.md:
- Example A — pure-logic util test (URL policy / parser).
- Example B — integration test with a real
EditorView (decoration / widget).
- Example C — React component test (view-stability invariant).
- Example D — Playwright e2e with intermediate-state assertions on a
keyboard sequence.