1
0
Files
nex/frontend/src/__tests__/components/ProviderTable.test.tsx
lanyuanxiaoyao 870004af23 feat: 现代化 UI 布局,实现侧边栏导航和统计仪表盘
- 重构 AppLayout 为可折叠侧边栏导航布局
- 实现统计仪表盘:统计摘要卡片 + 请求趋势图表
- Provider 页面使用 Card 包裹优化视觉层次
- 主题切换按钮移至侧边栏底部,支持折叠态
- Header 适配暗色主题,添加分隔线优化视觉过渡
- 添加全局样式重置(SCSS)
- 完善组件测试和 E2E 测试覆盖
- 同步 OpenSpec 规范到主 specs
2026-04-16 19:24:02 +08:00

149 lines
5.2 KiB
TypeScript

import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { ProviderTable } from '@/pages/Providers/ProviderTable';
import type { Provider } from '@/types';
const mockModelsData = [
{ id: 'model-1', providerId: 'openai', modelName: 'gpt-4o', enabled: true },
{ id: 'model-2', providerId: 'openai', modelName: 'gpt-3.5-turbo', enabled: false },
];
vi.mock('@/hooks/useModels', () => ({
useModels: vi.fn(() => ({ data: mockModelsData, isLoading: false })),
useDeleteModel: vi.fn(() => ({ mutate: vi.fn() })),
}));
const mockProviders: Provider[] = [
{
id: 'openai',
name: 'OpenAI',
apiKey: 'sk-abcdefgh12345678',
baseUrl: 'https://api.openai.com/v1',
enabled: true,
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
},
{
id: 'anthropic',
name: 'Anthropic',
apiKey: 'sk-ant-test',
baseUrl: 'https://api.anthropic.com',
enabled: false,
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
},
];
const defaultProps = {
providers: mockProviders,
loading: false,
onAdd: vi.fn(),
onEdit: vi.fn(),
onDelete: vi.fn(),
onAddModel: vi.fn(),
onEditModel: vi.fn(),
};
describe('ProviderTable', () => {
it('renders provider list with name, baseUrl, masked apiKey, and status tags', () => {
render(<ProviderTable {...defaultProps} />);
expect(screen.getByText('供应商列表')).toBeInTheDocument();
expect(screen.getByText('OpenAI')).toBeInTheDocument();
expect(screen.getByText('https://api.openai.com/v1')).toBeInTheDocument();
expect(screen.getByText('****5678')).toBeInTheDocument();
expect(screen.getByText('Anthropic')).toBeInTheDocument();
expect(screen.getByText('https://api.anthropic.com')).toBeInTheDocument();
expect(screen.getByText('****test')).toBeInTheDocument();
const enabledTags = screen.getAllByText('启用');
const disabledTags = screen.getAllByText('禁用');
expect(enabledTags.length).toBeGreaterThanOrEqual(1);
expect(disabledTags.length).toBeGreaterThanOrEqual(1);
});
it('renders within a Card component', () => {
const { container } = render(<ProviderTable {...defaultProps} />);
expect(container.querySelector('.ant-card')).toBeInTheDocument();
expect(container.querySelector('.ant-card-head')).toBeInTheDocument();
expect(container.querySelector('.ant-card-body')).toBeInTheDocument();
});
it('renders short api keys fully masked', () => {
const shortKeyProvider: Provider[] = [
{
...mockProviders[0],
id: 'short',
name: 'ShortKey',
apiKey: 'ab',
},
];
render(<ProviderTable {...defaultProps} providers={shortKeyProvider} />);
expect(screen.getByText('****')).toBeInTheDocument();
});
it('calls onAdd when clicking "添加供应商" button', async () => {
const user = userEvent.setup();
const onAdd = vi.fn();
render(<ProviderTable {...defaultProps} onAdd={onAdd} />);
await user.click(screen.getByRole('button', { name: '添加供应商' }));
expect(onAdd).toHaveBeenCalledTimes(1);
});
it('calls onEdit with correct provider when clicking "编辑"', async () => {
const user = userEvent.setup();
const onEdit = vi.fn();
render(<ProviderTable {...defaultProps} onEdit={onEdit} />);
const editButtons = screen.getAllByRole('button', { name: '编辑' });
await user.click(editButtons[0]);
expect(onEdit).toHaveBeenCalledTimes(1);
expect(onEdit).toHaveBeenCalledWith(mockProviders[0]);
});
it('calls onDelete with correct provider ID when delete is confirmed', async () => {
const user = userEvent.setup();
const onDelete = vi.fn();
render(<ProviderTable {...defaultProps} onDelete={onDelete} />);
// Find and click the delete button for the first row
const deleteButtons = screen.getAllByRole('button', { name: '删除' });
await user.click(deleteButtons[0]);
// Find and click the "确 定" confirm button in the Popconfirm popup
// antd renders the text with spaces between Chinese characters
const confirmButtons = await screen.findAllByText('确 定');
await user.click(confirmButtons[0]);
// Assert that onDelete was called with the correct provider ID
expect(onDelete).toHaveBeenCalledTimes(1);
expect(onDelete).toHaveBeenCalledWith('openai');
}, 10000);
it('shows loading state', () => {
render(<ProviderTable {...defaultProps} loading={true} />);
expect(document.querySelector('.ant-spin')).toBeInTheDocument();
});
it('renders expandable ModelTable when row is expanded', async () => {
const user = userEvent.setup();
render(<ProviderTable {...defaultProps} />);
// Find and click the expand button for the first row
const expandButtons = screen.getAllByRole('button', { name: /expand/i });
expect(expandButtons.length).toBeGreaterThanOrEqual(1);
await user.click(expandButtons[0]);
// Verify that ModelTable content is rendered with data from mocked useModels
expect(await screen.findByText('gpt-4o')).toBeInTheDocument();
expect(screen.getByText('gpt-3.5-turbo')).toBeInTheDocument();
});
});