CodeVetter · Repo Unpack · Testing signals
excalidraw/excalidraw
Tests are Vitest 3 on jsdom, with Testing Library, vitest-canvas-mock, and fake-indexeddb. Coverage thresholds are enforced (lines 60, branches 70, functions 63, statements 60). The editor and element packages are heavily tested, including snapshots; the collaboration suite is a single file that mocks Firebase and socket.io and its own comment says multi-client cases are missing. CI runs unit tests on master pushes, and lint, types, prettier, and coverage on pull requests.
- vitest.config.mts sets environment jsdom, globals, setupFiles ./setupTests.ts, sequence.hooks parallel, hideSkippedTests, and coverage reporters text, json-summary, json, html, lcovonly with ignoreEmptyLines false. Thresholds: lines 60, branches 70, functions 63, statements 60. Root scripts: yarn test and yarn test:app invoke vitest; yarn test:update passes --update; yarn test:coverage and yarn test:ui exist.
- setupTests.ts loads vitest-canvas-mock, @testing-library/jest-dom, the editor polyfill, fake-indexeddb/auto, a localStorage polyfill, and stubs setPointerCapture, matchMedia, and FontFace. It replaces @excalidraw/common throttleRAF with mockThrottleRAF. DOM dumps on Testing Library failures are suppressed unless VITE_DEBUG_DOM is true or 1.
- Editor interaction tests live in packages/excalidraw/tests (App, history, export, clipboard, drag create, selection, laser, lasso, library, mermaid, search, wheel, regression, plus data/restore.test.ts and data/reconcile.test.ts). Element geometry tests live in packages/element/tests (binding, elbow arrows, frames, fractional index, text wrapping, z-index). Math tests are in packages/math/tests. Common tests cover colors, binary heap, keys, queue, and url.
- App-level tests are only excalidraw-app/tests/collab.test.tsx, LanguageList.test.tsx, and MobileMenu.test.tsx (with a snapshot). collab.test renders the whole ExcalidrawApp and listens to h.store.onStoreIncrementEmitter. It mocks socket.io-client as a no-op emitter and stubs firebase load/save. The file comment says the suite should be extended to two simultaneous clients and that this is not done.
- CI does not run the full yarn test:all. Push-to-master runs only yarn test:app. Pull requests run prettier (test:other), eslint --max-warnings=0 (test:code), and tsc (test:typecheck) in lint.yml, and separately yarn test:coverage with davelosert/vitest-coverage-report-action. Node is pinned to 20.x in those workflows while package.json engines say >=18 and the Docker build uses node 24.
- Many editor tests snapshot rendered output under packages/excalidraw/tests/__snapshots__ (App, history, export, selection, drag, mermaid, context menu) and packages/excalidraw/components/__snapshots__/App.test.tsx.snap. yarn test:update is the documented way to refresh them (CLAUDE.md).
- Dev and test builds expose the live editor on window.h (state, setState, app, history, store, fonts). Tests reach it as const { h } = window after render. That hook is not installed in production because componentDidMount guards it with isTestEnv() || isDevEnv().
- There are no automated tests for firebase-project rules, the Docker/nginx image, the real Socket.IO server, VITE_APP_AI_BACKEND, Sentry release upload, or the self-unregistering public/service-worker.js. Firebase and socket modules are mocked out of the only collab test.
- tsconfig.json typechecks packages and excalidraw-app only (strict, noEmit, jsx react-jsx). examples and the top-level tests folder are excluded, so example apps are not part of yarn test:typecheck.
- Fractional-index invariants are part of the test contract: reconcile.ts calls validateFractionalIndices with shouldThrow when isTestEnv() or isDevEnv(), including bound-text checks and a reconciliationContext of local versus remote elements. A bad index in a test fails the test instead of being repaired quietly.