romgain / react-select-event

🦗 Simulate react-select events for react-testing-library
MIT License
210 stars 24 forks source link

v5.5.1 introduces act-type warning in tests #111

Open curtvict opened 2 years ago

curtvict commented 2 years ago

Hey! I just wanted to respectfully point out that the v5.5.1 patch might have actually brought along a breaking change, depending on your definition of "breaking change" ;)

We use Mind Renovate to manage our package upgrades automatically. We also use jest-fail-on-console to help keep on top of both, what I call, our "test barf" and to help us be a bit more careful about what non-critical warnings and errors might be raised and subsequently ignored by us during development.

Anyway, when Renovate tried to upgrade to react-select-event to v5.5.1 we started getting a failing build on that branch due to a new act-style warning being thrown... you know the one:

    Warning: An update to WhateverComponentLol 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 */

... snip ...

I just wanted to point this out because it was unexpected in a patch release and the removal of the act call seems like the entire reason why this patch was released in the first place.

We're on react 17.0.2 and @testing-library/react 12.1.5 for what it's worth which seems to meet this project's devDependencies requirements.

curtvict commented 2 years ago

cc: @romgain

geoffharcourt commented 2 years ago

We are seeing the same issue with React 17.0.2 and @testing-library/react 12.1.5.

bramdevries commented 2 years ago

Do you happen to be able to provide a reproducible test case? The tests in this package still use React 17 and there's no such warning, so it might only be occurring for specific usage?

antonvialibri1 commented 2 years ago

@curtvict

We get a similar issue when using Formik together with react-select (React 18, though I don't know if this happens on React 17 too):

import selectEvent from 'react-select-event';
// ...

// In test:
await selectEvent.select(
  select,
  ['TagA', 'TagB', 'TagC']
);
expect(screen.getByText('TagA')).toBeInTheDocument();

The test passes, but we get the annoying act warning:

    Warning: An update to Formik 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 */

The react-select component we are testing uses Formik under the hood and changes its state using the helpers returned by the Formik's useField hook:

  // In react-select component
  const [field, meta, helpers] = useField(props);

  const handleChange = (newValue) => {
    helpers.setValue(newValue);
  };

  // ...
  return (
    <Select
      onChange={handleChange}
      ...
     />
  );

@romgain Is there a solution to this? Thank you!

antonvialibri1 commented 2 years ago

@curtvict

I have found a workaround for now, I'm sharing it here so hopefully it helps you as well.

The following code doesn't cause the dreaded act warning:

import selectEvent from 'react-select-event';
import { act, render, screen, waitFor } from '@testing-library/react';
// ...

// In test:
await act(async () => 
  await selectEvent.select(
    select,
    ['TagA', 'TagB', 'TagC']
  );
);
expect(screen.getByText('TagA')).toBeInTheDocument();
expect(screen.getByText('TagB')).toBeInTheDocument();
expect(screen.getByText('TagC')).toBeInTheDocument();

With this code, there's no act warning for me.

☝️ But... The test above didn't pass. I noticed that when you pass an array of options to selectEvent.select like I did with ['TagA', 'TagB', 'TagC'], only the last options ends up being added to the select for some reason (in my case as I'm using React 18, I suppose that it has to do with React 18's Auto Batching, but I'm not sure as I didn't dig deeper).

So what I did was selecting one option at a time, and ended up with this working code:

import selectEvent from 'react-select-event';
import { act, render, screen, waitFor } from '@testing-library/react';
// ...

// In test:
const select = screen.getByRole('textbox', {
  name: /Tags/i
});

// Select one option at a time, reopening the react-select's dropdown each time.
// After all, that's how our users will end up using this component...
selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagA'
  );
);

selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagB'
  );
);

selectEvent.openMenu(select);
await act(async () => 
  await selectEvent.select(
    select,
    'TagC'
  );
);

expect(screen.getByText('TagA')).toBeInTheDocument();
expect(screen.getByText('TagB')).toBeInTheDocument();
expect(screen.getByText('TagC')).toBeInTheDocument();

A bit of boilerplate, but at least it works and doesn't give any error/warning in my case.

Hope it helps someone who's facing a similar issue.

nntdias commented 2 years ago

@antonvialibri1 in your code you missed the async
await act( ASYNC HERE () => await selectEvent.select( select, 'TagC' ); );

Talking about the warning messages I tried using the await waitFor(() => selectEvent.select(element, 'value')), and it works here.

antonvialibri1 commented 2 years ago

@nntdias Thanks for pointing that out, I updated the code snippets.

MarkusAbtion commented 1 year ago

All my tests are passing, but after using react-select-event I've started seeing the following warning in unrelated tests:

console.error
      Warning: The current testing environment is not configured to support act(...)
huarmenta commented 11 months ago

This works but prints "Warning: An update to Select inside a test was not wrapped in act(...)."

await selectEvent.select(element, 'label');

This works but prints "Warning: You called act(async () => ...) without await. This could lead to unexpected testing behaviour, interleaving multiple act calls and mixing their scopes. You should - await act(async () => ...);"

act(async () => {
  await selectEvent.select(element, 'label');
});

This does NOT work at all

await act(async () => {
  await selectEvent.select(element, 'label');
});

This does NOT work too (with or without the waitFor)

await waitFor(async () => {
  await selectEvent.select(element, 'label');
});

Not even opening the select before selecting works.

Any help here?

dennisoelkers commented 10 months ago

We are seeing the same problems and need to change lots of tests in order to be able to update. Is this really necessary or a regression that we can expect to get fixed at some point?

tgsoverly commented 2 months ago

This worked for me:

  await act(() => selectEvent.select(selectWrapper, 'Name'));
az-oolloow commented 1 week ago

While this works for select it doesn't work for clearAll method, that prints the warning no matter what I tried :(