···290290 // watcher's baseline and ignored, so newly added files wouldn't trigger a
291291 // rerun. Wait for the watcher to be ready before handing control back.
292292 if (watch && ctx) {
293293- await waitForWatcherReady(ctx.vite.watcher, ctx.config.root)
293293+ await waitForWatcherReady(ctx)
294294 }
295295296296 return {
···333333// time after it is discovered, and an edit made before the poller exists is
334334// folded into its baseline stat and never emits an event. No amount of chokidar
335335// bookkeeping (`_readyEmitted`, `getWatched()`) proves the pollers are live, so
336336-// verify end-to-end: keep touching a probe file in the root until the watcher
337337-// reports it. Once a probe event arrives, the initial scan is complete and
338338-// every pre-existing file has a poller, so subsequent edits are guaranteed to
339339-// fire. The probe produces no output: it matches no test glob and no fixture's
340340-// rerun triggers, so `VitestWatcher` ignores it without scheduling a rerun.
336336+// verify end-to-end: keep cycling a probe file in the root until the watcher
337337+// reports it. The probe is recreated rather than rewritten because a creation
338338+// folded into the initial scan of the file AND its directory leaves nothing to
339339+// rescan; deleting and recreating it bumps the directory mtime every cycle, so
340340+// a live directory poller always sees a difference, and any probe event
341341+// (`add`, `change` or `unlink`) proves delivery.
342342+//
343343+// The scan can also surface writes made by the PREVIOUS test (an `afterEach`
344344+// restoring a fixture right before this instance started) as fresh change
345345+// events, triggering a rerun the test never asked for. While waiting, neuter
346346+// every rerun attempt by clearing the watcher state (the rerun debounce
347347+// returns silently when `changedTests` is empty) and only hand control back
348348+// after the event backlog has been quiet for a couple of poll intervals.
341349//
342350// Only instances with an explicit small root (a fixture or an inline-test dir)
343351// get the probe: tests that assert on watch reruns always use one. Instances
···345353// `watch: true` without a root) never wait for file events, and polling their
346354// huge tree can take longer than any reasonable deadline, so for them only the
347355// cheap bookkeeping check runs, as before.
348348-async function waitForWatcherReady(watcher: FSWatcher, root: string): Promise<void> {
356356+async function waitForWatcherReady(ctx: Vitest): Promise<void> {
357357+ const watcher: FSWatcher = ctx.vite.watcher
358358+ const root = ctx.config.root
349359 const slash = (p: string) => p.replace(/\\/g, '/')
350360 const parent = slash(dirname(root))
351361 const bookkeepingDeadline = Date.now() + 2000
···365375366376 const deadline = Date.now() + 10_000
367377 const probe = resolve(root, '.vitest-watcher-ready-probe')
368368- let onProbeSeen!: () => void
369369- const probeSeen = new Promise<void>(resolve => (onProbeSeen = resolve))
370370- const onProbeEvent = (file: string) => {
378378+ const suppressRerun = () => {
379379+ ctx.watcher.changedTests.clear()
380380+ ctx.watcher.invalidates.clear()
381381+ }
382382+ let probeSeen = false
383383+ let lastEventAt = Date.now()
384384+ const onWatcherEvent = (_event: string, file: string) => {
385385+ suppressRerun()
371386 if (slash(file) === probe) {
372372- onProbeSeen()
387387+ probeSeen = true
388388+ }
389389+ else {
390390+ lastEventAt = Date.now()
373391 }
374392 }
375375- watcher.on('add', onProbeEvent)
376376- watcher.on('change', onProbeEvent)
393393+ watcher.on('all', onWatcherEvent)
377394 try {
378395 while (true) {
379379- // rewrite instead of writing once: if the initial scan captured a probe
380380- // write without emitting `add`, the next mtime bump still fires `change`
381381- fs.writeFileSync(probe, `${Date.now()}`, 'utf-8')
382382- const seen = await Promise.race([
383383- probeSeen.then(() => true),
384384- new Promise<boolean>(resolve => setTimeout(resolve, 50, false)),
385385- ])
386386- if (seen) {
396396+ if (probeSeen) {
387397 break
388398 }
389399 if (Date.now() > deadline) {
···392402 JSON.stringify(watcher.getWatched(), null, 2)}`,
393403 )
394404 }
405405+ fs.writeFileSync(probe, `${Date.now()}`, 'utf-8')
406406+ await new Promise(resolve => setTimeout(resolve, 50))
407407+ if (probeSeen) {
408408+ break
409409+ }
410410+ fs.rmSync(probe, { force: true })
411411+ await new Promise(resolve => setTimeout(resolve, 50))
412412+ }
413413+414414+ while (Date.now() - lastEventAt < 200 && Date.now() < deadline) {
415415+ await new Promise(resolve => setTimeout(resolve, 50))
395416 }
396417 }
397418 finally {
398398- watcher.off('add', onProbeEvent)
399399- watcher.off('change', onProbeEvent)
419419+ watcher.off('all', onWatcherEvent)
400420 fs.rmSync(probe, { force: true })
421421+ suppressRerun()
401422 }
402423}
403424