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

userEvent.type does not work when setState uses previous value in assignment

Open trainiac opened this issue 2 years ago • 8 comments

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.

trainiac avatar Mar 13 '23 23:03 trainiac

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.)

ph-fritsche avatar Mar 14 '23 08:03 ph-fritsche

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

trainiac avatar Mar 14 '23 15:03 trainiac

The resolved version of testing-library/dom that I have is 8.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

ph-fritsche avatar Mar 14 '23 18:03 ph-fritsche

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"
          }
        }
      }
    },

trainiac avatar Mar 14 '23 18:03 trainiac

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.

harlandjp avatar Mar 21 '23 04:03 harlandjp

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}
    />

smackfu avatar Mar 21 '23 13:03 smackfu

This workaround didn't fix it for me - Perhaps because I'm using Formik? 🤔

const handleChange = (e) => {
    const { value } = e.target;
    setInnerValue(() => value);
  };

harlandjp avatar Mar 23 '23 05:03 harlandjp

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:

  1. (After some process) dispatch inputEvent 'te'
  2. onChange called but setState is just scheduled not called
  3. As value={state}, input element setter called with not changed state value 't' (e.target.value = 't')
  4. setState callback 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)

FFloou avatar May 20 '25 06:05 FFloou