advertisement-management.integration.test.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. import { describe, it, expect, vi, beforeEach } from 'vitest';
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react';
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
  4. import { AdvertisementManagement } from '../../src/components/AdvertisementManagement';
  5. import { getAdvertisementClient } from '../../src/api/advertisementClient';
  6. // 完整的mock响应对象
  7. const createMockResponse = (status: number, data?: any) => ({
  8. status,
  9. ok: status >= 200 && status < 300,
  10. body: null,
  11. bodyUsed: false,
  12. statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
  13. headers: new Headers(),
  14. url: '',
  15. redirected: false,
  16. type: 'basic' as ResponseType,
  17. json: async () => data || {},
  18. text: async () => '',
  19. blob: async () => new Blob(),
  20. arrayBuffer: async () => new ArrayBuffer(0),
  21. formData: async () => new FormData(),
  22. clone: function() { return this; }
  23. });
  24. // Mock API client
  25. vi.mock('../../src/api/advertisementClient', () => {
  26. const mockAdvertisementClient = {
  27. $get: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  28. $post: vi.fn(() => Promise.resolve({ status: 201, body: null })),
  29. ':id': {
  30. $put: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  31. $delete: vi.fn(() => Promise.resolve({ status: 204, body: null })),
  32. },
  33. };
  34. const mockAdvertisementClientManager = {
  35. get: vi.fn(() => mockAdvertisementClient),
  36. };
  37. return {
  38. advertisementClientManager: mockAdvertisementClientManager,
  39. getAdvertisementClient: vi.fn(() => mockAdvertisementClient),
  40. };
  41. });
  42. // Mock toast
  43. vi.mock('sonner', () => ({
  44. toast: {
  45. success: vi.fn(() => {}),
  46. error: vi.fn(() => {}),
  47. },
  48. }));
  49. // Mock FileSelector
  50. vi.mock('@d8d/file-management-ui', () => ({
  51. FileSelector: ({ value, onChange, ...props }: any) => (
  52. <div data-testid="file-selector">
  53. <input
  54. type="number"
  55. value={value || ''}
  56. onChange={(e) => onChange?.(parseInt(e.target.value))}
  57. data-testid="file-selector-input"
  58. {...props}
  59. />
  60. </div>
  61. ),
  62. }));
  63. // Mock AdvertisementTypeSelector
  64. vi.mock('@d8d/advertisement-type-management-ui', () => ({
  65. AdvertisementTypeSelector: ({ value, onChange, ...props }: any) => (
  66. <div data-testid="advertisement-type-selector">
  67. <select
  68. value={value?.toString() || ''}
  69. onChange={(e) => onChange?.(parseInt(e.target.value))}
  70. data-testid="type-selector"
  71. {...props}
  72. >
  73. <option value="1">首页轮播</option>
  74. <option value="2">侧边栏广告</option>
  75. </select>
  76. </div>
  77. ),
  78. }));
  79. const createTestQueryClient = () =>
  80. new QueryClient({
  81. defaultOptions: {
  82. queries: {
  83. retry: false,
  84. },
  85. },
  86. });
  87. const renderWithProviders = (component: React.ReactElement) => {
  88. const queryClient = createTestQueryClient();
  89. return render(
  90. <QueryClientProvider client={queryClient}>
  91. {component as any}
  92. </QueryClientProvider>
  93. );
  94. };
  95. describe('广告管理集成测试', () => {
  96. beforeEach(() => {
  97. vi.clearAllMocks();
  98. });
  99. it('应该完成完整的广告CRUD流程', async () => {
  100. const mockAdvertisements = {
  101. data: [
  102. {
  103. id: 1,
  104. title: '测试广告',
  105. code: 'test-ad',
  106. typeId: 1,
  107. advertisementType: { id: 1, name: '首页轮播' },
  108. url: 'https://example.com',
  109. imageFileId: 1,
  110. imageFile: { id: 1, fullUrl: 'https://example.com/image.jpg' },
  111. sort: 1,
  112. status: 1,
  113. actionType: 1,
  114. createdAt: '2024-01-01T00:00:00Z',
  115. },
  116. ],
  117. pagination: {
  118. total: 1,
  119. page: 1,
  120. pageSize: 10,
  121. },
  122. };
  123. const { toast } = await import('sonner');
  124. // Mock initial advertisement list
  125. const client = getAdvertisementClient();
  126. (client.$get as any).mockResolvedValue(createMockResponse(200, mockAdvertisements));
  127. renderWithProviders(<AdvertisementManagement />);
  128. // Wait for initial data to load
  129. await waitFor(() => {
  130. expect(screen.getByText('测试广告')).toBeInTheDocument();
  131. });
  132. // Test create advertisement
  133. const createButton = screen.getByText('创建广告');
  134. fireEvent.click(createButton);
  135. // Fill create form
  136. const titleInput = screen.getByTestId('title-input');
  137. const codeInput = screen.getByTestId('code-input');
  138. const typeSelector = screen.getByTestId('type-selector');
  139. fireEvent.change(titleInput, { target: { value: '新广告' } });
  140. fireEvent.change(codeInput, { target: { value: 'new-ad' } });
  141. fireEvent.change(typeSelector, { target: { value: '1' } });
  142. // Mock successful creation
  143. (client.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, title: '新广告' }));
  144. const submitButton = screen.getByTestId('create-submit-button');
  145. fireEvent.click(submitButton);
  146. await waitFor(() => {
  147. expect(client.$post).toHaveBeenCalledWith({
  148. json: {
  149. title: '新广告',
  150. code: 'new-ad',
  151. typeId: 1,
  152. url: '',
  153. imageFileId: undefined,
  154. sort: 0,
  155. status: 1,
  156. actionType: 1
  157. },
  158. });
  159. expect(toast.success).toHaveBeenCalledWith('广告创建成功');
  160. });
  161. // Test edit advertisement
  162. const editButton = screen.getByTestId('edit-button-1');
  163. fireEvent.click(editButton);
  164. // Verify edit form is populated
  165. await waitFor(() => {
  166. expect(screen.getByDisplayValue('测试广告')).toBeInTheDocument();
  167. });
  168. // Update advertisement
  169. const updateTitleInput = screen.getByDisplayValue('测试广告');
  170. fireEvent.change(updateTitleInput, { target: { value: '更新后的广告' } });
  171. // Mock successful update
  172. (client[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
  173. const updateButton = screen.getByTestId('update-submit-button');
  174. fireEvent.click(updateButton);
  175. await waitFor(() => {
  176. expect(client[':id']['$put']).toHaveBeenCalledWith({
  177. param: { id: 1 },
  178. json: {
  179. title: '更新后的广告',
  180. typeId: 1,
  181. code: 'test-ad',
  182. url: 'https://example.com',
  183. imageFileId: 1,
  184. sort: 1,
  185. status: 1,
  186. actionType: 1
  187. },
  188. });
  189. expect(toast.success).toHaveBeenCalledWith('广告更新成功');
  190. });
  191. // Test delete advertisement
  192. const deleteButton = screen.getByTestId('delete-button-1');
  193. fireEvent.click(deleteButton);
  194. // Confirm deletion
  195. expect(screen.getByText('确认删除')).toBeInTheDocument();
  196. // Mock successful deletion
  197. (client[':id']['$delete'] as any).mockResolvedValue({
  198. status: 204,
  199. });
  200. const confirmDeleteButton = screen.getByTestId('confirm-delete-button');
  201. fireEvent.click(confirmDeleteButton);
  202. await waitFor(() => {
  203. expect(client[':id']['$delete']).toHaveBeenCalledWith({
  204. param: { id: 1 },
  205. });
  206. expect(toast.success).toHaveBeenCalledWith('广告删除成功');
  207. });
  208. });
  209. it('应该优雅处理API错误', async () => {
  210. const client = getAdvertisementClient();
  211. const { toast } = await import('sonner');
  212. // Mock API error
  213. (client.$get as any).mockRejectedValue(new Error('API Error'));
  214. renderWithProviders(<AdvertisementManagement />);
  215. // Should handle error without crashing
  216. await waitFor(() => {
  217. expect(screen.getByText('广告管理')).toBeInTheDocument();
  218. });
  219. // Test create advertisement error
  220. const createButton = screen.getByText('创建广告');
  221. fireEvent.click(createButton);
  222. const titleInput = screen.getByTestId('title-input');
  223. const codeInput = screen.getByTestId('code-input');
  224. fireEvent.change(titleInput, { target: { value: '测试广告' } });
  225. fireEvent.change(codeInput, { target: { value: 'test-ad' } });
  226. // Mock creation error
  227. (client.$post as any).mockRejectedValue(new Error('Creation failed'));
  228. const submitButton = screen.getByTestId('create-submit-button');
  229. fireEvent.click(submitButton);
  230. await waitFor(() => {
  231. expect(toast.error).toHaveBeenCalledWith('创建广告失败');
  232. });
  233. });
  234. it('应该处理搜索功能', async () => {
  235. const client = getAdvertisementClient();
  236. const mockAdvertisements = {
  237. data: [],
  238. pagination: { total: 0, page: 1, pageSize: 10 },
  239. };
  240. (client.$get as any).mockResolvedValue(createMockResponse(200, mockAdvertisements));
  241. renderWithProviders(<AdvertisementManagement />);
  242. // Test search
  243. const searchInput = screen.getByTestId('search-input');
  244. fireEvent.change(searchInput, { target: { value: '搜索关键词' } });
  245. const searchButton = screen.getByText('搜索');
  246. fireEvent.click(searchButton);
  247. await waitFor(() => {
  248. expect(client.$get).toHaveBeenCalledWith({
  249. query: {
  250. page: 1,
  251. pageSize: 10,
  252. keyword: '搜索关键词',
  253. },
  254. });
  255. });
  256. });
  257. });