Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
64 lines
3.6 KiB
Markdown
64 lines
3.6 KiB
Markdown
---
|
|
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.
|
|
|
|
```bash
|
|
cd packages/tldraw && yarn test run
|
|
cd packages/tldraw && yarn test run --grep "SelectTool"
|
|
cd packages/tldraw && yarn 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:
|
|
|
|
```typescript
|
|
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.
|
|
|
|
## 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.
|