Jest.js 如何设置react-chartjs-2组件的可视化回归

smtd7mpg  于 2023-11-15  发布在  Jest
关注(0)|答案(2)|浏览(159)

我正在尝试使用React Testing库为react-chartjs-2组件设置可视化回归测试。然而,所有正在生成的快照都是空白的,但组件在浏览器中正确呈现。
这是我目前正在测试的,我基本上是把这篇博客文章examplereact-chartjs-2的饼图例子结合起来的。

import React from 'react';
import {generateImage, debug} from 'jsdom-screenshot';
import {render} from '@testing-library/react';
import {Pie} from "react-chartjs-2";

it('has no visual regressions', async () => {
    window.ResizeObserver =
        window.ResizeObserver ||
        jest.fn().mockImplementation(() => ({
            disconnect: jest.fn(),
            observe: jest.fn(),
            unobserve: jest.fn(),
        }));

    const data = {
        labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
        datasets: [
            {
                label: '# of Votes',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(255, 206, 86, 0.2)',
                    'rgba(75, 192, 192, 0.2)',
                    'rgba(153, 102, 255, 0.2)',
                    'rgba(255, 159, 64, 0.2)',
                ],
                borderColor: [
                    'rgba(255, 99, 132, 1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(255, 206, 86, 1)',
                    'rgba(75, 192, 192, 1)',
                    'rgba(153, 102, 255, 1)',
                    'rgba(255, 159, 64, 1)',
                ],
                borderWidth: 1,
            },
        ],
    };
    render(<div><Pie data={data}/></div>)
    expect(await generateImage()).toMatchImageSnapshot();
});

字符串
我想知道这是否是一个时间问题。在expect之前运行debug()会显示一个宽度和高度为0的画布:

<canvas
  height="0"
  role="img"
  style="display: block; box-sizing: border-box; height: 0px; width: 0px;"
  width="0"
/>

uujelgoq

uujelgoq1#

您正在使用的图表库react-chartjs-2 Package 了chart.js,它依赖于canvas元素将图表呈现到屏幕上。需要注意的是,画布内容不是DOM的一部分,而是虚拟地呈现在单个DOM canvas元素的顶部。
记住这一点,看看jsdom-screenshot如何渲染图像(从方法的解释):
我们使用jsdom来获取我们想要截图的HTML的状态。消费者可以使用jsdom轻松地将组件置于他们想要截图的状态。jsdom-screenshot然后使用标记(“HTML”)在那一刻(of that state). jsdom-screenshot启动本地web服务器,并将获得的标记作为index.html提供。然后jsdom-screenshot使用puppeteer来截图,使用无头的Google Chrome来截图该页面。
当你调用generateImage()时,你发布的HTML(只是一个空的canvas元素)被复制到一个文件中,index.html,在Puppeteer中打开,然后截图。因为canvas元素不包括它们的内容在它们的标记中,当HTML被复制到一个文件中时,图表就丢失了。
简而言之,jsdom-screenshot不支持在canvas元素上绘图
我建议您查看jest-puppeteer,它在Puppeteer中运行您的整个测试套件。它将使用实际的Chromium DOM实现,而不是使用虚拟化DOM实现jsdom,这具有更接近运行时使用的额外好处。您可以使用page.screenshot()来拍摄图表的屏幕截图,就像它在浏览器中显示的那样,具有完整的DOM支持(包括canvas)。请查看jest-puppeteer自述文件以开始使用。

mutmk8jj

mutmk8jj2#

为了使用jest和jsdom测试react-chartjs-2,您不需要模拟组件或模拟画布渲染。您可以对react组件进行可视化回归测试。我使用:

chart.js v4.4.0
react-chartjs-2 v5.2.0
jsdom v19.0.0
jsdom-screenshot v4.0.0
jest v27.4.3
jest-image-snapshot v5.2.0
@jest/globals v27.4.2
@testing-library/jest-dom v5.16.0
@testing-library/react v12.1.2

