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

Error: Not implemented: HTMLCanvasElement.prototype.getContext #2

Closed
bytewiz opened this issue Feb 18, 2018 · 35 comments
Closed

Error: Not implemented: HTMLCanvasElement.prototype.getContext #2

bytewiz opened this issue Feb 18, 2018 · 35 comments

Comments

@bytewiz
Copy link

bytewiz commented Feb 18, 2018

Using jest-canvas-mock still throw this error when running jest:
Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)

@bytewiz
Copy link
Author

bytewiz commented Feb 18, 2018

I messed one thing up ✌️ seems to work now

@hustcc hustcc closed this as completed Feb 18, 2018
@icfantv
Copy link

icfantv commented Feb 27, 2018

@hustcc It looks like this is thrown by JSDom (and causes Jest to fail) simply by your initial getContext check calling getContext('2d') here.

I had to monkey-patch borrow your code and remove that line to get Jest to not fail for me. This has the desired effect of mocking canvas.

@hustcc hustcc reopened this Feb 28, 2018
@bytewiz
Copy link
Author

bytewiz commented Mar 1, 2018

@hustcc I just made a typo 😄

@hustcc
Copy link
Owner

hustcc commented Mar 1, 2018

@icfantv @dariusk ping~

@dariusk
Copy link

dariusk commented Mar 1, 2018

Thanks! Turns out all I actually had to do to fix this was, well, install the canvas npm package as described in the error message!

npm install --save-dev canvas

@icfantv
Copy link

icfantv commented Mar 1, 2018

@dariusk and others: installing and building canvas requires the nodeJS headers (plus other native libraries) so that it can be built locally. If your CI is in an environment that does not have Internet access for security reasons and uses, say, an Artifactory server, downloading canvas and building, or even other prebuilt binaries (e.g., canvas-prebuilt) this will not work and mocking may be the right way to go unless you need to test the actual canvas drawing.

As we do, we've solved this by building some base Docker images (with node-canvas installed and built locally) and using those images in CI as a base for our containers in which we run our unit and integration tests.

@hustcc sorry for the delay, let me see if i can whip something up quickly.

@icfantv
Copy link

icfantv commented Mar 1, 2018

@hustcc apologies, I misspoke earlier and updated my original comment to be more precise. While the tests pass, JSDom complains for each one. This is a known issue with JSDom but while node-canvas is still in alpha, they're not going to fix it.

Sadly, even wrapping with a try/catch (which probably isn't the best idea anyway) doesn't work. IMO, given that the point of this library is to mock the canvas element, deferring to a locally installed version, if present, isn't necessarily the right solution. People may want to test it, say via integration, and so it isn't necessary at the unit level.

Here's the stacktrace:

$ jest --config=jest.config.json
 PASS  src/app/components/modal/ModalContainer.test.tsx
  ● Console

    console.error node_modules/jsdom/lib/jsdom/virtual-console.js:29
      Error: Not implemented: HTMLCanvasElement.prototype.getContext (without installing the canvas npm package)
          at module.exports (/Users/adam.gordon/code/cban-web-client/node_modules/jsdom/lib/jsdom/browser/not-implemented.js:9:17)
          at HTMLCanvasElementImpl.getContext (/Users/adam.gordon/code/cban-web-client/node_modules/jsdom/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js:42:5)
          at HTMLCanvasElement.getContext (/Users/adam.gordon/code/cban-web-client/node_modules/jsdom/lib/jsdom/living/generated/HTMLCanvasElement.js:50:45)
          at Object.<anonymous>.exports.default (/Users/adam.gordon/code/cban-web-client/node_modules/jest-canvas-mock/lib/window.js:18:39)
          at Object.<anonymous> (/Users/adam.gordon/code/cban-web-client/node_modules/jest-canvas-mock/lib/index.js:10:38)
          at Runtime._execModule (/Users/adam.gordon/code/cban-web-client/node_modules/jest-runtime/build/index.js:448:13)
          at Runtime.requireModule (/Users/adam.gordon/code/cban-web-client/node_modules/jest-runtime/build/index.js:265:14)
          at new Runtime (/Users/adam.gordon/code/cban-web-client/node_modules/jest-runtime/build/index.js:149:14)
          at /Users/adam.gordon/code/cban-web-client/node_modules/jest-runner/build/run_test.js:64:15
          at Generator.next (<anonymous>) undefined

hustcc added a commit that referenced this issue Mar 2, 2018
@hustcc
Copy link
Owner

hustcc commented Mar 2, 2018

So catch the throw by try ... catch can solve the bug? 0c8d548

@icfantv
Copy link

icfantv commented Mar 2, 2018

@hustcc No, wrapping with try/catch won't solve it. I didn't dig into JSDom's code as to why, though it would probably only take a few minutes.

I might suggest allowing a configuration option (assuming Jest allows such a thing for setup files) to bypass a native canvas check and just use the mocked value.

@hustcc
Copy link
Owner

hustcc commented Mar 2, 2018

Code here: https://github.com/jsdom/jsdom/blob/4c7698f760fc64f20b2a0ddff450eddbdd193176/lib/jsdom/living/nodes/HTMLCanvasElement-impl.js#L42-L45

JSDom did not implement this method getContent, instead by throw an error Error: Not implemented: HTMLCanvasElement.prototype.getContext. So try catch the error, then use mock-canvas to mock the API of canvas, I think it is effective.

The point is I can not reproduce this problem locally, and what your jest and jsdom version?

hustcc added a commit that referenced this issue Mar 2, 2018
@bytewiz
Copy link
Author

bytewiz commented Mar 2, 2018

@hustcc actually I also installed canvas prebuilt that worked for me better than installing the canvas package 👍

That solved my problem

@icfantv
Copy link

icfantv commented Mar 2, 2018

@hustcc latest version of Jest, 22.3.0 and we do not install JSDom directly rather, Jest pulls it in (version 11.6.2 according to its package.json). In order to reproduce and utilize your actual mock code and have the error displayed, you will need to ensure that no prebuilt version of canvas is installed (and there are several as indicated above).

As I mentioned, the tests still pass, but each test that ultimately uses canvas will log that error.

@bytewiz That works for us when running locally, but our CI environments are locked down and do not have internet access for security reasons so downloading prebuilt binaries is not allowed.

@hustcc
Copy link
Owner

hustcc commented Mar 3, 2018

@icfantv Reproduce this problem locally. Jest@^22.0.0 will occur.

JSDom's function notImplemented is not throw error, it is console.error.

We can pass the ci(when console.error) locally, but remote ci can not.

Two way can solve it:

  • Remove the lines, I will done this.
  • In setup.js of jest, mock console.error. May has side effect.
global.window.console.error = (...a) = { console.warning(...a) };

@hustcc
Copy link
Owner

hustcc commented Mar 3, 2018

v1.0.1 published, used in echarts-for-react.

In addition, I think PR can be send to JSDom to fix this issue.

@icfantv
Copy link

icfantv commented Mar 3, 2018

@hustcc sorry, I’m confused. I did reproduce the problem locally. Note that it is a cosmetic error and our Jest tests do still pass. Since we pulled your code in-house and modified it for or purposes, I’m not sure we need to do anything else.

Regarding your modification of window.console, I don't think this is a good idea. Modification of the standard DOM is risky, even if only in the land of testing...

@hustcc
Copy link
Owner

hustcc commented Mar 4, 2018

use v1.0.1 can avoid the error.

the lastest jsdom implement the function getContext, but the function only print the error, and return undefined.

So when we run the ci with jest-canvas-mock@1.0.0, will execute print the error, because of the return is undefined, so we mock it successfully.

The correct way for jsdom is throw error instead of print error when the function is waiting for completed.

English is poor, I do not know can understand

@hustcc hustcc closed this as completed Mar 7, 2018
@mengsixing
Copy link

The error repetition at this version:

"jest": "^23.6.0",
"jest-canvas-mock": "^1.1.0",

@hustcc
Copy link
Owner

hustcc commented Jan 19, 2019

any detail information? such as screenshot, jsdom version, demo.

@mengsixing
Copy link

jest: (23.6.0)
jest-canvas-mock: (1.1.0)
jsdom: (11.12.0) is installed by jest.
travis-ci result
demo

image

@icfantv
Copy link

icfantv commented Jan 22, 2019

Since we pulled this in-house and modified it, we have had no issues. I'd recommend snapshot testing for testing canvas drawings.

Be aware that JSDom traps thrown errors and logs them to console.error. This has the effect as you see in the above screenshot and it won't fail your build. If you want it to fail your build, you will need to override console.error in your Jest setup.

@mengsixing
Copy link

Thanks. Finally I override console.error in my Jest setup to avoid the error. @icfantv

@hustcc
Copy link
Owner

hustcc commented Jan 23, 2019

I think can send a pr to jsdom.

@ssolders
Copy link

If you don't need to test the getContext-method explicitly and for the sake of getting a cleaner test-log you can mock the method by:

window.HTMLCanvasElement.prototype.getContext = () => {}

Example of a test case

describe('MyComponent should render', () => {
    it('should render', () => {
      window.HTMLCanvasElement.prototype.getContext = () => {}
      mount(MyComponent)
   })
 })

@jtenner
Copy link
Collaborator

jtenner commented Feb 18, 2019

@ssolders Here is the snippet that sets the prototype method.

HTMLCanvasElement.prototype.getContext = getContext2D;

And the tests look like this.

image

This issue is currently fixed in the alpha version up on npm. You might need to use the following command to install it.

npm install --save-dev jest-canvas-mock@latest

I just added these tests, and they will ship with the beta for clarity reasons.

@danieldanielecki
Copy link

Solution proposed by @ssolders was almost fine to avoid the following error

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

In my case it I had to mock this method in my .spec.ts file

HTMLCanvasElement.prototype.getContext = jest.fn();

However, in my scenario I tested particles.js, having Jest as a testing framework I still had another error

TypeError: Cannot read property 'fillRect' of undefined

With 1 line of code change was able to get rid off these 2, in essence to my Jest config setupFiles array (jest.config.js, using Nx workspace) imported jest-canvas-mock like so

'./../../node_modules/jest-canvas-mock/lib/index.js'

Finding this as a leaner approach, maybe for some of you guys might be useful, here is a link to Medium super short post about global mocks for external libraries. The author says about global mocks, but I took the part with setupFiles in package.json. As mentioned earlier, using Nx workspace) for Angular, for me it was to set it up in jest.config.js, and it works fine. In my package.json don't have jsdom, below necessary code snippets to reproduce, as well dependencies with its versions.

package.json

"dependencies": {
  ...
  "particles.js": "^2.0.0",
  ...
},
"devDependencies": {
  ...
  "jest": "^23.0.0",
  "jest-canvas-mock": "^2.0.0-alpha.3",
  "jest-preset-angular": "6.0.1"
  ...

jest.config.js

module.exports = {
  name: 'home-components',
  preset: '../../jest.config.js',
  coverageDirectory: '../../coverage/libs/home-components',
  setupFiles: [
    './../../node_modules/particles.js/particles.js', // Replace import * as particlesJS from 'particles.js' in the test file.
    './../../node_modules/jest-canvas-mock/lib/index.js' // Get rid off HTMLCanvasElement.prototype.getContext, and canvas errors. Now no need to mock in the test file explicitly.
  ]
};

Now my .spec.ts is simple as that, without throwing any error

import { 
  async, 
  TestBed 
} from '@angular/core/testing';
import { HomeComponentsModule } from './home-components.module';

describe('HomeComponentsModule', () => {
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      imports: [HomeComponentsModule]
    }).compileComponents();
  }));

  it('should create home components module', async(() => {
    expect(HomeComponentsModule).toBeTruthy();
  }));
});

Before even though the component was working totally on ng serve without any single error/warning. After running tests ng test I started to get all these errors, took 2-3 days for me to fix it, if anyone of you'd have similar issues, just try to follow the snippets, should be fine.

@chenxiaochun
Copy link

I solved this problem is:

  1. Installjest-canvas-mock
  2. Add config in jest config:setupFiles: ['<rootDir>/jest/setup.js', 'jest-canvas-mock'],

@JasonLunsford
Copy link

Solution proposed by @chenxiaochun solved my issue. Thank you!

@danieldanielecki
Copy link

Looks great @chenxiaochun, that time I was solving the issue jest-canvas-mock was in early alpha and didn't solve my issue, glad to see now it's much easier :)

shammamah-zz pushed a commit to plotly/dash-daq that referenced this issue Oct 2, 2019
shammamah-zz pushed a commit to plotly/dash-daq that referenced this issue Oct 2, 2019
shammamah-zz pushed a commit to plotly/dash-daq that referenced this issue Oct 4, 2019
* Update styled-components, webpack, and babel-loader.

* Remove builder and use dash-generate-components.

* Add webpack config.

* Update bundle location and .eslintignore.

* Remove old build artefacts.

* Fix tests.

Change shallow mount to full mount; call render(); check props and not state of components.

* Split exponentials and regular digits for PrecisionInput test.

* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps.

* Add default value for required precision prop in PrecisionInput.

