【初始化】前端工程项目

This commit is contained in:
chudong
2025-05-09 15:11:21 +08:00
parent c012704c9a
commit d7c556c3b0
524 changed files with 55595 additions and 112 deletions

View File

@@ -0,0 +1,237 @@
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