// Mock Taro 在导入之前 import { AsyncTaskManager, TaskStatus, TaskConfig } from '../AsyncTaskManager'; const mockTaro = { request: jest.fn(), setStorage: jest.fn().mockResolvedValue({}), getStorage: jest.fn().mockResolvedValue({ data: {} }), removeStorage: jest.fn().mockResolvedValue({}) }; // Mock '@tarojs/taro' 模块 jest.mock('@tarojs/taro', () => mockTaro); describe('AsyncTaskManager', () => { let taskManager: AsyncTaskManager; beforeEach(() => { jest.clearAllMocks(); jest.useFakeTimers(); // 确保 mock 函数正确重置 mockTaro.setStorage.mockResolvedValue({}); mockTaro.getStorage.mockResolvedValue({ data: {} }); // 注入 storage mock taskManager = new AsyncTaskManager(mockTaro); }); afterEach(() => { jest.useRealTimers(); jest.restoreAllMocks(); }); describe('任务提交和状态管理', () => { it('应该能创建新任务', async () => { const config: TaskConfig = { taskId: 'test-task-001', pollingInterval: 2000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); expect(taskManager.hasTask('test-task-001')).toBe(true); expect(taskManager.getTaskStatus('test-task-001')).toBe(TaskStatus.RUNNING); }); it('应该能获取任务状态', async () => { const config: TaskConfig = { taskId: 'test-task-002', pollingInterval: 1000, maxRetries: 2, timeout: 30000 }; await taskManager.startTask(config); const status = taskManager.getTaskStatus('test-task-002'); expect(status).toBe(TaskStatus.RUNNING); }); it('应该能取消正在运行的任务', async () => { const config: TaskConfig = { taskId: 'test-task-003', pollingInterval: 2000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); await taskManager.cancelTask('test-task-003'); expect(taskManager.getTaskStatus('test-task-003')).toBe(TaskStatus.CANCELLED); }); }); describe('轮询机制', () => { it('应该开始轮询任务状态', async () => { const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-poll-001', pollingInterval: 1000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); // 快进1秒触发轮询 jest.advanceTimersByTime(1000); await Promise.resolve(); // 等待异步操作 expect(mockStatusCheck).toHaveBeenCalledWith('test-poll-001'); }); it('应该在任务完成时停止轮询', async () => { const mockStatusCheck = jest.fn() .mockResolvedValueOnce(TaskStatus.RUNNING) .mockResolvedValueOnce(TaskStatus.SUCCESS); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-poll-002', pollingInterval: 1000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); // 触发两次轮询 jest.advanceTimersByTime(1000); await Promise.resolve(); jest.advanceTimersByTime(1000); await Promise.resolve(); expect(taskManager.getTaskStatus('test-poll-002')).toBe(TaskStatus.SUCCESS); }); }); describe('回调处理', () => { it('应该在任务成功时调用成功回调', async () => { const onSuccess = jest.fn(); const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.SUCCESS); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-callback-001', pollingInterval: 1000, maxRetries: 3, timeout: 60000, onSuccess }; await taskManager.startTask(config); jest.advanceTimersByTime(1000); await Promise.resolve(); expect(onSuccess).toHaveBeenCalled(); }); it('应该在任务失败时调用错误回调', async () => { const onError = jest.fn(); const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.FAILED); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-callback-002', pollingInterval: 1000, maxRetries: 3, timeout: 60000, onError }; await taskManager.startTask(config); jest.advanceTimersByTime(1000); await Promise.resolve(); expect(onError).toHaveBeenCalled(); }); it('应该在进度更新时调用进度回调', async () => { const onProgress = jest.fn(); const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-callback-003', pollingInterval: 1000, maxRetries: 3, timeout: 60000, onProgress }; await taskManager.startTask(config); jest.advanceTimersByTime(1000); await Promise.resolve(); expect(onProgress).toHaveBeenCalledWith(TaskStatus.RUNNING); }); }); describe('超时处理', () => { it('应该在超时后将任务标记为超时状态', async () => { const onTimeout = jest.fn(); const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-timeout-001', pollingInterval: 100, maxRetries: 3, timeout: 150, // 150毫秒超时 onTimeout }; await taskManager.startTask(config); // 快进到第一次轮询时间 jest.advanceTimersByTime(100); await Promise.resolve(); // 再快进到超时时间 jest.advanceTimersByTime(100); await Promise.resolve(); expect(taskManager.getTaskStatus('test-timeout-001')).toBe(TaskStatus.TIMEOUT); expect(onTimeout).toHaveBeenCalled(); }, 2000); // 2秒超时限制 }); describe('持久化存储', () => { it('应该将任务状态保存到本地存储', async () => { const config: TaskConfig = { taskId: 'test-storage-001', pollingInterval: 2000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); expect(mockTaro.setStorage).toHaveBeenCalledWith({ key: 'async_tasks', data: expect.objectContaining({ 'test-storage-001': expect.objectContaining({ id: 'test-storage-001', status: TaskStatus.RUNNING }) }) }); }, 2000); it('应该从本地存储恢复任务', async () => { const storedTasks = { 'restored-task-001': { id: 'restored-task-001', status: TaskStatus.RUNNING, startTime: Date.now() - 10000, retryCount: 0, lastPollTime: Date.now() - 5000, config: { taskId: 'restored-task-001', pollingInterval: 2000, maxRetries: 3, timeout: 60000 } } }; mockTaro.getStorage.mockResolvedValueOnce({ data: storedTasks }); await taskManager.resumeTasks(); expect(taskManager.hasTask('restored-task-001')).toBe(true); }); }); describe('错误处理和重试', () => { it('应该在网络错误时进行重试', async () => { const mockStatusCheck = jest.fn() .mockRejectedValueOnce(new Error('网络错误')) .mockRejectedValueOnce(new Error('网络错误')) .mockResolvedValueOnce(TaskStatus.SUCCESS); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-retry-001', pollingInterval: 1000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); // 触发多次轮询以测试重试 for (let i = 0; i < 3; i++) { jest.advanceTimersByTime(1000); await Promise.resolve(); } expect(mockStatusCheck).toHaveBeenCalledTimes(3); expect(taskManager.getTaskStatus('test-retry-001')).toBe(TaskStatus.SUCCESS); }); it('应该在达到最大重试次数后标记为失败', async () => { const mockStatusCheck = jest.fn().mockRejectedValue(new Error('持续网络错误')); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-retry-002', pollingInterval: 1000, maxRetries: 2, timeout: 60000 }; await taskManager.startTask(config); // 触发足够的轮询次数以超过重试限制 for (let i = 0; i < 4; i++) { jest.advanceTimersByTime(1000); await Promise.resolve(); } expect(taskManager.getTaskStatus('test-retry-002')).toBe(TaskStatus.FAILED); }); }); describe('任务暂停和恢复', () => { it('应该能暂停所有活跃任务的轮询', async () => { const config: TaskConfig = { taskId: 'test-pause-001', pollingInterval: 1000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); taskManager.pausePolling(); const mockStatusCheck = jest.fn(); taskManager.setStatusChecker(mockStatusCheck); // 即使时间前进,暂停状态下不应该触发轮询 jest.advanceTimersByTime(2000); await Promise.resolve(); expect(mockStatusCheck).not.toHaveBeenCalled(); }); it('应该能恢复所有任务的轮询', async () => { const mockStatusCheck = jest.fn().mockResolvedValue(TaskStatus.RUNNING); taskManager.setStatusChecker(mockStatusCheck); const config: TaskConfig = { taskId: 'test-resume-001', pollingInterval: 1000, maxRetries: 3, timeout: 60000 }; await taskManager.startTask(config); taskManager.pausePolling(); taskManager.activatePolling(); jest.advanceTimersByTime(1000); await Promise.resolve(); expect(mockStatusCheck).toHaveBeenCalled(); }); }); });