Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(utils): add comments, ts type declarations, and vitest test cases to utils functions #3154

Merged
merged 2 commits into from
Mar 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions packages/utils/src/after-leave/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { afterLeave } from '..'

describe('afterLeave 函数', () => {
// 模拟定时器
beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.restoreAllMocks()
})

it('当参数不完整时应抛出错误', () => {
// 缺少 instance 参数
expect(() => {
// @ts-expect-error 测试错误情况
afterLeave(null, () => {})
}).toThrow('instance & callback is required')

// 缺少 callback 参数
expect(() => {
// @ts-expect-error 测试错误情况
afterLeave({ $on: vi.fn(), $once: vi.fn() }, null)
}).toThrow('instance & callback is required')
})

it('应使用 $on 注册事件监听(默认情况)', () => {
const instance = {
$on: vi.fn(),
$once: vi.fn()
}
const callback = vi.fn()

afterLeave(instance, callback)

expect(instance.$on).toHaveBeenCalledTimes(1)
expect(instance.$on).toHaveBeenCalledWith('after-leave', expect.any(Function))
expect(instance.$once).not.toHaveBeenCalled()
})

it('当 once 为 true 时应使用 $once 注册事件监听', () => {
const instance = {
$on: vi.fn(),
$once: vi.fn()
}
const callback = vi.fn()

afterLeave(instance, callback, 300, true)

expect(instance.$once).toHaveBeenCalledTimes(1)
expect(instance.$once).toHaveBeenCalledWith('after-leave', expect.any(Function))
expect(instance.$on).not.toHaveBeenCalled()
})

it('应在指定的延时后自动触发回调', () => {
const instance = {
$on: vi.fn(),
$once: vi.fn()
}
const callback = vi.fn()
const speed = 200

afterLeave(instance, callback, speed)
expect(callback).not.toHaveBeenCalled()

// 快进时间
vi.advanceTimersByTime(speed + 100)
expect(callback).toHaveBeenCalledTimes(1)
})

it('事件回调应该只被执行一次', () => {
const instance = {
$on: vi.fn(),
$once: vi.fn()
}
const callback = vi.fn()

afterLeave(instance, callback)

// 获取注册的事件回调
const eventCallback = instance.$on.mock.calls[0][1]

// 手动触发事件回调两次
eventCallback()
eventCallback()

// 确认回调只被执行一次
expect(callback).toHaveBeenCalledTimes(1)
})

it('应正确传递参数给回调函数', () => {
const instance = {
$on: vi.fn(),
$once: vi.fn()
}
const callback = vi.fn()
const testArg1 = 'test1'
const testArg2 = 'test2'

afterLeave(instance, callback)

// 获取注册的事件回调
const eventCallback = instance.$on.mock.calls[0][1]

// 手动触发事件回调并传递参数
eventCallback(testArg1, testArg2)

// 确认回调被执行并接收到正确的参数
expect(callback).toHaveBeenCalledWith(testArg1, testArg2)
})
})
27 changes: 25 additions & 2 deletions packages/utils/src/after-leave/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,39 @@
*
*/

/**
* 事件名称常量
*/
const AfterLave = 'after-leave'

/**
* 默认过渡速度(毫秒)
*/
const Speed = 300

export function afterLeave(instance, callback, speed = Speed, once = false) {
/**
* 在实例上注册"after-leave"事件并设置超时回调
* @param instance - 需要注册事件的实例对象,必须支持$on或$once方法
* @param callback - 事件触发或超时后执行的回调函数
* @param speed - 过渡动画的速度,单位为毫秒
* @param once - 是否只监听一次事件
*
* 该函数用于处理元素过渡结束后的回调,并添加超时保障机制
* 确保回调函数只会被执行一次
*/
export function afterLeave(
instance: { $once: (event: string, callback: Function) => void; $on: (event: string, callback: Function) => void },
callback: Function,
speed: number = Speed,
once = false
): void {
Comment on lines +35 to +40
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Type definitions improve function signature clarity, but consider refining the Function type.

The TypeScript type annotations significantly improve the function signature by clearly defining parameter types and return type. However, using the Function type is discouraged as it's too generic.

Consider using more specific function types:

-  callback: Function,
+  callback: (...args: any[]) => void,

🏁 Script executed:

#!/bin/bash
# Check if the Function type is used elsewhere in the codebase
rg "callback: Function" --type ts

Length of output: 936


Refine callback type signature for improved type-safety

The current use of the generic Function type in the afterLeave function (and similar patterns across the codebase) can be made more explicit. Instead of using:

-  callback: Function,
+  callback: (...args: any[]) => void,

this change will enhance clarity and ensure better type checking. Please update the type accordingly in packages/utils/src/after-leave/index.ts (lines 35-40) and consider aligning similar patterns in other parts of the codebase if appropriate.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function afterLeave(
instance: { $once: (event: string, callback: Function) => void; $on: (event: string, callback: Function) => void },
callback: Function,
speed: number = Speed,
once = false
): void {
export function afterLeave(
instance: { $once: (event: string, callback: Function) => void; $on: (event: string, callback: Function) => void },
callback: (...args: any[]) => void,
speed: number = Speed,
once = false
): void {

if (!instance || !callback) {
throw new Error('instance & callback is required')
}

let called = false

const eventCallback = function () {
const eventCallback = function (): void {
if (called) {
return
}
Expand All @@ -40,5 +62,6 @@ export function afterLeave(instance, callback, speed = Speed, once = false) {
instance.$on(AfterLave, eventCallback)
}

// 设置超时保障,确保即使过渡事件没触发,回调也会在指定时间后执行
setTimeout(eventCallback, speed + 100)
}
213 changes: 213 additions & 0 deletions packages/utils/src/array/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* 数组工具函数单元测试
*/
import { describe, expect, it } from 'vitest'
import { indexOf, find, remove, sort, push, unique, toObject, transformPidToChildren, transformTreeData } from '..'

describe('数组工具函数', () => {
describe('indexOf 函数', () => {
it('应能找到普通数组中元素的索引', () => {
const arr = [1, 2, 3, 4]
expect(indexOf(arr, 2)).toBe(1)
})

it('应能找到包含 NaN 的数组中 NaN 的索引', () => {
const arr = [1, 2, NaN, 4]
expect(indexOf(arr, NaN)).toBe(2)
})

it('当元素不存在时应返回 -1', () => {
const arr = [1, 2, 3, 4]
expect(indexOf(arr, 5)).toBe(-1)
})

it('应支持自定义断言函数', () => {
const arr = [{ id: 1 }, { id: 2 }, { id: 3 }]
const index = indexOf(arr, { id: 2 }, (item, target) => item.id === target.id)
expect(index).toBe(1)
})
})

describe('find 函数', () => {
it('应能通过断言函数找到元素', () => {
const arr = [1, 2, 3, 4]
const result = find(arr, (value) => value > 2)
expect(result).toBe(3)
})

it('当找不到元素时应返回 undefined', () => {
const arr = [1, 2, 3, 4]
const result = find(arr, (value) => value > 10)
expect(result).toBeUndefined()
})
})

describe('remove 函数', () => {
it('应能从数组中删除指定元素', () => {
const arr = [1, 2, 3, 4]
remove(arr, 2)
expect(arr).toEqual([1, 3, 4])
})

it('应能从数组中删除指定数量的元素', () => {
const arr = [1, 2, 3, 4]
remove(arr, 2, 2)
expect(arr).toEqual([1, 4])
})

it('应能从数组中删除 NaN', () => {
const arr = [1, 2, NaN, 4]
remove(arr, NaN)
expect(arr).toEqual([1, 2, 4])
})

it('当元素不存在时不应有变化', () => {
const arr = [1, 2, 3, 4]
remove(arr, 5)
expect(arr).toEqual([1, 2, 3, 4])
})
})

describe('sort 函数', () => {
it('应按字段名称对对象数组进行升序排序', () => {
const arr = [{ a: 100 }, { a: 1 }, { a: 10 }]
const result = sort(arr, 'a')
expect(result).toEqual([{ a: 1 }, { a: 10 }, { a: 100 }])
})

it('应按字段名称对对象数组进行降序排序', () => {
const arr = [{ a: 100 }, { a: 1 }, { a: 10 }]
const result = sort(arr, 'a', 'desc')
expect(result).toEqual([{ a: 100 }, { a: 10 }, { a: 1 }])
})

it('应正确处理包含 NaN 值的情况', () => {
const arr = [{ a: 100 }, { a: 1 }, { a: NaN }, { a: 10 }]
const result = sort(arr, 'a')
expect(result).toEqual([{ a: 1 }, { a: 10 }, { a: 100 }, { a: NaN }])
})
})

describe('push 函数', () => {
it('应向数组中添加新元素', () => {
const arr = [1, 2, 3, 4]
push(arr, 5)
expect(arr).toEqual([1, 2, 3, 4, 5])
})

it('不应向数组中添加已存在的元素', () => {
const arr = [1, 2, 3, 4]
push(arr, 2)
expect(arr).toEqual([1, 2, 3, 4])
})

it('不应向数组中添加已存在的 NaN', () => {
const arr = [1, 2, NaN, 4]
push(arr, NaN)
expect(arr).toEqual([1, 2, NaN, 4])
})
})

describe('unique 函数', () => {
it('应去除数组中的重复元素', () => {
const arr = [1, 2, 2, 3, 4, 4]
const result = unique(arr)
expect(result).toEqual([1, 2, 3, 4])
})

it('应正确处理包含 NaN 的数组', () => {
const arr = [1, NaN, 2, NaN, 2, 3, 4]
const result = unique(arr)
expect(result).toEqual([1, NaN, 2, 3, 4])
})
})

describe('toObject 函数', () => {
it('应将对象数组转换为单一对象', () => {
const arr = [{ key1: 'value1' }, { key2: 'value2' }]
const result = toObject(arr)
expect(result).toEqual({ key1: 'value1', key2: 'value2' })
})

it('后面的对象字段应覆盖前面的同名字段', () => {
const arr = [{ key1: 'value1' }, { key1: 'value2' }]
const result = toObject(arr)
expect(result).toEqual({ key1: 'value2' })
})
})

describe('transformPidToChildren 函数', () => {
it('应将扁平数据转换为树状结构', () => {
const data = [
{ id: 100, pId: 0, label: '首页' },
{ id: 101, pId: 100, label: '指南' }
]
const result = transformPidToChildren(data)
expect(result).toEqual([
{
id: 100,
label: '首页',
children: [{ id: 101, label: '指南' }]
}
])
})

it('应支持自定义字段名', () => {
const data = [
{ uid: 100, parentId: 0, label: '首页' },
{ uid: 101, parentId: 100, label: '指南' }
]
const result = transformPidToChildren(data, 'parentId', 'items', 'uid')
expect(result).toEqual([
{
uid: 100,
label: '首页',
items: [{ uid: 101, label: '指南' }]
}
])
})
})

describe('transformTreeData 函数', () => {
it('应将扁平数据转换为树状结构', () => {
const data = [
{ id: 100, pId: 0, label: '首页' },
{ id: 101, pId: 100, label: '指南' }
]
const result = transformTreeData(data)
expect(result).toEqual([
{
id: 100,
label: '首页',
children: [{ id: 101, label: '指南' }]
}
])
})

it('应支持非数组输入', () => {
const data = { id: 100, pId: 0, label: '首页' }
const result = transformTreeData(data)
expect(result).toEqual([
{
id: 100,
label: '首页'
}
])
})

it('应支持自定义字段名', () => {
const data = [
{ uid: 100, parentId: 0, label: '首页' },
{ uid: 101, parentId: 100, label: '指南' }
]
const result = transformTreeData(data, 'uid', 'parentId')
expect(result).toEqual([
{
uid: 100,
label: '首页',
children: [{ uid: 101, label: '指南' }]
}
])
})
})
})
Loading
Loading