* Comply with prop type for min in GraduatedBar test.

* Comply with prop type for secondary in Indicator component.

* Add jest-canvas-mock for Chrome colorpicker.

See: hustcc/jest-canvas-mock#2 (comment)

* Update requirements.

* Move jest-canvas-mock to dev dependencies.

* Update CHANGELOG.

* Update version in package.json.

* Add back build artifacts.
@jjmerri
Copy link

jjmerri commented Oct 24, 2020

The fix for me was to npm install --save-dev jest-canvas-mock
and then add this to src/setupTests.js
import 'jest-canvas-mock';

@EfstathiadisD
Copy link

EfstathiadisD commented Nov 15, 2020

Can I say, that this is not a solution? Why on earth, would I need another dependency? This is not acceptable I am afraid. Can someone, fix this, please. It seems that this is a problem for almost a year, and nothing is being done? Come on!!

Until you can fix it though, please don't import unnecessary pacakges. Just do the following, either in the test, or in your jest setup:

HTMLCanvasElement.prototype.getContext = jest.fn();

@jtenner
Copy link
Collaborator

jtenner commented Nov 16, 2020

Jest canvas mock implements this function and emulates a lot of the functions properly. If perhaps importing a mock package is too much work or overhead, then don't use it?

briangann added a commit to grafana/grafana-polystat-panel that referenced this issue Mar 11, 2021
* Use global value mappings in Overrides

* Fix "Not implemented: HTMLCanvasElement.prototype.getContext" test output.

See: hustcc/jest-canvas-mock#2

* Update yarn.lock with the new jest-canvas-mock dependency

Co-authored-by: Brian Gann <briangann@users.noreply.github.com>
kamilmielnik added a commit to prezly/slate that referenced this issue Mar 24, 2021
…hen running tests:

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

See: hustcc/jest-canvas-mock#2 (comment)
@eli-front
Copy link

Thanks! Turns out all I actually had to do to fix this was, well, install the canvas npm package as described in the error message!

npm install --save-dev canvas

Canvas is super bulk. avoid using

@EdmundMai
Copy link

For anyone who is trying to switch from react-scripts / jest to Vitest

None of the snippets I found worked, I had to modify it to dynamically import it within a function since imports are hoisted

import { afterAll, vi } from 'vitest';
// @ts-expect-error: Global type missing
global.jest = vi;

const apis = [
  'Path2D',
  'CanvasGradient',
  'CanvasPattern',
  'CanvasRenderingContext2D',
  'DOMMatrix',
  'ImageData',
  'TextMetrics',
  'ImageBitmap',
  'createImageBitmap',
] as const;

const createCanvasWindow = async () => {
  const getCanvasWindow = await import('jest-canvas-mock/lib/window'); //<--------- this
  return getCanvasWindow({ document: window.document });
};

const canvasWindow = createCanvasWindow;

apis.forEach((api) => {
  global[api] = canvasWindow[api];
  global.window[api] = canvasWindow[api];
});

afterAll(() => {
  // @ts-expect-error: type
  delete global.jest;
  // @ts-expect-error: type
  delete global.window.jest;
});

@Oleksii14
Copy link

Oleksii14 commented Jun 29, 2023

For Vitest, this mocking worked for me.

// vitest.setup.ts

global.HTMLCanvasElement.prototype.getContext = () => null;
global.URL.createObjectURL = () => "";

@wklTomorrow
Copy link

For anyone who is trying to switch from react-scripts / jest to Vitest

None of the snippets I found worked, I had to modify it to dynamically import it within a function since imports are hoisted

import { afterAll, vi } from 'vitest';
// @ts-expect-error: Global type missing
global.jest = vi;

const apis = [
  'Path2D',
  'CanvasGradient',
  'CanvasPattern',
  'CanvasRenderingContext2D',
  'DOMMatrix',
  'ImageData',
  'TextMetrics',
  'ImageBitmap',
  'createImageBitmap',
] as const;

const createCanvasWindow = async () => {
  const getCanvasWindow = await import('jest-canvas-mock/lib/window'); //<--------- this
  return getCanvasWindow({ document: window.document });
};

const canvasWindow = createCanvasWindow;

apis.forEach((api) => {
  global[api] = canvasWindow[api];
  global.window[api] = canvasWindow[api];
});

afterAll(() => {
  // @ts-expect-error: type
  delete global.jest;
  // @ts-expect-error: type
  delete global.window.jest;
});

I tried, but it does't work, please show more

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