parent
8c1411a31d
commit
cc5812fce8
|
|
@ -1,140 +0,0 @@
|
|||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { describe, test, expect, vi } from 'vitest';
|
||||
import { EnhancedChatMessageV2 } from '../EnhancedChatMessageV2';
|
||||
|
||||
// Mock 数据
|
||||
const mockUserMessage = {
|
||||
id: '1',
|
||||
type: 'user' as const,
|
||||
content: '请推荐一些夏日穿搭',
|
||||
timestamp: new Date('2024-01-01T10:00:00Z'),
|
||||
status: 'sent' as const
|
||||
};
|
||||
|
||||
const mockAssistantMessage = {
|
||||
id: '2',
|
||||
type: 'assistant' as const,
|
||||
content: '夏日穿搭建议:选择轻薄透气的面料,如棉麻材质。推荐搭配清爽的色彩,如白色、浅蓝色等。',
|
||||
timestamp: new Date('2024-01-01T10:01:00Z'),
|
||||
status: 'sent' as const,
|
||||
metadata: {
|
||||
responseTime: 1500,
|
||||
modelUsed: 'gemini-pro',
|
||||
sources: [
|
||||
{
|
||||
title: '夏日穿搭指南',
|
||||
uri: 'https://example.com/summer-style.jpg',
|
||||
content: {
|
||||
type: 'image',
|
||||
image_url: 'https://example.com/summer-style.jpg',
|
||||
description: '清爽夏日穿搭示例'
|
||||
}
|
||||
}
|
||||
],
|
||||
grounding_metadata: {
|
||||
grounding_supports: [
|
||||
{
|
||||
start_index: 0,
|
||||
end_index: 20,
|
||||
grounding_chunk_indices: [0]
|
||||
}
|
||||
],
|
||||
sources: [
|
||||
{
|
||||
chunk_index: 0,
|
||||
title: '夏日穿搭指南',
|
||||
uri: 'https://example.com/summer-style.jpg',
|
||||
content: {
|
||||
type: 'image',
|
||||
image_url: 'https://example.com/summer-style.jpg',
|
||||
description: '清爽夏日穿搭示例'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('EnhancedChatMessageV2', () => {
|
||||
test('渲染用户消息', () => {
|
||||
render(<EnhancedChatMessageV2 message={mockUserMessage} />);
|
||||
|
||||
expect(screen.getByText('请推荐一些夏日穿搭')).toBeInTheDocument();
|
||||
expect(screen.getByText('10:00')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('渲染助手消息', () => {
|
||||
render(<EnhancedChatMessageV2 message={mockAssistantMessage} />);
|
||||
|
||||
expect(screen.getByText(/夏日穿搭建议/)).toBeInTheDocument();
|
||||
expect(screen.getByText('10:01')).toBeInTheDocument();
|
||||
expect(screen.getByText('• 响应时间: 1500ms')).toBeInTheDocument();
|
||||
expect(screen.getByText('• gemini-pro')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('显示素材来源', () => {
|
||||
render(
|
||||
<EnhancedChatMessageV2
|
||||
message={mockAssistantMessage}
|
||||
showSources={true}
|
||||
enableMaterialCards={true}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('相关素材 (1)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('复制消息功能', async () => {
|
||||
// Mock clipboard API
|
||||
const mockWriteText = vi.fn().mockImplementation(() => Promise.resolve());
|
||||
Object.assign(navigator, {
|
||||
clipboard: {
|
||||
writeText: mockWriteText,
|
||||
},
|
||||
});
|
||||
|
||||
render(<EnhancedChatMessageV2 message={mockUserMessage} />);
|
||||
|
||||
const copyButton = screen.getByTitle('复制消息');
|
||||
fireEvent.click(copyButton);
|
||||
|
||||
expect(mockWriteText).toHaveBeenCalledWith('请推荐一些夏日穿搭');
|
||||
});
|
||||
|
||||
test('展开/收起素材来源', () => {
|
||||
render(
|
||||
<EnhancedChatMessageV2
|
||||
message={mockAssistantMessage}
|
||||
showSources={true}
|
||||
enableMaterialCards={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// 检查是否显示了素材来源标题
|
||||
expect(screen.getByText('相关素材 (1)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('角标引用功能', () => {
|
||||
render(
|
||||
<EnhancedChatMessageV2
|
||||
message={mockAssistantMessage}
|
||||
enableReferences={true}
|
||||
/>
|
||||
);
|
||||
|
||||
// 检查是否渲染了消息内容
|
||||
expect(screen.getByText(/夏日穿搭建议/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('发送中状态显示', () => {
|
||||
const sendingMessage = {
|
||||
...mockUserMessage,
|
||||
status: 'sending' as const
|
||||
};
|
||||
|
||||
render(<EnhancedChatMessageV2 message={sendingMessage} />);
|
||||
|
||||
expect(screen.getByText('发送中...')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { ProjectCard } from '../ProjectCard';
|
||||
import { Project } from '../../types/project';
|
||||
|
||||
// Mock date-fns
|
||||
vi.mock('date-fns', () => ({
|
||||
formatDistanceToNow: vi.fn(() => '2 天前'),
|
||||
}));
|
||||
|
||||
vi.mock('date-fns/locale', () => ({
|
||||
zhCN: {},
|
||||
}));
|
||||
|
||||
const mockProject: Project = {
|
||||
id: '1',
|
||||
name: 'Test Project',
|
||||
path: '/path/to/project',
|
||||
description: 'Test description',
|
||||
created_at: '2023-01-01T00:00:00Z',
|
||||
updated_at: '2023-01-02T00:00:00Z',
|
||||
is_active: true,
|
||||
};
|
||||
|
||||
const mockProps = {
|
||||
project: mockProject,
|
||||
onOpen: vi.fn(),
|
||||
onEdit: vi.fn(),
|
||||
onDelete: vi.fn(),
|
||||
};
|
||||
|
||||
describe('ProjectCard', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders project information correctly', () => {
|
||||
render(<ProjectCard {...mockProps} />);
|
||||
|
||||
expect(screen.getByText('Test Project')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test description')).toBeInTheDocument();
|
||||
expect(screen.getByText('project')).toBeInTheDocument();
|
||||
expect(screen.getByText('2 天前')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onOpen when open button is clicked', () => {
|
||||
render(<ProjectCard {...mockProps} />);
|
||||
|
||||
const openButton = screen.getByText('打开');
|
||||
fireEvent.click(openButton);
|
||||
|
||||
expect(mockProps.onOpen).toHaveBeenCalledWith(mockProject);
|
||||
});
|
||||
|
||||
it('calls onEdit when edit button is clicked', () => {
|
||||
render(<ProjectCard {...mockProps} />);
|
||||
|
||||
const editButton = screen.getByText('编辑');
|
||||
fireEvent.click(editButton);
|
||||
|
||||
expect(mockProps.onEdit).toHaveBeenCalledWith(mockProject);
|
||||
});
|
||||
|
||||
it('renders without description', () => {
|
||||
const projectWithoutDescription = { ...mockProject, description: undefined };
|
||||
render(<ProjectCard {...mockProps} project={projectWithoutDescription} />);
|
||||
|
||||
expect(screen.getByText('Test Project')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Test description')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows menu when more button is clicked', () => {
|
||||
render(<ProjectCard {...mockProps} />);
|
||||
|
||||
// 找到更多按钮并点击 - 使用更通用的选择器
|
||||
const moreButtons = screen.getAllByRole('button');
|
||||
const moreButton = moreButtons.find(button =>
|
||||
button.querySelector('svg') && !button.textContent?.includes('编辑') && !button.textContent?.includes('打开')
|
||||
);
|
||||
|
||||
if (moreButton) {
|
||||
fireEvent.click(moreButton);
|
||||
|
||||
// 检查菜单项是否显示
|
||||
expect(screen.getByText('打开项目')).toBeInTheDocument();
|
||||
expect(screen.getAllByText('编辑')).toHaveLength(2); // 一个在菜单中,一个在按钮中
|
||||
expect(screen.getByText('删除')).toBeInTheDocument();
|
||||
}
|
||||
});
|
||||
|
||||
it('calls onDelete when delete menu item is clicked', () => {
|
||||
render(<ProjectCard {...mockProps} />);
|
||||
|
||||
// 打开菜单
|
||||
const moreButtons = screen.getAllByRole('button');
|
||||
const moreButton = moreButtons.find(button =>
|
||||
button.querySelector('svg') && !button.textContent?.includes('编辑') && !button.textContent?.includes('打开')
|
||||
);
|
||||
|
||||
if (moreButton) {
|
||||
fireEvent.click(moreButton);
|
||||
|
||||
// 点击删除
|
||||
const deleteButton = screen.getByText('删除');
|
||||
fireEvent.click(deleteButton);
|
||||
|
||||
expect(mockProps.onDelete).toHaveBeenCalledWith(mockProject.id);
|
||||
}
|
||||
});
|
||||
|
||||
it('extracts directory name correctly', () => {
|
||||
const projectWithWindowsPath = {
|
||||
...mockProject,
|
||||
path: 'C:\\Users\\test\\MyProject',
|
||||
};
|
||||
|
||||
render(<ProjectCard {...mockProps} project={projectWithWindowsPath} />);
|
||||
expect(screen.getByText('MyProject')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -1,117 +0,0 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import TaskCard from '../TaskCard';
|
||||
import { TaskDetail } from '../../types/uniComfyui';
|
||||
|
||||
// Mock the service
|
||||
jest.mock('../../services/uniComfyuiService', () => ({
|
||||
UniComfyUIService: {
|
||||
formatTaskStatus: jest.fn((status: string) => ({
|
||||
text: status === 'completed' ? '已完成' : '执行中',
|
||||
color: status === 'completed' ? 'text-green-600' : 'text-blue-600'
|
||||
})),
|
||||
getTaskStatusIcon: jest.fn(() => '✅'),
|
||||
formatTaskExecutionTime: jest.fn(() => '2分钟'),
|
||||
formatFileSize: jest.fn(() => '1.2 MB'),
|
||||
isImageFile: jest.fn(() => true),
|
||||
isVideoFile: jest.fn(() => false),
|
||||
generatePreviewUrl: jest.fn(() => '/test-image.jpg')
|
||||
}
|
||||
}));
|
||||
|
||||
const mockTask: TaskDetail = {
|
||||
id: 1,
|
||||
workflow_run_id: 'test-task-123',
|
||||
workflow_name: 'Test Workflow',
|
||||
task_type: 'single',
|
||||
status: 'completed',
|
||||
input_params: {},
|
||||
created_at: '2024-01-01T10:00:00Z',
|
||||
started_at: '2024-01-01T10:01:00Z',
|
||||
completed_at: '2024-01-01T10:03:00Z',
|
||||
result_files: [
|
||||
{
|
||||
id: 'file-1',
|
||||
task_id: 'test-task-123',
|
||||
file_name: 'result.jpg',
|
||||
file_path: '/path/to/result.jpg',
|
||||
file_url: 'http://example.com/result.jpg',
|
||||
file_type: 'image',
|
||||
file_size: 1024000,
|
||||
mime_type: 'image/jpeg',
|
||||
created_at: '2024-01-01T10:03:00Z',
|
||||
thumbnail_url: 'http://example.com/thumb.jpg'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
describe('TaskCard', () => {
|
||||
it('renders task information correctly', () => {
|
||||
render(<TaskCard task={mockTask} />);
|
||||
|
||||
expect(screen.getByText('Test Workflow')).toBeInTheDocument();
|
||||
expect(screen.getByText('ID: test-task-123')).toBeInTheDocument();
|
||||
expect(screen.getByText('单个任务')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onViewDetail when view button is clicked', () => {
|
||||
const mockOnViewDetail = jest.fn();
|
||||
render(<TaskCard task={mockTask} onViewDetail={mockOnViewDetail} />);
|
||||
|
||||
const viewButton = screen.getByTitle('查看详情');
|
||||
fireEvent.click(viewButton);
|
||||
|
||||
expect(mockOnViewDetail).toHaveBeenCalledWith(mockTask);
|
||||
});
|
||||
|
||||
it('calls onDownload when download button is clicked', () => {
|
||||
const mockOnDownload = jest.fn();
|
||||
render(<TaskCard task={mockTask} onDownload={mockOnDownload} />);
|
||||
|
||||
const downloadButton = screen.getByTitle('下载结果');
|
||||
fireEvent.click(downloadButton);
|
||||
|
||||
expect(mockOnDownload).toHaveBeenCalledWith(mockTask);
|
||||
});
|
||||
|
||||
it('shows retry button for failed tasks', () => {
|
||||
const failedTask = { ...mockTask, status: 'failed' as const };
|
||||
const mockOnRetry = jest.fn();
|
||||
|
||||
render(<TaskCard task={failedTask} onRetry={mockOnRetry} />);
|
||||
|
||||
const retryButton = screen.getByTitle('重试任务');
|
||||
expect(retryButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(retryButton);
|
||||
expect(mockOnRetry).toHaveBeenCalledWith(failedTask);
|
||||
});
|
||||
|
||||
it('displays error message for failed tasks', () => {
|
||||
const failedTask = {
|
||||
...mockTask,
|
||||
status: 'failed' as const,
|
||||
error_message: 'Task execution failed'
|
||||
};
|
||||
|
||||
render(<TaskCard task={failedTask} />);
|
||||
|
||||
expect(screen.getByText('Task execution failed')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders in compact mode', () => {
|
||||
render(<TaskCard task={mockTask} compact={true} />);
|
||||
|
||||
// In compact mode, the task name should have smaller text
|
||||
const taskName = screen.getByText('Test Workflow');
|
||||
expect(taskName).toHaveClass('text-sm');
|
||||
});
|
||||
|
||||
it('hides actions when showActions is false', () => {
|
||||
render(<TaskCard task={mockTask} showActions={false} />);
|
||||
|
||||
expect(screen.queryByTitle('查看详情')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTitle('下载结果')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue