|
@@ -2,10 +2,30 @@ import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
import { UserManagement } from '../../src/components/UserManagement';
|
|
import { UserManagement } from '../../src/components/UserManagement';
|
|
|
|
|
+import { userClient } from '../../src/api/userClient';
|
|
|
|
|
+
|
|
|
|
|
+// 完整的mock响应对象
|
|
|
|
|
+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 API client
|
|
// Mock API client
|
|
|
-vi.mock('../../src/api/userClient', () => ({
|
|
|
|
|
- userClient: {
|
|
|
|
|
|
|
+vi.mock('../../src/api/userClient', () => {
|
|
|
|
|
+ const mockUserClient = {
|
|
|
index: {
|
|
index: {
|
|
|
$get: vi.fn(),
|
|
$get: vi.fn(),
|
|
|
$post: vi.fn(),
|
|
$post: vi.fn(),
|
|
@@ -14,8 +34,11 @@ vi.mock('../../src/api/userClient', () => ({
|
|
|
$put: vi.fn(),
|
|
$put: vi.fn(),
|
|
|
$delete: vi.fn(),
|
|
$delete: vi.fn(),
|
|
|
},
|
|
},
|
|
|
- },
|
|
|
|
|
-}));
|
|
|
|
|
|
|
+ };
|
|
|
|
|
+ return {
|
|
|
|
|
+ userClient: mockUserClient,
|
|
|
|
|
+ };
|
|
|
|
|
+});
|
|
|
|
|
|
|
|
// Mock toast
|
|
// Mock toast
|
|
|
vi.mock('sonner', () => ({
|
|
vi.mock('sonner', () => ({
|
|
@@ -34,7 +57,7 @@ const createTestQueryClient = () =>
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const renderWithProviders = (component: React.ReactNode) => {
|
|
|
|
|
|
|
+const renderWithProviders = (component: React.ReactElement) => {
|
|
|
const queryClient = createTestQueryClient();
|
|
const queryClient = createTestQueryClient();
|
|
|
return render(
|
|
return render(
|
|
|
<QueryClientProvider client={queryClient}>
|
|
<QueryClientProvider client={queryClient}>
|
|
@@ -43,12 +66,12 @@ const renderWithProviders = (component: React.ReactNode) => {
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-describe('UserManagement Integration Tests', () => {
|
|
|
|
|
|
|
+describe('用户管理集成测试', () => {
|
|
|
beforeEach(() => {
|
|
beforeEach(() => {
|
|
|
vi.clearAllMocks();
|
|
vi.clearAllMocks();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should complete full user CRUD flow', async () => {
|
|
|
|
|
|
|
+ it('应该完成完整的用户CRUD流程', async () => {
|
|
|
const mockUsers = {
|
|
const mockUsers = {
|
|
|
data: [
|
|
data: [
|
|
|
{
|
|
{
|
|
@@ -71,14 +94,10 @@ describe('UserManagement Integration Tests', () => {
|
|
|
},
|
|
},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const { userClient } = await import('../../src/api/userClient');
|
|
|
|
|
const { toast } = await import('sonner');
|
|
const { toast } = await import('sonner');
|
|
|
|
|
|
|
|
// Mock initial user list
|
|
// Mock initial user list
|
|
|
- (userClient.index.$get as any).mockResolvedValue({
|
|
|
|
|
- status: 200,
|
|
|
|
|
- json: async () => mockUsers,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ (userClient.index.$get as any).mockResolvedValue(createMockResponse(200, mockUsers));
|
|
|
|
|
|
|
|
renderWithProviders(<UserManagement />);
|
|
renderWithProviders(<UserManagement />);
|
|
|
|
|
|
|
@@ -88,7 +107,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Test create user
|
|
// Test create user
|
|
|
- const createButton = screen.getByText('创建用户');
|
|
|
|
|
|
|
+ const createButton = screen.getByTestId('create-user-button');
|
|
|
fireEvent.click(createButton);
|
|
fireEvent.click(createButton);
|
|
|
|
|
|
|
|
// Fill create form
|
|
// Fill create form
|
|
@@ -101,11 +120,9 @@ describe('UserManagement Integration Tests', () => {
|
|
|
fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
|
|
fireEvent.change(emailInput, { target: { value: 'new@example.com' } });
|
|
|
|
|
|
|
|
// Mock successful creation
|
|
// Mock successful creation
|
|
|
- (userClient.index.$post as any).mockResolvedValue({
|
|
|
|
|
- status: 201,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ (userClient.index.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, username: 'newuser' }));
|
|
|
|
|
|
|
|
- const submitButton = screen.getByText('创建用户');
|
|
|
|
|
|
|
+ const submitButton = screen.getByTestId('create-user-submit-button');
|
|
|
fireEvent.click(submitButton);
|
|
fireEvent.click(submitButton);
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
await waitFor(() => {
|
|
@@ -114,7 +131,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
username: 'newuser',
|
|
username: 'newuser',
|
|
|
password: 'password123',
|
|
password: 'password123',
|
|
|
email: 'new@example.com',
|
|
email: 'new@example.com',
|
|
|
- nickname: undefined,
|
|
|
|
|
|
|
+ nickname: null,
|
|
|
phone: null,
|
|
phone: null,
|
|
|
name: null,
|
|
name: null,
|
|
|
isDisabled: 0,
|
|
isDisabled: 0,
|
|
@@ -124,7 +141,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Test edit user
|
|
// Test edit user
|
|
|
- const editButtons = screen.getAllByRole('button', { name: /edit/i });
|
|
|
|
|
|
|
+ const editButtons = screen.getAllByRole('button', { name: '编辑用户' });
|
|
|
fireEvent.click(editButtons[0]);
|
|
fireEvent.click(editButtons[0]);
|
|
|
|
|
|
|
|
// Verify edit form is populated
|
|
// Verify edit form is populated
|
|
@@ -137,9 +154,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
fireEvent.change(updateUsernameInput, { target: { value: 'updateduser' } });
|
|
fireEvent.change(updateUsernameInput, { target: { value: 'updateduser' } });
|
|
|
|
|
|
|
|
// Mock successful update
|
|
// Mock successful update
|
|
|
- (userClient[':id']['$put'] as any).mockResolvedValue({
|
|
|
|
|
- status: 200,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ (userClient[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
|
|
|
|
|
|
|
|
const updateButton = screen.getByText('更新用户');
|
|
const updateButton = screen.getByText('更新用户');
|
|
|
fireEvent.click(updateButton);
|
|
fireEvent.click(updateButton);
|
|
@@ -154,7 +169,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
phone: '1234567890',
|
|
phone: '1234567890',
|
|
|
name: 'Existing Name',
|
|
name: 'Existing Name',
|
|
|
password: undefined,
|
|
password: undefined,
|
|
|
- avatarFileId: null,
|
|
|
|
|
|
|
+ avatarFileId: undefined,
|
|
|
isDisabled: 0,
|
|
isDisabled: 0,
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
@@ -162,14 +177,14 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Test delete user
|
|
// Test delete user
|
|
|
- const deleteButtons = screen.getAllByRole('button', { name: /trash/i });
|
|
|
|
|
|
|
+ const deleteButtons = screen.getAllByRole('button', { name: '删除用户' });
|
|
|
fireEvent.click(deleteButtons[0]);
|
|
fireEvent.click(deleteButtons[0]);
|
|
|
|
|
|
|
|
// Confirm deletion
|
|
// Confirm deletion
|
|
|
expect(screen.getByText('确认删除')).toBeInTheDocument();
|
|
expect(screen.getByText('确认删除')).toBeInTheDocument();
|
|
|
|
|
|
|
|
// Mock successful deletion
|
|
// Mock successful deletion
|
|
|
- (userClient[':id']['$delete'] as any).mockResolvedValue({
|
|
|
|
|
|
|
+ userClient[':id']['$delete'].mockResolvedValue({
|
|
|
status: 204,
|
|
status: 204,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -184,7 +199,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should handle API errors gracefully', async () => {
|
|
|
|
|
|
|
+ it('应该优雅处理API错误', async () => {
|
|
|
const { userClient } = await import('../../src/api/userClient');
|
|
const { userClient } = await import('../../src/api/userClient');
|
|
|
const { toast } = await import('sonner');
|
|
const { toast } = await import('sonner');
|
|
|
|
|
|
|
@@ -211,7 +226,7 @@ describe('UserManagement Integration Tests', () => {
|
|
|
// Mock creation error
|
|
// Mock creation error
|
|
|
(userClient.index.$post as any).mockRejectedValue(new Error('Creation failed'));
|
|
(userClient.index.$post as any).mockRejectedValue(new Error('Creation failed'));
|
|
|
|
|
|
|
|
- const submitButton = screen.getByText('创建用户');
|
|
|
|
|
|
|
+ const submitButton = screen.getByTestId('create-user-submit-button');
|
|
|
fireEvent.click(submitButton);
|
|
fireEvent.click(submitButton);
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
await waitFor(() => {
|
|
@@ -219,18 +234,14 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('should handle search and filter integration', async () => {
|
|
|
|
|
|
|
+ it('应该处理搜索和过滤器集成', async () => {
|
|
|
const { userClient } = await import('../../src/api/userClient');
|
|
const { userClient } = await import('../../src/api/userClient');
|
|
|
-
|
|
|
|
|
const mockUsers = {
|
|
const mockUsers = {
|
|
|
data: [],
|
|
data: [],
|
|
|
pagination: { total: 0, page: 1, pageSize: 10 },
|
|
pagination: { total: 0, page: 1, pageSize: 10 },
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- (userClient.index.$get as any).mockResolvedValue({
|
|
|
|
|
- status: 200,
|
|
|
|
|
- json: async () => mockUsers,
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ (userClient.index.$get as any).mockResolvedValue(createMockResponse(200, mockUsers));
|
|
|
|
|
|
|
|
renderWithProviders(<UserManagement />);
|
|
renderWithProviders(<UserManagement />);
|
|
|
|
|
|
|
@@ -250,14 +261,19 @@ describe('UserManagement Integration Tests', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// Test filter
|
|
// Test filter
|
|
|
- const filterButton = screen.getByText('高级筛选');
|
|
|
|
|
|
|
+ const filterButton = screen.getByTestId('advanced-filter-button');
|
|
|
fireEvent.click(filterButton);
|
|
fireEvent.click(filterButton);
|
|
|
|
|
|
|
|
|
|
+ // Wait for filter panel to appear
|
|
|
|
|
+ await waitFor(() => {
|
|
|
|
|
+ expect(screen.getByTestId('status-filter-trigger')).toBeInTheDocument();
|
|
|
|
|
+ }, { timeout: 2000 });
|
|
|
|
|
+
|
|
|
// Apply status filter
|
|
// Apply status filter
|
|
|
- const statusSelect = screen.getByText('选择状态');
|
|
|
|
|
|
|
+ const statusSelect = screen.getByTestId('status-filter-trigger');
|
|
|
fireEvent.click(statusSelect);
|
|
fireEvent.click(statusSelect);
|
|
|
|
|
|
|
|
- const enabledOption = screen.getByText('启用');
|
|
|
|
|
|
|
+ const enabledOption = screen.getByTestId('status-enabled-option');
|
|
|
fireEvent.click(enabledOption);
|
|
fireEvent.click(enabledOption);
|
|
|
|
|
|
|
|
await waitFor(() => {
|
|
await waitFor(() => {
|