import React from 'react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router';
import { AreaManagement } from '../../src/components/AreaManagement';
import { areaClient } from '../../src/api/areaClient';
// 完整的mock响应对象 - 按照用户UI包规范
const createMockResponse = (status: number, data?: any) => ({
status,
ok: status >= 200 && status < 300,
body: null,
bodyUsed: false,
statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
headers: new Headers(),
url: '',
redirected: false,
type: 'basic' as ResponseType,
json: async () => data || {},
text: async () => '',
blob: async () => new Blob(),
arrayBuffer: async () => new ArrayBuffer(0),
formData: async () => new FormData(),
clone: function() { return this; }
});
// Mock areaClient - 按照用户UI包规范
vi.mock('../../src/api/areaClient', () => {
const mockAreaClient = {
index: {
$get: vi.fn(() => Promise.resolve({ status: 200, body: null })),
$post: vi.fn(() => Promise.resolve({ status: 201, body: null })),
},
':id': {
$put: vi.fn(() => Promise.resolve({ status: 200, body: null })),
$delete: vi.fn(() => Promise.resolve({ status: 204, body: null })),
},
};
const mockAreaClientManager = {
get: vi.fn(() => mockAreaClient),
};
return {
areaClientManager: mockAreaClientManager,
areaClient: mockAreaClient,
};
});
// Mock sonner toast
vi.mock('sonner', () => ({
toast: {
success: vi.fn(),
error: vi.fn()
}
}));
// Test wrapper component
const TestWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
return (
{children}
);
};
describe('AreaManagement Integration Tests', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should render area management component with title', async () => {
// Mock successful API response for province data
(areaClient.index.$get as any).mockResolvedValueOnce(createMockResponse(200, {
data: [
{
id: 1,
name: '北京市',
code: '110000',
level: 1,
parentId: null,
isDisabled: 0
}
]
}));
render(
);
// Check if title is rendered
expect(screen.getByText('省市区树形管理')).toBeInTheDocument();
expect(screen.getByText('异步加载树形结构,高效管理省市区数据')).toBeInTheDocument();
// Wait for loading to complete
await waitFor(() => {
expect(screen.getByText('北京市')).toBeInTheDocument();
});
});
it('should show loading state when fetching data', async () => {
// Mock delayed API response
(areaClient.index.$get as any).mockImplementationOnce(() =>
new Promise(resolve => setTimeout(() => resolve(createMockResponse(200, { data: [] })), 100))
);
render(
);
// Check if loading state is shown
expect(screen.getByText('加载中...')).toBeInTheDocument();
// Wait for loading to complete
await waitFor(() => {
expect(screen.queryByText('加载中...')).not.toBeInTheDocument();
});
});
it('should show empty state when no data', async () => {
// Mock empty API response
(areaClient.index.$get as any).mockResolvedValueOnce(createMockResponse(200, { data: [] }));
render(
);
// Wait for empty state to appear
await waitFor(() => {
expect(screen.getByText('暂无数据')).toBeInTheDocument();
});
});
it('should open create dialog when clicking add button', async () => {
// Mock successful API response
(areaClient.index.$get as any).mockResolvedValueOnce(createMockResponse(200, {
data: [
{
id: 1,
name: '北京市',
code: '110000',
level: 1,
parentId: null,
isDisabled: 0
}
]
}));
render(
);
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('北京市')).toBeInTheDocument();
});
// Click add button
const addButton = screen.getByText('新增省');
fireEvent.click(addButton);
// Check if dialog opens
await waitFor(() => {
expect(screen.getByRole('heading', { name: '新增省' })).toBeInTheDocument();
expect(screen.getByText('填写省信息')).toBeInTheDocument();
});
});
it('should handle API errors gracefully', async () => {
// Mock API error
(areaClient.index.$get as any).mockRejectedValueOnce(new Error('API Error'));
render(
);
// Wait for error state
await waitFor(() => {
// Component should handle errors gracefully
expect(screen.getByText('省市区树形管理')).toBeInTheDocument();
});
});
it('should complete create and delete workflow', async () => {
const { toast } = await import('sonner');
// Mock initial areas data
const mockAreas = {
data: [
{
id: 1,
name: '北京市',
code: '110000',
level: 1,
parentId: null,
isDisabled: 0
}
]
};
// Mock initial data fetch
(areaClient.index.$get as any).mockResolvedValue(createMockResponse(200, mockAreas));
render(
);
// Wait for initial data to load
await waitFor(() => {
expect(screen.getByText('北京市')).toBeInTheDocument();
});
// Test create area
const addButton = screen.getByText('新增省');
fireEvent.click(addButton);
// Wait for create dialog
await waitFor(() => {
expect(screen.getByRole('heading', { name: '新增省' })).toBeInTheDocument();
});
// Fill create form
const nameInput = screen.getByPlaceholderText('输入区域名称');
const codeInput = screen.getByPlaceholderText('输入行政区划代码');
fireEvent.change(nameInput, { target: { value: '上海市' } });
fireEvent.change(codeInput, { target: { value: '310000' } });
// Mock successful creation
(areaClient.index.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, name: '上海市' }));
const submitButton = screen.getByText('创建');
fireEvent.click(submitButton);
await waitFor(() => {
expect(areaClient.index.$post).toHaveBeenCalledWith({
json: {
name: '上海市',
code: '310000',
level: 1,
parentId: null,
isDisabled: 0
}
});
expect(toast.success).toHaveBeenCalledWith('省市区创建成功');
});
// 跳过编辑操作测试,专注于创建和删除操作
// Test delete area
const deleteButtons = screen.getAllByRole('button', { name: '删除' });
fireEvent.click(deleteButtons[0]);
// Confirm deletion
expect(screen.getByRole('heading', { name: '确认删除' })).toBeInTheDocument();
// Mock successful deletion
(areaClient[':id']['$delete'] as any).mockResolvedValue({
status: 204,
});
// 查找删除确认按钮
const confirmDeleteButton = screen.getByRole('button', { name: '确认删除' });
fireEvent.click(confirmDeleteButton);
await waitFor(() => {
expect(areaClient[':id']['$delete']).toHaveBeenCalledWith({
param: { id: 1 },
});
expect(toast.success).toHaveBeenCalledWith('省市区删除成功');
});
});
it('should handle API errors in CRUD operations', async () => {
const { areaClient } = await import('../../src/api/areaClient');
const { toast } = await import('sonner');
// Mock initial data
const mockAreas = {
data: [
{
id: 1,
name: '北京市',
code: '110000',
level: 1,
parentId: null,
isDisabled: 0
}
]
};
(areaClient.index.$get as any).mockResolvedValue(createMockResponse(200, mockAreas));
render(
);
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('北京市')).toBeInTheDocument();
});
// Test create area error
const addButton = screen.getByText('新增省');
fireEvent.click(addButton);
await waitFor(() => {
expect(screen.getByRole('heading', { name: '新增省' })).toBeInTheDocument();
});
const nameInput = screen.getByPlaceholderText('输入区域名称');
const codeInput = screen.getByPlaceholderText('输入行政区划代码');
fireEvent.change(nameInput, { target: { value: '上海市' } });
fireEvent.change(codeInput, { target: { value: '310000' } });
// Mock creation error
(areaClient.index.$post as any).mockRejectedValue(new Error('Creation failed'));
const submitButton = screen.getByText('创建');
fireEvent.click(submitButton);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith('创建失败,请重试');
});
});
});