1
0

feat: 前端集成 Prettier 代码格式化

This commit is contained in:
2026-04-24 13:40:53 +08:00
parent 52007c9461
commit 365943e4c4
61 changed files with 1968 additions and 1698 deletions

View File

@@ -1,52 +1,52 @@
import { render, screen } from '@testing-library/react';
import { BrowserRouter } from 'react-router';
import { describe, it, expect } from 'vitest';
import { AppLayout } from '@/components/AppLayout';
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router'
import { describe, it, expect } from 'vitest'
import { AppLayout } from '@/components/AppLayout'
const renderWithRouter = (component: React.ReactNode) => {
return render(<BrowserRouter>{component}</BrowserRouter>);
};
return render(<BrowserRouter>{component}</BrowserRouter>)
}
describe('AppLayout', () => {
it('renders sidebar with app name', () => {
renderWithRouter(<AppLayout />);
renderWithRouter(<AppLayout />)
const appNames = screen.getAllByText('AI Gateway');
expect(appNames.length).toBeGreaterThan(0);
});
const appNames = screen.getAllByText('AI Gateway')
expect(appNames.length).toBeGreaterThan(0)
})
it('renders navigation menu items', () => {
renderWithRouter(<AppLayout />);
renderWithRouter(<AppLayout />)
expect(screen.getByText('供应商管理')).toBeInTheDocument();
expect(screen.getByText('用量统计')).toBeInTheDocument();
});
expect(screen.getByText('供应商管理')).toBeInTheDocument()
expect(screen.getByText('用量统计')).toBeInTheDocument()
})
it('renders settings menu item', () => {
renderWithRouter(<AppLayout />);
renderWithRouter(<AppLayout />)
expect(screen.getByText('设置')).toBeInTheDocument();
});
expect(screen.getByText('设置')).toBeInTheDocument()
})
it('renders content outlet', () => {
const { container } = renderWithRouter(<AppLayout />);
const { container } = renderWithRouter(<AppLayout />)
// TDesign Layout content
expect(container.querySelector('.t-layout__content')).toBeInTheDocument();
});
expect(container.querySelector('.t-layout__content')).toBeInTheDocument()
})
it('renders sidebar', () => {
const { container } = renderWithRouter(<AppLayout />);
const { container } = renderWithRouter(<AppLayout />)
// TDesign Layout.Aside might render with different class names
// Check for Menu component which is in the sidebar
expect(container.querySelector('.t-menu')).toBeInTheDocument();
});
expect(container.querySelector('.t-menu')).toBeInTheDocument()
})
it('renders header with page title', () => {
const { container } = renderWithRouter(<AppLayout />);
const { container } = renderWithRouter(<AppLayout />)
// TDesign Layout header
expect(container.querySelector('.t-layout__header')).toBeInTheDocument();
});
});
expect(container.querySelector('.t-layout__header')).toBeInTheDocument()
})
})

View File

@@ -1,8 +1,8 @@
import { render, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { ModelForm } from '@/pages/Providers/ModelForm';
import type { Provider, Model } from '@/types';
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { ModelForm } from '@/pages/Providers/ModelForm'
import type { Provider, Model } from '@/types'
const mockProviders: Provider[] = [
{
@@ -25,7 +25,7 @@ const mockProviders: Provider[] = [
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
},
];
]
const mockModel: Model = {
id: 'gpt-4o',
@@ -34,7 +34,7 @@ const mockModel: Model = {
enabled: true,
createdAt: '2024-01-01T00:00:00Z',
unifiedId: 'openai/gpt-4o',
};
}
const defaultProps = {
open: true,
@@ -43,69 +43,63 @@ const defaultProps = {
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');
const dialog = document.querySelector('.t-dialog')
if (!dialog) {
throw new Error('Dialog not found');
throw new Error('Dialog not found')
}
return dialog;
return dialog
}
describe('ModelForm', () => {
it('renders form with provider select', () => {
render(<ModelForm {...defaultProps} />);
render(<ModelForm {...defaultProps} />)
const dialog = getDialog();
expect(within(dialog).getByText('添加模型')).toBeInTheDocument();
expect(within(dialog).getByText('供应商')).toBeInTheDocument();
expect(within(dialog).getByText('模型名称')).toBeInTheDocument();
expect(within(dialog).getByText('启用')).toBeInTheDocument();
});
const dialog = getDialog()
expect(within(dialog).getByText('添加模型')).toBeInTheDocument()
expect(within(dialog).getByText('供应商')).toBeInTheDocument()
expect(within(dialog).getByText('模型名称')).toBeInTheDocument()
expect(within(dialog).getByText('启用')).toBeInTheDocument()
})
it('defaults providerId to the passed providerId in create mode', () => {
render(<ModelForm {...defaultProps} />);
render(<ModelForm {...defaultProps} />)
const dialog = getDialog();
const dialog = getDialog()
// Form renders with provider select
expect(within(dialog).getByText('供应商')).toBeInTheDocument();
});
expect(within(dialog).getByText('供应商')).toBeInTheDocument()
})
it('shows validation error messages for required fields', async () => {
const user = userEvent.setup();
render(
<ModelForm
{...defaultProps}
providerId={undefined as unknown as string}
providers={[]}
/>,
);
const user = userEvent.setup()
render(<ModelForm {...defaultProps} providerId={undefined as unknown as string} providers={[]} />)
const dialog = getDialog();
const okButton = within(dialog).getByRole('button', { name: /保/ });
await user.click(okButton);
const dialog = getDialog()
const okButton = within(dialog).getByRole('button', { name: /保/ })
await user.click(okButton)
expect(await screen.findByText('请选择供应商')).toBeInTheDocument();
expect(screen.getByText('请输入模型名称')).toBeInTheDocument();
});
expect(await screen.findByText('请选择供应商')).toBeInTheDocument()
expect(screen.getByText('请输入模型名称')).toBeInTheDocument()
})
it('calls onSave with form values on successful submission', async () => {
const user = userEvent.setup();
const onSave = vi.fn();
render(<ModelForm {...defaultProps} onSave={onSave} />);
const user = userEvent.setup()
const onSave = vi.fn()
render(<ModelForm {...defaultProps} onSave={onSave} />)
const dialog = getDialog();
const dialog = getDialog()
// Only one input with placeholder "例如: gpt-4o" for model name
const modelNameInput = within(dialog).getByPlaceholderText('例如: gpt-4o');
const modelNameInput = within(dialog).getByPlaceholderText('例如: gpt-4o')
// Type into the model name field
await user.clear(modelNameInput);
await user.type(modelNameInput, 'gpt-4o-mini');
await user.clear(modelNameInput)
await user.type(modelNameInput, 'gpt-4o-mini')
const okButton = within(dialog).getByRole('button', { name: /保/ });
await user.click(okButton);
const okButton = within(dialog).getByRole('button', { name: /保/ })
await user.click(okButton)
// Wait for the onSave to be called
await vi.waitFor(() => {
@@ -114,30 +108,30 @@ describe('ModelForm', () => {
providerId: 'openai',
modelName: 'gpt-4o-mini',
enabled: true,
}),
);
});
}, 10000);
})
)
})
}, 10000)
it('renders pre-filled fields in edit mode', () => {
render(<ModelForm {...defaultProps} model={mockModel} />);
render(<ModelForm {...defaultProps} model={mockModel} />)
const dialog = getDialog();
expect(within(dialog).getByText('编辑模型')).toBeInTheDocument();
const dialog = getDialog()
expect(within(dialog).getByText('编辑模型')).toBeInTheDocument()
// Check model name input
const modelNameInput = within(dialog).getByPlaceholderText('例如: gpt-4o') as HTMLInputElement;
expect(modelNameInput.value).toBe('gpt-4o');
});
const modelNameInput = within(dialog).getByPlaceholderText('例如: gpt-4o') as HTMLInputElement
expect(modelNameInput.value).toBe('gpt-4o')
})
it('calls onCancel when clicking cancel button', async () => {
const user = userEvent.setup();
const onCancel = vi.fn();
render(<ModelForm {...defaultProps} onCancel={onCancel} />);
const user = userEvent.setup()
const onCancel = vi.fn()
render(<ModelForm {...defaultProps} onCancel={onCancel} />)
const dialog = getDialog();
const cancelButton = within(dialog).getByRole('button', { name: /取/ });
await user.click(cancelButton);
expect(onCancel).toHaveBeenCalledTimes(1);
});
});
const dialog = getDialog()
const cancelButton = within(dialog).getByRole('button', { name: /取/ })
await user.click(cancelButton)
expect(onCancel).toHaveBeenCalledTimes(1)
})
})

View File

