Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mock canvas #1782

Closed
endel opened this issue Mar 22, 2017 · 26 comments
Closed

Mock canvas #1782

endel opened this issue Mar 22, 2017 · 26 comments

Comments

@endel
Copy link

endel commented Mar 22, 2017

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)
@endel
Copy link
Author

endel commented Mar 22, 2017

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;

@endel endel closed this as completed Mar 22, 2017
@domenic
Copy link
Member

domenic commented Mar 22, 2017

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

@endel
Copy link
Author

endel commented Mar 23, 2017

Thanks @domenic !

@cattermo
Copy link

@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.

@molant
Copy link

molant commented Oct 18, 2017

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).

@domenic
Copy link
Member

domenic commented Oct 18, 2017

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.

@endel
Copy link
Author

endel commented Oct 18, 2017

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);

@cattermo
Copy link

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

@micabe
Copy link

micabe commented Oct 24, 2017

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)

@cattermo
Copy link

@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?

@micabe
Copy link

micabe commented Oct 30, 2017

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

@hustcc
Copy link

hustcc commented Feb 1, 2018

Maybe jest-canvas-mock can help.

@noscripter
Copy link

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

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

@viniciusd
Copy link

viniciusd commented Oct 25, 2018

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.

@YevgenSnuffkin
Copy link

You can just do:

HTMLCanvasElement.prototype.getContext = jest.fn()

if actual implementation is not important for you

@paradite
Copy link

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 #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

@danieldanielecki
Copy link

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.

@grtjn
Copy link

grtjn commented Mar 1, 2019

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:

jestjs/jest#8016

@grtjn
Copy link

grtjn commented Mar 1, 2019

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..

@danieldanielecki
Copy link

@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

@grtjn
Copy link

grtjn commented Mar 5, 2019

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.

@danieldanielecki
Copy link

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

@grtjn
Copy link

grtjn commented Mar 10, 2019

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

@danieldanielecki
Copy link

@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.

@cmdcolin
Copy link

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.

@eaemilio
Copy link

You can just do:

HTMLCanvasElement.prototype.getContext = jest.fn()

if actual implementation is not important for you

I got TypeError: Cannot assign to read only property 'getContext' of object '[object Object]' when trying to do this, did someone know how to fix it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests