Jsdom: Mock canvas

Created on 22 Mar 2017  ·  25Comments  ·  Source: jsdom/jsdom

Is it possible to mock canvas without having the actual implementation (canvas/canvas-prebuilt)?

I'd like to prevent this error from happening, as the canvas functionality is not really important for me:

Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)

Most helpful comment

Here's a simple way to mock canvas I came up with:

//
// Mock Canvas / Context2D calls
//
function mockCanvas (window) {
    window.HTMLCanvasElement.prototype.getContext = function () {
        return {
            fillRect: function() {},
            clearRect: function(){},
            getImageData: function(x, y, w, h) {
                return  {
                    data: new Array(w*h*4)
                };
            },
            putImageData: function() {},
            createImageData: function(){ return []},
            setTransform: function(){},
            drawImage: function(){},
            save: function(){},
            fillText: function(){},
            restore: function(){},
            beginPath: function(){},
            moveTo: function(){},
            lineTo: function(){},
            closePath: function(){},
            stroke: function(){},
            translate: function(){},
            scale: function(){},
            rotate: function(){},
            arc: function(){},
            fill: function(){},
            measureText: function(){
                return { width: 0 };
            },
            transform: function(){},
            rect: function(){},
            clip: function(){},
        };
    }

    window.HTMLCanvasElement.prototype.toDataURL = function () {
        return "";
    }
}
const document = jsdom.jsdom(undefined, {
  virtualConsole: jsdom.createVirtualConsole().sendTo(console)
});

const window = document.defaultView;
mockCanvas(window);

All 25 comments

In case anyone needs this, here's how I solved it:

const utils = require("jsdom/lib/jsdom/utils");
const canvasMock = require("canvas-mock");

function Canvas () {
    canvasMock(this);
    this.toDataURL = function() { return ""; }
}
utils.Canvas = Canvas;

Please don't do that. It will break in a future patch release. Instead, override the getContext() method, i.e. window.HTMLCanvasElement.prototype.getContext = ...

Thanks @domenic !

@domenic I can't get that to work. My overridden method does not get called. I use jest and setup script file as follows:

import { jsdom } from 'jsdom';
import mockLocalStorage from './mockLocalStorage';
import jQuery from 'jquery';
import Backbone from 'backbone';
import moment from 'moment';

const dom = jsdom('<!doctype html><html><body></body></html>');
const { window } = dom.defaultView;

function copyProps(src, target) {
    const props = Object.getOwnPropertyNames(src)
        .filter(prop => typeof target[prop] === 'undefined')
        .map(prop => Object.getOwnPropertyDescriptor(src, prop));
    Object.defineProperties(target, props);
}

//Mock canvas (used by qtip)
window.HTMLCanvasElement.prototype.getContext = () => {
    return {};
};

global.window = window;
global.document = window.document;
global.navigator = {
    userAgent: 'node.js',
};
global.localStorage = mockLocalStorage;
global.jQuery = jQuery;
global.$ = jQuery;
global.fetch = () => Promise.resolve();
Backbone.$ = jQuery;
copyProps(window, global);

//Mock Mousetrap (only works in browser)
jest.mock('mousetrap', () => { return { bind: () => {}}});

//Set moment locale for all tests
moment.locale('sv');

Sorry for polluting global in node but this is a very old and big code base which I try to migrate to Jest.
Jest version: 21.2.1
Jsdom version: 9.12.0

Would be very nice if mocking canvas (without using canvas package since not supported in Windows) was covered by official docs and not only as a comment in an issue.

without using canvas package since not supported in Windows

Why isn't canvas-prebuilt an option for you? That's what we are using in our project without any issue (Win, Mac and Linux, although everything has to be x64).

In general we're not planning to add docs or help with one-off mocking issues. We've covered this in https://github.com/tmpvar/jsdom#intervening-before-parsing in general and any specific issues are going to be related to your specific codebase.

Here's a simple way to mock canvas I came up with:

//
// Mock Canvas / Context2D calls
//
function mockCanvas (window) {
    window.HTMLCanvasElement.prototype.getContext = function () {
        return {
            fillRect: function() {},
            clearRect: function(){},
            getImageData: function(x, y, w, h) {
                return  {
                    data: new Array(w*h*4)
                };
            },
            putImageData: function() {},
            createImageData: function(){ return []},
            setTransform: function(){},
            drawImage: function(){},
            save: function(){},
            fillText: function(){},
            restore: function(){},
            beginPath: function(){},
            moveTo: function(){},
            lineTo: function(){},
            closePath: function(){},
            stroke: function(){},
            translate: function(){},
            scale: function(){},
            rotate: function(){},
            arc: function(){},
            fill: function(){},
            measureText: function(){
                return { width: 0 };
            },
            transform: function(){},
            rect: function(){},
            clip: function(){},
        };
    }

    window.HTMLCanvasElement.prototype.toDataURL = function () {
        return "";
    }
}
const document = jsdom.jsdom(undefined, {
  virtualConsole: jsdom.createVirtualConsole().sendTo(console)
});

const window = document.defaultView;
mockCanvas(window);

Thanks a lot! The canvas prebuilt package seems to be working.

Hello @cattermo,
I have added canvas-prebuilt to my deps, do i need to change my jest configuration ? I can't make it to work it gives me
Not implemented: HTMLCanvasElement.prototype.toBlob (without installing the canvas npm package)

@micabe
I did not make any configuration changes. Jsdom will pick up canvas prebuilt if it's present in node_modules.

Maybe that specific function is not supported?

It s working now after yarn cache clean Sorry for bothering you ! thanks @cattermo

Maybe jest-canvas-mock can help.

The solution is simple, just install canvas as a devDependency and rerun your jest tests.

ref: https://github.com/jsdom/jsdom#canvas-support

That is still not working because of the way jsdom checks for the canvas modules at lib/jsdom/utils.js:

  exports.Canvas = require(moduleName);
  if (typeof exports.Canvas !== "function") {
    // In browserify, the require will succeed but return an empty object
    exports.Canvas = null;
  }

Doing require('canvas') returns an Object that has the Canvas function. It does not returns the Canvas function straight away. Or am I wrong on this issue? It worked well when I was using the canvas-prebuilt module, but it seems the canvas module has a different API. I am using canvas' latest version, 2.0.1.
Edit
I tested the previous major version (1.6.x) and it works fine, it is an API change that JSDOM is not treating.

You can just do:

HTMLCanvasElement.prototype.getContext = jest.fn()

if actual implementation is not important for you

That is still not working because of the way jsdom checks for the canvas modules at lib/jsdom/utils.js:

  exports.Canvas = require(moduleName);
  if (typeof exports.Canvas !== "function") {
    // In browserify, the require will succeed but return an empty object
    exports.Canvas = null;
  }

Doing require('canvas') returns an Object that has the Canvas function. It does not returns the Canvas function straight away. Or am I wrong on this issue? It worked well when I was using the canvas-prebuilt module, but it seems the canvas module has a different API. I am using canvas' latest version, 2.0.1.
Edit
I tested the previous major version (1.6.x) and it works fine, it is an API change that JSDOM is not treating.

I have the same problem. Looks like the issues is not with jsdom as the fix was merged a while ago in https://github.com/jsdom/jsdom/pull/1964 and available since version 13.
However, the jsdom version in jest-environment-jsdom is still stuck at ^11.5.1 and I believe that is causing the problem:
https://github.com/facebook/jest/blob/2e2d2c8dedb76e71c0dfa85ed36b81d1f89e0d87/packages/jest-environment-jsdom/package.json#L14

Without installing canvas, or canvas-prebuilt was able to get rid off errors related to HTMLCanvasElement, as well any others canvas methods nicely proposed by @endel as a hack to get around this problem. As @hustcc proposed, used jest-canvas-mock with finding quite clean solution. For technical details please take a look on this comment.

I was seeing messages like "Cannot read webkitBackingStorePixelRatio of null", as well as "
Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)", and installing canvas(-prebuilt) was not enough. I went inside package-lock.json, fixed the jest-environment-jsdom to point to jsdom 13, threw away node_modules, and reran npm install. The error messages went away. I think @paradite is right, and opened a ticket against jest:

https://github.com/facebook/jest/issues/8016

Got feedback. It is related to jest being stuck with older node versions. If you need support for latest node, look here: https://www.npmjs.com/package/jest-environment-jsdom-thirteen. This is jest specific though, so perhaps a little off-topic here. Sharing anyhow for posterity..

@grtjn - checked the jest-environment-jsdom-thirteen dependency, but in my case I still got these 2 exactly the same errors

Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
TypeError: Cannot read property 'fillRect' of null

Keeping my solution mentioned earlier, about Node version I wouldn't say 10.15 is a deprecated one, as I got the problems for such environment

Angular CLI: 7.2.4
Node: 10.15.0
OS: win32 x64
Angular: 7.2.4

FYI: I had to append "testEnvironment": "jest-environment-jsdom-thirteen" to my jest config, and install canvas package, as well as the Cairo OS toolset. canvas-prebuilt didn't seem to work for me. I am using Vue.

Link to Cairo OS toolset dependency? Couldn't find that one.

@danieldanielecki See main page of 'canvas' package: https://www.npmjs.com/package/canvas#compiling

@grtjn thanks for it - maybe at some point will take an advantage of it. I was missing it, but looks more complicated than my solution so keeping the project as is.

Sorry to necropost but @grtjn suggestion of installing jest-environment-jsdom-thirteen (I used fourteen just since it's more recent) fixed my issue. I tried using jest-canvas-mock but we did some weird stuff where we fake createImageBitmap using node-canvas when OffscreenCanvas is not available, which it is not in many browsers, and jest-canvas-mock got pretty confused by this. Maybe another day we'll get that working...it is a cool mock library.

Was this page helpful?
0 / 5 - 0 ratings