jest-canvas-mock icon indicating copy to clipboard operation
jest-canvas-mock copied to clipboard

Doesn't work with latest Create React App

Open ztolley opened this issue 4 years ago • 17 comments

Steps to reproduce:

  1. npx create-react-app myapp --template typescript

  2. cd myapp

  3. npm install jest-canvas-mock

4: Edit App.test.tsx and add the following test

import "jest-canvas-mock";

test("Canvas support works with context", () => {
  const canvas: HTMLCanvasElement = document.createElement("canvas");
  const context = canvas.getContext("2d");

  expect(context).not.toBeUndefined();
});
  1. Run npm run test

The test should be able to create a canvas context but it comes back undefined

ztolley avatar Nov 27 '20 11:11 ztolley

Same here. It worked with "react-scripts": "^3.4.4" but not with "react-scripts": "4.0.0". Probably due to the different Jest version used by the CRA 4? In your tests, according to your package.json, you use "jest": "^25.3.0". CRA 4 uses Jest 26. It would be very much appreciated if you could upgrade to Jest 26 / CRA 4. Thank you!

albert-schilling avatar Nov 30 '20 09:11 albert-schilling

Update: As far as I can tell, Jest / jsdom can handle canvas elements if the library node canvas is installed as a peer dependency. https://github.com/jsdom/jsdom#canvas-support https://www.npmjs.com/package/canvas Thus, making this library obsolete, at least for testing purposes?

albert-schilling avatar Nov 30 '20 11:11 albert-schilling

My restriction is I cannot do this, hence using this package

ztolley avatar Dec 14 '20 15:12 ztolley

@albert-schilling Do you have any resource how to make it working? What I did:

  1. Added peerDependencies to my package.json "peerDependencies": { "canvas": "^2.6.1" }

  2. npm install but no success...

Thanks!

mregula avatar Jan 11 '21 16:01 mregula

@mregula
I think it suffices if you install canvas as a regular package. npm i --save-dev canvas At least, this is all we did. I guess JSDOM tries to import canvas from your node_modules folder and uses the library if it can import it.

albert-schilling avatar Jan 11 '21 20:01 albert-schilling

Thanks @albert-schilling !

What I did: npm install --save-dev canvas. If your component draws on canvas and uses img.onload to put the data into canvas, you need to mock also Image object in your jest test file:

import { Image } from "canvas";
beforeAll(() => {
  (global as any).Image = Image;
});

mregula avatar Jan 12 '21 10:01 mregula

I'm running in to the same problem here. I follow the steps outlines above by @ztolley and I also update the file setupTests.ts as suggested here adding the line import 'jest-canvas-mock' but the result doesn't change, the test still fails.

Afterwards I follow the suggestion of @albert-schilling installing node canvas and the test passed !, jsdom indeed was able to instantiate a canvas object.

However it is my understanding that node canvas is less portable then jest-canvas-mock because it relies on Cairo v1.10.0 libraries, and so using jest-canvas-mock would make the code more easily portable.

VittorioAccomazzi avatar Jan 17 '21 18:01 VittorioAccomazzi

Hey! Following up on that, the node canvas library indeed fixes the issue but I am not sure that would work when the test cannot control the creation of the canvas directly. For example if the canvas creation is happening inside a React component. How would you go about that?

For example:

test('test canvas', () => {
  render(<canvas data-testid="canvas" />);
  let canvas = screen.getByTestId("canvas")
  expect(canvas.getContext("2d")).not.toBeUndefined()

});

nicolas-chaulet avatar Feb 12 '21 16:02 nicolas-chaulet

Actually, the solution is to remove all dependencies to jest-canvas-dom and install canvas instead, that's it. Node takes care of the rest. The test above works just fine after that change.

nicolas-chaulet avatar Feb 12 '21 16:02 nicolas-chaulet

Actually, the solution is to remove all dependencies to jest-canvas-dom and install canvas instead, that's it. Node takes care of the rest. The test above works just fine after that change.

The lib canvas is quite limited, it doesn't support Video element as an argument and many other things. Any suggestion for use this in lasted CRA?

marlonmleite avatar Jul 01 '21 14:07 marlonmleite

I'm having this same problem. While using canvas works, it is not a good solution if you need to actually assert on your canvas, as you don't get any spy/mock functionality if you use the canvas library.

heisenbugged avatar Sep 07 '21 18:09 heisenbugged

jest-canvas-mock does not work with CRA 4 ([email protected]) - I tried this with freshly started project and it fails to work. On the other hand, with Parcel it works like a charm (tested with https://github.com/kolorobot/parcel-starters/tree/master/parcel-react-typescript-starter)

Using canvas is not an option for me neither.

  • Are there any plans to fix this?
  • Is there any solution that could work with CRA?

kolorobot avatar Nov 26 '21 19:11 kolorobot

Let me look into this, but I believe that this package doesn't work with the latest react-create-app is because of the fact that the jest configuration file has resetMocks to true.

I was testing against a file that had

beforeEach(() => {
  jest.resetAllMocks();
})

which is functionally equivalent to having resetMocks set to true. So try flipping the resetMocks flag in the jest configuration file to false and the library should work.

ggayowsky avatar Jan 26 '22 00:01 ggayowsky

  • I created new CRA (5.0.0) app
  • I added the following configuration to package.json:
 "jest": {
    "resetMocks": false
  }

Now the tests do not fail anymore.

Source code of my dummy component:

// @ts-nocheck
import * as React from 'react';
import { createRef, Ref, useState } from 'react';

type CanvasProps = {
    onUpdate?: (dataURL: string) => void,
}

const Canvas: React.FC<CanvasProps> = ({ onUpdate }) => {
    const canvasRef: Ref<HTMLCanvasElement> = createRef();

    const draw = () => {
        // https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Tutorial/Drawing_shapes
        const canvas = canvasRef.current;

        const ctx = canvas.getContext('2d');

        ctx.beginPath();
        ctx.arc(75, 75, 50, 0, Math.PI * 2, true);
        ctx.moveTo(110, 75);
        ctx.arc(75, 75, 35, 0, Math.PI, false);
        ctx.moveTo(65, 65);
        ctx.arc(60, 65, 5, 0, Math.PI * 2, true);
        ctx.moveTo(95, 65);
        ctx.arc(90, 65, 5, 0, Math.PI * 2, true);
        ctx.stroke();
        onUpdate && onUpdate(canvas.toDataURL());
    };

    const clear = () => {
        const canvas = canvasRef.current;
        const ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        onUpdate && onUpdate(canvas.toDataURL());
    };

    return (
        <>
            <div>
                <button onClick={draw}>Draw</button>
                <button onClick={clear}>Clear</button>
            </div>
            <div>
                <canvas data-testid='canvas' ref={canvasRef} width={150} height={150}
                        style={{ border: '1px solid #000' }} />
            </div>
        </>
    );
};

export default Canvas;

And the test:

// @ts-nocheck
import * as React from 'react';
import 'jest-canvas-mock';
import Canvas from './Canvas';
import { getByTestId, getByText, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

describe('Canvas', () => {

    it('renders empty canvas', async () => {
        const { container } = render(<Canvas />);
        expect(container).toMatchSnapshot();
    });

    it('renders canvas element', async () => {
        const { container } = render(<Canvas />);

        userEvent.click(getByText(container, 'Draw'));
        expect(container).toMatchSnapshot();
    });

    it('validates canvas state after draw was clicked', () => {
        const { container } = render(<Canvas />);

        userEvent.click(getByText(container, 'Draw'));

        const canvas: HTMLCanvasElement = getByTestId(container, 'canvas');
        const ctx = canvas.getContext('2d');

        const events = ctx.__getEvents();

        expect(events).toMatchSnapshot();
    });

    it('gets updated with `dataURL` on draw', () => {
        const callback = jest.fn();
        const { container } = render(<Canvas onUpdate={callback} />);

        userEvent.click(getByText(container, 'Draw'));

        expect(callback).toBeCalledWith('data:image/png;base64,00');
    });
});

So, @ggayowsky you were right as it goes to the workaround.

kolorobot avatar Jan 26 '22 18:01 kolorobot

Same here, but import "jest-canvas-mock"; and "resetMocks": false solved my error. Thank you very much :)

wowry avatar Feb 05 '22 19:02 wowry

would the maintainers accept a PR to expose mockWindow https://github.com/hustcc/jest-canvas-mock/blob/master/src/index.js#L6, so that my test can setup the mock itself?

I'm working in a codebase where lots of existing tests use resetAllMocks, which breaks this library.

nicks avatar Aug 01 '22 22:08 nicks

I was able to workaround this by yarn patch-ing https://github.com/hustcc/jest-canvas-mock/pull/98 into my project.

nicks avatar Aug 03 '22 21:08 nicks