testing-library / user-event

šŸ• Simulate user events
https://testing-library.com/user-event
MIT License
2.14k stars 242 forks source link

`userEvent.click()` behavior is inconsistent with browser when `onPointerDown` prevents default #1211

Closed githorse closed 4 weeks ago

githorse commented 2 months ago

The behavior of userEvent.click() is inconsistent with what happens in the browser under the specific condition when I preventDefault inside onPointerDown:

test("clicking doesn't work :(", async () => {
  const user = userEvent.setup()
  const spy = vi.fn(() => console.log('clicked!'))
  render(
    <Button
      data-testid='clickme'
      onClick={spy}
      onPointerDown={(e) => {
        e.preventDefault()  // <-- the problem
      }}
    >
      Click Me
    </Button>
  )
  await user.click(screen.getByTestId('clickme'))
  expect(spy).toHaveBeenCalledOnce() // āœ— fails
})

If I comment out the problematic line (e.preventDefault()), the test passes:

test("clicking works :)", async () => {
  const user = userEvent.setup()
  const spy = vi.fn(() => console.log('clicked!'))
  render(
    <Button
      data-testid='clickme'
      onClick={spy}
      onPointerDown={() => {
        // e.preventDefault()  <-- ok
      }}
    >
      Click Me
    </Button>
  )
  await user.click(screen.getByTestId('clickme'))
  expect(spy).toHaveBeenCalledOnce() // āœ”ļø passes
})

If I use fireEvent instead of userEvent, it works:

test("clicking works :)", async () => {
  const spy = vi.fn(() => console.log('clicked!'))
  render(
    <Button
      data-testid='clickme'
      onClick={spy}
      onPointerDown={(e) => {
        e.preventDefault()
      }}
    >
      Click Me
    </Button>
  )
  fireEvent.click(screen.getByTestId('clickme')) // <-- ok
  expect(spy).toHaveBeenCalledOnce() // āœ”ļø passes
})

If I use onMouseDown instead of onPointerDown, it's also ok:

test("clicking works :)", async () => {
  const user = userEvent.setup()
  const spy = vi.fn(() => console.log('clicked!'))
  render(
    <Button
      data-testid='clickme'
      onClick={spy}
      onMouseDown={(e) => {  // <-- ok
        e.preventDefault()
      }}
    >
      Click Me
    </Button>
  )
  await user.click(screen.getByTestId('clickme'))
  expect(spy).toHaveBeenCalledOnce() // āœ”ļø passes
})

More importantly, if I put this exact same button on the page and click it (in Chrome 123.0.6312.124), it works fine; the onClick fires as expected.

<Button
  onClick={() => console.log('clicked!')}  // <-- ok
  onPointerDown={(e) => {
    e.preventDefault()
  }}
>
  Click Me
</Button>

So userEvent.click() is doing something differently than what seems be happening with the real user interaction.

Leaving aside the question of whether the code inside onPointerDown is a good idea or not (my real code is a little more complicated), why is userEvent doing something different here than what happens in the browser? Is there a workaround?

ph-fritsche commented 4 weeks ago

Closing as duplicate of #1206