字符串
在我的jest.config.js中,我有:

testEnvironment: 'jsdom',
setupFilesAfterEnv: [
  '<rootDir>/jest.setup.afterEnv.js',
],


在我的jest.setup.afterEnv.js中,我有:

import { expect } from '@jest/globals';

import { configureToMatchImageSnapshot } from 'jest-image-snapshot';

const toMatchImageSnapshot = configureToMatchImageSnapshot({
  failureThresholdType: 'pixel',
  customSnapshotsDir: `${__dirname}/test/snapshots/`,
});

// extend Jest expect
expect.extend({ toMatchImageSnapshot });


通过上面的设置,我可以从通用<Canvas />组件的jest测试中获得一个图像。

const getCanvasSnapshot = (canvas: HTMLCanvasElement, failureThreshold = 0) => {
  const image = canvas.toDataURL();
  const data = image.replace(/^data:image\/\w+;base64,/, '');
  const snapshot = Buffer.from(data, 'base64');

  expect(snapshot).toMatchImageSnapshot({ failureThreshold });
};

describe('<Canvas />', () => {
  test('render into canvas', () => {
    // @see https://medium.com/@pdx.lucasm/canvas-with-react-js-32e133c05258
    const Canvas = (props: any) => {
      const { draw, ...rest } = props;
      const canvasRef = React.useRef(null);

      React.useEffect(() => {
        const canvas = canvasRef.current as unknown as HTMLCanvasElement;
        const context = canvas.getContext('2d');
        draw(context);
      }, [draw]);

      return <canvas ref={canvasRef} {...rest} />;
    };

    const App = () => {
      const draw = (ctx: CanvasRenderingContext2D) => {
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        ctx.fillStyle = '#0000ff';
        ctx.beginPath();
        ctx.arc(50, 50, 5, 0, 2 * Math.PI);
        ctx.fill();
      };

      return <Canvas draw={draw} width={100} height={100} />;
    };

    const { container } = render(<App />);

    const canvas = container.querySelector('canvas') as HTMLCanvasElement;
    getCanvasSnapshot(canvas);
  });
});


然后将其扩展到react-chartjs-2很简单。我遇到的几个问题。在浏览器模式下,Chart可以从父容器获取其大小,但当jest运行时,Chart无法获取大小。所以我必须设置

options.responsive = false; // only when jest is running


options.maintainAspectRatio的设置对我的应用程序来说是不变的。
然后我必须直接在Chart组件上设置宽度和高度

return (
    <Chart
      {...(isJestRunning() ? { width: '198' } : {})}
      {...(isJestRunning() ? { height: '202' } : {})}
      type="scatter"
      data={data as ChartData}
      options={options as ChartOptions}
      plugins={plugins}
    />
  );


然后jest/jsdom测试将产生一个图像。注意,<Plot />只是<Chart />的一个简单 Package 器。

describe('<Plot />', () => {
  let Wrapper: any;

  beforeEach(() => {
    window.ResizeObserver =
      window.ResizeObserver ||
      jest.fn().mockImplementation(() => ({
        disconnect: jest.fn(),
        observe: jest.fn(),
        unobserve: jest.fn(),
      }));

    // wrap the code with hooks; otherwise we get
    // Invalid hook call. Hooks can only be called inside the body of a function component. This could happen for one of the following reasons:
    Wrapper = () => (
      <Plot
        xScale={{ type: 'linear', min: 0, max: 1 }}
        yScale={{ type: 'linear', min: 0, max: 1 }}
        ...
        ...
      />
    );
  });

  afterEach(cleanup);

  test('render component', () => {
    const { container } = render(<Wrapper />);

    const canvas = container.querySelector('canvas') as HTMLCanvasElement;

    // make sure the canvas has the correct size
    expect(canvas.width).toEqual(198);
    expect(canvas.height).toEqual(202);

    // get an image of the canvas
    getCanvasSnapshot(canvas);
  });
});


希望这对你有帮助。

相关问题