@@ -1,8 +1,8 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { ModelTable } from '@/pages/Providers/ModelTable';
import type { Model } from '@/types';
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { ModelTable } from '@/pages/Providers/ModelTable'
import type { Model } from '@/types'
const mockModels: Model[] = [
{
@@ -21,103 +21,103 @@ const mockModels: Model[] = [
createdAt: '2024-01-02T00:00:00Z',
unifiedId: 'openai/gpt-3.5-turbo',
},
];
]
const mockMutate = vi.fn();
const mockMutate = vi.fn()
vi.mock('@/hooks/useModels', () => ({
useModels: vi.fn((providerId: string) => {
if (providerId === 'openai') {
return { data: mockModels, isLoading: false };
return { data: mockModels, isLoading: false }
}
return { data: [], isLoading: false };
return { data: [], isLoading: false }
}),
useDeleteModel: vi.fn(() => ({ mutate: mockMutate })),
}));
}))
const defaultProps = {
providerId: 'openai',
onAdd: vi.fn(),
onEdit: vi.fn(),
};
}
describe('ModelTable', () => {
beforeEach(() => {
mockMutate.mockClear();
});
mockMutate.mockClear()
})
it('renders model list with unified ID and model name', () => {
render(<ModelTable {...defaultProps} />);
render(<ModelTable {...defaultProps} />)
expect(screen.getByText(/关联模型/)).toBeInTheDocument();
expect(screen.getByText('openai/gpt-4o')).toBeInTheDocument();
expect(screen.getByText('openai/gpt-3.5-turbo')).toBeInTheDocument();
expect(screen.getByText('gpt-4o')).toBeInTheDocument();
expect(screen.getByText('gpt-3.5-turbo')).toBeInTheDocument();
});
expect(screen.getByText(/关联模型/)).toBeInTheDocument()
expect(screen.getByText('openai/gpt-4o')).toBeInTheDocument()
expect(screen.getByText('openai/gpt-3.5-turbo')).toBeInTheDocument()
expect(screen.getByText('gpt-4o')).toBeInTheDocument()
expect(screen.getByText('gpt-3.5-turbo')).toBeInTheDocument()
})
it('renders status tags correctly', () => {
render(<ModelTable {...defaultProps} />);
render(<ModelTable {...defaultProps} />)
const enabledTags = screen.getAllByText('启用');
const disabledTags = screen.getAllByText('禁用');
expect(enabledTags.length).toBeGreaterThanOrEqual(1);
expect(disabledTags.length).toBeGreaterThanOrEqual(1);
});
const enabledTags = screen.getAllByText('启用')
const disabledTags = screen.getAllByText('禁用')
expect(enabledTags.length).toBeGreaterThanOrEqual(1)
expect(disabledTags.length).toBeGreaterThanOrEqual(1)
})
it('calls onAdd when clicking "添加模型" button', async () => {
const user = userEvent.setup();
const onAdd = vi.fn();
render(<ModelTable {...defaultProps} onAdd={onAdd} />);
const user = userEvent.setup()
const onAdd = vi.fn()
render(<ModelTable {...defaultProps} onAdd={onAdd} />)
await user.click(screen.getByRole('button', { name: '添加模型' }));
expect(onAdd).toHaveBeenCalledTimes(1);
});
await user.click(screen.getByRole('button', { name: '添加模型' }))
expect(onAdd).toHaveBeenCalledTimes(1)
})
it('calls onEdit with correct model when clicking "编辑"', async () => {
const user = userEvent.setup();
const onEdit = vi.fn();
render(<ModelTable {...defaultProps} onEdit={onEdit} />);
const user = userEvent.setup()
const onEdit = vi.fn()
render(<ModelTable {...defaultProps} onEdit={onEdit} />)
const editButtons = screen.getAllByRole('button', { name: /编 ?辑/ });
await user.click(editButtons[0]);
const editButtons = screen.getAllByRole('button', { name: /编 ?辑/ })
await user.click(editButtons[0])
expect(onEdit).toHaveBeenCalledTimes(1);
expect(onEdit).toHaveBeenCalledWith(mockModels[0]);
});
expect(onEdit).toHaveBeenCalledTimes(1)
expect(onEdit).toHaveBeenCalledWith(mockModels[0])
})
it('calls deleteModel.mutate with correct model ID when delete is confirmed', async () => {
const user = userEvent.setup();
const user = userEvent.setup()
render(<ModelTable {...defaultProps} />);
render(<ModelTable {...defaultProps} />)
// Find and click the delete button for the first row
const deleteButtons = screen.getAllByRole('button', { name: '删除' });
await user.click(deleteButtons[0]);
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);
const confirmButton = await screen.findByRole('button', { name: '确定' })
await user.click(confirmButton)
// Assert that deleteModel.mutate was called with the correct model ID
expect(mockMutate).toHaveBeenCalledTimes(1);
expect(mockMutate).toHaveBeenCalledWith('model-1');
}, 10000);
expect(mockMutate).toHaveBeenCalledTimes(1)
expect(mockMutate).toHaveBeenCalledWith('model-1')
}, 10000)
it('shows custom empty text when models list is empty', () => {
render(<ModelTable providerId="anthropic" />);
expect(screen.getByText('暂无模型,点击上方按钮添加')).toBeInTheDocument();
});
render(<ModelTable providerId='anthropic' />)
expect(screen.getByText('暂无模型,点击上方按钮添加')).toBeInTheDocument()
})
it('does not render add button when onAdd is not provided', () => {
render(<ModelTable providerId="openai" />);
render(<ModelTable providerId='openai' />)
expect(screen.queryByRole('button', { name: '添加模型' })).not.toBeInTheDocument();
});
expect(screen.queryByRole('button', { name: '添加模型' })).not.toBeInTheDocument()
})
it('does not render edit button when onEdit is not provided', () => {
render(<ModelTable providerId="openai" onAdd={vi.fn()} />);
render(<ModelTable providerId='openai' onAdd={vi.fn()} />)
expect(screen.queryByRole('button', { name: /编 ?辑/ })).not.toBeInTheDocument();
});
});
expect(screen.queryByRole('button', { name: /编 ?辑/ })).not.toBeInTheDocument()
})
})

View File

@@ -1,8 +1,8 @@
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';
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',
@@ -13,187 +13,193 @@ const mockProvider: Provider = {
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');
const dialog = document.querySelector('.t-dialog')
if (!dialog) {
throw new Error('Dialog not found');
throw new Error('Dialog not found')
}
return dialog;
return dialog
}
describe('ProviderForm', () => {
it('renders form fields in create mode', () => {
render(<ProviderForm {...defaultProps} />);
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).getByText('启用')).toBeInTheDocument();
expect(within(dialog).getByPlaceholderText('例如: openai')).toBeInTheDocument();
expect(within(dialog).getByPlaceholderText('例如: OpenAI')).toBeInTheDocument();
expect(within(dialog).getByPlaceholderText('例如: https://api.openai.com/v1')).toBeInTheDocument();
});
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(<ProviderForm {...defaultProps} provider={mockProvider} />);
render(<ProviderForm {...defaultProps} provider={mockProvider} />)
const dialog = getDialog();
expect(within(dialog).getByText('编辑供应商')).toBeInTheDocument();
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 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 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 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');
});
const apiKeyInput = within(dialog).getByPlaceholderText('sk-...') as HTMLInputElement
expect(apiKeyInput.value).toBe('sk-old-key')
})
it('shows API Key label in edit mode', () => {
render(<ProviderForm {...defaultProps} provider={mockProvider} />);
render(<ProviderForm {...defaultProps} provider={mockProvider} />)
const dialog = getDialog();
expect(within(dialog).getByText('API Key')).toBeInTheDocument();
});
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 user = userEvent.setup()
render(<ProviderForm {...defaultProps} />)
const dialog = getDialog();
const okButton = within(dialog).getByRole('button', { name: /保/ });
await user.click(okButton);
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();
});
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(<ProviderForm {...defaultProps} onSave={onSave} />);
const onSave = vi.fn()
render(<ProviderForm {...defaultProps} onSave={onSave} />)
const dialog = getDialog();
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;
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' } });
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);
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);
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(<ProviderForm {...defaultProps} onCancel={onCancel} />);
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);
});
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: /保/ });
render(<ProviderForm {...defaultProps} loading={true} />)
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');
});
expect(okButton).toHaveClass('t-is-loading')
})
it('shows validation error for invalid URL format', async () => {
const user = userEvent.setup();
render(<ProviderForm {...defaultProps} />);
const user = userEvent.setup()
render(<ProviderForm {...defaultProps} />)
const dialog = getDialog();
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');
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');
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);
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);
expect(screen.getByText('请输入有效的 URL')).toBeInTheDocument()
})
}, 15000)
it('renders protocol select field with default value', () => {
render(<ProviderForm {...defaultProps} />);
render(<ProviderForm {...defaultProps} />)
const dialog = getDialog();
expect(within(dialog).getByText('协议')).toBeInTheDocument();
});
const dialog = getDialog()
expect(within(dialog).getByText('协议')).toBeInTheDocument()
})
it('includes protocol field in form submission', async () => {
const onSave = vi.fn();
render(<ProviderForm {...defaultProps} onSave={onSave} />);
const onSave = vi.fn()
render(<ProviderForm {...defaultProps} onSave={onSave} />)
const dialog = getDialog();
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;
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' } });
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);
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);
});
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)
})

View File

@@ -1,18 +1,24 @@
import { render, screen } 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';
import { render, screen } 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, unifiedId: 'openai/gpt-4o' },
{ id: 'model-2', providerId: 'openai', modelName: 'gpt-3.5-turbo', enabled: false, unifiedId: 'openai/gpt-3.5-turbo' },
];
{
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[] = [
{
@@ -35,7 +41,7 @@ const mockProviders: Provider[] = [
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
},
];
]
const defaultProps = {
providers: mockProviders,
@@ -45,36 +51,36 @@ const defaultProps = {
onDelete: vi.fn(),
onAddModel: vi.fn(),
onEditModel: vi.fn(),
};
}
describe('ProviderTable', () => {
it('renders provider list with name, baseUrl, apiKey, and status tags', () => {
render(<ProviderTable {...defaultProps} />);
render(<ProviderTable {...defaultProps} />)
expect(screen.getByText('供应商列表')).toBeInTheDocument();
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('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();
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);
});
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} />);
const { container } = render(<ProviderTable {...defaultProps} />)
// TDesign Card component
expect(container.querySelector('.t-card')).toBeInTheDocument();
expect(container.querySelector('.t-card__header')).toBeInTheDocument();
expect(container.querySelector('.t-card__body')).toBeInTheDocument();
});
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[] = [
@@ -84,99 +90,99 @@ describe('ProviderTable', () => {
name: 'ShortKey',
apiKey: 'ab',
},
];
render(<ProviderTable {...defaultProps} providers={shortKeyProvider} />);
]
render(<ProviderTable {...defaultProps} providers={shortKeyProvider} />)
expect(screen.getByText('ab')).toBeInTheDocument();
});
expect(screen.getByText('ab')).toBeInTheDocument()
})
it('calls onAdd when clicking "添加供应商" button', async () => {
const user = userEvent.setup();
const onAdd = vi.fn();
render(<ProviderTable {...defaultProps} onAdd={onAdd} />);
const user = userEvent.setup()
const onAdd = vi.fn()
render(<ProviderTable {...defaultProps} onAdd={onAdd} />)
await user.click(screen.getByRole('button', { name: '添加供应商' }));
expect(onAdd).toHaveBeenCalledTimes(1);
});
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 user = userEvent.setup()
const onEdit = vi.fn()
render(<ProviderTable {...defaultProps} onEdit={onEdit} />)
const editButtons = screen.getAllByRole('button', { name: /编 ?辑/ });
await user.click(editButtons[0]);
const editButtons = screen.getAllByRole('button', { name: /编 ?辑/ })
await user.click(editButtons[0])
expect(onEdit).toHaveBeenCalledTimes(1);
expect(onEdit).toHaveBeenCalledWith(mockProviders[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} />);
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]);
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);
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);
expect(onDelete).toHaveBeenCalledTimes(1)
expect(onDelete).toHaveBeenCalledWith('openai')
}, 10000)
it('shows loading state', () => {
const { container } = render(<ProviderTable {...defaultProps} loading={true} />);
const { container } = render(<ProviderTable {...defaultProps} loading={true} />)
// TDesign Table loading indicator
const loadingElement = container.querySelector('.t-table__loading') || container.querySelector('.t-loading');
expect(loadingElement).toBeInTheDocument();
});
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(<ProviderTable {...defaultProps} />);
const user = userEvent.setup()
const { container } = render(<ProviderTable {...defaultProps} />)
// TDesign Table expand icon is rendered as a button with specific class
const expandIcon = container.querySelector('.t-table__expandable-icon');
const expandIcon = container.querySelector('.t-table__expandable-icon')
if (expandIcon) {
await user.click(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();
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);
expect(true).toBe(true)
}
});
})
it('sets fixed width and ellipsis on name column', () => {
const { container } = render(<ProviderTable {...defaultProps} />);
const { container } = render(<ProviderTable {...defaultProps} />)
// TDesign Table
const table = container.querySelector('.t-table');
expect(table).toBeInTheDocument();
});
const table = container.querySelector('.t-table')
expect(table).toBeInTheDocument()
})
it('shows custom empty text when providers list is empty', () => {
render(<ProviderTable {...defaultProps} providers={[]} />);
expect(screen.getByText('暂无供应商,点击上方按钮添加')).toBeInTheDocument();
});
render(<ProviderTable {...defaultProps} providers={[]} />)
expect(screen.getByText('暂无供应商,点击上方按钮添加')).toBeInTheDocument()
})
it('renders protocol column with correct tags', () => {
const { container } = render(<ProviderTable {...defaultProps} />);
const { container } = render(<ProviderTable {...defaultProps} />)
// Check that protocol tags are displayed in the table
const protocolCells = container.querySelectorAll('[data-colkey="protocol"]');
expect(protocolCells.length).toBeGreaterThan(0);
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);
});
const tags = container.querySelectorAll('.t-tag')
expect(tags.length).toBeGreaterThan(0)
})
it('displays protocol tag for each provider', () => {
const singleProvider: Provider[] = [
@@ -190,11 +196,11 @@ describe('ProviderTable', () => {
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
},
];
const { container } = render(<ProviderTable {...defaultProps} providers={singleProvider} />);
]
const { container } = render(<ProviderTable {...defaultProps} providers={singleProvider} />)
// Should display protocol column
const protocolCell = container.querySelector('[data-colkey="protocol"]');
expect(protocolCell).toBeInTheDocument();
});
});
const protocolCell = container.querySelector('[data-colkey="protocol"]')
expect(protocolCell).toBeInTheDocument()
})
})

