Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

'event was not wrapped in act' warning with React 18, react-datepicker and user.type #1231

Open
pzaczkiewicz-athenahealth opened this issue Aug 10, 2023 · 5 comments

Comments

@pzaczkiewicz-athenahealth

Relevant code or config:

describe('ReactDatepicker', () => {
  beforeEach(() => (user = userEvent.setup()));

  it('should not emit act warnings', async () => {
    const now = new Date();
    const endOfNextYear = new Date(now.getFullYear() + 1, 11, 31);
    const dateFormat = 'M/d/yyyy';
    const userFormattedDate = format(endOfNextYear, dateFormat);
    const defaultFormattedDate = format(endOfNextYear, defaultDateFormat);

    render(<ReactDatepicker />);
    const input = screen.getByRole('textbox');
    await user.type(input, `${userFormattedDate}{Enter}`);
    expect(await screen.findByDisplayValue(defaultFormattedDate)).toBeInTheDocument()
  });

  it('should pass with fireEvent', async () => {
    const now = new Date();
    const endOfNextYear = new Date(now.getFullYear() + 1, 11, 31);
    const dateFormat = 'M/d/yyyy';
    const userFormattedDate = format(endOfNextYear, dateFormat);
    const defaultFormattedDate = format(endOfNextYear, defaultDateFormat);

    render(<ReactDatepicker />);
    const input = screen.getByRole('textbox');
    fireEvent.change(input, { target: { value: userFormattedDate } });
    fireEvent.keyDown(input, { key: 'Enter' });
    expect(await screen.findByDisplayValue(defaultFormattedDate)).toBeInTheDocument()
  });
});

What you did:

use userEvent.type when targeting react-datepicker to enter a date. The weird thing is the "not wrapped in act" error only appears when a valid date is typed and the {Enter} key is pressed. This is apparency causing some special condition to happen within the implementation of react-datepicker which userEvent isn't act wrapping appropriately.

What happened:

  console.error
    Warning: An update to r inside a test was not wrapped in act(...).

    When testing, code that causes React state updates should be wrapped into act(...):

    act(() => {
      /* fire events that update state */
    });
    /* assert on the output */

    This ensures that you're testing the behavior the user would see in the browser. Learn more at https://reactjs.org/link/wrap-tests-with-act
        at r (C:\git\pzaczkiewicz\forge-contrib-react-datepicker-user-event-test-failure\.yarn\__virtual__\react-datepicker-virtual-69b8f1e761\0\cache\react-datepicker-npm-4.16.0-c94778dc5d-59da0305ca.zip\node_modules\react-datepicker\dist\index.js:1:78951)
        at ReactDatepicker (C:\git\pzaczkiewicz\forge-contrib-react-datepicker-user-event-test-failure\packages\react-datepicker-user-event-test-failure\src\ReactDatepicker\ReactDatepicker.tsx:7:45)

Reproduction:

https://github.com/pzaczkiewicz-athenahealth/react-datepicker-user-event-test-failure

Problem description:

Not wrapped in act warnings are usually good indications that what you are testing is not what the user sees. Ignoring them may hide bigger issues in tests.

Suggested solution:

Unknown. I dug into testing-library source code and couldn't completely wrap my head around why asyncWrapper and eventWrapper flip-flopped IS_REACT_ACT_ENVIRONMENT.

@pzaczkiewicz-athenahealth
Copy link
Author

Similar issue as #1216, but this happens in the middle of await user.type instead of upon unmount.

@chawes13
Copy link

chawes13 commented Sep 14, 2023

I'm experiencing this issue as well after upgrading from @testing-library/react@^12 to @testing-library/react@^14 and react@^17 to react@^18.

