user-event
user-event copied to clipboard
userEvent.type does not work when setState uses previous value in assignment
Reproduction example
https://codesandbox.io/s/mystifying-jones-xpe3p2?file=/src/App.test.js:63-90
Prerequisites
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { useState } from "react";
const Test = () => {
const [state, setState] = useState({ name: "" });
return (
<input
onChange={(e) => {
setState((last) => ({ ...last, name: e.target.value }));
}}
value={state.name}
/>
);
};
describe("MyInput", () => {
it("should update input", async () => {
const user = userEvent.setup();
render(<Test />);
const simpleNameTextBox = screen.getByRole<HTMLInputElement>("textbox");
await user.type(simpleNameTextBox, "test");
expect(simpleNameTextBox).toHaveValue("test");
});
});
Expected behavior
I expected the test to pass and the input being updated with "test".
Actual behavior
Output:
Expected the element to have value:
test
Received:
t
User-event version
14.4.3
Environment
Testing Library framework:
"@testing-library/react": "12.1.5",
JS framework:
"react": "17.0.2"
Test environment:
"jest": "29.4.1"
DOM implementation:
"jsdom": "20.0.0"
Additional context
I'm not sure how to configure sandbox to work so my repository example is broken but this should be enough info.
Could you post your lockfile? This might be another issue with the @testing-library/dom peer dependency.
(Btw a Codesandbox template for userEvent+React is linked in the bug report template.)
Ah ok thanks! Here is the test failing on codesandbox https://codesandbox.io/s/mystifying-jones-xpe3p2?file=/src/App.test.js:63-90
The resolved version of testing-library/dom that I have is 8.20.0
The resolved version of
testing-library/domthat I have is8.20.0
Could you check if there are multiple copies of @testing-library/dom?
E.g. node_modules/@testing-library/dom and node_modules/@testing-library/react/node_modules/@testing-library/dom
There are not, it's just the one 8.20.0. Here are all of the deps that reference it
"@testing-library/react": {
"version": "12.1.5",
"resolved": "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz",
"integrity": "sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg==",
"dev": true,
"requires": {
"@babel/runtime": "^7.12.5",
"@testing-library/dom": "^8.0.0",
"@types/react-dom": "<18.0.0"
}
},
"eslint-plugin-jest-dom": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest-dom/-/eslint-plugin-jest-dom-4.0.3.tgz",
"integrity": "sha512-9j+n8uj0+V0tmsoS7bYC7fLhQmIvjRqRYEcbDSi+TKPsTThLLXCyj5swMSSf/hTleeMktACnn+HFqXBr5gbcbA==",
"dev": true,
"requires": {
"@babel/runtime": "^7.16.3",
"@testing-library/dom": "^8.11.1",
"requireindex": "^1.2.0"
}
},
"node_modules/@testing-library/user-event": {
"version": "14.4.3",
"resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.4.3.tgz",
"integrity": "sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==",
"dev": true,
"engines": {
"node": ">=12",
"npm": ">=6"
},
"peerDependencies": {
"@testing-library/dom": ">=7.21.4"
}
},
And here is the resolved package
"@testing-library/dom": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-8.20.0.tgz",
"integrity": "sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "^5.0.0",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.4.4",
"pretty-format": "^27.0.2"
},
"dependencies": {
"ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"requires": {
"color-convert": "^2.0.1"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dev": true,
"requires": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
}
},
"has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true
},
"supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"requires": {
"has-flag": "^4.0.0"
}
}
}
},
Having the same issue with @testing-library/[email protected]" and @testing-library/[email protected]" (Verified with yarn why there are no other versions of the packages.
I think this is the same root cause as https://github.com/testing-library/react-testing-library/issues/591 since the workaround suggested there fixed the issue for me.
<input
onChange={(e) => {
const { value } = e.target;
setState((last) => ({ ...last, name: value }));
}}
value={state.name}
/>
This workaround didn't fix it for me - Perhaps because I'm using Formik? 🤔
const handleChange = (e) => {
const { value } = e.target;
setInnerValue(() => value);
};
Having same issue and found point after some digging...
When userEvent.type called, some events are dispatched, setState inside onChange called and then set input element's value.
but with setState uses previous value, callback inside setState called after input element value setted.
E.g. state='t' and userEvent.type(input, 'e') flow like this:
- (After some process) dispatch
inputEvent 'te' onChangecalled butsetStateis just scheduled not called- As
value={state}, input element setter called with not changed state value 't' (e.target.value = 't') setStatecallback calculated:setState('t')
If setState called with value(not funciton with prev value), it will change immediately when onChange called .
then element value setter could called with changed value.
After changing input element value setter to empty function, test success
Object.definePorperty(screen.getByRole('textbox'), 'value', { set: () => {} })
To add,
It works well on browser but fails in test,
I guess changing input element value property process is different between browser and @testing-library.
and same code with "react": "18.2.0" do not have this issue.
(Maybe auto batching in react18 solve this problem)