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