Here's my stacktrace:

      at printWarning (node_modules/react-dom/cjs/react-dom.development.js:86:30)
      at error (node_modules/react-dom/cjs/react-dom.development.js:60:7)
      at warnIfUpdatesNotWrappedWithActDEV (node_modules/react-dom/cjs/react-dom.development.js:27589:9)
      at scheduleUpdateOnFiber (node_modules/react-dom/cjs/react-dom.development.js:25508:5)
      at Object.enqueueSetState (node_modules/react-dom/cjs/react-dom.development.js:14067:7)
      at r.Object.<anonymous>.Component.setState (node_modules/react/cjs/react.development.js:354:16)
      at node_modules/react-datepicker/dist/index.js:1:82654
      at HTMLUnknownElement.callCallback (node_modules/react-dom/cjs/react-dom.development.js:4164:14)
      at HTMLUnknownElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
      at HTMLUnknownElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
      at HTMLUnknownElementImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:104:17)
      at HTMLUnknownElement.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:241:34)
      at Object.invokeGuardedCallbackDev (node_modules/react-dom/cjs/react-dom.development.js:4213:16)
      at invokeGuardedCallback (node_modules/react-dom/cjs/react-dom.development.js:4277:31)
      at invokeGuardedCallbackAndCatchFirstError (node_modules/react-dom/cjs/react-dom.development.js:4291:25)
      at executeDispatch (node_modules/react-dom/cjs/react-dom.development.js:9041:3)
      at processDispatchQueueItemsInOrder (node_modules/react-dom/cjs/react-dom.development.js:9073:7)
      at processDispatchQueue (node_modules/react-dom/cjs/react-dom.development.js:9086:5)
      at dispatchEventsForPlugins (node_modules/react-dom/cjs/react-dom.development.js:9097:3)
      at node_modules/react-dom/cjs/react-dom.development.js:9288:12
      at batchedUpdates$1 (node_modules/react-dom/cjs/react-dom.development.js:26140:12)
      at batchedUpdates (node_modules/react-dom/cjs/react-dom.development.js:3991:12)
      at dispatchEventForPluginEventSystem (node_modules/react-dom/cjs/react-dom.development.js:9287:3)
      at dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay (node_modules/react-dom/cjs/react-dom.development.js:6465:5)
      at dispatchEvent (node_modules/react-dom/cjs/react-dom.development.js:6457:5)
      at dispatchDiscreteEvent (node_modules/react-dom/cjs/react-dom.development.js:6430:5)
      at HTMLDivElement.callTheUserObjectsOperation (node_modules/jsdom/lib/jsdom/living/generated/EventListener.js:26:30)
      at innerInvokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:350:25)
      at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:286:3)
      at HTMLInputElementImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:233:9)
      at Object.exports.fireFocusEventWithTargetAdjustment (node_modules/jsdom/lib/jsdom/living/helpers/focusing.js:103:10)
      at HTMLInputElementImpl.blur (node_modules/jsdom/lib/jsdom/living/nodes/HTMLOrSVGElement-impl.js:79:14)
      at HTMLInputElement.blur (node_modules/jsdom/lib/jsdom/living/generated/HTMLElement.js:126:34)
      at r.setBlur (node_modules/react-datepicker/dist/index.js:1:81473)

It looks like the function that calls setState is coming from setOpen on blur of the input.

Could it be possible that their use of nested second args to setState (e.g., https://github.com/Hacker0x01/react-datepicker/blob/3b6ff0163514facbfd6d38a84e1fe874c4388d77/src/index.jsx#L439) is not getting flushed with act? TBH I'm not exactly sure how I would go about testing that theory.

Here's another repro (if that's helpful): LaunchPadLab/lp-components#613

@chawes13
Copy link

I also tried this with waitFor (below) as well as invoking cleanup and unmount at the end of this specific test to no avail.

  user.type(input, '02/02/2023{Enter}')
  await waitFor(() => {
    expect(screen.queryByLabelText('Next Month')).not.toBeInTheDocument()
    expect(input).toHaveValue('02/02/2023')
  })

@pzaczkiewicz-athenahealth
Copy link
Author

Could it be possible that their use of nested second args to setState (e.g., https://github.com/Hacker0x01/react-datepicker/blob/3b6ff0163514facbfd6d38a84e1fe874c4388d77/src/index.jsx#L439) is not getting flushed with act? TBH I'm not exactly sure how I would go about testing that theory.

Good find! That's definitely some non-standard behavior. Hopefully one of the testing-library maintainers is able to figure out what to do.

The error is definitely coming from within user.type, because I've mocked console.error as such:

const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {
  return;
});

describe('DateInput', () => {
  beforeEach(() => {
    user = userEvent.setup();
    consoleError.mockClear();
  });
  afterEach(() => {
    expect(consoleError).not.toBeCalled();
  });

I can then check for its usage in my test:

      await user.type(inputField, `${userFormattedDate}{Enter}`);
      // Ignoring act warnings in this instance because of a testing-library bug:
      // https://github.com/testing-library/react-testing-library/issues/1216
      expect(consoleError).toBeCalled();
      consoleError.mockClear();

I want my tests to fail if a console error happens because they are usually indicative of behavior I need to correct. This is the only way around the bug while still giving me a clean console.

@KutynaMateusz
Copy link

Any update on this? I'm having a similar issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants