Files
Pre_Seg_Server/src/components/Dashboard.test.tsx
admin d583b32221 更新品牌文案与演示项目名称
- 登录页和侧栏统一使用根目录 logo_square.png,并更新登录系统名称与副标题。

- 更新 Dashboard、项目库和工作区时间轴文案,移除底层时序视频图层说明。

- 演示视频项目显示名改为“演视LC视频序列”,启动时兼容迁移旧 Data_MyVideo_1 名称,恢复出厂设置使用新名。

- 调整侧栏用户管理入口为用户图标,底部当前用户入口为退出图标,并让退出提示不接收鼠标事件。

- 补充前端组件测试、后端演示重置测试和文档说明。
2026-05-07 15:14:53 +08:00

278 lines
8.3 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 { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { Dashboard } from './Dashboard';
const apiMock = vi.hoisted(() => ({
getDashboardOverview: vi.fn(),
cancelTask: vi.fn(),
retryTask: vi.fn(),
getTask: vi.fn(),
}));
const wsMock = vi.hoisted(() => {
const state = {
callback: undefined as undefined | ((data: any) => void),
statusCallback: undefined as undefined | ((status: any) => void),
connected: false,
};
return {
state,
progressWS: {
connect: vi.fn(() => { state.connected = true; }),
disconnect: vi.fn(() => { state.connected = false; }),
isConnected: vi.fn(() => state.connected),
onProgress: vi.fn((cb: (data: any) => void) => {
state.callback = cb;
return vi.fn();
}),
onStatus: vi.fn((cb: (status: any) => void) => {
state.statusCallback = cb;
cb(state.connected ? 'connected' : 'disconnected');
return vi.fn();
}),
},
};
});
vi.mock('../lib/websocket', () => ({
progressWS: wsMock.progressWS,
}));
vi.mock('../lib/api', () => ({
getDashboardOverview: apiMock.getDashboardOverview,
cancelTask: apiMock.cancelTask,
retryTask: apiMock.retryTask,
getTask: apiMock.getTask,
}));
describe('Dashboard', () => {
beforeEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
wsMock.state.connected = false;
wsMock.state.callback = undefined;
wsMock.state.statusCallback = undefined;
apiMock.getDashboardOverview.mockResolvedValue({
summary: {
project_count: 2,
parsing_task_count: 1,
annotation_count: 5,
frame_count: 100,
template_count: 3,
system_load_percent: 12,
},
tasks: [
{
id: 'project-1',
project_id: 1,
name: '真实项目.mp4',
progress: 60,
status: 'pending',
raw_status: 'running',
error: null,
frame_count: 10,
updated_at: '2026-05-01T00:00:00Z',
},
],
activity: [
{
id: 'activity-1',
kind: 'project',
time: '2026-05-01T00:00:00Z',
message: '项目状态: pending',
project: '真实项目.mp4',
},
],
});
});
it('loads dashboard stats, tasks, and activity from the backend overview endpoint', async () => {
render(<Dashboard />);
await waitFor(() => expect(apiMock.getDashboardOverview).toHaveBeenCalled());
expect(screen.getByText('项目总数')).toBeInTheDocument();
expect(screen.getByText('已存标注')).toBeInTheDocument();
expect(screen.getByText('真实项目.mp4')).toBeInTheDocument();
expect(screen.getByText('项目状态: pending')).toBeInTheDocument();
expect(screen.getByText('系统全局数据监控')).toBeInTheDocument();
expect(screen.queryByText('City_Driving_Dataset_004.mp4')).not.toBeInTheDocument();
});
it('keeps a recently completed task visible in the progress panel', async () => {
apiMock.getDashboardOverview.mockResolvedValueOnce({
summary: {
project_count: 1,
parsing_task_count: 0,
annotation_count: 0,
frame_count: 120,
template_count: 1,
system_load_percent: 8,
},
tasks: [
{
id: 'task-20',
task_id: 20,
project_id: 1,
name: 'completed.mp4',
progress: 100,
status: '解析完成',
raw_status: 'success',
error: null,
frame_count: 120,
updated_at: '2026-05-01T00:00:00Z',
},
],
activity: [],
});
render(<Dashboard />);
expect(await screen.findByText('任务进度 (当前 / 最近)')).toBeInTheDocument();
expect(screen.getByText('completed.mp4')).toBeInTheDocument();
expect(screen.getByText('100%')).toBeInTheDocument();
expect(screen.getByText('解析完成')).toBeInTheDocument();
expect(screen.queryByText(/当前无处理任务/)).not.toBeInTheDocument();
});
it('connects to the progress stream and updates progress tasks', async () => {
render(<Dashboard />);
await waitFor(() => expect(wsMock.progressWS.connect).toHaveBeenCalled());
act(() => {
wsMock.state.callback?.({
type: 'progress',
taskId: 'task-1',
projectName: 'demo.mp4',
progress: 44,
status: '正在截取帧',
});
});
expect(await screen.findByText('demo.mp4')).toBeInTheDocument();
expect(screen.getByText('44%')).toBeInTheDocument();
});
it('updates the websocket badge from connection status callbacks', async () => {
render(<Dashboard />);
await waitFor(() => expect(wsMock.progressWS.onStatus).toHaveBeenCalled());
expect(screen.getByText('WebSocket 断开')).toBeInTheDocument();
act(() => {
wsMock.state.connected = true;
wsMock.state.statusCallback?.('connected');
});
expect(screen.getByText('WebSocket 已连接')).toBeInTheDocument();
});
it('adds activity logs for complete and status messages', async () => {
render(<Dashboard />);
act(() => {
wsMock.state.callback?.({ type: 'status', message: 'Progress stream active' });
wsMock.state.callback?.({ type: 'complete', taskId: '1', filename: 'done.mp4' });
});
await waitFor(() => expect(screen.getByText('Progress stream active')).toBeInTheDocument());
expect(screen.getByText('解析完成: done.mp4')).toBeInTheDocument();
});
it('cancels, retries, and opens task failure details', async () => {
apiMock.getDashboardOverview.mockResolvedValueOnce({
summary: {
project_count: 1,
parsing_task_count: 1,
annotation_count: 0,
frame_count: 0,
template_count: 0,
system_load_percent: 5,
},
tasks: [
{
id: 'task-10',
task_id: 10,
project_id: 1,
name: 'running.mp4',
progress: 30,
status: '正在下载媒体文件',
raw_status: 'running',
error: null,
frame_count: 0,
updated_at: '2026-05-01T00:00:00Z',
},
{
id: 'task-11',
task_id: 11,
project_id: 1,
name: 'failed.mp4',
progress: 100,
status: '解析失败',
raw_status: 'failed',
error: 'ffmpeg failed',
frame_count: 0,
updated_at: '2026-05-01T00:01:00Z',
},
],
activity: [],
});
apiMock.cancelTask.mockResolvedValueOnce({
id: 10,
task_type: 'parse_video',
status: 'cancelled',
progress: 100,
message: '任务已取消',
project_id: 1,
error: 'Cancelled by user',
result: null,
payload: { source_type: 'video' },
created_at: 'created',
updated_at: 'updated',
});
apiMock.retryTask.mockResolvedValueOnce({
id: 12,
task_type: 'parse_video',
status: 'queued',
progress: 0,
message: '重试任务已入队(源任务 #11',
project_id: 1,
error: null,
result: null,
payload: { source_type: 'video', retry_of: 11 },
created_at: 'created',
updated_at: 'updated',
});
apiMock.getTask.mockResolvedValueOnce({
id: 11,
task_type: 'parse_video',
status: 'failed',
progress: 100,
message: '解析失败',
project_id: 1,
celery_task_id: 'celery-11',
payload: { source_type: 'video' },
result: null,
error: 'ffmpeg failed',
created_at: 'created',
started_at: 'started',
finished_at: 'finished',
updated_at: 'updated',
});
render(<Dashboard />);
await screen.findByText('running.mp4');
fireEvent.click(screen.getByRole('button', { name: '取消' }));
await waitFor(() => expect(apiMock.cancelTask).toHaveBeenCalledWith(10));
fireEvent.click(screen.getAllByRole('button', { name: '详情' })[1]);
await waitFor(() => expect(apiMock.getTask).toHaveBeenCalledWith(11));
expect(await screen.findByText('任务详情 #11')).toBeInTheDocument();
expect(screen.getByText('ffmpeg failed')).toBeInTheDocument();
fireEvent.click(screen.getAllByRole('button', { name: '重试' })[1]);
await waitFor(() => expect(apiMock.retryTask).toHaveBeenCalledWith(11));
});
});