import { effectScope, nextTick, ref } from 'vue' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useDebouncedWatch } from '../../composables/useDebouncedWatch' describe('useDebouncedWatch', () => { beforeEach(() => { vi.useFakeTimers() }) afterEach(() => { vi.useRealTimers() }) it('debounces source changes and runs the latest callback value', async () => { const query = ref('') const calls: string[] = [] const watcher = useDebouncedWatch(query, value => { calls.push(value) }, { delay: 200 }) query.value = 'glu' await nextTick() query.value = 'glucose' await nextTick() expect(watcher.isPending.value).toBe(true) vi.advanceTimersByTime(199) expect(calls).toEqual([]) vi.advanceTimersByTime(1) expect(calls).toEqual(['glucose']) expect(watcher.isPending.value).toBe(false) }) it('can cancel and flush pending callbacks without stopping the watcher', async () => { const query = ref('') const calls: string[] = [] const watcher = useDebouncedWatch(query, value => { calls.push(value) }) query.value = 'first' await nextTick() watcher.cancel() vi.runAllTimers() expect(calls).toEqual([]) query.value = 'second' await nextTick() watcher.flush() expect(calls).toEqual(['second']) }) it('runs callback cleanup when stopped or invalidated', async () => { const query = ref('') const cleanup = vi.fn() const watcher = useDebouncedWatch(query, (_value, _oldValue, onCleanup) => { onCleanup(cleanup) }, { delay: 100 }) query.value = 'first' await nextTick() vi.advanceTimersByTime(100) query.value = 'second' await nextTick() expect(cleanup).toHaveBeenCalledTimes(1) vi.advanceTimersByTime(100) watcher.stop() expect(cleanup).toHaveBeenCalledTimes(2) }) it('clears pending callbacks when the current effect scope disposes', async () => { const query = ref('') const calls: string[] = [] const scope = effectScope() scope.run(() => { useDebouncedWatch(query, value => { calls.push(value) }) }) query.value = 'pending' await nextTick() scope.stop() vi.runAllTimers() expect(calls).toEqual([]) }) })