Closed nasdan closed 4 years ago
@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:
npm package (@latest dist-tag)
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
Could you add a test to reproduce this issue?
https://github.com/testing-library/user-event/blob/master/src/__tests__/type.js#L717
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:
npm package (@latest dist-tag)
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 inact
(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?
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,`EmailThatIsCorrect@Email.com`)
expect(emailInput).toHaveValue('EmailThatIsCorrect@Email.com' );
Terminal:
Expected the element to have value:
EmailThatIsCorrect@Email.com
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).
I had this issue when migrating from fireEvent to userEvent. I found that the userEvents were unnecessarily wrapped in act
I got burned by a version of this today and want to document it for anyone else having a similar problem. Also, this is my current understanding of the problem so I welcome any and all suggestions to make it more correct/complete.
As I understand it, this is a classic case of mishandling closed-over data in the implementation of the state-altering function. It becomes apparent when the client (in my case, userEvent.type(element, text)
) queues up several events (looping over the characters in your type
d string).
render(<App formAction={myAssertions} />);
userEvent.type(screen.getByPlaceholderText('Password'), 'p@ssw0rd');
userEvent.type(screen.getByPlaceholderText('Email'), 'some@email.com');
userEvent.click(screen.getByText('Submit'));
function handleChange(ev: FormEvent, key = "") {
ev.preventDefault();
// β
Passes
const value = (ev.target as HTMLInputElement).value;
setState(s => {
// β Fails (individual characters instead of progressively built up strings)
// const value = (ev.target as HTMLInputElement).value;
return { ...s, [key]: value };
});
}
My understanding: if we call handleChange
in a loop (which I believe the libraries are) and don't use let/const
to capture the event first, it's our handleChange
function's responsibility to capture the event (or use a closure) before referring to it inside setState
. If we don't, the setState
functions can be executed after the last event is passed in, and javascript will not find the correct event, since the events were never scoped to the individual loop.
I don't have any suggestions here because I'm not super familiar with the libraries, but I would love to hear other's opinions on how to remove this pain outright, because it seems like an easy mistake that's hard to explain.
@maxscott You're nearly correct.
Properties are resolved when the expression is executed. So when you access a property in a function and pass this function to be executed at some point later it will yield the value the property will have then.
const a = { foo: 'bar' }
const b = () => a.foo
b() // is 'bar'
a.foo = 'baz'
b() // is 'baz'
React optimizes state changes and the resulting rendering by batching these changes. It queues them and they will be executed at the end of the event handling.
function Foo() {
const [, setStateA] = useState()
const [, setStateB] = useState()
return <button onClick={() => { // a click on this button will only cause one rerender
setStateA({});
setStateB({});
}/>
}
React also queues up state changes that happen inside act
as described above
render(<Foo/>)
act(() => { // this will only rerender once at the end of act
screen.getByRole('button').click()
screen.getByRole('button').click()
})
This also means that the event handler for the second click
in that act
is executed before the rerender and if it accesses the DOM, it will get properties before the rerender.
userEvent.type
accesses element.value
to determine the new value, so when the event handlers triggered by userEvent.type
are executed in act
they will all access the DOM before any rerender so any controlled input will only receive the last change.
That was the issue - everything else in this thread is unrelated to the initial issue and only adds confusion.
Should you experience any problems that look like the issues described above please file a new one with a reproduction at codesandbox.
@ph-fritsche Thank you for the insight here, especially into act
and type
, and thank you for your work on this great library! This also confirms my suspicion about type
's implementation and controlled inputs. I hope this isn't too common a pitfall, and if you don't think the above use case warrants it's own issue, I'll defer for now.
Hey @kentcdodds so is there a solution for this? I try to use userEvent instead of fireEvent. The input here has type "number". I am trying to test that entering an alphabetic character is ignored. I want only numeric characters to be allowed. It seems like its only getting the last character
userEvent.type(input, '20a1');
expect(input.value).toBe('201');
I am getting
Expected: "201"
Received: "1"
Edit: I notice when I change the input type to text then it works as expected... I guess thats the fix then. dont use input type number
FWIW I've still got this problem (specifically: without act()
I get an error and with act()
I get only the last letter) with 13.5.0
.
Workaround that seems to work is: keep act()
and add {delay: 0.00001}
to userEvent.type()
options.
@wsanchez Unfortunately, does not work for me.
In my case the received value just doesn't change at all. Also when i search for workarounds and try that out.
Even if i wrapped it with act() or try to use waitFor() or not - also async and not async. I use version "@testing-library/user-event": "^13.3.0".
Has this maybe also to do with formik? Because i wrote a formik wrapper/mock for that test? Or has this to do, because i use three input fields which gets at the end wrapped in one <Field/>
The initial issue is explained above.
This was resolved in v12.0.11.
Since then, it is not required and strongly advised against wrapping calls to userEvent
in act
because this conflicts with batched state changes as explained above.
That was the issue - everything else in this thread is unrelated to the initial issue and only adds confusion.
I strongly recommend you update to v14-beta. If you still have problems, please consider to open a discussion or join us at Discord.
@testing-library/user-event
version: 12.0.9Relevant code or config
name-edit.js
name-edit.spec.js
What happened:
Since I update to v12.0.9 this spec fails because it gets only last letter.
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