|
|
@@ -0,0 +1,470 @@
|
|
|
+import React from 'react';
|
|
|
+import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
+import { render, screen, waitFor } from '@testing-library/react';
|
|
|
+import userEvent from '@testing-library/user-event';
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
+import { UnifiedAdvertisementManagement } from '../../src/components/UnifiedAdvertisementManagement';
|
|
|
+import { unifiedAdvertisementClientManager } from '../../src/api/unifiedAdvertisementClient';
|
|
|
+import { unifiedAdvertisementTypeClientManager } from '../../src/api/unifiedAdvertisementTypeClient';
|
|
|
+
|
|
|
+// Mock RPC 客户端
|
|
|
+vi.mock('../../src/api/unifiedAdvertisementClient', () => ({
|
|
|
+ unifiedAdvertisementClientManager: {
|
|
|
+ get: vi.fn()
|
|
|
+ }
|
|
|
+}));
|
|
|
+
|
|
|
+vi.mock('../../src/api/unifiedAdvertisementTypeClient', () => ({
|
|
|
+ unifiedAdvertisementTypeClientManager: {
|
|
|
+ get: vi.fn()
|
|
|
+ }
|
|
|
+}));
|
|
|
+
|
|
|
+// Mock FileSelector 组件
|
|
|
+vi.mock('@d8d/file-management-ui-mt', () => ({
|
|
|
+ FileSelector: ({ value, onChange }: { value?: number; onChange: (val: number) => void }) => (
|
|
|
+ <div data-testid="file-selector">
|
|
|
+ <button onClick={() => onChange(123)}>选择文件</button>
|
|
|
+ {value && <span data-testid="selected-file-id">{value}</span>}
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}));
|
|
|
+
|
|
|
+// Mock DataTablePagination 组件
|
|
|
+vi.mock('@d8d/shared-ui-components/components/admin/DataTablePagination', () => ({
|
|
|
+ DataTablePagination: ({ currentPage, pageSize, totalCount, onPageChange }: any) => (
|
|
|
+ <div data-testid="pagination">
|
|
|
+ <span>第 {currentPage} 页</span>
|
|
|
+ <span>共 {totalCount} 条</span>
|
|
|
+ <button onClick={() => onPageChange(currentPage + 1, pageSize)}>下一页</button>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}));
|
|
|
+
|
|
|
+const createTestQueryClient = () =>
|
|
|
+ new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: {
|
|
|
+ retry: false,
|
|
|
+ gcTime: 0
|
|
|
+ },
|
|
|
+ mutations: {
|
|
|
+ retry: false
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+const renderWithProviders = (component: React.ReactElement) => {
|
|
|
+ const queryClient = createTestQueryClient();
|
|
|
+ return render(
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
+ {component as any}
|
|
|
+ </QueryClientProvider>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+describe('UnifiedAdvertisementManagement - 广告类型选择器交互测试', () => {
|
|
|
+ const mockGetClient = vi.mocked(unifiedAdvertisementClientManager.get);
|
|
|
+ const mockTypeGetClient = vi.mocked(unifiedAdvertisementTypeClientManager.get);
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ const setupMocks = () => {
|
|
|
+ mockGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [], total: 0, page: 1, pageSize: 10 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $post: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({ code: 201, data: { id: 1 } }),
|
|
|
+ status: 201
|
|
|
+ })
|
|
|
+ },
|
|
|
+ ':id': {
|
|
|
+ $get: vi.fn(),
|
|
|
+ $put: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({ code: 200, data: { id: 1 } }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $delete: vi.fn()
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ mockTypeGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: {
|
|
|
+ list: [
|
|
|
+ { id: 1, name: '首页轮播', code: 'home', status: 1 },
|
|
|
+ { id: 2, name: '分类页广告', code: 'category', status: 1 },
|
|
|
+ { id: 3, name: '详情页广告', code: 'detail', status: 1 }
|
|
|
+ ],
|
|
|
+ total: 3
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+ };
|
|
|
+
|
|
|
+ describe('创建表单中的广告类型选择器', () => {
|
|
|
+ it('应该显示所有可用的广告类型选项', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ setupMocks();
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('create-unified-advertisement-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击类型选择器触发器
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+
|
|
|
+ // 验证所有选项都显示
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('type-selector-item-1')).toHaveTextContent('首页轮播');
|
|
|
+ expect(screen.getByTestId('type-selector-item-2')).toHaveTextContent('分类页广告');
|
|
|
+ expect(screen.getByTestId('type-selector-item-3')).toHaveTextContent('详情页广告');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确选择广告类型', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ setupMocks();
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('create-unified-advertisement-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 打开下拉列表
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+
|
|
|
+ // 选择"分类页广告"
|
|
|
+ await user.click(screen.getByTestId('type-selector-item-2'));
|
|
|
+
|
|
|
+ // 验证选择已生效
|
|
|
+ await waitFor(() => {
|
|
|
+ const selectedValue = screen.getByTestId('type-selector-trigger');
|
|
|
+ expect(selectedValue).toHaveTextContent('分类页广告');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在提交时包含选中的广告类型ID', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ setupMocks();
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('create-unified-advertisement-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 填写表单
|
|
|
+ await user.type(screen.getByTestId('title-input'), '测试广告');
|
|
|
+ await user.type(screen.getByTestId('code-input'), 'test_ad');
|
|
|
+
|
|
|
+ // 选择广告类型
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+ await user.click(screen.getByTestId('type-selector-item-3'));
|
|
|
+
|
|
|
+ // 提交表单
|
|
|
+ await user.click(screen.getByTestId('create-submit-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockGetClient().index.$post).toHaveBeenCalledWith({
|
|
|
+ json: expect.objectContaining({
|
|
|
+ typeId: 3
|
|
|
+ })
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('编辑表单中的广告类型选择器', () => {
|
|
|
+ it('应该预填充当前选中的广告类型', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+
|
|
|
+ const mockAdData = {
|
|
|
+ id: 1,
|
|
|
+ title: '测试广告',
|
|
|
+ code: 'test_ad',
|
|
|
+ typeId: 2,
|
|
|
+ status: 1,
|
|
|
+ sort: 50,
|
|
|
+ url: '',
|
|
|
+ actionType: 0,
|
|
|
+ imageFileId: null,
|
|
|
+ createdAt: '2024-01-01T00:00:00Z',
|
|
|
+ updatedAt: '2024-01-01T00:00:00Z',
|
|
|
+ imageFile: null,
|
|
|
+ advertisementType: { id: 2, name: '分类页广告', code: 'category' }
|
|
|
+ };
|
|
|
+
|
|
|
+ mockGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [mockAdData], total: 1, page: 1, pageSize: 10 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $post: vi.fn()
|
|
|
+ },
|
|
|
+ ':id': {
|
|
|
+ $get: vi.fn(),
|
|
|
+ $put: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({ code: 200, data: { ...mockAdData } }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $delete: vi.fn()
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ mockTypeGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: {
|
|
|
+ list: [
|
|
|
+ { id: 1, name: '首页轮播', code: 'home', status: 1 },
|
|
|
+ { id: 2, name: '分类页广告', code: 'category', status: 1 },
|
|
|
+ { id: 3, name: '详情页广告', code: 'detail', status: 1 }
|
|
|
+ ],
|
|
|
+ total: 3
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('测试广告')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('edit-button-1'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toHaveTextContent('编辑广告');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证广告类型选择器显示当前值
|
|
|
+ await waitFor(() => {
|
|
|
+ const typeSelector = screen.getByTestId('type-selector-trigger');
|
|
|
+ expect(typeSelector).toHaveTextContent('分类页广告');
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该允许修改已选的广告类型', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+
|
|
|
+ const mockAdData = {
|
|
|
+ id: 1,
|
|
|
+ title: '测试广告',
|
|
|
+ code: 'test_ad',
|
|
|
+ typeId: 1,
|
|
|
+ status: 1,
|
|
|
+ sort: 50,
|
|
|
+ url: '',
|
|
|
+ actionType: 0,
|
|
|
+ imageFileId: null,
|
|
|
+ createdAt: '2024-01-01T00:00:00Z',
|
|
|
+ updatedAt: '2024-01-01T00:00:00Z',
|
|
|
+ imageFile: null,
|
|
|
+ advertisementType: { id: 1, name: '首页轮播', code: 'home' }
|
|
|
+ };
|
|
|
+
|
|
|
+ mockGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [mockAdData], total: 1, page: 1, pageSize: 10 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $post: vi.fn()
|
|
|
+ },
|
|
|
+ ':id': {
|
|
|
+ $get: vi.fn(),
|
|
|
+ $put: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({ code: 200, data: { ...mockAdData, typeId: 3 } }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $delete: vi.fn()
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ mockTypeGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: {
|
|
|
+ list: [
|
|
|
+ { id: 1, name: '首页轮播', code: 'home', status: 1 },
|
|
|
+ { id: 2, name: '分类页广告', code: 'category', status: 1 },
|
|
|
+ { id: 3, name: '详情页广告', code: 'detail', status: 1 }
|
|
|
+ ],
|
|
|
+ total: 3
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('测试广告')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('edit-button-1'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toHaveTextContent('编辑广告');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 修改广告类型
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+ await user.click(screen.getByTestId('type-selector-item-3'));
|
|
|
+
|
|
|
+ // 验证选择已更新
|
|
|
+ await waitFor(() => {
|
|
|
+ const typeSelector = screen.getByTestId('type-selector-trigger');
|
|
|
+ expect(typeSelector).toHaveTextContent('详情页广告');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 提交更新
|
|
|
+ await user.click(screen.getByTestId('update-submit-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ const putCall = mockGetClient()[':id'].$put as any;
|
|
|
+ expect(putCall).toHaveBeenCalled();
|
|
|
+ const callArgs = putCall.mock.calls[0][0];
|
|
|
+ expect(callArgs.json.typeId).toBe(3);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('边界条件', () => {
|
|
|
+ it('应该处理空的广告类型列表', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+
|
|
|
+ mockGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [], total: 0, page: 1, pageSize: 10 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $post: vi.fn()
|
|
|
+ },
|
|
|
+ ':id': { $get: vi.fn(), $put: vi.fn(), $delete: vi.fn() }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ mockTypeGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [], total: 0 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('create-unified-advertisement-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击类型选择器应该显示空状态
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+
|
|
|
+ // 验证没有可选选项
|
|
|
+ expect(screen.queryByTestId('type-selector-item-1')).not.toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理禁用状态的广告类型', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+
|
|
|
+ mockGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: { list: [], total: 0, page: 1, pageSize: 10 }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ }),
|
|
|
+ $post: vi.fn()
|
|
|
+ },
|
|
|
+ ':id': { $get: vi.fn(), $put: vi.fn(), $delete: vi.fn() }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ mockTypeGetClient.mockReturnValue({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn().mockResolvedValue({
|
|
|
+ json: async () => ({
|
|
|
+ code: 200,
|
|
|
+ data: {
|
|
|
+ list: [
|
|
|
+ { id: 1, name: '首页轮播', code: 'home', status: 1 },
|
|
|
+ { id: 2, name: '分类页广告', code: 'category', status: 0 } // 禁用
|
|
|
+ ],
|
|
|
+ total: 2
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ status: 200
|
|
|
+ })
|
|
|
+ }
|
|
|
+ } as any);
|
|
|
+
|
|
|
+ renderWithProviders(<UnifiedAdvertisementManagement />);
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('create-unified-advertisement-button'));
|
|
|
+
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('modal-title')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ await user.click(screen.getByTestId('type-selector-trigger'));
|
|
|
+
|
|
|
+ // 验证只显示启用状态的广告类型
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByTestId('type-selector-item-1')).toHaveTextContent('首页轮播');
|
|
|
+ // 禁用的广告类型可能不会显示在列表中
|
|
|
+ });
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|