Coder Social home page Coder Social logo

jest-canvas-mock's Introduction

jest-canvas-mock

Mock canvas when run unit test cases with jest. For more browser environment.

  • jest-random-mock Mock Math.random in jest, with deterministic random generator.
  • jest-date-mock Mock Date when run unit test with jest, test Date easily.

Build Status Coverage Status npm npm Mentioned in Awesome Jest

Install

This should only be installed as a development dependency (devDependencies) as it is only designed for testing.

npm i --save-dev jest-canvas-mock

Setup

In your package.json under the jest, create a setupFiles array and add jest-canvas-mock to the array.

{
  "jest": {
    "setupFiles": ["jest-canvas-mock"]
  }
}

If you already have a setupFiles attribute you can also append jest-canvas-mock to the array.

{
  "jest": {
    "setupFiles": ["./__setups__/other.js", "jest-canvas-mock"]
  }
}

More about in configuration section.

Setup file

Alternatively you can create a new setup file which then requires this module or add the require statement to an existing setup file.

__setups__/canvas.js

import 'jest-canvas-mock';
// or
require('jest-canvas-mock');

Add that file to your setupFiles array:

"jest": {
  "setupFiles": [
    "./__setups__/canvas.js"
  ]
}

Reset

If you reset the jest mocks (for example, with jest.resetAllMocks()), you can call setupJestCanvasMock() to re-create it.

import { setupJestCanvasMock } from 'jest-canvas-mock';

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

Mock Strategy

This mock strategy implements all the canvas functions and actually verifies the parameters. If a known condition would cause the browser to throw a TypeError or a DOMException, it emulates the error. For instance, the CanvasRenderingContext2D#arc function will throw a TypeError if the radius is negative, or if it was not provided with enough parameters.

// arc throws a TypeError when the argument length is less than 5
expect(() => ctx.arc(1, 2, 3, 4)).toThrow(TypeError);

// when radius is negative, arc throws a dom exception when all parameters are finite
expect(() => ctx.arc(0, 0, -10, 0, Math.PI * 2)).toThrow(DOMException);

The function will do Number type coercion and verify the inputs exactly like the browser does. So this is valid input.

expect(() => ctx.arc('10', '10', '20', '0', '6.14')).not.toThrow();

Another part of the strategy is to validate input types. When using the CanvasRenderingContext2D#fill function, if you pass it an invalid fillRule it will throw a TypeError just like the browser does.

expect(() => ctx.fill('invalid!')).toThrow(TypeError);
expect(() => ctx.fill(new Path2D(), 'invalid!')).toThrow(TypeError);

We try to follow the ECMAScript specification as closely as possible.

Snapshots

There are multiple ways to validate canvas state using snapshots. There are currently three methods attached to the CanvasRenderingContext2D class. The first way to use this feature is by using the __getEvents method.

/**
 * In order to see which functions and properties were used for the test, you can use `__getEvents`
 * to gather this information.
 */
const events = ctx.__getEvents();

expect(events).toMatchSnapshot(); // jest will assert the events match the snapshot

The second way is to inspect the current path associated with the context.

ctx.beginPath();
ctx.arc(1, 2, 3, 4, 5);
ctx.moveTo(6, 7);
ctx.rect(6, 7, 8, 9);
ctx.closePath();

/**
 * Any method that modifies the current path (and subpath) will be pushed to an event array. When
 * using the `__getPath` method, that array will sliced and usable for snapshots.
 */
const path = ctx.__getPath();
expect(path).toMatchSnapshot();

The third way is to inspect all of the successful draw calls submitted to the context.

ctx.drawImage(img, 0, 0);

/**
 * Every drawImage, fill, stroke, fillText, or strokeText function call will be logged in an event
 * array. This method will return those events here for inspection.
 */
const calls = ctx.__getDrawCalls();
expect(calls).toMatchSnapshot();

In some cases it may be useful to clear the events or draw calls that have already been logged.

// Clear events
ctx.__clearEvents();

// Clear draw calls
ctx.__clearDrawCalls();

Finally, it's possible to inspect the clipping region calls by using the __getClippingRegion function.

const clippingRegion = ctx.__getClippingRegion();
expect(clippingRegion).toMatchSnapshot();

The clipping region cannot be cleared because it's based on the stack values and when the .clip() function is called.

Override default mock return value

You can override the default mock return value in your test to suit your need. For example, to override return value of toDataURL:

canvas.toDataURL.mockReturnValueOnce(
  'data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
);

Contributors

License

MIT@hustcc.

jest-canvas-mock's People

Contributors

andypearson avatar cibernox avatar danielrentz avatar doug2k1 avatar evanoc3 avatar feliciousx avatar gmartigny avatar hrd543 avatar hustcc avatar jdufresne avatar jtenner avatar kevinbarabash avatar lekha avatar litomore avatar lualparedes avatar milahu avatar nicks avatar paradite avatar pedroordep avatar robinelvin avatar stijndepestel avatar tvthatsme avatar virtuoushub avatar yonatankra avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

jest-canvas-mock's Issues

getContext() should be idempotent

I have some code that uses an old jQuery plugin and it ends up calling getContext() more than once one the same Canvas element. This works fine in the browser with the real Canvas element. This causes _stackIndex along with all of the other internal instance variables in CanvasRenderingContext2D.js to be reset when they shouldn't be. This should be pretty easy to fix by adding a Symbol to the Canvas element containing the context from getContext() and then checking if that Symbol exists (and returning it) whenever getContext() is called. Would you be interested in a pull request for this issue?

Can't use jest-canvas-mock without jest

Example setup needed to bootstrap jest manually that can be a pain point:

const jest = {
  fn: (arg) => arg,
};
global.jest = jest;
const CanvasRenderingContext2D = require("jest-canvas-mock/lib/classes/CanvasRenderingContext2D").default;
const Path2D = require("jest-canvas-mock/lib/classes/Path2D").default;
global.Path2D = Path2D;

@hustcc is this something worth supporting?

request : please make a version for mocha

I am having a nightmare trying to mock the canvas using jsdom-global and mocha!

I run mocha --reporter min --require esm --require jsdom-global/register -b and it works great with jsdom, but as soon as it hits a canvas element i get TypeError: Cannot read property 'getContext' of undefined

In my test file I can write:

document.body.innerHTML =
  '<canvas width="500" height="500" id="canvas"></canvas>';

And it works, but I can't seem to write something like:

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

My jsdom is installed globally, and that seems to add to the headache. I tried everything, it is beyond me. Having a package to install that automated it would be awesome :)

createImageData() returns wrong type

My code uses node-qrcode to generate a QR code in a canvas. It calls createImageData() and then tries to use the data property of the returned object which fails with:

TypeError: Cannot set property '0' of undefined

node-qrcode accesses the data property here: canvas.js#L39

The current mock implementation is:

context2d.js#L22

createImageData: () => [],

I think it should follow the spec at MDN - createImageData()

When I change the code to the following my test works:

createImageData: () => {width: 1, height: 1, data: []},

I'm not sure if the dimensions should be set to anything in particular but the above works for my case.

[RFC]: Add Testing Feature ctx.__getClipRegion()

Hello,

According to...

https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save

The drawing state that gets saved onto a stack consists of:

  • ...
  • The current clipping region.
  • ...

... the clipping region is also involved in ctx.save() and ctx.restore() operations, but that seems not to be implemented in jest-canvas-mock.

A very easy solution to this would be to create a _clipStack object on the main class to keep the saved clips. Not sure though which should be the initial clip (on the bottom of the stack).

PS: sorry to open so many issues lately, but this library is a fine piece of work even more incredible after solving those small issues 👌. Thank you!

Not recognising 'transparent' colour

Drawing a linear gradient which trails off to transparency using this code:

    this.ctx = this.canv.current.getContext("2d");
    const gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
    gradient.addColorStop(0.0, this.state.colour);
    gradient.addColorStop(0.925, this.state.colour);
    gradient.addColorStop(1.0, "transparent");
    this.ctx.fillStyle = gradient;
    this.ctx.fillRect(x1, y1, w, h);

The above renders fine in Firefox, but is unable to build in the unit tests, caused by this error:
Screenshot 2020-04-08 at 13 10 05

After initially looking into it, I presume this is an issue to do with the parse-color module you are using, but it is quite old.

Is there a possibility of changing the library being used for this?

Docs: Add examples of mocking canvas calls

