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

Vitest + act raising warnings #4640

Open
BreakBB opened this issue Mar 26, 2024 · 10 comments
Open

Vitest + act raising warnings #4640

BreakBB opened this issue Mar 26, 2024 · 10 comments

Comments

@BreakBB
Copy link
Contributor

BreakBB commented Mar 26, 2024

Describe the bug

When running tests using act that interact with react-datepicker a warning is raised saying:

Warning: The current testing environment is not configured to support act(...)

This warning does not appear when not interacting with the datepicker.

To Reproduce
Steps to reproduce the behavior:

I tried to created a minimal reproducible example in this repo: https://github.com/BreakBB/act-test

So clone the repo, run npm ci and run the two tests in App.test.js:

  1. "should allow act" which interacts with the datepicker and raises the warning
  2. "should allow act without date picker" which renders the same component, but does not interact with the datepicker and does not raise the warning.

Expected behavior
I want both tests to continue passing, but without the warning message.

Dependencies

Node 20.11.1
react: 18.2.0
react-datepicker: 6.6.0
vite: 5.2.6
vitest: 1.4.0
@testing-library/jest-dom: 6.4.2
@testing-library/react: 14.2.2

@yuki0410-dev
Copy link
Contributor

@BreakBB
I think user-event@14 is an asynchronous method and does not need to be wrapped in act.
By modifying the repository you have currently provided as follows, the test will complete without warning.

describe('App', () => {
    it('should allow act', async () => {
        render(<App />);

        // await act(async () => {
            await userEvent.type(screen.getByRole('textbox'), '1');
        // });

        expect(screen.getByRole('textbox')).toHaveValue('03/26/20241');
    });

    it('should allow act without date picker', async () => {
        render(<App />);

        await act(async () => {
            await userEvent.click(screen.getByText(/count is 0/i));
        });

        expect(screen.getByText(/count is 1/i)).toBeDefined();
    });
});

testing-library/user-event#497

@BreakBB
Copy link
Contributor Author

BreakBB commented Mar 26, 2024

Thanks for your response @yuki0410-dev

You are right, that the test will stay green without a warning, when removing the act.

However in a more complex setup, the act can be required (if I understand it correctly) and these tests were only created for reproduction purpose.

I am confused this warning is only raised when interacting with the datepicker. Not with a default <button /> and neither with a default <input /> (both setting a state).

@yuki0410-dev
Copy link
Contributor

@BreakBB Thanks for the reply.

Since there is a similar issue in the RTL issue, I suspect that it is not a react-datepicker-specific issue, but rather occurs when testing a component that is updating the state internally. (asynchronously?).
Probably does not occur for buttons and inputs because they do not have an internal state.

testing-library/user-event#1104
testing-library/react-testing-library#1061