View File

@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import { StatCards } from '@/pages/Stats/StatCards';
import type { UsageStats } from '@/types';
import { render, screen } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { StatCards } from '@/pages/Stats/StatCards'
import type { UsageStats } from '@/types'
const mockStats: UsageStats[] = [
{
@@ -25,31 +25,31 @@ const mockStats: UsageStats[] = [
requestCount: 150,
date: '2024-01-02',
},
];
]
describe('StatCards', () => {
it('renders all statistic cards', () => {
render(<StatCards stats={mockStats} />);
render(<StatCards stats={mockStats} />)
expect(screen.getByText('总请求量')).toBeInTheDocument();
expect(screen.getByText('活跃模型数')).toBeInTheDocument();
expect(screen.getByText('活跃供应商数')).toBeInTheDocument();
expect(screen.getByText('今日请求量')).toBeInTheDocument();
});
expect(screen.getByText('总请求量')).toBeInTheDocument()
expect(screen.getByText('活跃模型数')).toBeInTheDocument()
expect(screen.getByText('活跃供应商数')).toBeInTheDocument()
expect(screen.getByText('今日请求量')).toBeInTheDocument()
})
it('renders with empty stats', () => {
render(<StatCards stats={[]} />);
render(<StatCards stats={[]} />)
expect(screen.getByText('总请求量')).toBeInTheDocument();
expect(screen.getByText('活跃模型数')).toBeInTheDocument();
expect(screen.getByText('活跃供应商数')).toBeInTheDocument();
expect(screen.getByText('今日请求量')).toBeInTheDocument();
});
expect(screen.getByText('总请求量')).toBeInTheDocument()
expect(screen.getByText('活跃模型数')).toBeInTheDocument()
expect(screen.getByText('活跃供应商数')).toBeInTheDocument()
expect(screen.getByText('今日请求量')).toBeInTheDocument()
})
it('renders suffix units', () => {
render(<StatCards stats={mockStats} />);
render(<StatCards stats={mockStats} />)
expect(screen.getAllByText('次').length).toBeGreaterThan(0);
expect(screen.getAllByText('个').length).toBeGreaterThan(0);
});
});
expect(screen.getAllByText('次').length).toBeGreaterThan(0)
expect(screen.getAllByText('个').length).toBeGreaterThan(0)
})
})

View File

@@ -1,7 +1,7 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { StatsTable } from '@/pages/Stats/StatsTable';
import type { Provider, UsageStats } from '@/types';
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { StatsTable } from '@/pages/Stats/StatsTable'
import type { Provider, UsageStats } from '@/types'
const mockProviders: Provider[] = [
{
@@ -24,7 +24,7 @@ const mockProviders: Provider[] = [
createdAt: '2024-01-02T00:00:00Z',
updatedAt: '2024-01-02T00:00:00Z',
},
];
]
const mockStats: UsageStats[] = [
{
@@ -41,7 +41,7 @@ const mockStats: UsageStats[] = [
requestCount: 50,
date: '2024-01-15',
},
];
]
const defaultProps = {
providers: mockProviders,
@@ -53,80 +53,80 @@ const defaultProps = {
onProviderIdChange: vi.fn(),
onModelNameChange: vi.fn(),
onDateRangeChange: vi.fn(),
};
}
describe('StatsTable', () => {
it('renders stats table with data', () => {
render(<StatsTable {...defaultProps} />);
render(<StatsTable {...defaultProps} />)
expect(screen.getByText('gpt-4o')).toBeInTheDocument();
expect(screen.getByText('claude-3-opus')).toBeInTheDocument();
const dateCells = screen.getAllByText('2024-01-15');
expect(dateCells.length).toBe(2);
expect(screen.getByText('100')).toBeInTheDocument();
expect(screen.getByText('50')).toBeInTheDocument();
});
expect(screen.getByText('gpt-4o')).toBeInTheDocument()
expect(screen.getByText('claude-3-opus')).toBeInTheDocument()
const dateCells = screen.getAllByText('2024-01-15')
expect(dateCells.length).toBe(2)
expect(screen.getByText('100')).toBeInTheDocument()
expect(screen.getByText('50')).toBeInTheDocument()
})
it('shows provider name from providers prop instead of providerId', () => {
render(<StatsTable {...defaultProps} />);
render(<StatsTable {...defaultProps} />)
expect(screen.getByText('OpenAI')).toBeInTheDocument();
const allAnthropic = screen.getAllByText('Anthropic');
expect(allAnthropic.length).toBeGreaterThanOrEqual(1);
});
expect(screen.getByText('OpenAI')).toBeInTheDocument()
const allAnthropic = screen.getAllByText('Anthropic')
expect(allAnthropic.length).toBeGreaterThanOrEqual(1)
})
it('renders filter controls with Select, Input, and DatePicker', () => {
const { container } = render(<StatsTable {...defaultProps} />);
const { container } = render(<StatsTable {...defaultProps} />)
// TDesign Select component
const selects = document.querySelectorAll('.t-select');
expect(selects.length).toBeGreaterThanOrEqual(1);
const selects = document.querySelectorAll('.t-select')
expect(selects.length).toBeGreaterThanOrEqual(1)
const modelInput = screen.getByPlaceholderText('模型名称');
expect(modelInput).toBeInTheDocument();
const modelInput = screen.getByPlaceholderText('模型名称')
expect(modelInput).toBeInTheDocument()
// TDesign Select placeholder is shown in the input
const selectInput = document.querySelector('.t-select .t-input__inner');
expect(selectInput).toBeInTheDocument();
const selectInput = document.querySelector('.t-select .t-input__inner')
expect(selectInput).toBeInTheDocument()
// TDesign DateRangePicker - could be .t-date-picker or .t-range-input
const rangePicker = container.querySelector('.t-date-picker') || container.querySelector('.t-range-input');
expect(rangePicker).toBeInTheDocument();
});
const rangePicker = container.querySelector('.t-date-picker') || container.querySelector('.t-range-input')
expect(rangePicker).toBeInTheDocument()
})
it('renders table headers correctly', () => {
render(<StatsTable {...defaultProps} />);
render(<StatsTable {...defaultProps} />)
expect(screen.getAllByText('供应商').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('模型').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('日期').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('请求数').length).toBeGreaterThanOrEqual(1);
});
expect(screen.getAllByText('供应商').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('模型').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('日期').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('请求数').length).toBeGreaterThanOrEqual(1)
})
it('falls back to providerId when provider not found in providers prop', () => {
const limitedProviders = [mockProviders[0]];
render(<StatsTable {...defaultProps} providers={limitedProviders} />);
const limitedProviders = [mockProviders[0]]
render(<StatsTable {...defaultProps} providers={limitedProviders} />)
expect(screen.getByText('OpenAI')).toBeInTheDocument();
expect(screen.getByText('anthropic')).toBeInTheDocument();
});
expect(screen.getByText('OpenAI')).toBeInTheDocument()
expect(screen.getByText('anthropic')).toBeInTheDocument()
})
it('renders with empty stats data', () => {
render(<StatsTable {...defaultProps} stats={[]} />);
render(<StatsTable {...defaultProps} stats={[]} />)
expect(screen.getAllByText('供应商').length).toBeGreaterThanOrEqual(1);
expect(screen.getAllByText('模型').length).toBeGreaterThanOrEqual(1);
});
expect(screen.getAllByText('供应商').length).toBeGreaterThanOrEqual(1)
expect(screen.getAllByText('模型').length).toBeGreaterThanOrEqual(1)
})
it('shows loading state', () => {
const { container } = render(<StatsTable {...defaultProps} loading={true} />);
const { container } = render(<StatsTable {...defaultProps} loading={true} />)
// TDesign Table loading indicator - could be .t-table__loading or .t-loading
const loadingElement = container.querySelector('.t-table__loading') || container.querySelector('.t-loading');
expect(loadingElement).toBeInTheDocument();
});
const loadingElement = container.querySelector('.t-table__loading') || container.querySelector('.t-loading')
expect(loadingElement).toBeInTheDocument()
})
it('shows custom empty text when stats data is empty', () => {
render(<StatsTable {...defaultProps} stats={[]} />);
expect(screen.getByText('暂无统计数据')).toBeInTheDocument();
});
});
render(<StatsTable {...defaultProps} stats={[]} />)
expect(screen.getByText('暂无统计数据')).toBeInTheDocument()
})
})

View File

@@ -1,18 +1,18 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { UsageChart } from '@/pages/Stats/UsageChart';
import type { UsageStats } from '@/types';
import { render, screen } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { UsageChart } from '@/pages/Stats/UsageChart'
import type { UsageStats } from '@/types'
// Mock Recharts components
vi.mock('recharts', () => ({
ResponsiveContainer: vi.fn(({ children }) => <div data-testid="mock-chart-container">{children}</div>),
AreaChart: vi.fn(() => <div data-testid="mock-area-chart" />),
ResponsiveContainer: vi.fn(({ children }) => <div data-testid='mock-chart-container'>{children}</div>),
AreaChart: vi.fn(() => <div data-testid='mock-area-chart' />),
Area: vi.fn(() => null),
XAxis: vi.fn(() => null),
YAxis: vi.fn(() => null),
CartesianGrid: vi.fn(() => null),
Tooltip: vi.fn(() => null),
}));
}))
const mockStats: UsageStats[] = [
{
@@ -36,36 +36,36 @@ const mockStats: UsageStats[] = [
requestCount: 150,
date: '2024-01-02',
},
];
]
describe('UsageChart', () => {
it('renders chart title', () => {
render(<UsageChart stats={mockStats} />);
render(<UsageChart stats={mockStats} />)
expect(screen.getByText('请求趋势')).toBeInTheDocument();
});
expect(screen.getByText('请求趋势')).toBeInTheDocument()
})
it('renders with data', () => {
const { container } = render(<UsageChart stats={mockStats} />);
const { container } = render(<UsageChart stats={mockStats} />)
// TDesign Card component
expect(container.querySelector('.t-card')).toBeInTheDocument();
expect(container.querySelector('.t-card')).toBeInTheDocument()
// Mocked chart container
expect(screen.getByTestId('mock-chart-container')).toBeInTheDocument();
});
expect(screen.getByTestId('mock-chart-container')).toBeInTheDocument()
})
it('renders empty state when no data', () => {
render(<UsageChart stats={[]} />);
render(<UsageChart stats={[]} />)
expect(screen.getByText('暂无数据')).toBeInTheDocument();
});
expect(screen.getByText('暂无数据')).toBeInTheDocument()
})
it('aggregates data by date correctly', () => {
const { container } = render(<UsageChart stats={mockStats} />);
const { container } = render(<UsageChart stats={mockStats} />)
// TDesign Card component
expect(container.querySelector('.t-card')).toBeInTheDocument();
expect(container.querySelector('.t-card')).toBeInTheDocument()
// Mocked chart should render
expect(screen.getByTestId('mock-chart-container')).toBeInTheDocument();
});
});
expect(screen.getByTestId('mock-chart-container')).toBeInTheDocument()
})
})