user-event icon indicating copy to clipboard operation
user-event copied to clipboard

userEvent.type not working in v14

Open yasin459 opened this issue 2 years ago • 28 comments

Reproduction example

zip file listed bellow

Prerequisites

please download this project demo and run it using npm install react-testing-library-bug.zip

it has 2 tests: 1- testThatFails 2- testThatWorks

in thre first example, test fails because and can not type into coponent using userEvent.type:

image code for failing test:


const ExampleThatFails = () => {
  const [state, dispatch] = useReducer(reducer, [
    {
      name: "Age",
      show: true,
      filterValueType: "integer",
      value: ["", ""],
      optionType: "exact",
      key: "age",
    }
  ]);

  return (
    <div>
      <FilterCombo filters={state} dispatch={dispatch} />
      <FilterItem dispatch={dispatch} idx={0} filter={state[0]} />
    </div>
  );
};

it.each(Array(100).fill(null))("ExampleThatFails", async () => {
  render(<ExampleThatFails />);

  const AgeFilterOption = screen.getAllByTestId("filter-item")[0];
  const AgeSelectBox = within(AgeFilterOption).getByRole("button", {
    name: "exact",
  });
  await userEvent.click(AgeSelectBox);
  const AllAgeOptions = within(AgeFilterOption).getAllByRole("option");

  await userEvent.click(AllAgeOptions[1]);

  const numberInputs = screen.getAllByRole("spinbutton");

  await userEvent.type(numberInputs[0], "12333");
  expect(numberInputs[0]).toHaveDisplayValue("12333");
},10000);


code for working test:


const ExampleThatWorks = () => {
  const [state, dispatch] = useReducer(reducer, [
    {
      name: "Age",
      show: true,
      filterValueType: "integer",
      value: ["", ""],
      optionType: "between",
      key: "age",
    }
  ]);

  return (
    <div>
      <FilterItem dispatch={dispatch} filter={state[0]} idx={0} />
    </div>
  );
};

it.each(Array(100).fill(null))("ExampleThatWorks", async () => {

  render(<ExampleThatWorks />);

  const numberInputs = screen.getAllByRole("spinbutton");
  await userEvent.type(numberInputs[0], "12333");
  expect(numberInputs[0]).toHaveDisplayValue("12333");
},10000);

Expected behavior

expected to pass all tests on v14

Actual behavior

testThatFails fails in v14, but when using v13.5 it works properly testTahtWorks passes on both versions

User-event version

14.1.1(actually all v14 dists act the same)

Environment

{
  "name": "userevent-react",
  "keywords": [],
  "description": "",
  "private": true,
  "dependencies": {
    "@headlessui/react": "1.7.16",
    "@testing-library/jest-dom": "5.16.3",
    "@testing-library/react": "14.0.0",
    "@testing-library/user-event": "^14.1.1",
    "jest": "29.5.0",
    "react": "18.0.0",
    "react-dom": "18.0.0",
    "react-scripts": "^5.0.1"
  },
  "devDependencies": {
    "husky": "^4.2.3",
    "jest-environment-jsdom": "^29.6.2",
    "lint-staged": "^10.0.8",
    "prettier": "^1.19.1",
    "ts-jest": "^29.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "format": "prettier --write \"**/*.+(js|json|css|md|mdx|html)\""
  },
  "eslintConfig": {
    "extends": "react-app"
  },
  "browserslist": [
    ">0.2%",
    "not dead",
    "not ie <= 11",
    "not op_mini all"
  ]
}

Additional context

No response

yasin459 avatar Aug 05 '23 10:08 yasin459

Getting this too with v14.4.3:

My test:

    const inputText = wrapper.getByLabelText('my-label');

    await event.type(inputText, textString);
    
    expect(inputText.value).toBe(textString)

And the result:

AssertionError: expected 't1t5e rlse tltoenrgs long' to be '15 letters long' // Object.is equality

- Expected
+ Received

- 15 letters long
+ t1t5e rlse tltoenrgs long

Downgrading to v13.5.0 was the only resolution I could find.

endymion1818 avatar Aug 30 '23 12:08 endymion1818

I'm getting the same behavior as well (in a project with lots of dependencies). I worked around the issue by refactoring to one call to userEvent.type per character that I want to type.

Hatlen avatar Sep 18 '23 11:09 Hatlen

Getting similar behavior, where it seems to be random how many of the characters in the type() make it into a text box. Also tried keyboard() directly with the same result.

finken2 avatar Sep 18 '23 14:09 finken2

You are likely not awaiting your changes with waitFor() or findBy. From v14 methods are not synchronous. The test works as intended when awaiting the changes.

kristiannilsson avatar Sep 21 '23 21:09 kristiannilsson

I'm getting the same behavior as well (in a project with lots of dependencies). I worked around the issue by refactoring to one call to userEvent.type per character that I want to type.

I was able to get await userEvent.type() to work correctly after upgrading @testing-library/dom to version 9.3.3 or 8.19.0 (previously my lock file also had version 9.0.1 and 8.13 (don't know which version is actually used, should be 9.33 since version 8 is a dependency of a dependency).

You are likely not awaiting your changes with waitFor() or findBy. From v14 methods are not synchronous. The test works as intended when awaiting the changes.

This was part of the issue, when I awaited the user.type() calls I got an not wrapped in act error, then I removed the await and I didn't get an error but the calls to type just entered one character. Even when I tried to wait 1s after the call to type there was only one character in the inputs.

Hatlen avatar Sep 26 '23 07:09 Hatlen

You are likely not awaiting your changes with waitFor() or findBy. From v14 methods are not synchronous. The test works as intended when awaiting the changes.

I am using waitFor, and an await on the user.type:

    const filenameField = getByLabelText('Filename');
    await user.clear(filenameField);
    await user.type(filenameField, '.startswithperiod');

    await waitFor(() => expect(filenameField).toBeInvalid());
    await waitFor(() => expect(filenameField.value).toEqual('.startswithperiod'));

Pretty straightforward, but if you see something wrong there, let me know. In this test, it fails with only part of the string being the filenameField.value, e.g.

  Expected: ".startswithperiod"
  Received: ".starts"

finken2 avatar Sep 28 '23 21:09 finken2

Same issue here, type is sometimes not being awaited and it randomly fails...

I'm using the following versions:

    "@testing-library/angular": "14.3.0",
    "@testing-library/dom": "9.3.3",
    "@testing-library/jasmine-dom": "1.3.3",
    "@testing-library/user-event": "14.4.3",
    "karma": "6.4.2",

Example of my code:

    await user.type(
          document.querySelector('textarea'),
          'Test example'
        );
    
    await user.click(
      getByText(
        transloco.instant('action.save_example')
      )
    );

    const [submission] = submitData.calls.mostRecent().args;
    expect(submission.text).toBe('Test example');

Error: Expected 'Test' to be 'Test example'.

LMatass avatar Oct 20 '23 14:10 LMatass

In case this helps anyone else, I was running into this issue when using onKeyDown as an event handler for a textInput. Switching to using onChange resolved the issue.

it('Renders a text input', async () => {
  const Template = () => {
    const [value, setValue] = useState('')
    // switching from `onKeyDown` to `onChange` here fixed the issue for me
    return <TextInput value={value} onChange={(e) => setValue(e.currentTarget.value)} />
  }
  render(<Template />)

  expect(screen.getByRole('textbox')).toBeInTheDocument()
  await userEvent.type(screen.getByRole('textbox'), 'text content')
  expect(screen.getByRole('textbox')).toHaveValue('text content')
})

tortilaman avatar Oct 21 '23 01:10 tortilaman

You are likely not awaiting your changes with waitFor() or findBy. From v14 methods are not synchronous. The test works as intended when awaiting the changes.

I am using waitFor, and an await on the user.type:

    const filenameField = getByLabelText('Filename');
    await user.clear(filenameField);
    await user.type(filenameField, '.startswithperiod');

    await waitFor(() => expect(filenameField).toBeInvalid());
    await waitFor(() => expect(filenameField.value).toEqual('.startswithperiod'));

Pretty straightforward, but if you see something wrong there, let me know. In this test, it fails with only part of the string being the filenameField.value, e.g.

  Expected: ".startswithperiod"
  Received: ".starts"

I'm still with the same issue, the days that the machine runs slower, the tests fails more. Did you find any workaround? I tried with setTimeouts, waitFor.... and none of them worked.

LMatass avatar Oct 31 '23 07:10 LMatass

I have tried your example with

it.each(Array(100).fill(null))("ExampleThatFails",
  async () => {
    const user = userEvent.setup({ delay: null }); // <-- added
    render(<ExampleThatFails />);

    // replaced every userEvent with user

and that seems to work. Why? I don't know. But maybe it's helpful.

haagOS avatar Dec 02 '23 16:12 haagOS

By now I think it is clear this is a real bug. It is not just people forgetting await. Here is my test that is failing (using vitest):

const { container } = render( <MyComponent /> );
const input = container.querySelector<HTMLInputElement>(
  '#inputfield',
);
const user = userEvent.setup();
await user.type(input, '61857600265');
expect(input.value).toBe('61857600265')

Sti2nd avatar Dec 12 '23 10:12 Sti2nd

Wrapping user.type in my own act call removes the warnings and makes my tests deterministic.

Before:

await user.type(textarea, '@');

After:

 await act(async () => {
          await user.type(textarea, '@');
});

I thought that user.type would take care of act for me, however, in this case it doesn't seem to.

iammerrick avatar Dec 12 '23 21:12 iammerrick

Wrapping in act doesn't fix it for me 🤔. My test fails consistently, though.

"react": "18.2.0", "@testing-library/jest-dom": "6.1.5", "@testing-library/react": "14.1.2", "@testing-library/user-event": "14.5.1", "vitest": "0.34.4"

Sti2nd avatar Dec 13 '23 07:12 Sti2nd

+1. None of the suggestions work for me, upgrading to react 18 and v14.5.1

EmmaB avatar Dec 16 '23 13:12 EmmaB

None of the suggestions work for me too. It just says "AssertionError: expected '' to be 't' // Object.is equality"

Trying this

test("Should allow user to enter passwords into the input", async () => {

  const user = userEvent.setup();
  const { getByRole, getByTestId } = render(
    <div>
      <label
        htmlFor="currentPassword"
        className="text-white block mb-2 text-sm font-medium dark:text-white"
      >
        Current Password
      </label>
      <input
        type="password"
        role="textbox"
        name="currentPassword"
        id="currentPassword"
        data-testid="currentPassword"
        required
        autoFocus
        value={""}
        onChange={(event) => vi.fn()}
      />
    </div>,
  );
//also tried getByRole
  const currentPasswordInputElement = getByTestId("currentPassword");

  await user.type(currentPasswordInputElement, "t");

  await waitFor(() =>
    expect((currentPasswordInputElement as HTMLInputElement).value).toBe("t"),
  );


});

package.json (parts that matter)

"dependencies": { 
   "next": "^14.0.4",
   "react": "^18.2.0",
   "react-dom": "^18.2.0",
 },
 "devDependencies": {
   "@testing-library/jest-dom": "^6.1.5",
   "@testing-library/react": "^14.1.2",
   "@testing-library/user-event": "^14.5.1",
   "@types/node": "^20.10.5",
   "@types/react": "^18.2.45",
   "@types/react-dom": "^18.2.18",
   "@vitejs/plugin-react": "^4.2.1",
   "jsdom": "^23.0.1",
   "typescript": "^5.3.3",
   "vitest": "^1.1.0"
 }
}

Meags27 avatar Dec 24 '23 01:12 Meags27

Still broken :(

applesaucesome avatar Jan 11 '24 20:01 applesaucesome

I await it and it doesnt work for me, neither the other suggestions

daacrx avatar Jan 24 '24 14:01 daacrx

setting up userEvent first and using await works for me:

it("should be able to type username", async () => {
  const user = userEvent.setup()
  render(<App />);
  const element = screen.getByLabelText("Username") as HTMLInputElement;

  await user.type(element, "Admin");
  expect(element.value).toBe("Admin");
}); 

nguyenduy avatar Jan 30 '24 03:01 nguyenduy

Same issue here, none of the above hints works for me

asfg84 avatar Feb 09 '24 12:02 asfg84

I have the same issue when i test component that accepts state from the parent component. When I call user.type() just entered one character and so several times,character by character according to the number of character in the test word.

Solar5503 avatar Feb 21 '24 06:02 Solar5503

Same issue, it seems the onChange events are fired properly simulating the typing but when I check the input.value it doesn't change.

UPDATE: I found a solution for my scenario. I was replacing the value property of the input from props on every change, that works on browser, but it seems it causes conflict when testing. I just changed the value for defaultValue in the input element and the error was solved.

itmarck avatar Mar 04 '24 05:03 itmarck

I've noticed that it types the text letter by letter, and with number input, it does not adds the letters, but each time overrides it (I have a controlled component, could be that the issue?)

image

So for my test purpose, I will just set single digit number...

my 2 cents

MiroslavPetrik avatar Mar 22 '24 10:03 MiroslavPetrik

I am experiencing the same issue as @MiroslavPetrik; When typing, my onChange handler is only receiving the most recent character typed, not the additive string.

obryckim avatar Apr 10 '24 19:04 obryckim

@MiroslavPetrik @obryckim I've found you'll need to await the userEvent.type statement inside of your act block to see the entire string.

omidmogasemi avatar May 17 '24 16:05 omidmogasemi

Like @omidmogasemi said, adding this await for user.type did the trick. No need of act():

const user = userEvent.setup();

const input = screen.getByTestId('input-search');

user.click(input);
await user.type(input, 'abc');

expect(input).toHaveValue('abc');

AllanTAP avatar Oct 04 '24 14:10 AllanTAP

Removing

jest.useFakeTimers();

or adding{delay: null} to userEvent.setup.

Makes more, but not all, tests pass. Perhaps that's a hint to what's going on.

Adding await or act or any combination of the two doesn't work.

tibbe avatar Oct 25 '24 05:10 tibbe

make sure @testing-library/dom is installed in your project. that fixed it for me

drizco avatar Dec 13 '24 22:12 drizco