User-event: userEvent.type gets only last letter on v12.0.9

Created on 25 Jun 2020  ยท  25Comments  ยท  Source: testing-library/user-event

Relevant code or config

_name-edit.js_

import React from 'react';

export const NameEdit = () => {
  const [userName, setUserName] = React.useState('');

  return (
    <input value={userName} onChange={(e) => setUserName(e.target.value)} />
  );
};

_name-edit.spec.js_

import React from 'react';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { NameEdit } from './name-edit';

describe('NameEdit component specs', () => {
  it('should update value when input changes', () => {
    // Arrange

    // Act
    render(<NameEdit />);

    const inputElement = screen.getByRole('textbox', {
      name: '',
    });

    userEvent.type(inputElement, 'John');

    // Assert
    expect(inputElement.value).toEqual('John');
  });
});

What happened:

Since I update to v12.0.9 this spec fails because it gets only last letter.

image

It's working on v12.0.8

Reproduction repository:

Sandbox with v12.0.9: https://codesandbox.io/s/quizzical-surf-wpkq1
Sandbox with v12.0.8: https://codesandbox.io/s/eager-lamport-2hwhb

released

Most helpful comment

Hi guys, I have a similar issue with userEvent.type, is only typed the first letter of the string. Does anyone have idea about this weird behavior?

 const emailInput = getByTestId(container, "login-form-email");

 userEvent.type(emailInput,`[email protected]`)

 expect(emailInput).toHaveValue('[email protected]' );

Terminal:

Expected the element to have value:
  [email protected]
Received:
  E

All 25 comments

@kentcdodds looking at the changes introduced in 12.0.9 and the code and comments in type.js, looks like type wasn't wrapped by default on purpose and should only be wrapped if a delay was passed otherwise it's considered a sync op? Should that specific change be reverted?
In addition, passing a { delay: 1 } to .type() makes the test pass but throws an "overlapping act() calls" warning so that's probably not the intended behavior.

I did some minimal debugging and looks like if the <input/> uses defaultValue instead of value then the test passes as well. Not sure what that means for how the library handles typing as I'm still very new to the codebase but maybe it'll mean something to you.

I hope this helps.

I think we need to wrap it with the event wrapper only if it's not using delay. Otherwise it should be wrapped in only the async wrapper

:tada: This issue has been resolved in version 12.0.10 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

It seams that the issue is not solved yet. I have tried here, but the problem is the same

I think that the problem is that react is not rerendering after input change.

It seams to happen at the beginning and at the end

I agree it seems specific to React. (Or at least, I can't reproduce in vanilla but I can in React.)

I think I understand the problem.

We have type function that is wrapped by act. At the same time all fire event functions are wrapped by act.

act will flush microtask only when there are no more pending act. So because we have nested act this will be done only after typing and not during it.

I don't know if I'm wrong in something

Yup, verified that intuition is correct @marcosvega91 :+1:

import React from 'react'
import ReactDOM from 'react-dom'
import {act} from 'react-dom/test-utils'

test('nested act does not flush until the top parent act finishes', () => {
  let setName
  const NameEdit = () => {
    const [userName, setUserName] = React.useState('')
    setName = setUserName
    console.log({userName})

    return (
      <input
        value={userName}
        onChange={(e) => {
          console.log(e.target.value)
          setUserName(e.target.value)
        }}
      />
    )
  }

  const div = document.createElement('div')
  ReactDOM.render(<NameEdit />, div)
  act(() => {
    act(() => {
      setName('John')
    })

    // this fails
    expect(div.firstChild.value).toBe('John')
  })
  // this passes
  // expect(div.firstChild.value).toBe('John')
})

So what I'm going to do is change from option 2 to option 1 (ref: https://github.com/testing-library/user-event/issues/384)

Yes this will solve the problem :)

:tada: This issue has been resolved in version 12.0.11 :tada:

The release is available on:

Your semantic-release bot :package::rocket:

I am on version 12.1.5 and I still get this error

import React, { Fragment } from "react";
import { render, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";

test("Gets a code and renders it in a new route", () => {
  const { getByLabelText } = render(<App />);
  act(() => {
    userEvent.type(getByLabelText("Code:"), "12345");
  });
  expect(getByLabelText("Code:")).toHaveValue("12345");
});
  โ— Gets a code and renders it in a new route

    expect(element).toHaveValue(12345)

    Expected the element to have value:
      12345
    Received:
      5

I get this issue as well, it seems like it sends 1 character at a time instead of all

eg:
userEvent.type(input, "group") is entered as g r o u p instead of g gr gro grou group

As I found out by Kent's blog post here
userEvent calls shouldn't be wrapped in act (because they already come pre-wrapped) and it says that the warning might come from some other problem in the code.

Can confirm this issue for me. Code below is working at 12.0.8, however current latest 12.1.6 is logging an error saying that userEvent.type should be wrapped in act. If I attempt it, the issue with having only the last letter typed arises.

    const input = screen.queryByTestId('inline-edit-input');
    expect(input.value).toBe('Text');
    userEvent.type(input, '{backspace}{backspace}tris');
    expect(input.value).toBe('Tetris');

As I found out by Kent's blog post here
userEvent calls shouldn't be wrapped in act (because they already come pre-wrapped) and it says that the warning might come from some other problem in the code.

Correct me, if I'am wrong, but it says that render and fireEvent are already wrapped in act.

I have the same issue. Without wrapping userEvent.type(..) in act, I get a warning.
Wrapped in act only the last character appears as input value.

Warnings about type being not wrapped in act might be caused by a delayed state/hook change.
Your code might defer a call per setTimeout or not await a promise.

    act(() => {
        jest.useFakeTimers()
        // Fire event
        jest.runAllTimers()
    })

I am still suffering this error while testing next component:

import React from 'react';
import { useField } from 'formik';
import MuiTextArea, { TextFieldProps } from '@material-ui/core/TextField';

export const TextAreaComponent: React.FunctionComponent<TextFieldProps> = props => {
  const [field, meta] = useField(props.name);
  const textAreaProps = Boolean(field) ? field : props;
  const hasError = Boolean(meta && meta.touched && meta.error);

  return (
    <MuiTextArea
      {...props}
      name={textAreaProps.name}
      onChange={textAreaProps.onChange}
      onBlur={textAreaProps.onBlur}
      value={textAreaProps.value ?? ''}
      multiline={true}
      type={'TextareaAutosize'}
      variant={'standard'}
      error={hasError}
      helperText={hasError ? meta.error : ''}
    />
  );
};

Using this test:

import React from 'react';
import { Formik, Form } from 'formik';
import { render, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TextAreaComponent } from './textArea-field.component';

describe('textfield component specs', () => {
  const renderWithFormik = (component, initialValues) => ({
    ...render(
      <Formik initialValues={initialValues} onSubmit={console.log}>
        {() => <Form>{component}</Form>}
      </Formik>
    ),
  });

 it('"input" should change value when typing', () => {
    // Arrange

    // Act
    renderWithFormik(<TextAreaComponent name={name} />, { name: 'test name' });

    const textarea = screen.getByRole('textbox') as HTMLInputElement;

    userEvent.type(textarea, 'test input text');

    // Assert
    expect(textarea.value).toEqual('test input text');
  });
});

The test doesn't pass because expect receives 'est input textt' instead of 'test input text'.
If I use act function to wrap userEvent expect only receives the last letter.

What's going on here? Does anyone know the solution to this?


  • node: v12.18.2
  • @testing-library/jest-dom: "^4.2.4"
  • @testing-library/react: "^9.3.2"
  • @testing-library/user-event: "^12.1.10"
  • jest: "^24.9.0"
  • @types/jest: "^24.0.23",

Hi guys, I have a similar issue with userEvent.type, is only typed the first letter of the string. Does anyone have idea about this weird behavior?

 const emailInput = getByTestId(container, "login-form-email");

 userEvent.type(emailInput,`[email protected]`)

 expect(emailInput).toHaveValue('[email protected]' );

Terminal:

Expected the element to have value:
  [email protected]
Received:
  E

This works fine on CodeSandbox. Can you post a reproduction either on the site or a downloadable zip/gist/repository/etc.?

Codesandbox was updated to use jsdom a while ago. But it sometimes seems to have other issues which is why I typically avoid using it if I need to do much.

I've just created a brand new project with create-react-app and I'm able to reproduce both the act warning and getting the error with userEvent only typing the last character.
https://github.com/Hyllesen/testing-library-formik-bug
I could not reproduce the error in codesandbox, https://codesandbox.io/s/youthful-river-0wxrz

For what it's worth, I'm using the latest user-event (12.6.0) and just updated to React 17 (17.0.1 from 16.13.1) and started seeing this issue. Currently using react testing library 10.4.8, but still seemed to have the issue when upgrading to latest there as well.

Unfortunately I can't set up an example repo at the moment, but will try to do so later if it would be helpful. But wondering if there could be something in React 17 that might contribute to this?

EDIT: Turns out my issue is actually that the input element is never getting focused, and that's messing with some of our custom logic. In version 17, React uses focusin/focusout events instead of focus/blur, and older versions of jsdom (<16.3.0) didn't fire those events on focus/blur.

Also I've edited this a few times, so feel free to check out the edit history to see the stages I went through to get here =)

For what it's worth, I'm using the latest user-event (12.6.0) and just updated to React 17 (17.0.1 from 16.13.1) and started seeing this issue. Currently using react testing library 10.4.8, but still seemed to have the issue when upgrading to latest there as well.

Unfortunately I can't set up an example repo at the moment, but will try to do so later if it would be helpful. But wondering if there could be something in React 17 that might contribute to this?

EDIT: Turns out my issue is actually that the input element is never getting focused, and that's messing with some of our custom logic. In version 17, React uses focusin/focusout events instead of focus/blur, and older versions of jsdom (<16.3.0) didn't fire those events on focus/blur.

Also I've edited this a few times, so feel free to check out the edit history to see the stages I went through to get here =)

Could you give an example of how you solved this issue?
Isn't the whole purpose of userEvent to automate things like focus and blur vs. fireEvent?

Yeah so userEvent correctly calls element.focus() and element.blur(). It just took me a minute to realize that. The problem we were seeing was that jsdom 16.2.2 (what we were using at the time) actually didn't fire focusin or focusout events when those calls were made, as browsers do. But it turns out jsdom actually fixed that in 16.3.0, so just had to upgrade. Since we get jsdom from Jest, we needed to upgrade jest to 26.5.0.

I want to reiterate though that our issue was actually due to custom logic we had running when the user typed and the input wasn't focused (which shouldn't really ever happen). We only saw this because of the way React changed onFocus to bind to focusin event instead of focus event in version 17, and the way jsdom fired those events (or in our case, didn't fire them).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

kellengreen picture kellengreen  ยท  7Comments

visualjerk picture visualjerk  ยท  4Comments

marquesm91 picture marquesm91  ยท  6Comments

dvargas92495 picture dvargas92495  ยท  4Comments

ahayes91 picture ahayes91  ยท  3Comments