It would be very useful if there were some examples in the docs of how to mock specific calls to canvas instances (since it was hinted at in #60 (comment).

The exported typescript files don't seem to point to how that might be done.

Thanks.

Webpack importing package?

Not sure what's causing this but webpack is building this package into my bundle dispite it only being used in setupScripts? i get an error about jest not being defined.

Am i missing something stupid?

[Feature Request/Bug] Add compatibility with `Blob` class provided by `node-fetch`

Referenced here: jefflau/jest-fetch-mock#105

The node-fetch package actually mocks it's own Blob constructor and it becomes impossible to determine if the image provided to createImageBitmap(blob) is actually satisfies the predicate img instanceof Blob.

We can technically change line 24 of createImageBitmap.js to be:

if (img instanceof Blob || (img && img.constructor && img.constructor.name === "Blob")) validImage = true;

As a side effect, this will cause createImageBitmap(new class Blob{}) to resolve to a valid ImageBitmap. I do not like this solution, but I am open to many ideas.

[with solution]: Problem with context.save () and context.restore ()

The problem

The jest-canvas-mock/CanvasRenderingContext2D class save and restore operations should behave in the same way as browser/CanvasRenderingContext2D but it doesn't.

If we execute the following code:

function _do (cont) {
    var tmp = [];
    
    cont.scale(0.5, 0.5);
    cont.save ();
    tmp.push (cont.getTransform ().m11);
    cont.scale (0.5, 0.5);
    tmp.push (cont.getTransform ().m11);
    cont.restore ();
    tmp.push (cont.getTransform ().m11);
    
    console.log (tmp);
}

var the_canvas = some real canvas;

console.log ("save/restore sequence for browser/CanvasRenderingContext2D")

_do (
    the_canvas.getContext('2d')
);

console.log ("save/restore sequence for jest-canvas-mock/CanvasRenderingContext2D")

_do (
    new CanvasRenderingContext2D (
        the_canvas
    )
);

We get the following output:

save/restore sequence for browser/CanvasRenderingContext2D
Array(3) [ 0.5, 0.25, 0.5 ]

save/restore sequence for jest-canvas-mock/CanvasRenderingContext2D
Array(3) [ 0.5, 0.25, 0.25 ]

Expected behaviour

The output for jest-canvas-mock/CanvasRenderingContext2D should be the same as browser/CanvasRenderingContext2D...

Array(3) [ 0.5, 0.25, 0.5 ]

... instead of...

Array(3) [ 0.5, 0.25, 0.25 ]

(notice the 0.25 intead of 0.5 as last element in the array)

putImageData not updating the underlying data set

I've been experimenting with getImageData and putImageData, it's relatively new to me so apologies if I've missed a trick with this one. There are a few quirks some of which are outlined here notably that the data property is read only.

In the below test case when you attempt to get the imageData after it has been updated, the returned value is in fact the old data set. The same test when run in Chrome returns the newer data set. Is this a bug or misuse?

  const canvas = document.getElementById('game');
  canvas.width = 1;
  canvas.height = 1;

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

  const imageData = ctx.getImageData(0, 0, 1, 1);
  imageData.data[0] = 0;
  imageData.data[1] = 0;
  imageData.data[2] = 0;
  imageData.data[3] = 255;
  ctx.putImageData(imageData, 0, 0);
  console.log(ctx.getImageData(0, 0, 1, 1).data);
  expect(ctx.getImageData(0, 0, 1, 1).data).toEqual([0, 0, 0, 255]);

Q: where to go for DOMMatrix.invertSelf support

DOMMatrix.invertSelf and the other DOMMatrix functions are super useful for working with objects that Canvas2d context functions use natively. Where do people get it from? I'm running things in current Jest, and get an Error: Uncaught [Error: Not implemented]

Specifically I use it via

const { a, d } = ctx.getTransform().invertSelf().scale(ctx.canvas.width, ctx.canvas.height)

A nice simple example.

For those of us who are newbies, it'd be really useful if you had a nice simple example of a complete test in your docs.
Something very minimal but complete that just gives us an idea of how the mock should be implemented in our tests.

Readme has wrong expectation

    Expected name: "TypeError"                                                                                                                           
    Received name: "Failed to execute 'arc' on 'CanvasRenderingContext2D': The radius provided (-10) is negative."                                       
                                                                                                                                                         
    Received message: "IndexSizeError"                                                                                                                   
                                                                                                                                                         
          3 |   const ctx = canvas.getContext('2d')                                                                                                      
          4 |   expect(() => ctx.arc(1, 2, 3, 4)).toThrow(TypeError);                                                                                    
        > 5 |   expect(() => ctx.arc(0, 0, -10, 0, Math.PI * 2)).toThrow(TypeError); 

This can be fixed before 2.1.0

Add mock for Path2D

Hi,
Your mocking package don't support Path2D.
If you don't have time, tell me and I'll make a PR.

Thanks for you work.

Running tests in node environment

I have jest-canvas-mock configured in my package.json:

"jest": {
  "setupFiles": [
    "jest-canvas-mock"
  ]
}

This is working great for my tests 🎉

However, I'm running some new tests in the node test environment by using the docblock at the top of the file. In this case jest-canvas-mock is throwing an error that window is not defined (which is expected I guess)

Is there any way that the canvas mock can be disabled for jest test files that are run in the node environment?

Be able to use with create-react-scripts

It would be great to be able to use this with create-react-app

$ react-scripts test --watchAll=false

Out of the box, Create React App only supports overriding these Jest options:

  • clearMocks
  • collectCoverageFrom
  • coveragePathIgnorePatterns
  • coverageReporters
  • coverageThreshold
  • displayName
  • extraGlobals
  • globalSetup
  • globalTeardown
  • moduleNameMapper
  • resetMocks
  • resetModules
  • restoreMocks
  • snapshotSerializers
  • transform
  • transformIgnorePatterns
  • watchPathIgnorePatterns.

These options in your package.json Jest configuration are not currently supported by Create React App:

  • setupFiles

If you wish to override other Jest options, you need to eject from the default setup. You can do so by running npm run eject but remember that this is a one-way operation. You may also file an issue with Create React App to discuss supporting more options out of the box.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

canvas.toBlob Callback

Hi guys,

I have updated the lastest version of the package, now toBlob is implemented but the callback of this function never called. I mean:

canvas.toBlob(blob => {
      someFunction();
    }, 'image/jpeg');

someFunction is never executed and I would like to test that.

Thanks!

Parameter 1 is not of type 'Path2D'

Hi, we are trying to replace the canvas npm dependency with just your mock to speedup our ci infrastructure.
I've replaced the library and I'm getting the following error:

'CanvasRenderingContext2D': parameter 1 is not of type 'Path2D'

when something like this exists in my code:

ctx.stroke(new Path2D(line));

The problem seems caused by the fact that Path2D is available in my jest environment when running the test (Path2D doesn't probably need any special os-dependent treatment and is available in the jsdom) and you, correctly, don't mock if it is present:

if (!win.Path2D) win.Path2D = Path2D;

The thing is that you check for a path2d being an instance of Your Path2D (and not the possible already available Path2D)
if (arguments.length === 2 && !(path instanceof Path2D))
throw new TypeError(
"Failed to execute 'drawFocusIfNeeded' on '" +
this.constructor.name +
"': parameter 1 is not of type 'Path2D'."
);

Usage with Jest 23?

Is there an older version compatible with Jest 23? The oldest release looks like it uses 24.

can it be use with uve?

Hi,
i am a new comer for jest and working on a project with some feature to draw on a canvas element.
in my test, i found that it didn't work when i tried to expect on a call on the mocked canvas.ctx.fillText. let me explain the details as below.
`
//the render from '@testing-library/vue' to spin up the detectedObjects component with only a canvas element.
const { container } = render(detectedObjects);

const canv = container.querySelector('canvas');

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

advance(50);  // after 50ms, the component will call ctx.fillText to draw some text on the canvas.

expect(ctx.fillText).toHaveBeenCalledTimes(1);  // however it fails at this step always.

`
i can assure that the code in the component that calls fillText has been executed before the expect. when i experimented by adding "ctx.fillText('hello world!', 1, 2)" before the expect, the test passed.

can you help to check why my test case doesn't work? thanks a lot in advance for your help.

Add ability to clear event lists

Hello! First of all, thank you so much for writing and releasing this library. I am trying to build a Photoshop like editor that uses <canvas> for rendering and I have no idea how I would have tested it without the help of jest-canvas-mock!

In the interface I am building the user can add layers to the canvas, and the canvas automatically updates for each layer that is added.

What I would like to do is compare a snapshot of the final canvas state, but not the interim states where the user hasn't finished adding layers.

The way I was thinking about doing it was to clear the set of events before the final layers is added in the test, and then take a snapshot of the result. At the moment it is not possible to clear the recorded events on the context, as those are stored internally.

What would be handy would be a matching set of clear methods for the current API:

  • __clearEvents
  • __clearPath
  • __clearDrawCalls

These would clear the corresponding internal arrays.

Does this sound like it would be a useful addition to the library? If so, I could try and scrabble together a PR.

Alternatively, do you think there is another way of approaching my requirement?

Doesn't work with latest Create React App

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

Consider adopting a CHANGELOG

Currently, the only way to figure it out breaking (or important) changes is by searching and comparing commits. It would be very helpful if a CHANGELOG file was annotated with important changes made in the code base.

Use with OffscreenCanvas?

Hi there,
I am trying to test OffscreenCanvas in an app. Is this something that jest-canvas-mock can help with or is there a type of thing that should be handled separately?

[with solution]: incorrect event value stored for context.setTransform ()

If we look at the following code from setTransform method...

const event = createCanvasEvent(
'setTransform',
getTransformSlice(this),
{ a, b, c, d, e, f },
);

... we see that the value property of the event is provided as { a, b, c, d, e, f }.

If we type in the javascript console:

{ 1, 2, 3, 4, 5, 6 }

We get this output:

6

Comparing the code to that of the resetTransform method...

const event = createCanvasEvent(
'resetTransform',
getTransformSlice(this),
{ a: 1,
b: 0,
c: 0,
d: 1,
e: 0,
f: 0, },
);

If we type in the javascript console:

{ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}

We get this output:

{ a: 1, b: 2, c: 3, d: 4, e: 5, f: 6}

So, it seems that this line...

... should be changed to:

{ a: a, b: b, c: c, d: d, e: e, f: f}

getImageData returns undefined

This code line fails in Jest, because getImageData returns undefined:

    const pixel = context.getImageData(1, 1, 1, 1).data;

Missing index.js

errorMessage
Module jest-canvas-mock in the setupFiles option was not found.
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.