Created
August 14, 2025 18:52
-
-
Save joshkel/5c62dade4da0aa1f8abe9bb1185f5fd1 to your computer and use it in GitHub Desktop.
Jest mock canvas
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Creates a mock HTMLCanvasElement and CanvasRenderingContext2D that records | |
| * all calls made to it. | |
| * | |
| * jest-canvas-mock offers a potential alternative, but it's only designed to | |
| * work within jsdom: https://github.com/hustcc/jest-canvas-mock/issues/124 | |
| * | |
| * This is not a complete implementation; it mocks enough properties to work | |
| * with Chart.js. | |
| */ | |
| export function mockCanvas(): HTMLCanvasElement & { _calls: unknown[][] } { | |
| const _calls: unknown[][] = []; | |
| const canvas: Partial<HTMLCanvasElement> & { _calls: unknown[][] } = { | |
| _calls, | |
| width: 800, | |
| height: 600, | |
| getContext(contextId) { | |
| if (contextId !== '2d') { | |
| throw new Error('Unsupported'); | |
| } | |
| return new Proxy(ctx, { | |
| get(target, prop, _receiver) { | |
| if (!(prop in target)) { | |
| throw new Error(`Unable to get ctx.${String(prop)}`); | |
| } | |
| return (target as any)[prop]; | |
| }, | |
| set(target, prop, value, _receiver) { | |
| if (!(prop in target)) { | |
| throw new Error(`Unable to set ctx.${String(prop)}`); | |
| } | |
| (target as any)[prop] = value; | |
| return true; | |
| }, | |
| }) as any; | |
| }, | |
| // Used by Chart.js implementation | |
| ['length' as any]: undefined, | |
| ['canvas' as any]: undefined, | |
| }; | |
| const textMetrics: TextMetrics = { | |
| width: 10, | |
| actualBoundingBoxAscent: 0, | |
| actualBoundingBoxDescent: 0, | |
| fontBoundingBoxAscent: 0, | |
| fontBoundingBoxDescent: 0, | |
| emHeightAscent: 0, | |
| emHeightDescent: 0, | |
| hangingBaseline: 0, | |
| alphabeticBaseline: 0, | |
| ideographicBaseline: 0, | |
| actualBoundingBoxLeft: 0, | |
| actualBoundingBoxRight: 0, | |
| }; | |
| const ctx: any = { | |
| canvas: canvas as HTMLCanvasElement, | |
| }; | |
| for (const simpleMethod of [ | |
| 'arc', | |
| 'beginPath', | |
| 'clearRect', | |
| 'clip', | |
| 'closePath', | |
| 'fill', | |
| 'fillRect', | |
| 'fillText', | |
| 'lineTo', | |
| 'moveTo', | |
| 'rect', | |
| 'resetTransform', | |
| 'restore', | |
| 'rotate', | |
| 'save', | |
| 'setLineDash', | |
| 'setTransform', | |
| 'stroke', | |
| 'translate', | |
| ]) { | |
| ctx[simpleMethod] = function (...args: unknown[]) { | |
| _calls.push([simpleMethod, ...args]); | |
| }; | |
| } | |
| for (const [method, result] of [['measureText', textMetrics]] as [string, unknown][]) { | |
| ctx[method] = function (...args: unknown[]) { | |
| _calls.push([method, ...args]); | |
| return result; | |
| }; | |
| } | |
| for (const setter of [ | |
| 'fillStyle', | |
| 'font', | |
| 'lineCap', | |
| 'lineDashOffset', | |
| 'lineJoin', | |
| 'lineWidth', | |
| 'strokeStyle', | |
| 'textAlign', | |
| 'textBaseline', | |
| ]) { | |
| Object.defineProperty(ctx, setter, { | |
| get() { | |
| return this[`_${setter}`]; | |
| }, | |
| set(value) { | |
| _calls.push([setter, value]); | |
| this[`_${setter}`] = value; | |
| }, | |
| }); | |
| } | |
| return new Proxy(canvas, { | |
| get(target, prop, _receiver) { | |
| if (!(prop in target)) { | |
| throw new Error(`Unable to get canvas.${String(prop)}`); | |
| } | |
| return (target as any)[prop]; | |
| }, | |
| set(_target, prop, value, _receiver) { | |
| throw new Error(`Unable to set canvas.${String(prop)} to ${value}`); | |
| }, | |
| }) as HTMLCanvasElement & { _calls: unknown[][] }; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment