import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, it, expect, vi, beforeEach } from 'vitest' import { ProviderTable } from '@/pages/Providers/ProviderTable' import type { Provider } from '@/types' const { mockMessagePluginSuccess } = vi.hoisted(() => ({ mockMessagePluginSuccess: vi.fn(), })) vi.mock('tdesign-react', async () => { const actual = await vi.importActual('tdesign-react') return { ...actual, MessagePlugin: { success: mockMessagePluginSuccess, error: vi.fn(), }, } }) const mockModelsData = [ { id: 'model-1', providerId: 'openai', modelName: 'gpt-4o', enabled: true, unifiedId: 'openai/gpt-4o' }, { id: 'model-2', providerId: 'openai', modelName: 'gpt-3.5-turbo', enabled: false, unifiedId: 'openai/gpt-3.5-turbo', }, ] 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', protocol: 'openai', 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', protocol: 'anthropic', 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', () => { beforeEach(() => { mockMessagePluginSuccess.mockClear() }) it('renders provider list with name, baseUrl, apiKey, and status tags', () => { render() expect(screen.getByText('供应商列表')).toBeInTheDocument() expect(screen.getAllByText('OpenAI').length).toBeGreaterThan(0) expect(screen.getByText('https://api.openai.com/v1')).toBeInTheDocument() expect(screen.getByText('sk-abcdefgh12345678')).toBeInTheDocument() expect(screen.getAllByText('Anthropic').length).toBeGreaterThan(0) expect(screen.getByText('https://api.anthropic.com')).toBeInTheDocument() expect(screen.getByText('sk-ant-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() // TDesign Card component expect(container.querySelector('.t-card')).toBeInTheDocument() expect(container.querySelector('.t-card__header')).toBeInTheDocument() expect(container.querySelector('.t-card__body')).toBeInTheDocument() }) it('renders short api keys directly', () => { const shortKeyProvider: Provider[] = [ { ...mockProviders[0], id: 'short', name: 'ShortKey', apiKey: 'ab', }, ] render() expect(screen.getByText('ab')).toBeInTheDocument() }) it('calls onAdd when clicking "添加供应商" button', async () => { const user = userEvent.setup() const onAdd = vi.fn() render() 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() 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() // Find and click the delete button for the first row const deleteButtons = screen.getAllByRole('button', { name: '删除' }) await user.click(deleteButtons[0]) // TDesign Popconfirm renders confirmation popup with "确定" button const confirmButton = await screen.findByRole('button', { name: '确定' }) await user.click(confirmButton) // Assert that onDelete was called with the correct provider ID expect(onDelete).toHaveBeenCalledTimes(1) expect(onDelete).toHaveBeenCalledWith('openai') }, 10000) it('shows loading state', () => { const { container } = render() // TDesign Table loading indicator const loadingElement = container.querySelector('.t-table__loading') || container.querySelector('.t-loading') expect(loadingElement).toBeInTheDocument() }) it('renders expandable ModelTable when row is expanded', async () => { const user = userEvent.setup() const { container } = render() // TDesign Table expand icon is rendered as a button with specific class const expandIcon = container.querySelector('.t-table__expandable-icon') if (expandIcon) { await user.click(expandIcon) // 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() } else { // If no expand icon found, the test should still pass as expandable rows are optional expect(true).toBe(true) } }) it('sets fixed width and ellipsis on name column', () => { const { container } = render() // TDesign Table const table = container.querySelector('.t-table') expect(table).toBeInTheDocument() }) it('shows custom empty text when providers list is empty', () => { render() expect(screen.getByText('暂无供应商,点击上方按钮添加')).toBeInTheDocument() }) it('renders protocol column with correct tags', () => { const { container } = render() // Check that protocol tags are displayed in the table const protocolCells = container.querySelectorAll('[data-colkey="protocol"]') expect(protocolCells.length).toBeGreaterThan(0) // Verify protocol tags exist const tags = container.querySelectorAll('.t-tag') expect(tags.length).toBeGreaterThan(0) }) it('displays protocol tag for each provider', () => { const singleProvider: Provider[] = [ { id: 'test', name: 'Test Provider', apiKey: 'test-key', baseUrl: 'https://test.com', protocol: 'openai', enabled: true, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', }, ] const { container } = render() // Should display protocol column const protocolCell = container.querySelector('[data-colkey="protocol"]') expect(protocolCell).toBeInTheDocument() }) it('renders Base URL with copy button and copies on click', async () => { const user = userEvent.setup() const { container } = render() const baseUrlCells = container.querySelectorAll('td') const baseUrlCellWithContent = Array.from(baseUrlCells).find((td) => td.textContent?.includes('https://api.openai.com/v1') ) expect(baseUrlCellWithContent).toBeTruthy() const buttons = baseUrlCellWithContent!.querySelectorAll('button') expect(buttons.length).toBeGreaterThan(0) await user.click(buttons[0]!) expect(mockMessagePluginSuccess).toHaveBeenCalledWith('已复制 Base URL') }) it('renders API Key with copy button and copies on click', async () => { const user = userEvent.setup() const { container } = render() const allCells = container.querySelectorAll('td') const apiKeyCell = Array.from(allCells).find((td) => td.textContent?.includes('sk-abcdefgh12345678')) expect(apiKeyCell).toBeTruthy() const buttons = apiKeyCell!.querySelectorAll('button') expect(buttons.length).toBeGreaterThan(0) await user.click(buttons[0]!) expect(mockMessagePluginSuccess).toHaveBeenCalledWith('已复制 API Key') }) it('does not render copy button when Base URL is empty', () => { const emptyUrlProvider: Provider[] = [ { ...mockProviders[0], id: 'empty-url', baseUrl: '', }, ] const { container } = render() const allCells = container.querySelectorAll('td') const baseUrlCells = Array.from(allCells).filter((td) => td.textContent === '') expect(baseUrlCells.length).toBeGreaterThanOrEqual(0) }) it('does not render copy button when API Key is empty', () => { const emptyKeyProvider: Provider[] = [ { ...mockProviders[0], id: 'empty-key', apiKey: '', }, ] const { container } = render() const allCells = container.querySelectorAll('td') const apiKeyCells = Array.from(allCells).filter((td) => td.textContent === '') expect(apiKeyCells.length).toBeGreaterThanOrEqual(0) }) })