advertisement-management.integration.test.tsx 10 KB

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