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

The validity state of input is not updated after `user.type`

Open xuhdev opened this issue 5 months ago • 0 comments

Reproduction example

https://codesandbox.io/p/sandbox/userevent-dom-djcc7b

Prerequisites

  1. Render an input with a pattern and a value that matches the pattern. Make sure the css specifies the style based on the :valid/invalid pseudoclass.
  2. user.type to change the value of the input to mismatch the pattern.
  3. Query the style to test if it matches the style specified in the css file.

Relevant code:

const css = `
      input.test-input:valid {
        color: #ffffff;
      }
      input.test-input:invalid {
        color: #000000;
      }
`;
test("Test", async () => {
  const user = userEvent.setup();
  render(
    <>
      <style>{css}</style>
      <input
        data-testid="element"
        class="test-input"
        type="text"
        pattern="must-match-this"
        value="must-match-this"
      />
    </>,
  );
  await user.type(screen.getByTestId("element"), "aaa");
  expect(screen.getByTestId("element")).toHaveStyle("color: rgb(0, 0, 0)"); // Fails
});

Expected behavior

The input element should now have an invalid state.

Actual behavior

The input element still have a valid state

User-event version

14.5.2

Environment

Testing Library framework: @testing-library/[email protected]

Test environment: [email protected]

DOM implementation: [email protected]

Additional context

A bit clarification why I think this is likely a user-event bug

If I remove the user.type line and change the initial value to mismatch the pattern, the style test would pass.

const css = `
      input.test-input:valid {
        color: #ffffff;
      }
      input.test-input:invalid {
        color: #000000;
      }
`;
test("Test", async () => {
  const user = userEvent.setup();
  render(
    <>
      <style>{css}</style>
      <input
        data-testid="element"
        class="test-input"
        type="text"
        pattern="must-match-this"
        value="must-match-thisaaa"
      />
    </>,
  );
  expect(screen.getByTestId("element")).toHaveStyle("color: rgb(0, 0, 0)"); // succeeds
});

If I have an initially matched value, but set it to a mismatching value from container, the test also succeeds:

const css = `
      input.test-input:valid {
        color: #ffffff;
      }
      input.test-input:invalid {
        color: #000000;
      }
`;
test("Test", async () => {
  const user = userEvent.setup();
  const {container} = render(
    <>
      <style>{css}</style>
      <input
        data-testid="element"
        class="test-input"
        type="text"
        pattern="must-match-this"
        value="must-match-this"
      />
    </>,
  );
  const input = container.querySelector("input.test-input");
  input.value = 'must-match-thisaaa';
  expect(screen.getByTestId("element")).toHaveStyle("color: rgb(0, 0, 0)");  // succeeds
});

But waitFor won't help:

const css = `
      input.test-input:valid {
        color: #ffffff;
      }
      input.test-input:invalid {
        color: #000000;
      }
`;
test("Test", async () => {
  const user = userEvent.setup();
  const {container} = render(
    <>
      <style>{css}</style>
      <input
        data-testid="element"
        class="test-input"
        type="text"
        pattern="must-match-this"
        value="must-match-this"
      />
    </>,
  );
  const input = container.querySelector("input.test-input");
  await user.type(screen.getByTestId("element"), "aaa");
  await waitFor(() => expect(screen.getByTestId("element")).toHaveStyle("color: rgb(0, 0, 0)"));  // fails
});

xuhdev avatar Jan 21 '24 21:01 xuhdev