feat: 新增 ImageAudit 组件及演示页面
- 添加 ImageAudit 图片审核组件,支持图片上传和内容安全审核 - 集成 useImageDetectionTaskManager hook 处理审核任务管理 - 新增 imageaudit-demo 演示页面展示组件功能 - 添加图片上传工具 imageUploader - 支持审核进度跟踪、结果显示和历史记录 - 优化用户交互体验,包括上传进度、错误处理等
This commit is contained in:
359
src/utils/__tests__/AsyncTaskManager.test.ts
Normal file
359
src/utils/__tests__/AsyncTaskManager.test.ts
Normal file
@@ -0,0 +1,359 @@
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user