react-webcam icon indicating copy to clipboard operation
react-webcam copied to clipboard

Jest tests show unstable_flushDiscreteUpdates warning in 6.0.0

Open shiraze opened this issue 3 years ago • 7 comments

Is anyone else seeing a warning similar to the following when upgrading from v5.2.4 to v6.0.0?

PASS src/components/imageCaptureDialog/imageCaptureControlMount.test.jsx ● Console

console.error
  Warning: unstable_flushDiscreteUpdates: Cannot flush updates when React is already rendering.
      in video (created by Webcam)
      in Webcam (at imageCaptureControl.jsx:163)
      in div (at imageCaptureControl.jsx:162)
      in ForwardRef (created by WrapperComponent)
      in WrapperComponent

  at printWarning (node_modules/react-dom/cjs/react-dom.development.js:88:30)
  at error (node_modules/react-dom/cjs/react-dom.development.js:60:5)
  at flushDiscreteUpdates (node_modules/react-dom/cjs/react-dom.development.js:21817:9)
  at flushDiscreteUpdatesIfNeeded (node_modules/react-dom/cjs/react-dom.development.js:829:5)
  at dispatchDiscreteEvent (node_modules/react-dom/cjs/react-dom.development.js:4167:3)
  at HTMLVideoElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
  at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:338:25)
  at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:274:3)

Code/tests remain unchanged, but worked fine with v5.2.4

shiraze avatar Aug 14 '21 19:08 shiraze

same error here, @shiraze any solution for this problem?

worked fine with v5.2.4 too.

mateusdearaujo avatar Aug 18 '21 15:08 mateusdearaujo

I'm currently using Enzyme, but we're in the process of moving some tests to Testing-library. If that helps, I'll update this thread

shiraze avatar Aug 18 '21 20:08 shiraze

@shiraze we're still getting the exact warning while using React Testing Library as well, so I would say it's not due to the testing library used.

Anyone encountered this so far and has a solution?

sorinpav avatar Feb 21 '22 18:02 sorinpav

We got this working in Enzyme by adding the following mock:

    Object.defineProperty(HTMLMediaElement.prototype, "muted", {
      set: () => {},
    });

shiraze avatar Feb 21 '22 19:02 shiraze

@shiraze - really appreciate that snippet! I set me down a rabbit hole for a few days, but posting our solution here. Essentially what we wanted for our testing was a way to mock a dummy screenshot value. This ended up meaning we had to mock a few HTML element properties that this library uses internally, which may be too much hacking, but worked for us. We ended up writing this helper method:

const mockReactWebcamScreenshot = ({ image, height, width }) => {
  const videoHeight = height || 360;
  const videoWidth = width || 720;

  // This is necessary to avoid an `unstable_flushDiscreteUpdates` warning
  Object.defineProperty(HTMLMediaElement.prototype, 'muted', {
    set: () => {},
  });

  // Here we're mocking fake media devices so the webcam can return a screenshot
  const fakeMedia = {
    stop: () => {}
  }

  const mockMediaDevices = {
    getUserMedia: jest.fn().mockResolvedValue(fakeMedia),
  };

  Object.defineProperty(window.navigator, 'mediaDevices', {
    value: mockMediaDevices,
  });

  Object.defineProperty(HTMLVideoElement.prototype, 'videoHeight', {
    get: () => videoHeight,
  });

  Object.defineProperty(HTMLVideoElement.prototype, 'videoWidth', {
    get: () => videoWidth,
  });

  const mockCanvasRenderingContext = {
    drawImage: () => {},
    translate: () => {},
    scale: () => {},
  }

  jest.spyOn(HTMLCanvasElement.prototype, 'getContext')
    .mockImplementation(() => mockCanvasRenderingContext)

  jest.spyOn(HTMLCanvasElement.prototype, 'toDataURL')
    .mockImplementation(() => image)
}

And then as an example of a test we have for our component which uses this library, we have:

import React from 'react';
import { render, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

const dummyImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII';

beforeEach(() => {
  mockReactWebcamScreenshot({ image: dummyImage })
});

afterEach(() => {
  jest.restoreAllMocks();
});

describe('WebcamPhoto', () => {
  it('submits screenshot on Capture', async () => {
    const submitSpy = jest.fn();
    const { getByRole } = render(<WebcamPhoto onSubmit={submitSpy} />);

    let captureButton = getByRole('button', { name: 'Capture photo' });
    expect(captureButton).toBeInTheDocument();

    userEvent.click(captureButton);

    expect(submitSpy).toHaveBeenCalledTimes(1);
    expect(submitSpy).toHaveBeenCalledWith(dummyImage);
  });
});

I'd love some feedback on this approach, and would also be curious if it makes sense to include some kind of test helper like this within this library so if the internal implementation changes we could keep the test helper in sync with the code?

brycesenz avatar Feb 02 '23 19:02 brycesenz

Hi @brycesenz I'm no longer involved in the project where I first encountered this issue. @sorinpav is, though, and I'm not sure whether the test we used to have in Enzyme has now been moved to React Testing Library. With RTL, though, it's best to avoid mocking (that's the whole point, innit?!), but maybe there's no way out for this scenario.

shiraze avatar Feb 02 '23 21:02 shiraze

@shiraze - I hear your point about trying to avoid mocking; I just don't know another approach that is going to work with JSDom. Hoping others might see this and chime in with something better 🤷‍♂️

brycesenz avatar Feb 02 '23 21:02 brycesenz