It may also occur when combining act and userEvent.
(I can't find the source in English, sorry for the Japanese blog)
https://de-milestones.com/globalthis_is_react_act_environment-not_working/

@BreakBB
Copy link
Contributor Author

BreakBB commented Mar 27, 2024

but rather occurs when testing a component that is updating the state internally.

I also think it is related to this.

The posts you linked (except for the Japanese one) are the sources I also found and which I tried to match on the behavior of react-datepicker.

In my actual test setup, I am facing the issue, that I can not wait for any UI element to update, because the warning is raised while typing to the date input.

I'll try to produce an example where the datepicker raises Warning: An update to DatePicker inside a test was not wrapped in act(...), to see if that is related to any specific prop I am using in my actual test.

@yuki0410-dev
Copy link
Contributor

I don't know if this will help, but I encountered a similar case in this Repository test, but I got around it by putting expect in waitFor.

@BreakBB
Copy link
Contributor Author

BreakBB commented May 29, 2024

We finally managed to work around these issues and were also able further pin the actual issue down.

In our actual app (not the repo I linked above), we were typing a date and then used await userEvent.tab() to trigger the validation of that field:

const DeliveryDateInput = forwardRef(({ hasError, ...rest }: { hasError: boolean }, ref) => (
    <Tooltip title={hasError ? "Please enter a delivery date" : ""}>
        <TextField {...rest} inputRef={ref} label="Delivery Date" size="small" error={hasError} />
    </Tooltip>
));

This DeliveryDate component is handed to the customInput prop of the DatePicker.

However, when removing the onKeyDown function from rest and not handing it to the TextField, no act-warning is raised. That removes a lot of functionality though (obviously).

Now to work around the warnings, we changed the things we tested and no longer use await userEvent.tab(). For example instead of testing that an invalid date will be removed after pressing Tab/Enter, we test that the date can not be clicked in the DatePicker. That is also closer to what most of our users do (clicking instead of typing/pasting dates).

So from my side this issue can be closed as we no longer have any act-warnings. Even though we didn't manage to fully understand what is causing the issue.

@commanderz
Copy link

this is special solution for datepicker from @mui, maybe work good for other case:

// old code:
// import { act } from "@testing-library/react"
// new code:
import { waitFor } from "@testing-library/react"
// old code:
//await act(async () => {
 //           await userEvent.click(screen.getByText(/count is 0/i));
//});
// new code:
await waitFor(async () => {
           await userEvent.click(screen.getByText(/count is 0/i));
});

@BreakBB
Copy link
Contributor Author

BreakBB commented Aug 8, 2024

This is not a solution @commanderz - This is a workaround, because it will just cover the issue by an additional wait. Using waitFor in combination with userEvent.click is a bad practice.

@sm3sher
Copy link

sm3sher commented Sep 22, 2024

I encountered the same issue, and it only appeared when using await userEvent.tab(). I do not wrap await userEvent.type(dateInput) inside an act. While I believe this warning shouldn’t be triggered, I found that in a form validation context, using await userEvent.click(submitButton) was sufficient to make the validation work and eliminate the warning.

However, this workaround doesn’t seem ideal. I’m still unsure why this discrepancy arises, but it seems tied to how the state is managed asynchronously within the datepicker component.

@BreakBB
Copy link
Contributor Author

BreakBB commented Sep 26, 2024

After the latest update to react-datepicker I encountered new act warnings. After a long day of pulling out hair, I now seem to have found the underlying issue on my side.

The DeliveryDateInput I posted above is used as customInput for the DatePicker. However, I am using the it in a form environment (useForm) and therefore the form is in charge of handling everything:

export const DeliveryDateFormField = ({
    name,
    methods,
    required,
    hasError,
}: Props) => (
    <Controller
        name={name}
        control={methods.control}
        rules={{
            validate: value => (required ? !!value && isFutureDateAndTime(value) : true),
        }}
        render={({ field: { onChange, onBlur, value } }) => {
            const selectedDate = new Date(value);
            return (
                <StyledDatePicker
                    selected={!value ? null : selectedDate}
                    onChange={onChange}
                    onBlur={onBlur}
                    locale="de"
                    dateFormat="dd.MM.yyyy HH:mm"
                    customInput={<DeliveryDateInput hasError={hasError} />}
                    showTimeSelect
                    timeIntervals={15}
                    minDate={new Date()}
                    minTime={getMinTime(name, selectedDate)}
                    maxTime={new Date(0, 0, 0, 18, 0)}
                    timeCaption={"Uhrzeit"}
                    sx={{
                        height: "2.5rem",
                        border: "none",
                        width: "100%",
                        marginBottom: "0.75rem",
                    }}
                />
            );
        }}
    />
);

I got rid of my act warnings by NOT handing the ref to the TextField, that is used inside DeliveryDateInput:

- const DeliveryDateInput = forwardRef(({ hasError, ...rest }: { hasError: boolean }, ref) => (
+ const DeliveryDateInput = forwardRef(({ hasError, ...rest }: { hasError: boolean }) => (
    <Tooltip title={hasError ? "Please enter a delivery date" : ""}>
-        <TextField {...rest} inputRef={ref} label="Delivery Date" size="small" error={hasError} />
+        <TextField {...rest} label="Liefertermin" size="small" error={hasError} />
    </Tooltip>
));

In my head this makes sense, because by wrapping the DatePicker in a form-control, I bind these together and want the form to take care of handling the change events and stuff. But when handing the ref, I also bind the DatePicker to the TextField, that might cause loops and additional events, which are not required. I still need to use forwardRef as react-datepicker expects it.

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

4 participants