import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AdvertisementManagement } from '../../src/components/AdvertisementManagement';
import { advertisementClientManager } from '../../src/api/advertisementClient';
// 完整的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
vi.mock('../../src/api/advertisementClient', () => {
const mockAdvertisementClient = {
index: {
$get: vi.fn(() => Promise.resolve(createMockResponse(200, {
data: [
{
id: 1,
title: '测试广告',
alias: 'test-ad',
typeId: 1,
imageUrl: 'https://example.com/image.jpg',
linkUrl: 'https://example.com',
status: 1,
sortOrder: 1,
remark: '测试备注',
createdAt: '2024-01-01T00:00:00Z',
updatedAt: '2024-01-01T00:00:00Z',
createdBy: 1,
updatedBy: 1
}
],
pagination: {
page: 1,
pageSize: 10,
total: 1,
totalPages: 1
}
}))),
$post: vi.fn(() => Promise.resolve(createMockResponse(201, { id: 2, title: '新广告' }))),
},
':id': {
$put: vi.fn(() => Promise.resolve(createMockResponse(200, { id: 1, title: '更新后的广告' }))),
$delete: vi.fn(() => Promise.resolve(createMockResponse(204))),
},
};
const mockAdvertisementClientManager = {
get: vi.fn(() => mockAdvertisementClient),
};
return {
advertisementClientManager: mockAdvertisementClientManager,
advertisementClient: mockAdvertisementClient,
};
});
// Mock toast
vi.mock('sonner', () => ({
toast: {
success: vi.fn(() => {}),
error: vi.fn(() => {}),
},
}));
// Mock FileSelector
vi.mock('@d8d/file-management-ui', () => ({
FileSelector: ({ value, onChange, testId, maxSize, uploadPath, previewSize, filterType, ...props }: any) => (
onChange?.(parseInt(e.target.value))}
data-testid="file-selector-input"
{...props}
/>
),
}));
// Mock AdvertisementTypeSelector
vi.mock('@d8d/advertisement-type-management-ui', () => ({
AdvertisementTypeSelector: ({ value, onChange, testId, ...props }: any) => (
),
}));
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});
const renderWithProviders = (component: React.ReactElement) => {
const queryClient = createTestQueryClient();
return render(
{component as any}
);
};
describe('广告管理集成测试', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('应该完成完整的广告CRUD流程', async () => {
const mockAdvertisements = {
data: [
{
id: 1,
title: '测试广告',
code: 'test-ad',
typeId: 1,
advertisementType: { id: 1, name: '首页轮播' },
url: 'https://example.com',
imageFileId: 1,
imageFile: { id: 1, fullUrl: 'https://example.com/image.jpg' },
sort: 1,
status: 1,
actionType: 1,
createdAt: '2024-01-01T00:00:00Z',
},
],
pagination: {
total: 1,
page: 1,
pageSize: 10,
},
};
const { toast } = await import('sonner');
// Mock initial advertisement list
const client = advertisementClientManager.get();
(client.index.$get as any).mockResolvedValue({
...createMockResponse(200),
json: async () => mockAdvertisements
});
renderWithProviders();
// Wait for initial data to load
await waitFor(() => {
expect(screen.getByText('测试广告')).toBeInTheDocument();
});
// Test create advertisement
const createButton = screen.getByText('创建广告');
fireEvent.click(createButton);
// Fill create form
const titleInput = screen.getByTestId('title-input');
const codeInput = screen.getByTestId('code-input');
const typeSelector = screen.getByTestId('type-selector');
fireEvent.change(titleInput, { target: { value: '新广告' } });
fireEvent.change(codeInput, { target: { value: 'new-ad' } });
fireEvent.change(typeSelector, { target: { value: '1' } });
// Mock successful creation
(client.index.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, title: '新广告' }));
const submitButton = screen.getByTestId('create-submit-button');
fireEvent.click(submitButton);
await waitFor(() => {
expect(client.index.$post).toHaveBeenCalledWith({
json: {
title: '新广告',
code: 'new-ad',
typeId: 1,
url: '',
imageFileId: undefined,
sort: 0,
status: 1,
actionType: 1
},
});
expect(toast.success).toHaveBeenCalledWith('广告创建成功');
});
// Test edit advertisement
const editButton = screen.getByTestId('edit-button-1');
fireEvent.click(editButton);
// Verify edit form is populated
await waitFor(() => {
expect(screen.getByDisplayValue('测试广告')).toBeInTheDocument();
});
// Update advertisement
const updateTitleInput = screen.getByDisplayValue('测试广告');
fireEvent.change(updateTitleInput, { target: { value: '更新后的广告' } });
// Mock successful update
(client[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
const updateButton = screen.getByTestId('update-submit-button');
fireEvent.click(updateButton);
await waitFor(() => {
expect(client[':id']['$put']).toHaveBeenCalledWith({
param: { id: 1 },
json: {
title: '更新后的广告',
typeId: 1,
code: 'test-ad',
url: 'https://example.com',
imageFileId: 1,
sort: 1,
status: 1,
actionType: 1
},
});
expect(toast.success).toHaveBeenCalledWith('广告更新成功');
});
// Test delete advertisement
const deleteButton = screen.getByTestId('delete-button-1');
fireEvent.click(deleteButton);
// Confirm deletion
expect(screen.getByText('确认删除')).toBeInTheDocument();
// Mock successful deletion
(client[':id']['$delete'] as any).mockResolvedValue({
status: 204,
ok: true,
body: null,
bodyUsed: false,
statusText: 'No Content',
headers: new Headers(),
url: '',
redirected: false,
type: 'basic' as ResponseType,
json: async () => ({}),
text: async () => '',
blob: async () => new Blob(),
arrayBuffer: async () => new ArrayBuffer(0),
formData: async () => new FormData(),
clone: function() { return this; }
});
const confirmDeleteButton = screen.getByTestId('confirm-delete-button');
fireEvent.click(confirmDeleteButton);
await waitFor(() => {
expect(client[':id']['$delete']).toHaveBeenCalledWith({
param: { id: 1 },
});
expect(toast.success).toHaveBeenCalledWith('广告删除成功');
});
});
it('应该优雅处理API错误', async () => {
const client = advertisementClientManager.get();
const { toast } = await import('sonner');
// Mock API error
(client.index.$get as any).mockRejectedValue(new Error('API Error'));
renderWithProviders();
// Should handle error without crashing
await waitFor(() => {
expect(screen.getByText('广告管理')).toBeInTheDocument();
});
// Test create advertisement error
const createButton = screen.getByText('创建广告');
fireEvent.click(createButton);
const titleInput = screen.getByTestId('title-input');
const codeInput = screen.getByTestId('code-input');
fireEvent.change(titleInput, { target: { value: '测试广告' } });
fireEvent.change(codeInput, { target: { value: 'test-ad' } });
// Mock creation error
(client.index.$post as any).mockRejectedValue(new Error('Creation failed'));
const submitButton = screen.getByTestId('create-submit-button');
fireEvent.click(submitButton);
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith('Creation failed');
});
});
it('应该处理搜索功能', async () => {
const client = advertisementClientManager.get();
const mockAdvertisements = {
data: [],
pagination: { total: 0, page: 1, pageSize: 10 },
};
(client.index.$get as any).mockResolvedValue(createMockResponse(200, mockAdvertisements));
renderWithProviders();
// Test search
const searchInput = screen.getByTestId('search-input');
fireEvent.change(searchInput, { target: { value: '搜索关键词' } });
const searchButton = screen.getByText('搜索');
fireEvent.click(searchButton);
await waitFor(() => {
expect(client.index.$get).toHaveBeenCalledWith({
query: {
page: 1,
pageSize: 10,
keyword: '搜索关键词',
},
});
});
});
});