Simple testing example of canvas element using Jest

Viewed 1302

I am trying to create a jest test for the example found in this sandbox: https://codesandbox.io/s/r3f-basic-demo-forked-297k3?file=/src/App.js

Basically I would like to test the onClick functionality on each box. I have already come across with the fact that jest tests don't run in a real browser. Jest uses jsdom for mocking the necessary parts of the DOM. Consequently I may need canvas support for jsdom, yet I am not quite sure exactly what to do.

App.js

import React, { useRef, useState } from 'react'
import { Canvas, useFrame } from 'react-three-fiber'

function Box(props) {
  // This reference will give us direct access to the mesh
  const mesh = useRef()
  // Set up state for the hovered and active state
  const [hovered, setHover] = useState(false)
  const [active, setActive] = useState(false)
  // Rotate mesh every frame, this is outside of React without overhead
  useFrame(() => {
    mesh.current.rotation.x = mesh.current.rotation.y += 0.01
  })
  return (
    <mesh
      {...props}
      ref={mesh}
      scale={active ? [1.5, 1.5, 1.5] : [1, 1, 1]}
      onClick={(e) => setActive(!active)}
      onPointerOver={(e) => setHover(true)}
      onPointerOut={(e) => setHover(false)}>
      <boxBufferGeometry args={[1, 1, 1]} />
      <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
    </mesh>
  )
}

export const App = () => {
  return (
    <>
      <Canvas>
        <ambientLight intensity={0.5} />
        <spotLight position={[10, 10, 10]} angle={0.15} penumbra={1} />
        <pointLight position={[-10, -10, -10]} />
        <Box position={[-1.2, 0, 0]} />
        <Box position={[1.2, 0, 0]} />
      </Canvas>
    </>
  )
}
1 Answers

It's not much of an answer but I found this article useful: https://www.valentinog.com/blog/canvas/.

The suggestion is for proper testing of a canvas element one should not be using Jest (or any unit test) but instead, use some sort of e2e testing so that it can actually render the canvas.

I was able to get some smaller tests working in jest with the following:

Firstly render your page normally using jest. You can either give your canvas a test-id or (as in my case) put a div around your canvas and give the div a test-id.

I ended up with the following:

let canvasContainer = getByTestId("canvas-container");
let canvas = canvasContainer.getElementsByTagName("canvas")[0];
let canvasContext = canvas.getContext("2d");

In either case, your aim is to get the canvas' elements context.

From there you can inspect it with canvasContext.__getDrawCalls()

This will give you some information on all of the "drawCalls" that have been done so far (such as drawing lines or rectangles or what have you).

It takes a little of a bit of inspection into the __getDrawCalls but you should find that every call so far is included.

I wrote some funcitons to help me get the exact details I wanted:

const getLineInfo = (lineInfo) => {
    if (lineInfo.type !== "stroke") {
        return "not a line";
    }

    let lineInfoDetail = lineInfo.props.path[3];

    return {
        lineStart: {
            x: lineInfoDetail.transform[4],
            y: lineInfoDetail.transform[5],
        },
        lineEnd: {
            x: lineInfoDetail.transform[4] + lineInfoDetail.props.x,
            y: lineInfoDetail.transform[5] + lineInfoDetail.props.y,
        },
    };
};

const getRectInfo = (rectInfo) => {
    if (rectInfo.type !== "fill") {
        return "not a rect";
    }

    let rectInfoDetail = rectInfo.props.path[1];

    return {
        rectStart: {
            x: rectInfoDetail.transform[4],
            y: rectInfoDetail.transform[5],
        },
        rectEnd: {
            x: rectInfoDetail.transform[4] + rectInfoDetail.props.width,
            y: rectInfoDetail.transform[5] + rectInfoDetail.props.height,
        },
    };
};

So for example if I know that the second draw is a line I can do the following:

    expect(getLineInfo(canvasContext.__getDrawCalls()[1])).toStrictEqual({
        lineStart: { x: 150, y: 250 },
        lineEnd: { x: 150, y: 350 },
    });

This is testing that that element is a line with a start point of { x: 150, y: 250 } and an endpoint of { x: 150, y: 350 }

The issue with this though is that you can't trigger click and drag events of the canvas elements within jest and therefore using jest to test canvas seems to be very limited.

Edit: It looks like https://yonatankra.com/how-to-test-html5-canvas-with-jest/ gives another explanation.

Related