vitest-canvas-mock
A drop-in HTML canvas mock for Vitest that emulates spec-accurate CanvasRenderingContext2D behavior in jsdom test environments.
Repository Health
Technical Analysis
vitest-canvas-mock lets you run unit tests that touch the HTML canvas API without a real browser or a native canvas binding. jsdom does not implement getContext('2d'), so any test that renders to a <canvas> element normally crashes or has to skip canvas assertions entirely. This package patches window with full implementations of CanvasRenderingContext2D, Path2D, CanvasGradient, CanvasPattern, DOMMatrix, ImageData, TextMetrics, and ImageBitmap, wiring each drawing method through vi.fn() so calls are spy-able and assertable.
It is a fork of the popular jest-canvas-mock project, retargeted at Vitest’s vi mocking APIs instead of Jest’s jest global, so teams migrating from Jest to Vitest can keep the same canvas-testing behavior. Rather than returning stub values for everything, the mock validates arguments the way a real browser would: calling arc() with too few arguments throws a TypeError, a negative radius throws a DOMException, and numeric arguments go through the same Number() coercion the spec requires. This makes it possible to write tests that assert on canvas API misuse, not just on successful draw calls.
The library also exposes introspection helpers on the mocked context — __getEvents(), __getPath(), __getDrawCalls(), and __getClippingRegion() — so tests can snapshot exactly what a component drew to the canvas without needing pixel-level rendering. A setupVitestCanvasMock() export lets the mock be re-applied after vi.resetAllMocks() clears the spies, which matters for test suites that reset mocks between test cases.
What You Get
- A full mock of
CanvasRenderingContext2Dcovering path construction (arc,bezierCurveTo,rect,roundRect), drawing (fillRect,strokeText,drawImage), and state (fillStyle,globalAlpha,lineWidth) withvi.fn()-wrapped spies on every method - Companion mock classes for
Path2D,CanvasGradient,CanvasPattern,DOMMatrix,ImageData,TextMetrics, andImageBitmapso code that constructs these types in a canvas workflow keeps working under test - Browser-accurate argument validation — wrong arity throws
TypeError, invalid enum values (like an unrecognizedfillRule) throwTypeError, and negative radii throwDOMException, matching real canvas error behavior - Snapshot-friendly introspection via
__getEvents(),__getPath(),__getDrawCalls(), and__getClippingRegion()for asserting on what a component actually drew - A
setupVitestCanvasMock()export to re-install the mock aftervi.resetAllMocks()clears the spy state between tests
Common Use Cases
- Unit-testing chart or data-visualization components (canvas-based charting libraries) in a jsdom environment without spinning up a real browser
- Testing canvas-drawing utility functions and verifying the exact sequence of draw calls via
__getDrawCalls()snapshots - Migrating a test suite from
jest-canvas-mockto Vitest while keeping the same canvas-mocking behavior and assertions - Validating that application code handles canvas API errors (invalid arguments, negative radii) the same way a real browser would
- CI pipelines that run component tests headlessly and need canvas APIs to exist on
windowwithout a nativecanvasnpm package build step
Under The Hood
Architecture
The package has a small, single-purpose architecture: src/index.js is the entry point that, on import, calls mockWindow(globalThis.window) from src/window.js to patch the global window object, and also exports setupVitestCanvasMock() for manual re-invocation. src/window.js conditionally assigns each canvas-related global (Path2D, CanvasGradient, CanvasPattern, CanvasRenderingContext2D, DOMMatrix, ImageData, TextMetrics, ImageBitmap, createImageBitmap) only if it is not already defined, then delegates prototype patching to src/mock/prototype.js. Each mocked type lives in its own file under src/classes/, keeping CanvasRenderingContext2D (by far the largest file, implementing dozens of canvas methods) isolated from the smaller supporting classes like Path2D and TextMetrics. This one-way patch-on-import flow with no external state means the whole library is a side-effecting shim layer rather than a runtime with its own control flow.
Tech Stack
Written in plain JavaScript (not TypeScript) with hand-written type declarations shipped separately under types/. It depends on cssfontparser for CSS font string parsing (used by the font setter) and moo-color for CSS color parsing (used by fillStyle/strokeStyle/shadowColor setters), and declares vitest as a peer dependency spanning versions 3 through 5. The project builds with vite-plus (vp pack), a unified toolchain wrapping Vite, Rolldown, and Vitest, publishing both ESM (dist/index.js) and CJS (dist/index.cjs) builds from a single src/index.js entry with platform: neutral.
Code Quality
The test suite is extensive: __tests__/classes/ contains one dedicated test file per CanvasRenderingContext2D method (arity checks, type coercion, thrown-error assertions), plus dedicated files for the other mocked classes, run via two separate test tasks — one against the bundled Vitest runner and one (test:compat) against the vitest version pinned in devDependencies — specifically to prove compatibility across the supported peer range. Linting runs with type-aware checking enabled (lint.options.typeAware/typeCheck) even though the source is JavaScript, and CI enforces both vp lint and vp test run. Error handling throughout mirrors the DOM spec closely, throwing typed TypeError/DOMException errors with browser-matching messages rather than silently no-op-ing on invalid input.
What Makes It Unique
Unlike mocks that simply stub every canvas method to return undefined, this library reimplements enough of the canvas 2D context’s actual semantics — transform stacks, path tracking, clip regions, save/restore state stacks — to let tests assert on real drawing behavior and catch spec-violating calls the same way a browser would. Being a Vitest-native fork of jest-canvas-mock also means teams get a maintained migration path for canvas-testing behavior that would otherwise need to be hand-ported when moving test runners.
Used by 7 apps in this directory
Dify
No Code Platforms · AI Development · Developer Tools
Visual LLM workflow platform with RAG pipelines, agent capabilities, and model management for building production AI applications.
Element Web
Team Chat · Collaboration
A polished, self-hostable Matrix client for secure, decentralized messaging and collaboration that puts your organization in full control of its data.
highlight.io
Developer Tools · Analytics · Monitoring
Open-source full-stack monitoring that unifies session replay, error tracking, logging, and distributed tracing so you can stop context-switching between tools.
LobeHub
AI Assistants · Productivity · Automation
Your Chief Agent Operator — build, schedule, and collaborate with an entire AI team in one self-hostable workspace.
OpenObserve
Monitoring · Analytics · Devops
Open source observability platform for logs, metrics, traces, and real user monitoring — delivering 140x lower storage costs than Elasticsearch with a single binary you can run in under 2 minutes.
Arize Phoenix
Devops · Analytics · Monitoring
Open-source AI observability platform for tracing, evaluating, and debugging LLM applications with built-in intelligence and MCP support.
Traefik
Devops · Automation · Security
A cloud-native reverse proxy and load balancer that auto-configures itself from Docker, Kubernetes, and other orchestrators — zero manual routing required.