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

.clear in v14 produces an act warning when used with React hook form. #938

Closed
mikaelrss opened this issue Apr 21, 2022 · 11 comments · Fixed by #952
Closed

.clear in v14 produces an act warning when used with React hook form. #938

mikaelrss opened this issue Apr 21, 2022 · 11 comments · Fixed by #952
Labels
bug Something isn't working released

Comments

@mikaelrss
Copy link

Reproduction example

https://github.com/mikaelrss/rhf-rtl-act-bug-repro

Prerequisites

  1. git clone git@github.com:mikaelrss/rhf-rtl-act-bug-repro.git
  2. cd rhf-act-bug-repro
  3. yarn install
  4. yarn test --> See that the tests pass without any act warnings.
  5. git checkout user-event@14
  6. yarn install
  7. yarn test --> See that the tests pass but now with an act warning.

Expected behavior

I expect that the test below passes without any act warnings.

it("should show an error message when title field is cleared", async () => {
  render(<Form />);

  expect(screen.queryByText("Title is required")).not.toBeInTheDocument();

  await userEvent.clear(screen.getByLabelText("Title"));

  expect(await screen.findByText("Title is required")).toBeInTheDocument();
});

Actual behavior

The following test produces an act warning.

it("should show an error message when title field is cleared", async () => {
  render(<Form />);

  expect(screen.queryByText("Title is required")).not.toBeInTheDocument();

  await userEvent.clear(screen.getByLabelText("Title"));

  expect(await screen.findByText("Title is required")).toBeInTheDocument();
});

Wrapping the await userEvent.clear(screen.getByLabelText("Title")) in an act removes the warning.

it("should show an error message when title field is cleared", async () => {
  render(<Form />);

  expect(screen.queryByText("Title is required")).not.toBeInTheDocument();

  await act(async () => {
    await userEvent.clear(screen.getByLabelText("Title"));
  });

  expect(await screen.findByText("Title is required")).toBeInTheDocument();
});

This works as expected with @testing-library/user-event@13.2.1 but breaks in @testing-library/user-event@14.1.1

User-event version

14.1.1

Environment

Testing Library framework: @testing-library/react@13.0.0

JS framework: react@18.0.0 and react-hook-form@7.30.0

Test environment: react-scripts@5.0.1 which uses jest@27.5.1

Additional context

Working version on main branch of repro is @testing-library/user-event@13.2.1

@mikaelrss mikaelrss added bug Something isn't working needs assessment This needs to be looked at by a team member labels Apr 21, 2022
@ph-fritsche
Copy link
Member

Thanks for the report. ❤️

This is an issue with the state update happening during the first await after the input event.
Here is a shortened version showcasing what happens:

import { createEvent, getConfig, render, screen } from "@testing-library/react";
import { eventMap } from '@testing-library/dom/dist/event-map.js'
import Form from "./Form";

const config = getConfig()
const originalAsyncWrapper = config.asyncWrapper
config.asyncWrapper = cb => {
  console.log('start asyncWrapper', cb)
  const p = originalAsyncWrapper(cb)
  console.log('end asyncWrapper', cb)
  return p
}

it("should show an error message when title field is cleared", async () => {
  render(<Form />);

  expect(screen.queryByText("Title is required")).not.toBeInTheDocument();

  const input = screen.getByLabelText<HTMLInputElement>("Title")
  expect(input).toHaveValue('A title')

  await clearApi(input)

  console.log('after API')

  expect(await screen.findByText("Title is required")).toBeInTheDocument()
});

async function clearApi(el: HTMLInputElement) {
  const p = config.asyncWrapper(() => clearImpl(el))
  console.log('after asyncWrapper')

  // await here let's the state update happen
  await p
}

async function clearImpl(input: HTMLInputElement) {
  // This happens too but is irrelevant for the issue:

  // input.focus()
  // input.setSelectionRange(0, input.value.length)

  // config.eventWrapper(() => {
  //   input.dispatchEvent(createEvent('beforeinput', input, {
  //     data: '',
  //     inputType: 'deleteContentBackward',
  //   }, eventMap['input']))
  // })

  Object.getOwnPropertyDescriptor(
    Object.getPrototypeOf(input),
    'value',
  )?.set?.call(input, '')

  config.eventWrapper(() => {
    input.dispatchEvent(createEvent('input', input, {
      data: '',
      inputType: 'deleteContentBackward',
    }, eventMap['input']))
  })

  // This line makes the state update happen durinc asyncWrapper
  // await Promise.resolve()
}

Maybe we should add a wait() call after every API. See #659 (comment)

@ph-fritsche ph-fritsche removed the needs assessment This needs to be looked at by a team member label Apr 21, 2022
@dackmin
Copy link

dackmin commented Apr 28, 2022

While waiting for a fix, you can monkey-patch calls that produce act warnings inside, well, act calls 😄 :

import userEvent from '@testing-library/real-user-event';
import { act } from '@testing-library/react';

const original = {
  click: userEvent.click,
  clear: userEvent.clear,
  type: userEvent.type,
};

userEvent.click = async (...args) =>
  await act(async () => await original.click(...args));

userEvent.clear = async (...args) =>
  await act(async () => await original.clear(...args));

userEvent.type = async (...args) =>
  await act(async () => await original.type(...args));

export default userEvent;

And map your module inside your jest config:

moduleNameMapper: {
  '^@testing-library/user-event$': '<rootDir>/tests/act-user-event.js',
  '^@testing-library/real-user-event$': require
    .resolve('@testing-library/user-event'),
},

@github-actions
Copy link

🎉 This issue has been resolved in version 14.2.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@ph-fritsche
Copy link
Member

@all-contributors add @mikaelrss bug

@allcontributors
Copy link
Contributor

@ph-fritsche

I've put up a pull request to add @mikaelrss! 🎉

@mikaelrss
Copy link
Author

Thank you for this! 🎉 This solves all the issues I experienced with act-warnings when doing userEvent.clear.

@nemanjam
Copy link

nemanjam commented Jun 12, 2022

Why simple clear() and type() in field does not work? Such a simple and trivial thing and it does not work, it creates race while typing. Very annoying and time wasting. Version: "@testing-library/user-event": "^14.2.0".

image

Example from docs is broken.

image

@ph-fritsche
Copy link
Member

ph-fritsche commented Jun 12, 2022

Why simple clear() and type() in field does not work?

Might be a bug in the component. Might be a bug in the environment. Might be a bug here.
I guess we will never know without a proper bug report with a reproduction example.

Such a simple and trivial thing [and instead there are useless cross posts on unrelated issues]. Very annoying and time wasting.

@nemanjam
Copy link

I also use unstable React Hook Form "react-hook-form": "^8.0.0-alpha.4" which is unstable with React 18, that might be problem. It has other bugs with state update too.

Here is screenshot of the exception: #970

@nemanjam
Copy link

More info about this bug is that the input field is frozen for first 2 characters (interactions). Just typing a bit in the field fixes it. Either userEvent v14 or React Hook Form v8 are broken.

// NOTE: this fixes a bug in userEvent.clear() or React Hook Form
// field is frozen for first 2 characters
// user0 name + 123 -> user0 name3
await userEvent.type(nameInput, '123');

// edit name
await userEvent.clear(nameInput);
expect(nameInput).toHaveValue(''); // now it works

@magnattic
Copy link

magnattic commented Jul 9, 2022

For me, this error reappears when I update

from
"@testing-library/dom": "8.13.0",
to
"@testing-library/dom": "8.14.0",

while using

    "@testing-library/jest-dom": "5.16.4",
    "@testing-library/react": "13.3.0",
    "@testing-library/user-event": "14.2.1",

Any idea what may cause this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working released
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants