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([]); }); }); });