Files
knowai/test/unit/utils/data.test.ts
tobegold574 6a81b7bb13
Some checks reported errors
continuous-integration/drone/push Build was killed
feat(image): 新建 knowai-core:1.0.0 镜像并完成推送
- 搭建 api、auth、utils 等逻辑模块
- 通过 tsc、eslint、vitest 测试验证

BREAKING CHANGE: 新镜像分支
2025-11-10 20:20:25 +08:00

483 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
throttle,
deepClone,
deepEqual,
pick,
omit,
merge,
toQueryString,
fromQueryString,
unique,
groupBy,
sortBy,
debounce
} from '@/utils/data';
describe('数据处理工具函数', () => {
describe('throttle', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('应该节流函数调用', () => {
const mockFn = vi.fn();
const throttledFn = throttle(mockFn, 100);
// 多次调用
throttledFn();
throttledFn();
throttledFn();
// 在等待时间内,函数应该只被调用一次
expect(mockFn).toHaveBeenCalledTimes(1);
// 快进时间
vi.advanceTimersByTime(100);
// 再次调用
throttledFn();
throttledFn();
// 函数应该被调用第二次
expect(mockFn).toHaveBeenCalledTimes(2);
});
it('应该支持leading选项', () => {
const mockFn = vi.fn();
const throttledFn = throttle(mockFn, 100, { leading: false });
// 多次调用
throttledFn();
throttledFn();
// 由于leading为false函数不应该被调用
expect(mockFn).toHaveBeenCalledTimes(0);
// 快进时间
vi.advanceTimersByTime(100);
// 函数应该被调用
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该支持trailing选项', () => {
const mockFn = vi.fn();
const throttledFn = throttle(mockFn, 100, { trailing: false });
// 多次调用
throttledFn();
throttledFn();
// 快进时间
vi.advanceTimersByTime(100);
// 由于trailing为false函数应该只被调用一次第一次调用
expect(mockFn).toHaveBeenCalledTimes(1);
});
});
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('应该防抖函数调用', () => {
const mockFn = vi.fn();
const debouncedFn = debounce(mockFn, 100);
// 多次调用
debouncedFn();
debouncedFn();
debouncedFn();
// 在等待时间内,函数不应该被调用
expect(mockFn).toHaveBeenCalledTimes(0);
// 快进时间
vi.advanceTimersByTime(100);
// 函数应该被调用一次
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该支持immediate选项', () => {
const mockFn = vi.fn();
const debouncedFn = debounce(mockFn, 100, true);
// 第一次调用
debouncedFn();
// 由于immediate为true函数应该立即被调用
expect(mockFn).toHaveBeenCalledTimes(1);
// 再次调用(在等待时间内)
debouncedFn();
// 函数仍然应该只被调用一次(第二次调用被防抖了)
expect(mockFn).toHaveBeenCalledTimes(1);
// 快进时间超过等待期
vi.advanceTimersByTime(100);
// 再次调用(等待期后)
debouncedFn();
// 函数应该被调用第二次(立即执行)
expect(mockFn).toHaveBeenCalledTimes(2);
});
});
describe('deepClone', () => {
it('应该深拷贝基本类型', () => {
expect(deepClone(null)).toBe(null);
expect(deepClone(undefined)).toBe(undefined);
expect(deepClone(123)).toBe(123);
expect(deepClone('string')).toBe('string');
expect(deepClone(true)).toBe(true);
});
it('应该深拷贝日期对象', () => {
const date = new Date('2023-05-15T10:30:45Z');
const clonedDate = deepClone(date);
expect(clonedDate).not.toBe(date); // 不是同一个对象
expect(clonedDate.getTime()).toBe(date.getTime()); // 但时间相同
});
it('应该深拷贝数组', () => {
const array = [1, 'string', { a: 1 }, [2, 3]];
const clonedArray = deepClone(array);
expect(clonedArray).not.toBe(array); // 不是同一个对象
expect(clonedArray).toEqual(array); // 但内容相同
expect(clonedArray[2]).not.toBe(array[2]); // 嵌套对象也被深拷贝
expect(clonedArray[3]).not.toBe(array[3]); // 嵌套数组也被深拷贝
});
it('应该深拷贝对象', () => {
const obj = {
a: 1,
b: 'string',
c: {
d: 2,
e: [3, 4]
},
f: new Date('2023-05-15T10:30:45Z')
};
const clonedObj = deepClone(obj);
expect(clonedObj).not.toBe(obj); // 不是同一个对象
expect(clonedObj).toEqual(obj); // 但内容相同
expect(clonedObj.c).not.toBe(obj.c); // 嵌套对象也被深拷贝
expect(clonedObj.c.e).not.toBe(obj.c.e); // 嵌套数组也被深拷贝
expect(clonedObj.f).not.toBe(obj.f); // 日期对象也被深拷贝
});
});
describe('deepEqual', () => {
it('应该比较基本类型', () => {
expect(deepEqual(1, 1)).toBe(true);
expect(deepEqual('string', 'string')).toBe(true);
expect(deepEqual(true, true)).toBe(true);
expect(deepEqual(null, null)).toBe(true);
expect(deepEqual(undefined, undefined)).toBe(true);
expect(deepEqual(1, 2)).toBe(false);
expect(deepEqual('string', 'other')).toBe(false);
expect(deepEqual(true, false)).toBe(false);
expect(deepEqual(null, undefined)).toBe(false);
});
it('应该比较数组', () => {
expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true);
expect(deepEqual([1, 2, 3], [1, 2, 4])).toBe(false);
expect(deepEqual([1, 2, 3], [1, 2])).toBe(false);
expect(deepEqual([1, 2, 3], [3, 2, 1])).toBe(false);
});
it('应该比较对象', () => {
expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true);
expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 3 })).toBe(false);
expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false);
expect(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true); // 顺序不重要
});
it('应该深度比较嵌套对象', () => {
const obj1 = { a: 1, b: { c: 2, d: [3, 4] } };
const obj2 = { a: 1, b: { c: 2, d: [3, 4] } };
const obj3 = { a: 1, b: { c: 2, d: [3, 5] } };
expect(deepEqual(obj1, obj2)).toBe(true);
expect(deepEqual(obj1, obj3)).toBe(false);
});
});
describe('pick', () => {
it('应该从对象中选取指定的属性', () => {
const obj = { a: 1, b: 2, c: 3, d: 4 };
const result = pick(obj, ['a', 'c']);
expect(result).toEqual({ a: 1, c: 3 });
expect(result).not.toBe(obj); // 返回新对象
});
it('应该处理不存在的属性', () => {
const obj = { a: 1, b: 2 };
const result = pick(obj, ['a', 'c']);
expect(result).toEqual({ a: 1 });
});
it('应该处理空键数组', () => {
const obj = { a: 1, b: 2 };
const result = pick(obj, []);
expect(result).toEqual({});
});
});
describe('omit', () => {
it('应该从对象中排除指定的属性', () => {
const obj = { a: 1, b: 2, c: 3, d: 4 };
const result = omit(obj, ['a', 'c']);
expect(result).toEqual({ b: 2, d: 4 });
expect(result).not.toBe(obj); // 返回新对象
});
it('应该处理不存在的属性', () => {
const obj = { a: 1, b: 2 };
const result = omit(obj, ['a', 'c']);
expect(result).toEqual({ b: 2 });
});
it('应该处理空键数组', () => {
const obj = { a: 1, b: 2 };
const result = omit(obj, []);
expect(result).toEqual(obj);
expect(result).not.toBe(obj); // 仍然返回新对象
});
});
describe('merge', () => {
it('应该合并多个对象', () => {
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };
const obj3 = { d: 5 };
const result = merge(obj1, obj2, obj3);
expect(result).toEqual({ a: 1, b: 3, c: 4, d: 5 });
});
it('应该处理null或undefined对象', () => {
const obj1 = { a: 1 };
const obj2 = null;
const obj3 = undefined;
const obj4 = { b: 2 };
const result = merge(obj1, obj2, obj3, obj4);
expect(result).toEqual({ a: 1, b: 2 });
});
it('应该处理非对象值', () => {
const obj1 = { a: 1 };
const obj2 = 'string';
const obj3 = 123;
const result = merge(obj1, obj2, obj3);
expect(result).toEqual({ a: 1 });
});
});
describe('toQueryString', () => {
it('应该将对象转换为查询字符串', () => {
const obj = { a: '1', b: 'test', c: 'value' };
const result = toQueryString(obj);
expect(result).toBe('a=1&b=test&c=value');
});
it('应该处理null和undefined值', () => {
const obj = { a: '1', b: null, c: undefined, d: '4' };
const result = toQueryString(obj);
expect(result).toBe('a=1&d=4');
});
it('应该处理数字和布尔值', () => {
const obj = { a: 1, b: true, c: false };
const result = toQueryString(obj);
expect(result).toBe('a=1&b=true&c=false');
});
it('应该处理空对象', () => {
const obj = {};
const result = toQueryString(obj);
expect(result).toBe('');
});
});
describe('fromQueryString', () => {
it('应该将查询字符串转换为对象', () => {
const queryString = 'a=1&b=test&c=value';
const result = fromQueryString(queryString);
expect(result).toEqual({ a: '1', b: 'test', c: 'value' });
});
it('应该处理空查询字符串', () => {
const queryString = '';
const result = fromQueryString(queryString);
expect(result).toEqual({});
});
it('应该处理特殊字符', () => {
const queryString = 'a=hello%20world&b=test%20%26%20value';
const result = fromQueryString(queryString);
expect(result).toEqual({ a: 'hello world', b: 'test & value' });
});
});
describe('unique', () => {
it('应该对基本类型数组去重', () => {
const array = [1, 2, 3, 2, 4, 1, 5];
const result = unique(array);
expect(result).toEqual([1, 2, 3, 4, 5]);
});
it('应该使用键函数对复杂对象数组去重', () => {
const array = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 1, name: 'Alice Smith' },
{ id: 3, name: 'Charlie' },
{ id: 2, name: 'Bob Johnson' }
];
const result = unique(array, (item: { id: number }) => item.id);
expect(result).toEqual([
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]);
});
it('应该处理空数组', () => {
const array: number[] = [];
const result = unique(array);
expect(result).toEqual([]);
});
});
describe('groupBy', () => {
it('应该按指定键对数组分组', () => {
const array = [
{ category: 'fruit', name: 'Apple' },
{ category: 'vegetable', name: 'Carrot' },
{ category: 'fruit', name: 'Banana' },
{ category: 'fruit', name: 'Orange' },
{ category: 'vegetable', name: 'Broccoli' }
];
const result = groupBy(array, (item: { category: string }) => item.category);
expect(result).toEqual({
fruit: [
{ category: 'fruit', name: 'Apple' },
{ category: 'fruit', name: 'Banana' },
{ category: 'fruit', name: 'Orange' }
],
vegetable: [
{ category: 'vegetable', name: 'Carrot' },
{ category: 'vegetable', name: 'Broccoli' }
]
});
});
it('应该处理空数组', () => {
const array: any[] = [];
const result = groupBy(array, (item: { category: string }) => item.category);
expect(result).toEqual({});
});
it('应该处理数字键', () => {
const array = [
{ score: 90, name: 'Alice' },
{ score: 80, name: 'Bob' },
{ score: 90, name: 'Charlie' },
{ score: 70, name: 'David' }
];
const result = groupBy(array, (item: { score: number }) => item.score);
expect(result).toEqual({
90: [
{ score: 90, name: 'Alice' },
{ score: 90, name: 'Charlie' }
],
80: [
{ score: 80, name: 'Bob' }
],
70: [
{ score: 70, name: 'David' }
]
});
});
});
describe('sortBy', () => {
it('应该使用默认比较函数对数组排序', () => {
const array = [3, 1, 4, 1, 5, 9, 2, 6];
const result = sortBy(array);
expect(result).toEqual([1, 1, 2, 3, 4, 5, 6, 9]);
expect(result).not.toBe(array); // 返回新数组
});
it('应该使用自定义比较函数对数组排序', () => {
const array = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
const result = sortBy(array, (a: { age: number }, b: { age: number }) => a.age - b.age);
expect(result).toEqual([
{ name: 'Bob', age: 25 },
{ name: 'Alice', age: 30 },
{ name: 'Charlie', age: 35 }
]);
});
it('应该处理空数组', () => {
const array: any[] = [];
const result = sortBy(array);
expect(result).toEqual([]);
});
});
});