vitest
vitest copied to clipboard
v0.2.0+ useFakeTimers breaks VTU with nested component
Describe the bug
Test fails:
FAIL src/components/Tested.spec.ts > Tested > should work
AssertionError: expected '' to include 'cat'
The setValue doesn't work on the nested <input> inside the child component.
Test passes if you comment vi.useFakeTimers() or if you downgrade to vitest 0.1.27.
Reproduction
https://github.com/Akryum/repro-vite-fake-timers-issue-649/blob/main/src/components/Tested.spec.ts
System Info
System:
OS: Linux 5.13 elementary OS 6.1 Jólnir
CPU: (24) x64 AMD Ryzen 9 3900XT 12-Core Processor
Memory: 23.64 GB / 62.72 GB
Container: Yes
Shell: 5.0.17 - /bin/bash
Binaries:
Node: 16.13.1 - ~/.nvm/versions/node/v16.13.1/bin/node
Yarn: 1.22.15 - ~/.yarn/bin/yarn
npm: 8.1.2 - ~/.nvm/versions/node/v16.13.1/bin/npm
Browsers:
Chrome: 97.0.4692.99
npmPackages:
@vitejs/plugin-vue: ^2.0.1 => 2.1.0
vite: ^2.7.13 => 2.7.13
vitest: ^0.2.4 => 0.2.4
Used Package Manager
pnpm
Validations
- [X] Follow our Code of Conduct
- [X] Read the Contributing Guidelines.
- [X] Read the docs.
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [X] The provided reproduction is a minimal reproducible example of the bug.
Maybe caused by https://github.com/vitest-dev/vitest/commit/cf30e5a46f5da52164fea56f7d78d9112089acd2 ?
We had our own fake timers implementation, but it was largly incompatible with modern jest implementation. The current fake timers are identical to jest.
Does your code work with jest?
I have encountered the same problem in tests using useFakeTimers and VTU. In my case, the test worked correctly with Jest.
"vite": "^2.6.4",
"vitest": "^0.6.0",
"@vue/test-utils": "^2.0.0-rc.18",
I have encountered the same problem in tests using useFakeTimers and VTU. In my case, the test worked correctly with Jest.
"vite": "^2.6.4", "vitest": "^0.6.0", "@vue/test-utils": "^2.0.0-rc.18",
+1
But if i change for environment: 'happy-dom', current test working(but other files breaked with happy-dom)
And after move vi.useFakeTimers() to end of beforeAll(after first component mount), all working
For me, events not triggered their handlers
Triggering events also doesn't work:
<div @click="$emit('event')">
Title
</div>
await userEvent.click(getByText('title'))
console.log(mounted.emitted())
https://stackblitz.com/edit/vitest-dev-vitest-feebp8?file=vite.config.ts,package.json,test%2Fsuite.test.ts&initialPath=vitest
So, the problem is with this code:
https://github.com/vuejs/core/blob/3bdc41dff305422cb5334a64353c314bce1202a4/packages/runtime-dom/src/modules/events.ts#L120
When Event is triggered its timestamp is equal to mocked time, but Vue always uses real time, because it caches _getNow. And the check is not getting triggered. There are two solutions:
- Call
vi.useFakeTimers()with appropriate config:
vi.useFakeTimers({
toFake: ['setTimeout', 'clearTimeout']
})
- Mock navigator, so this check will return true:
https://github.com/vuejs/core/blob/3bdc41dff305422cb5334a64353c314bce1202a4/packages/runtime-dom/src/modules/events.ts#L33
Wanted to follow up on this since the vue codebase appears to have gotten an update since @sheremet-va posted the workarounds above.
We are using VTU and ran into this issue recently:
- using jest when we tried to update
vuefrom3.2.40to3.2.41 - switching our project from jest to vitest on vue
3.2.40
In both of these scenarios, tests that used jest.useFakeTimers or vi.useFakeTimers caused vue component event bindings to stop working.
In some cases, the workaround above of only mocking certain timers did help:
vi.useFakeTimers({
toFake: ['setTimeout', 'clearTimeout']
})
Faking the navigator did not work, as this check appears to have been removed from vue.
In one occasion, we had a component using lodash/debounce to delay API calls, and we wanted fine grained control over advancing the timers. lodash/debounce uses now() internally, so we had to update our toFake config to include Date:
vi.useFakeTimers({
toFake: ['setTimeout', 'clearTimeout', 'Date']
})
This caused vue event bindings to stop working again, because vue is also using Date.now() internally.
https://github.com/vuejs/core/blob/5ee40532a63e0b792e0c1eccf3cf68546a4e23e9/packages/runtime-dom/src/modules/events.ts#L100-L104
vue uses timestamps internally to ensure that events are never processed by handlers that were attached after the event was triggered. This is to ensure an event handler that causes the DOM to update doesn't attach new listeners that then handle the same event. To achieve this vue adds a timestamp to each event, and a timestamp to each handler, and if the event timestamp is equal or older than the handler timestamp the handler is not invoked.
The solution we found for this is to advance the faked timers by 1ms right before triggering the event:
vi.useFakeTimers();
const wrapper = mount(SomeComponent); // handlers get a timestamp of Date.now() = 0
vi.advanceTimersByTime(1);
wrapper.find('selector').trigger('click'); // event created with timestamp of Date.now() = 1
// event triggered because event._vts > handler.attached
None of these solution did work for me. I have a search component, which triggers a new search with the help of lodasg.debounce after 200ms from the last stroke. As soon as I use vi.useFakeTimers my test will run in the timeout.
This should be fixed with https://github.com/vuejs/test-utils/issues/1854