feat: 完成前端重构,采用 Ant Design 5 和完整测试体系
- 采用 Ant Design 5 作为 UI 组件库,替换自定义组件 - 集成 React Router v7 提供路由导航 - 使用 TanStack Query v5 管理数据获取和缓存 - 建立 Vitest + React Testing Library 测试体系 - 添加 Playwright E2E 测试覆盖 - 使用 MSW mock API 响应 - 配置 TypeScript strict 模式 - 采用 SCSS Modules 组织样式 - 更新 OpenSpec 规格以反映前端架构变更 - 归档 frontend-refactor 变更记录
This commit is contained in:
147
frontend/src/__tests__/components/ProviderForm.test.tsx
Normal file
147
frontend/src/__tests__/components/ProviderForm.test.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import { render, screen, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { ProviderForm } from '@/pages/Providers/ProviderForm';
|
||||
import type { Provider } from '@/types';
|
||||
|
||||
const mockProvider: Provider = {
|
||||
id: 'openai',
|
||||
name: 'OpenAI',
|
||||
apiKey: 'sk-old-key',
|
||||
baseUrl: 'https://api.openai.com/v1',
|
||||
enabled: true,
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z',
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
open: true,
|
||||
onSave: vi.fn(),
|
||||
onCancel: vi.fn(),
|
||||
loading: false,
|
||||
};
|
||||
|
||||
function getDialog() {
|
||||
return screen.getByRole('dialog');
|
||||
}
|
||||
|
||||
describe('ProviderForm', () => {
|
||||
it('renders form fields in create mode', () => {
|
||||
render(<ProviderForm {...defaultProps} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
expect(within(dialog).getByText('添加供应商')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('ID')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('名称')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('API Key')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('Base URL')).toBeInTheDocument();
|
||||
expect(within(dialog).getByText('启用')).toBeInTheDocument();
|
||||
expect(within(dialog).getByPlaceholderText('例如: openai')).toBeInTheDocument();
|
||||
expect(within(dialog).getByPlaceholderText('例如: OpenAI')).toBeInTheDocument();
|
||||
expect(within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders pre-filled fields in edit mode', () => {
|
||||
render(<ProviderForm {...defaultProps} provider={mockProvider} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
expect(within(dialog).getByText('编辑供应商')).toBeInTheDocument();
|
||||
|
||||
const idInput = within(dialog).getByPlaceholderText('例如: openai') as HTMLInputElement;
|
||||
expect(idInput.value).toBe('openai');
|
||||
expect(idInput).toBeDisabled();
|
||||
|
||||
const nameInput = within(dialog).getByPlaceholderText('例如: OpenAI') as HTMLInputElement;
|
||||
expect(nameInput.value).toBe('OpenAI');
|
||||
|
||||
const baseUrlInput = within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1') as HTMLInputElement;
|
||||
expect(baseUrlInput.value).toBe('https://api.openai.com/v1');
|
||||
});
|
||||
|
||||
it('shows API Key label variant in edit mode', () => {
|
||||
render(<ProviderForm {...defaultProps} provider={mockProvider} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
expect(within(dialog).getByText('API Key(留空则不修改)')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows validation error messages for required fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ProviderForm {...defaultProps} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
const okButton = within(dialog).getByRole('button', { name: /保/ });
|
||||
await user.click(okButton);
|
||||
|
||||
// Wait for validation messages to appear
|
||||
expect(await screen.findByText('请输入供应商 ID')).toBeInTheDocument();
|
||||
expect(screen.getByText('请输入名称')).toBeInTheDocument();
|
||||
expect(screen.getByText('请输入 API Key')).toBeInTheDocument();
|
||||
expect(screen.getByText('请输入 Base URL')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onSave with form values on successful submission', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onSave = vi.fn();
|
||||
render(<ProviderForm {...defaultProps} onSave={onSave} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: openai'), 'test-provider');
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: OpenAI'), 'Test Provider');
|
||||
await user.type(within(dialog).getByPlaceholderText('sk-...'), 'sk-test-key');
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1'), 'https://api.test.com/v1');
|
||||
|
||||
const okButton = within(dialog).getByRole('button', { name: /保/ });
|
||||
await user.click(okButton);
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
id: 'test-provider',
|
||||
name: 'Test Provider',
|
||||
apiKey: 'sk-test-key',
|
||||
baseUrl: 'https://api.test.com/v1',
|
||||
enabled: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('calls onCancel when clicking cancel button', async () => {
|
||||
const user = userEvent.setup();
|
||||
const onCancel = vi.fn();
|
||||
render(<ProviderForm {...defaultProps} onCancel={onCancel} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
const cancelButton = within(dialog).getByRole('button', { name: /取/ });
|
||||
await user.click(cancelButton);
|
||||
expect(onCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('shows confirm loading state', () => {
|
||||
render(<ProviderForm {...defaultProps} loading={true} />);
|
||||
const dialog = getDialog();
|
||||
const okButton = within(dialog).getByRole('button', { name: /保/ });
|
||||
expect(okButton).toHaveClass('ant-btn-loading');
|
||||
});
|
||||
|
||||
it('shows validation error for invalid URL format', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<ProviderForm {...defaultProps} />);
|
||||
|
||||
const dialog = getDialog();
|
||||
|
||||
// Fill in required fields
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: openai'), 'test-provider');
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: OpenAI'), 'Test Provider');
|
||||
await user.type(within(dialog).getByPlaceholderText('sk-...'), 'sk-test-key');
|
||||
|
||||
// Enter an invalid URL in the Base URL field
|
||||
await user.type(within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1'), 'not-a-url');
|
||||
|
||||
// Submit the form
|
||||
const okButton = within(dialog).getByRole('button', { name: /保/ });
|
||||
await user.click(okButton);
|
||||
|
||||
// Verify that a URL validation error message appears
|
||||
expect(await screen.findByText('请输入有效的 URL')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user