furniture-3d-system-Wu/tests/lib/performance.test.ts
WuYongJie 75db836d29 feat(3D核心功能): 完成所有必需实现任务
- 文件上传验证: ModelUploader组件增强、上传API错误处理
- GLTF解析器: 增强节点提取和元数据
- 板材编辑: PanelEditor客户端验证 + panels API服务端验证
- 装配步骤编辑: 修复未使用代码,添加预览按钮
- 装配动画播放: AssemblyPlayer步骤导航边界检查优化
- 分享链接: 完整CRUD API(创建、获取、删除),含验证和过期检查

跳过可选测试任务以加快MVP开发
2025-12-22 16:18:13 +08:00

404 lines
10 KiB
TypeScript

/**
* 性能优化工具函数单元测试
* 测试懒加载、防抖、节流等性能优化功能
*/
import {
debounce,
throttle,
preloadImage,
preloadImages,
isSlowNetwork,
isDataSaverEnabled,
measurePerformance,
measureAsyncPerformance,
requestIdleCallback,
cancelIdleCallback,
} from '@/lib/performance';
// Mock 浏览器 API
global.Image = class {
onload: (() => void) | null = null;
onerror: ((error: Error) => void) | null = null;
src = '';
constructor() {
// 模拟图片加载成功
setTimeout(() => {
if (this.onload) {
this.onload();
}
}, 10);
}
} as any;
describe('性能优化工具函数测试', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('debounce - 防抖函数', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('应该延迟执行函数', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 300);
debouncedFn('test');
expect(mockFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(300);
expect(mockFn).toHaveBeenCalledWith('test');
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该在延迟期间重新触发时重置计时器', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 300);
debouncedFn('first');
jest.advanceTimersByTime(200);
debouncedFn('second');
jest.advanceTimersByTime(200);
expect(mockFn).not.toHaveBeenCalled();
jest.advanceTimersByTime(100);
expect(mockFn).toHaveBeenCalledWith('second');
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该支持多个参数', () => {
const mockFn = jest.fn();
const debouncedFn = debounce(mockFn, 300);
debouncedFn('arg1', 'arg2', 'arg3');
jest.advanceTimersByTime(300);
expect(mockFn).toHaveBeenCalledWith('arg1', 'arg2', 'arg3');
});
});
describe('throttle - 节流函数', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
it('应该立即执行第一次调用', () => {
const mockFn = jest.fn();
const throttledFn = throttle(mockFn, 300);
throttledFn('test');
expect(mockFn).toHaveBeenCalledWith('test');
expect(mockFn).toHaveBeenCalledTimes(1);
});
it('应该在限制时间内忽略后续调用', () => {
const mockFn = jest.fn();
const throttledFn = throttle(mockFn, 300);
throttledFn('first');
throttledFn('second');
throttledFn('third');
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith('first');
});
it('应该在限制时间后允许再次执行', () => {
const mockFn = jest.fn();
const throttledFn = throttle(mockFn, 300);
throttledFn('first');
expect(mockFn).toHaveBeenCalledTimes(1);
jest.advanceTimersByTime(300);
throttledFn('second');
expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith('second');
});
});
describe('preloadImage - 图片预加载', () => {
beforeEach(() => {
// Mock 图片加载成功
global.Image = class {
onload: (() => void) | null = null;
onerror: ((error: Error) => void) | null = null;
src = '';
constructor() {
setTimeout(() => {
if (this.onload) {
this.onload();
}
}, 10);
}
} as any;
});
it('应该成功预加载图片', async () => {
const src = '/test-image.jpg';
await expect(preloadImage(src)).resolves.toBeUndefined();
});
it('应该处理图片加载失败', async () => {
// Mock 图片加载失败
global.Image = class {
onload: (() => void) | null = null;
onerror: ((error: Error) => void) | null = null;
src = '';
constructor() {
setTimeout(() => {
if (this.onerror) {
this.onerror(new Error('加载失败'));
}
}, 10);
}
} as any;
const src = '/invalid-image.jpg';
await expect(preloadImage(src)).rejects.toThrow();
});
});
describe('preloadImages - 批量预加载图片', () => {
beforeEach(() => {
// Mock 图片加载成功
global.Image = class {
onload: (() => void) | null = null;
onerror: ((error: Error) => void) | null = null;
src = '';
constructor() {
setTimeout(() => {
if (this.onload) {
this.onload();
}
}, 10);
}
} as any;
});
it('应该成功预加载多张图片', async () => {
const srcs = ['/image1.jpg', '/image2.jpg', '/image3.jpg'];
await expect(preloadImages(srcs)).resolves.toBeUndefined();
});
it('应该处理空数组', async () => {
await expect(preloadImages([])).resolves.toBeUndefined();
});
});
describe('isSlowNetwork - 检测慢速网络', () => {
it('应该在没有 connection API 时返回 false', () => {
// @ts-ignore
delete navigator.connection;
expect(isSlowNetwork()).toBe(false);
});
it('应该检测慢速连接类型', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: {
effectiveType: '2g',
},
});
expect(isSlowNetwork()).toBe(true);
});
it('应该检测低下载速度', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: {
effectiveType: '4g',
downlink: 0.5,
},
});
expect(isSlowNetwork()).toBe(true);
});
it('应该在快速网络时返回 false', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: {
effectiveType: '4g',
downlink: 10,
},
});
expect(isSlowNetwork()).toBe(false);
});
});
describe('isDataSaverEnabled - 检测数据节省模式', () => {
it('应该在没有 connection API 时返回 false', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: undefined,
});
expect(isDataSaverEnabled()).toBe(false);
});
it('应该检测数据节省模式已启用', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: {
saveData: true,
},
});
expect(isDataSaverEnabled()).toBe(true);
});
it('应该检测数据节省模式未启用', () => {
Object.defineProperty(navigator, 'connection', {
writable: true,
value: {
saveData: false,
},
});
expect(isDataSaverEnabled()).toBe(false);
});
});
describe('measurePerformance - 性能监控', () => {
it('应该测量同步函数执行时间', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const testFn = (x: number, y: number) => x + y;
const measuredFn = measurePerformance(testFn, '加法运算');
const result = measuredFn(1, 2);
expect(result).toBe(3);
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('[性能] 加法运算:')
);
consoleSpy.mockRestore();
});
it('应该保持函数的返回值', () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const testFn = () => 'test result';
const measuredFn = measurePerformance(testFn, '测试函数');
expect(measuredFn()).toBe('test result');
consoleSpy.mockRestore();
});
});
describe('measureAsyncPerformance - 异步性能监控', () => {
it('应该测量异步函数执行时间', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const asyncFn = async () => {
await new Promise(resolve => setTimeout(resolve, 100));
return 'async result';
};
const result = await measureAsyncPerformance(asyncFn, '异步操作');
expect(result).toBe('async result');
expect(consoleSpy).toHaveBeenCalledWith(
expect.stringContaining('[性能] 异步操作:')
);
consoleSpy.mockRestore();
});
it('应该处理异步函数错误', async () => {
const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
const asyncFn = async () => {
throw new Error('测试错误');
};
await expect(
measureAsyncPerformance(asyncFn, '错误操作')
).rejects.toThrow('测试错误');
consoleSpy.mockRestore();
});
});
describe('requestIdleCallback - 空闲回调', () => {
it('应该使用原生 requestIdleCallback', () => {
const mockRequestIdleCallback = jest.fn().mockReturnValue(123);
window.requestIdleCallback = mockRequestIdleCallback;
const callback = jest.fn();
const id = requestIdleCallback(callback);
expect(mockRequestIdleCallback).toHaveBeenCalledWith(callback, undefined);
expect(id).toBe(123);
});
it('应该支持 timeout 选项', () => {
const mockRequestIdleCallback = jest.fn().mockReturnValue(123);
window.requestIdleCallback = mockRequestIdleCallback;
const callback = jest.fn();
const options = { timeout: 1000 };
requestIdleCallback(callback, options);
expect(mockRequestIdleCallback).toHaveBeenCalledWith(callback, options);
});
it('应该在不支持时降级到 setTimeout', () => {
// @ts-ignore
delete window.requestIdleCallback;
const mockSetTimeout = jest.spyOn(window, 'setTimeout');
const callback = jest.fn();
requestIdleCallback(callback);
expect(mockSetTimeout).toHaveBeenCalledWith(callback, 1);
mockSetTimeout.mockRestore();
});
});
describe('cancelIdleCallback - 取消空闲回调', () => {
it('应该使用原生 cancelIdleCallback', () => {
const mockCancelIdleCallback = jest.fn();
window.cancelIdleCallback = mockCancelIdleCallback;
cancelIdleCallback(123);
expect(mockCancelIdleCallback).toHaveBeenCalledWith(123);
});
it('应该在不支持时降级到 clearTimeout', () => {
// @ts-ignore
delete window.cancelIdleCallback;
const mockClearTimeout = jest.spyOn(window, 'clearTimeout');
cancelIdleCallback(123);
expect(mockClearTimeout).toHaveBeenCalledWith(123);
mockClearTimeout.mockRestore();
});
});
});