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);
});