-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Update to v14 breaks @testing-library/user-event on Vitest #1197
Comments
Moving to user-event until we have a repro that's just using |
I was about to submit what I think is the same issue. Here is a repro using @testing-library/react: ^ 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 |
@Lokua You need to set the 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'))
}) |
This is an issue with the When we use 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 |
Thank you very much for the info. I get it now. Just for clarity, in your example, |
@Lokua Yes, the |
@ph-fritsche I ran into the same issue but I couldn't get my scenario to work just with 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. |
Same problem |
I believe this might be related to #1187 as vitest uses sinon fake timers |
Is there any ETA to fix this issue? It should be testing-framework agnostic instead of sticking with One temporarily workaround for In your test suites using fake timers import { beforeAll, vi, describe } from 'vitest';
describe('this suite uses fake timers', () => {
// Temporarily workaround for bug in @testing-library/react when use user-event with `vi.useFakeTimers()`
beforeAll(() => {
const _jest = globalThis.jest;
globalThis.jest = {
...globalThis.jest,
advanceTimersByTime: vi.advanceTimersByTime.bind(vi)
};
return () => void (globalThis.jest = _jest);
});
}) |
How did Vitest fake timers ever work before? We generally only support Jest fake timers at the moment. Using any other fake timers means you're on your own. We'd love to add support for more fake timers but that should come with a general purpose API so that we can support any fake timers (e.g. #1187). |
From what I can see here, According to this, my workaround above will work 100% perfectly just by binding So basically I've no idea why But whatever, thanks for the great testing library! |
Because it is the most popular testing framework so supporting their fake timers out of the box made sense to help adoption. I'd like to support more timers but so far no community contributions have been made to do that. And since I'm not using Vitest in any projects I'm involved in, I didn't have a use-case for myself. PRs are welcome though. |
Supporting one framework's fake timers is one thing, making the library framework-dependent by breaking it for all other testing frameworks is another. This code should NOT be run on non-Jest environments, and it does. Please see my original post. |
Does it break in vitest with real timers or with their fake timers? I |
Fake timers |
Ran into same problem. Shared my workaround here: testing-library/user-event#1115 (comment) |
Supporting Vitest timers should be as easy as calling |
@nickmccurdy would absolutely love that! It's literally the only thing stopping me from moving to Vitest 🙏 |
We're using import sinon from 'sinon'
const _sinonUseFakeTimers = sinon.useFakeTimers;
let _sinonClock;
sinon.useFakeTimers = function (...args) {
_sinonClock = _sinonUseFakeTimers.call(sinon, ...args);
globalThis.jest = { advanceTimersByTime: _sinonClock.tickAsync.bind(_sinonClock) };
return _sinonClock;
}; Might be helpful for those who have similar setup. It allows to focus on the testing, not on hacking things, and can be deleted once sinon timers are supported. Idea was borrowed from #1197 (comment). |
It would be great to be able to pass your framework in the import {configure} from '@testing-library/dom'
import {vi} from 'vitest'
// ...
configure({framework: vi}) The object passed through would adhere to a specific interface. Even if the code has to be messy right now, you will eventually need something that's much more agnostic. Jest won't remain the dominant framework forever. |
I found that userEvent can work nicely with vitest fake timers using: const user = userEvent.setup({
advanceTimers: ms => vi.advanceTimersByTime(ms),
});
Edit, I take that back, I was still using v13 it turns out. With v14, the suggested approach above does seem to work for globalThis.jest = {
...globalThis.jest,
advanceTimersByTime: vi.advanceTimersByTime.bind(vi)
}; |
Unfortunately the above solution didn't work for me. I've downgraded to Is a fix being worked on for future releases? |
@IanVS 's method works for me. Should we add this to the doc? |
The following worked for my case:
|
I don't think that's a real solution. What So far I didn't find any solution which would work for Vitest. |
I agree it's not a real solution. But if you're dealing with dates in terms of days (my use case), weeks, months or years it's a perfectly fine workaround for now. If your use case needs the precision of milliseconds or seconds then it's not a good workaround. |
Fair enough - in some use cases it could be a valid solution. |
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
asyncWrapper
to be justcb => cb()
resolves the issue.react-testing-library/src/pure.js
Lines 41 to 52 in f78839b
if (jestFakeTimersAreEnabled()) { ... }
to wrap the entire block mentioned above resolves the issue.vi.advanceTimersByTime(0);
manually afteruser.click(...)
but beforeawait
ing returned promise, even multiple times, does NOT helpSo my suggestion is to:
if (jestFakeTimersAreEnabled()) { ... }
to wrap the entire block mentioned above, acknowledging that the fix is now Jest-only.The text was updated successfully, but these errors were encountered: