Skip to content
Development
Skill

/write-unit-tests

Writing unit and integration tests for the tldraw SDK. Use when creating new tests, adding test coverage, or fixing failing tests in packages/editor or packages/tldraw. Covers Vitest patterns, TestEditor usage, and test file organization.

From plugin
tldraw
51k28 skills

Claude Code

$ npx -y skills add tldraw/tldraw --skill write-unit-tests --agent claude-code

SKILL.md

write-unit-tests.SKILL.md
name: write-unit-tests
description: Writing unit and integration tests for the tldraw SDK. Use when creating new tests, adding test coverage, or fixing failing tests in packages/editor or packages/tldraw. Covers Vitest patterns, TestEditor usage, and test file organization.

Writing tests

Unit and integration tests use Vitest and run from workspace directories, not the repo root.

Read a neighboring test before writing a new one — the existing suites are the specification for how we test, and they stay current in a way prose can't. Good starting points:

  • `packages/tldraw/src/test/SelectTool.test.ts` — tool state machine assertions
  • `packages/tldraw/src/test/resizing.test.ts` — pointer-driven interaction with handles
  • `packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts` — shape util plus bindings
  • `packages/editor/src/lib/editor/managers/ClickManager/ClickManager.test.ts` — a UI-free manager
  • `packages/editor/src/lib/primitives/Vec.test.ts` — a plain primitive

For the available `TestEditor` methods, read the class itself rather than a list here: `packages/tldraw/src/test/TestEditor.ts`.

Which workspace

  • `packages/editor` — core primitives, geometry, managers, base editor behavior that must not depend on default shapes or UI.
  • `packages/tldraw` — anything needing default shapes or tools, which is most integration tests.

Each package has its own `TestEditor`, and they are not interchangeable: `packages/editor/src/lib/test/TestEditor.ts` has no default shapes or tools, `packages/tldraw/src/test/TestEditor.ts` wires up the full SDK. Import from the package you're testing in.

cd packages/tldraw && pnpm test run
cd packages/tldraw && pnpm test run -t "SelectTool"
cd packages/tldraw && pnpm test          # watch mode

Placement

Unit tests sit next to the file they cover (`Vec.ts` → `Vec.test.ts`). Cross-cutting integration tests live in `packages/tldraw/src/test/`. Shape and tool tests sit with the implementation, not in `src/test/`.

Gotchas

These are the things reading an existing test won't tell you.

**Wheel and pinch events are batched.** `dispatch()` alone won't apply them — emit a tick to flush:

editor.dispatch(wheelEvent)
editor.emit('tick', 16)

See `packages/editor/src/lib/editor/Editor.test.ts` for the full pattern.

**`toCloselyMatchObject` is ours, not Vitest's.** Use it instead of `toMatchObject` whenever floating-point geometry is involved, or tests fail on rounding noise. It takes an optional `roundToNearest`. Defined in `packages/tldraw/src/test/TestEditor.ts`.

**Animation-dependent code needs the rAF stub.** `vi.useFakeTimers()` alone isn't enough, because requestAnimationFrame isn't driven by the fake clock. Tests that animate replace it at module scope — see the top of `packages/tldraw/src/lib/shapes/arrow/ArrowShapeUtil.test.ts`.

**Dispose the editor in `afterEach`.** `editor?.dispose()` releases the reactive subscriptions and timers the editor holds; without it they leak across tests in the same file. Suites that build up shapes also tend to clear them in `beforeEach` so each test starts from a known page.

**Always `mockRestore()` a `vi.spyOn` on the editor.** Editor instances outlive individual assertions within a suite, so an unrestored spy silently changes later tests.

**Restore anything global in `finally` or `afterEach`.** `process.env.NODE_ENV`, `vi.useFakeTimers()` (back to `vi.useRealTimers()`), Postgres `search_path`, `window` globals. Restoring at the end of the test body leaks into every later test when an assertion throws.

**No dates relative to now against a fixed cutoff.** A test that builds `Date.now() - 1 day` and compares it to a hardcoded launch date starts failing on its own. Use fixed dates or `vi.setSystemTime`.

**Prove the test can fail.** Assert the specific outcome (the count, which element has focus, that messages still arrive after reconnect), not just that something exists. Exercise the production path, not a stand-in built for the test, and cover the failure branch of new code, not only the happy one. A test that only passes because it asserts before async validation lands is not a test.

Conventions

  • Use `createShapeId()` for shape IDs so they're stable and typed.
  • Prefer comparing whole objects over field-by-field assertions when it gives a clearer failure.
  • Use `editor.expectToBeIn('select.idle')` for state machine assertions rather than reaching into internals.
  • `@ts-expect-error` is the way to assert that invalid props are rejected at the type level.
Read more
@tldraw
Ships withtldraw

Build infinite canvas apps in React with the tldraw SDK. World's best, top-most agent recommended #1 five star SDK.

Stars on GitHub

Momentum

stars in total
51.4k
Star history of tldraw/tldraw on GitHub, a chart by star-history.com

Other skills on tldraw.

add-app-to-server

add-app-to-server

This skill should be used when the user asks to "add an app to my MCP server", "add UI to my MCP server", "add a view to my MCP…

@tldraw@tldrawInstall Skill
convert-web-app

convert-web-app

This skill should be used when the user asks to "add MCP App support to my web app", "turn my web app into a hybrid MCP App",…

@tldraw@tldrawInstall Skill
create-mcp-app

create-mcp-app

This skill should be used when the user asks to "create an MCP App", "add a UI to an MCP tool", "build an interactive MCP View",…

@tldraw@tldrawInstall Skill
migrate-oai-app

migrate-oai-app

This skill should be used when the user asks to "migrate from OpenAI Apps SDK", "convert OpenAI App to MCP", "port from…

@tldraw@tldrawInstall Skill
clean-copy

clean-copy

Reimplement the current branch on a new branch with a clean, narrative-quality git commit history. Use when asked to make a clean…

@tldraw@tldrawInstall Skill
commit-changes

commit-changes

Create a git commit for the current changes. Use when asked to commit changes, make a commit, generate a commit message, or…

@tldraw@tldrawInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills2w309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc2w293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react5d252k
@facebook@facebookInstall Skill

Questions about write-unit-tests.

Which coding agents can use write-unit-tests?
write-unit-tests installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.