Skip to content

Update to v14 breaks @testing-library/user-event on Vitest #1197

@wojtekmaj

Description

@wojtekmaj

What you did:

A simple update from v13 to v14 broke my Vitest-based test where I was using await user.click(...) as the promise no longer resolves.

Reproduction:

Run repo at the following commit: wojtekmaj/react-async-button@fa41b3b

Suggested solution:

After long debug session, I have determined that

// Drain microtask queue.
// Otherwise we'll restore the previous act() environment, before we resolve the `waitFor` call.
// The caller would have no chance to wrap the in-flight Promises in `act()`
await new Promise(resolve => {
setTimeout(() => {
resolve()
}, 0)
if (jestFakeTimersAreEnabled()) {
jest.advanceTimersByTime(0)
}
})

  • Moving if (jestFakeTimersAreEnabled()) { ... } to wrap the entire block mentioned above resolves the issue.
  • Calling vi.advanceTimersByTime(0); manually after user.click(...) but before awaiting returned promise, even multiple times, does NOT help
  • The only workaround that worked for me was this: wojtekmaj/react-async-button@2d26f21

So my suggestion is to:

  • Roll back the fix and perhaps reintroduce when advanceTimers will be configurable and not jest dependent
  • OR move if (jestFakeTimersAreEnabled()) { ... } to wrap the entire block mentioned above, acknowledging that the fix is now Jest-only.

Activity

eps1lon

eps1lon commented on Mar 25, 2023

@eps1lon
Member

Moving to user-event until we have a repro that's just using @testing-library/react

Lokua

Lokua commented on Mar 28, 2023

@Lokua

I was about to submit what I think is the same issue. await userEvent.click causes any test that is ran after calling jest.useFakeTimers to fail in v14.4.3. Took me a pretty long time to isolate it, but it appears to be the combination of using fake timers and awaiting userEvent, as remove either of those things and the tests pass.

Here is a repro using @testing-library/react:
https://github.com/Lokua/user-event-repro

^ downgrading to v13 or using fireEvent, no issue.

Happy to create a separate issue if you feel this is not the same.

Edit: If this is the same issue, I suggest renaming it to better reflect the actual bug so others can find it more easily. Perhaps v14 causes tests using fake timers to fail when awaiting userEvent.click

ph-fritsche

ph-fritsche commented on Mar 29, 2023

@ph-fritsche
Member

@Lokua You need to set the advanceTimers option if you're working with fake timers.

We recommend using a setup function:

function setup(jsx) {
  return {
    user: userEvent.setup({
      advanceTimers: jest.advanceTimersByTime,
    }),
    ...render(jsx),
  }
}

test('some click', async () => {
  jest.useFakeTimers()
  const { user } = setup(<button/>)

  await user.click(screen.getByRole('button'))
})
ph-fritsche

ph-fritsche commented on Mar 29, 2023

@ph-fritsche
Member

@wojtekmaj @eps1lon

This is an issue with the setTimeout call in asyncWrapper and is independent of user-event. It just happens to use it.

When we use setTimeout in user-event we also call the advanceTimers callback.
I think we should add the same option to @testing-library/react.

import DTL from '@testing-library/dom'
const userEventConfig = {
  delay: 0,
  advanceTimers: jest.advanceTimersByTime,
}
const userEventWait = () => Promise.all([
   new Promise(r => setTimeout(r, userEventConfig.delay)),
   userEventConfig.advanceTimers(userEventConfig.delay),
])
const someUserEventApi = () => {
  return DTL.getConfig().asyncWrapper(() => {
     DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('foo')))
     DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('bar')))
     await userEventWait()
     DTL.getConfig().eventWrapper(() => document.dispatchEvent(new Event('baz')))
     await userEventWait()
   })
}

await someUserEventApi() // with non-jest fake timers this will time out because of https://github.com/testing-library/react-testing-library/blob/f78839bf4147a777a823e33a429bcf5de9562f9e/src/pure.js#L44-L52
Lokua

Lokua commented on Mar 29, 2023

@Lokua

@Lokua You need to set the advanceTimers option if you're working with fake timers.

We recommend using a setup function:

function setup(jsx) {
  return {
    user: userEvent.setup({
      advanceTimers: jest.advanceTimersByTime,
    }),
    ...render(jsx),
  }
}

test('some click', async () => {
  jest.useFakeTimers()
  const { user } = render(<button/>)

  await user.click(screen.getByRole('button'))
})

Thank you very much for the info. I get it now. Just for clarity, in your example, setup should actually be called render, or vis a versa, yes?

ph-fritsche

ph-fritsche commented on Mar 29, 2023

@ph-fritsche
Member

@Lokua Yes, the setup function should be called in the test, I fixed the code example above.

runofthemillgeek

runofthemillgeek commented on Mar 31, 2023

@runofthemillgeek

@ph-fritsche I ran into the same issue but I couldn't get my scenario to work just with advanceTimers option. See repro link: https://stackblitz.com/edit/vitest-dev-vitest-2xdjiw?file=test%2Fcountdown.test.tsx

import Countdown from "src/components/Countdown";
import { render, screen, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";

describe("Mocking timers", () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.runOnlyPendingTimers();
    vi.useRealTimers();
  });

  it("mocks timers", async () => {
    const user = userEvent.setup({
      advanceTimers: ms => vi.advanceTimersByTime(ms),
    });

    render(<Countdown from={5} />);

    await user.click(screen.getByRole("button"));

    expect(screen.getByRole("alert")).toHaveTextContent("5");
    await act(() => vi.runAllTimers());
    expect(screen.getByRole("alert")).toHaveTextContent("0");
  });
});

If I do:

vi.useFakeTimers({ shouldAdvanceTime: true });

It makes the test pass but I believe it's not a good option to use.

azat-io

azat-io commented on Apr 6, 2023

@azat-io

Same problem

bfsgr

bfsgr commented on Apr 11, 2023

@bfsgr

I believe this might be related to #1187 as vitest uses sinon fake timers

36 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      Participants

      @xuhdev@danielholmes@nickserv@runofthemillgeek@falldowngoboone

      Issue actions

        Update to v14 breaks @testing-library/user-event on Vitest · Issue #1197 · testing-library/react-testing-library