import { render, screen, within, fireEvent } 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', protocol: 'openai', 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() { // TDesign Dialog doesn't have role="dialog", use class selector const dialog = document.querySelector('.t-dialog'); if (!dialog) { throw new Error('Dialog not found'); } return dialog; } describe('ProviderForm', () => { it('renders form fields in create mode', () => { render(); 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).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(); 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'); const apiKeyInput = within(dialog).getByPlaceholderText('sk-...') as HTMLInputElement; expect(apiKeyInput.value).toBe('sk-old-key'); }); it('shows API Key label in edit mode', () => { render(); const dialog = getDialog(); expect(within(dialog).getByText('API Key')).toBeInTheDocument(); }); it('shows validation error messages for required fields', async () => { const user = userEvent.setup(); render(); 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 onSave = vi.fn(); render(); const dialog = getDialog(); // Get form instance and set values directly const idInput = within(dialog).getByPlaceholderText('例如: openai') as HTMLInputElement; const nameInput = within(dialog).getByPlaceholderText('例如: OpenAI') as HTMLInputElement; const apiKeyInput = within(dialog).getByPlaceholderText('sk-...') as HTMLInputElement; const baseUrlInput = within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1') as HTMLInputElement; // Simulate user input by directly setting values fireEvent.change(idInput, { target: { value: 'test-provider' } }); fireEvent.change(nameInput, { target: { value: 'Test Provider' } }); fireEvent.change(apiKeyInput, { target: { value: 'sk-test-key' } }); fireEvent.change(baseUrlInput, { target: { value: 'https://api.test.com/v1' } }); const okButton = within(dialog).getByRole('button', { name: /保/ }); fireEvent.click(okButton); // Wait for the onSave to be called await vi.waitFor(() => { expect(onSave).toHaveBeenCalled(); }, { timeout: 5000 }); }, 10000); it('calls onCancel when clicking cancel button', async () => { const user = userEvent.setup(); const onCancel = vi.fn(); render(); const dialog = getDialog(); const cancelButton = within(dialog).getByRole('button', { name: /取/ }); await user.click(cancelButton); expect(onCancel).toHaveBeenCalledTimes(1); }); it('shows confirm loading state', () => { render(); const dialog = getDialog(); const okButton = within(dialog).getByRole('button', { name: /保/ }); // TDesign uses t-is-loading class for loading state expect(okButton).toHaveClass('t-is-loading'); }); it('shows validation error for invalid URL format', async () => { const user = userEvent.setup(); render(); 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 await vi.waitFor(() => { expect(screen.getByText('请输入有效的 URL')).toBeInTheDocument(); }); }, 15000); it('renders protocol select field with default value', () => { render(); const dialog = getDialog(); expect(within(dialog).getByText('协议')).toBeInTheDocument(); }); it('includes protocol field in form submission', async () => { const onSave = vi.fn(); render(); const dialog = getDialog(); // Get form instance and set values directly const idInput = within(dialog).getByPlaceholderText('例如: openai') as HTMLInputElement; const nameInput = within(dialog).getByPlaceholderText('例如: OpenAI') as HTMLInputElement; const apiKeyInput = within(dialog).getByPlaceholderText('sk-...') as HTMLInputElement; const baseUrlInput = within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1') as HTMLInputElement; // Simulate user input by directly setting values fireEvent.change(idInput, { target: { value: 'test-provider' } }); fireEvent.change(nameInput, { target: { value: 'Test Provider' } }); fireEvent.change(apiKeyInput, { target: { value: 'sk-test-key' } }); fireEvent.change(baseUrlInput, { target: { value: 'https://api.test.com/v1' } }); const okButton = within(dialog).getByRole('button', { name: /保/ }); fireEvent.click(okButton); // Wait for the onSave to be called await vi.waitFor(() => { expect(onSave).toHaveBeenCalled(); // Verify that the saved data includes a protocol field const savedData = onSave.mock.calls[0][0]; expect(savedData).toHaveProperty('protocol'); }, { timeout: 5000 }); }, 10000); });