allinssl/frontend/packages/vue/hooks/test/debounce-fn.spec.js

238 lines
7.7 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import useDebounceFn from '../src/debounce-fn'
import { mount } from '@vue/test-utils'
import { ref, nextTick } from 'vue'
describe('useDebounceFn', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.restoreAllMocks()
vi.useRealTimers()
})
it('应该在指定延迟后执行函数', async () => {
const mockFn = vi.fn().mockReturnValue('测试结果')
const debounced = useDebounceFn(mockFn, 100)
const promise = debounced('参数1', '参数2')
expect(mockFn).not.toHaveBeenCalled()
// 前进100ms
vi.advanceTimersByTime(100)
const result = await promise
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('参数1', '参数2')
expect(result).toBe('测试结果')
})
it('应该在多次调用时只执行最后一次', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100)
debounced('调用1')
debounced('调用2')
debounced('调用3')
expect(mockFn).not.toHaveBeenCalled()
// 前进100ms
vi.advanceTimersByTime(100)
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('调用3')
})
it('应该支持函数形式的延迟参数', async () => {
const mockFn = vi.fn()
const getDelay = () => 200
const debounced = useDebounceFn(mockFn, getDelay)
debounced()
expect(mockFn).not.toHaveBeenCalled()
// 前进100ms
vi.advanceTimersByTime(100)
expect(mockFn).not.toHaveBeenCalled()
// 再前进100ms
vi.advanceTimersByTime(100)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('应该支持executeDelay选项', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100, { executeDelay: 50 })
debounced()
expect(mockFn).not.toHaveBeenCalled()
// 前进100ms - 防抖结束但还没执行
vi.advanceTimersByTime(100)
expect(mockFn).not.toHaveBeenCalled()
// 再前进50ms - executeDelay后应该执行
vi.advanceTimersByTime(50)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('如果在等待期间再次调用应该重置定时器', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100)
debounced()
// 前进50ms
vi.advanceTimersByTime(50)
expect(mockFn).not.toHaveBeenCalled()
// 再次调用
debounced()
// 再前进50ms - 原来的定时器时间到了,但由于重置应该还没执行
vi.advanceTimersByTime(50)
expect(mockFn).not.toHaveBeenCalled()
// 再前进50ms - 新定时器时间到了,应该执行
vi.advanceTimersByTime(50)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('应该支持立即执行选项', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100, { immediate: true })
// 第一次调用立即执行
debounced('立即参数')
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('立即参数')
// 延迟期间再次调用不会立即执行
debounced('第二次参数')
expect(mockFn).toHaveBeenCalledTimes(1)
// 前进100ms后应该执行最后一次调用
vi.advanceTimersByTime(100)
expect(mockFn).toHaveBeenCalledTimes(2)
expect(mockFn).toHaveBeenLastCalledWith('第二次参数')
})
it('应该支持防抖函数的取消功能', async () => {
const mockFn = vi.fn()
const { run, cancel } = useDebounceFn(mockFn, 100)
run()
expect(mockFn).not.toHaveBeenCalled()
// 取消防抖
cancel()
// 前进100ms
vi.advanceTimersByTime(100)
expect(mockFn).not.toHaveBeenCalled()
})
it('应该支持防抖函数的立即执行功能', async () => {
const mockFn = vi.fn()
const { run, flush } = useDebounceFn(mockFn, 100)
run('待执行参数')
expect(mockFn).not.toHaveBeenCalled()
// 立即执行
flush()
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('待执行参数')
// 前进100ms, 由于已经执行过,不会再次执行
vi.advanceTimersByTime(100)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('在组件卸载时应该清除定时器', () => {
const mockFn = vi.fn()
// 模拟组件中使用hook
const wrapper = mount({
template: '<div></div>',
setup() {
const debounced = useDebounceFn(mockFn, 100)
debounced()
return { debounced }
},
})
expect(mockFn).not.toHaveBeenCalled()
// 卸载组件,此时应该清除定时器
wrapper.unmount()
// 前进100ms由于组件已卸载定时器应该被清除
vi.advanceTimersByTime(100)
expect(mockFn).not.toHaveBeenCalled()
})
it('应该保留函数的上下文', async () => {
const context = {
value: '上下文值',
fn() {
return this.value
},
}
const spy = vi.spyOn(context, 'fn')
const debounced = useDebounceFn(context.fn.bind(context), 100)
debounced()
vi.advanceTimersByTime(100)
expect(spy).toHaveBeenCalledTimes(1)
expect(spy.mock.results[0].value).toBe('上下文值')
})
it('应该支持动态修改延迟时间', async () => {
const mockFn = vi.fn()
const delay = ref(100)
const debounced = useDebounceFn(mockFn, delay)
debounced()
expect(mockFn).not.toHaveBeenCalled()
// 修改延迟为50ms
delay.value = 50
await nextTick()
// 前进50ms
vi.advanceTimersByTime(50)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('应该支持Promise返回值', async () => {
const mockFn = vi.fn().mockResolvedValue('Promise结果')
const debounced = useDebounceFn(mockFn, 100)
const promise = debounced()
vi.advanceTimersByTime(100)
const result = await promise
expect(result).toBe('Promise结果')
})
it('应该处理函数执行期间的错误', async () => {
const error = new Error('测试错误')
const mockFn = vi.fn().mockRejectedValue(error)
const debounced = useDebounceFn(mockFn, 100)
const promise = debounced()
vi.advanceTimersByTime(100)
await expect(promise).rejects.toThrow('测试错误')
})
it('在多次调用时应该只保留最后一个Promise', async () => {
let callIndex = 0
const mockFn = vi.fn().mockImplementation(() => {
callIndex++
return Promise.resolve(`结果${callIndex}`)
})
const debounced = useDebounceFn(mockFn, 100)
const promise1 = debounced()
const promise2 = debounced()
const promise3 = debounced()
vi.advanceTimersByTime(100)
// 所有promise应该解析为最后一次调用的结果
expect(await promise1).toBe('结果1')
expect(await promise2).toBe('结果1')
expect(await promise3).toBe('结果1')
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('应该支持maxWait选项确保函数在指定时间内至少执行一次', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100, { maxWait: 300 })
debounced()
// 前进50ms
vi.advanceTimersByTime(50)
// 再次调用重置防抖
debounced()
// 再前进50ms
vi.advanceTimersByTime(50)
// 再次调用重置防抖
debounced()
// 再前进50ms
vi.advanceTimersByTime(50)
// 再次调用重置防抖
debounced()
// 再前进50ms
vi.advanceTimersByTime(50)
// 再次调用重置防抖
debounced()
// 再前进100ms此时总共过去了300ms应该由于maxWait触发函数执行
vi.advanceTimersByTime(100)
expect(mockFn).toHaveBeenCalledTimes(1)
})
it('应该支持组合使用immediate和trailing选项', async () => {
const mockFn = vi.fn()
const debounced = useDebounceFn(mockFn, 100, {
immediate: true,
trailing: false,
})
// 第一次调用立即执行
debounced('第一次')
expect(mockFn).toHaveBeenCalledTimes(1)
expect(mockFn).toHaveBeenCalledWith('第一次')
// 延迟期间再次调用
debounced('第二次')
// 前进100ms
vi.advanceTimersByTime(100)
// 由于trailing为false不会执行最后一次调用
expect(mockFn).toHaveBeenCalledTimes(1)
})
})
//# sourceMappingURL=debounce-fn.spec.js.map