order-management.integration.test.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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 { OrderManagement } from '../../src/components/OrderManagement';
  5. import { adminOrderClient } from '../../src/api/orderClient';
  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/orderClient', () => {
  26. const mockAdminOrderClient = {
  27. $get: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  28. ':id': {
  29. $put: vi.fn(() => Promise.resolve({ status: 200, body: null })),
  30. },
  31. };
  32. const mockOrderClientManager = {
  33. getAdminOrderClient: vi.fn(() => mockAdminOrderClient),
  34. };
  35. return {
  36. orderClientManager: mockOrderClientManager,
  37. adminOrderClient: mockAdminOrderClient,
  38. };
  39. });
  40. // Mock toast
  41. vi.mock('sonner', () => ({
  42. toast: {
  43. success: vi.fn(() => {}),
  44. error: vi.fn(() => {}),
  45. },
  46. }));
  47. const createTestQueryClient = () =>
  48. new QueryClient({
  49. defaultOptions: {
  50. queries: {
  51. retry: false,
  52. },
  53. },
  54. });
  55. const renderWithProviders = (component: React.ReactElement) => {
  56. const queryClient = createTestQueryClient();
  57. return render(
  58. <QueryClientProvider client={queryClient}>
  59. {component as any}
  60. </QueryClientProvider>
  61. );
  62. };
  63. describe('订单管理集成测试', () => {
  64. beforeEach(() => {
  65. vi.clearAllMocks();
  66. });
  67. it('应该加载订单列表并显示数据', async () => {
  68. const mockOrders = {
  69. data: [
  70. {
  71. id: 1,
  72. orderNo: 'ORDER001',
  73. user: { username: 'testuser' },
  74. userPhone: '13800138000',
  75. recevierName: '张三',
  76. receiverMobile: '13800138001',
  77. amount: 100.00,
  78. payAmount: 100.00,
  79. freightAmount: 0.00,
  80. state: 0,
  81. payState: 2,
  82. orderType: 1,
  83. payType: 1,
  84. address: '北京市朝阳区',
  85. remark: '测试订单',
  86. createdAt: '2024-01-01T00:00:00Z',
  87. orderGoods: [
  88. {
  89. id: 1,
  90. goodsName: '测试商品1',
  91. price: 50.00,
  92. num: 2,
  93. specification: '红色/L',
  94. imageFile: {
  95. fullUrl: 'https://example.com/image1.jpg'
  96. }
  97. },
  98. {
  99. id: 2,
  100. goodsName: '测试商品2',
  101. price: 25.00,
  102. num: 1,
  103. specification: '蓝色/M',
  104. imageFile: {
  105. fullUrl: 'https://example.com/image2.jpg'
  106. }
  107. }
  108. ]
  109. },
  110. ],
  111. pagination: {
  112. total: 1,
  113. page: 1,
  114. pageSize: 10,
  115. },
  116. };
  117. // Mock initial order list
  118. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  119. renderWithProviders(<OrderManagement />);
  120. // Wait for initial data to load
  121. await waitFor(() => {
  122. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  123. expect(screen.getByText('testuser')).toBeInTheDocument();
  124. expect(screen.getByText('张三')).toBeInTheDocument();
  125. // Use getAllByText for amount since there are multiple elements with the same text
  126. const amountElements = screen.getAllByText('¥100.00');
  127. expect(amountElements.length).toBeGreaterThan(0);
  128. });
  129. // Verify API was called with correct parameters
  130. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  131. query: {
  132. page: 1,
  133. pageSize: 10,
  134. keyword: '',
  135. filters: undefined,
  136. },
  137. });
  138. });
  139. it('应该处理订单搜索功能', async () => {
  140. const mockOrders = {
  141. data: [],
  142. pagination: { total: 0, page: 1, pageSize: 10 },
  143. };
  144. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  145. renderWithProviders(<OrderManagement />);
  146. // Wait for search elements to be available (search area is always available even during loading)
  147. await waitFor(() => {
  148. expect(screen.getByTestId('order-search-input')).toBeInTheDocument();
  149. expect(screen.getByTestId('order-search-button')).toBeInTheDocument();
  150. });
  151. // Test search
  152. const searchInput = screen.getByTestId('order-search-input');
  153. fireEvent.change(searchInput, { target: { value: 'ORDER001' } });
  154. const searchButton = screen.getByTestId('order-search-button');
  155. fireEvent.click(searchButton);
  156. await waitFor(() => {
  157. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  158. query: {
  159. page: 1,
  160. pageSize: 10,
  161. keyword: 'ORDER001',
  162. filters: undefined,
  163. },
  164. });
  165. });
  166. });
  167. it('应该处理订单状态过滤', async () => {
  168. const mockOrders = {
  169. data: [],
  170. pagination: { total: 0, page: 1, pageSize: 10 },
  171. };
  172. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  173. renderWithProviders(<OrderManagement />);
  174. // Wait for component to load
  175. await waitFor(() => {
  176. expect(screen.getByTestId('order-status-select')).toBeInTheDocument();
  177. });
  178. // Test status filter
  179. const statusSelect = screen.getByTestId('order-status-select');
  180. fireEvent.click(statusSelect);
  181. const statusOption = screen.getByText('已发货');
  182. fireEvent.click(statusOption);
  183. await waitFor(() => {
  184. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  185. query: {
  186. page: 1,
  187. pageSize: 10,
  188. keyword: '',
  189. filters: expect.stringContaining('"state":1'),
  190. },
  191. });
  192. });
  193. });
  194. it('应该处理支付状态过滤', async () => {
  195. const mockOrders = {
  196. data: [],
  197. pagination: { total: 0, page: 1, pageSize: 10 },
  198. };
  199. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  200. renderWithProviders(<OrderManagement />);
  201. // Wait for component to load
  202. await waitFor(() => {
  203. expect(screen.getByTestId('order-pay-status-select')).toBeInTheDocument();
  204. });
  205. // Test pay status filter
  206. const payStatusSelect = screen.getByTestId('order-pay-status-select');
  207. fireEvent.click(payStatusSelect);
  208. const payStatusOption = screen.getByText('支付成功');
  209. fireEvent.click(payStatusOption);
  210. await waitFor(() => {
  211. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  212. query: {
  213. page: 1,
  214. pageSize: 10,
  215. keyword: '',
  216. filters: expect.stringContaining('"payState":2'),
  217. },
  218. });
  219. });
  220. });
  221. it('应该处理订单编辑功能', async () => {
  222. const { toast } = await import('sonner');
  223. const mockOrders = {
  224. data: [
  225. {
  226. id: 1,
  227. orderNo: 'ORDER001',
  228. state: 0,
  229. payState: 2,
  230. remark: '原始备注',
  231. user: { username: 'testuser' },
  232. userPhone: '13800138000',
  233. recevierName: '张三',
  234. receiverMobile: '13800138001',
  235. amount: 100.00,
  236. payAmount: 100.00,
  237. freightAmount: 0.00,
  238. orderType: 1,
  239. payType: 1,
  240. address: '北京市朝阳区',
  241. createdAt: '2024-01-01T00:00:00Z',
  242. },
  243. ],
  244. pagination: {
  245. total: 1,
  246. page: 1,
  247. pageSize: 10,
  248. },
  249. };
  250. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  251. renderWithProviders(<OrderManagement />);
  252. // Wait for data to load
  253. await waitFor(() => {
  254. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  255. });
  256. // Click edit button using test ID
  257. const editButtons = screen.getAllByTestId('order-edit-button');
  258. fireEvent.click(editButtons[0]);
  259. // Wait for edit modal to open and form elements to be available
  260. await waitFor(() => {
  261. expect(screen.getByText('编辑订单')).toBeInTheDocument();
  262. expect(screen.getByTestId('edit-order-status-select')).toBeInTheDocument();
  263. expect(screen.getByTestId('edit-remark-textarea')).toBeInTheDocument();
  264. }, { timeout: 5000 });
  265. // Change order status
  266. const statusSelect = screen.getByTestId('edit-order-status-select');
  267. fireEvent.click(statusSelect);
  268. // Use getAllByText and select the first option
  269. const newStatusOptions = screen.getAllByText('已发货');
  270. fireEvent.click(newStatusOptions[0]);
  271. // Change remark
  272. const remarkTextarea = screen.getByTestId('edit-remark-textarea');
  273. fireEvent.change(remarkTextarea, { target: { value: '更新后的备注' } });
  274. // Mock successful update
  275. (adminOrderClient[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
  276. // Submit form using test ID
  277. const saveButton = screen.getByTestId('order-save-button');
  278. fireEvent.click(saveButton);
  279. await waitFor(() => {
  280. expect(adminOrderClient[':id']['$put']).toHaveBeenCalledWith({
  281. param: { id: '1' },
  282. json: {
  283. state: 0,
  284. payState: 2,
  285. remark: '更新后的备注',
  286. },
  287. });
  288. expect(toast.success).toHaveBeenCalledWith('订单更新成功');
  289. });
  290. });
  291. it('应该处理订单详情查看', async () => {
  292. const mockOrders = {
  293. data: [
  294. {
  295. id: 1,
  296. orderNo: 'ORDER001',
  297. state: 0,
  298. payState: 2,
  299. remark: '测试订单',
  300. user: { username: 'testuser' },
  301. userPhone: '13800138000',
  302. recevierName: '张三',
  303. receiverMobile: '13800138001',
  304. amount: 100.00,
  305. payAmount: 100.00,
  306. freightAmount: 0.00,
  307. orderType: 1,
  308. payType: 1,
  309. address: '北京市朝阳区',
  310. createdAt: '2024-01-01T00:00:00Z',
  311. },
  312. ],
  313. pagination: {
  314. total: 1,
  315. page: 1,
  316. pageSize: 10,
  317. },
  318. };
  319. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  320. renderWithProviders(<OrderManagement />);
  321. // Wait for data to load
  322. await waitFor(() => {
  323. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  324. });
  325. // Click view details button using test ID
  326. const viewButtons = screen.getAllByTestId('order-view-button');
  327. fireEvent.click(viewButtons[0]);
  328. // Wait for detail modal to open
  329. await waitFor(() => {
  330. expect(screen.getByText('订单详情')).toBeInTheDocument();
  331. }, { timeout: 5000 });
  332. // Check modal content
  333. expect(screen.getByText('订单详情')).toBeInTheDocument();
  334. // Use getAllByText for ORDER001 since there are multiple elements with the same text
  335. const orderNoElements = screen.getAllByText('ORDER001');
  336. expect(orderNoElements.length).toBeGreaterThan(0);
  337. // Use getAllByText for 实物订单 since there are multiple elements with the same text
  338. const orderTypeElements = screen.getAllByText('实物订单');
  339. expect(orderTypeElements.length).toBeGreaterThan(0);
  340. // Use getAllByText for amount since there are multiple elements with the same text
  341. const amountElements = screen.getAllByText('¥100.00');
  342. expect(amountElements.length).toBeGreaterThan(0);
  343. expect(screen.getByText('测试订单')).toBeInTheDocument();
  344. });
  345. it('应该优雅处理API错误', async () => {
  346. const { toast } = await import('sonner');
  347. // Mock API error
  348. (adminOrderClient.$get as any).mockRejectedValue(new Error('API Error'));
  349. renderWithProviders(<OrderManagement />);
  350. // Should handle error without crashing
  351. await waitFor(() => {
  352. expect(screen.getByText('订单管理')).toBeInTheDocument();
  353. });
  354. // Test edit error scenario
  355. const mockOrders = {
  356. data: [
  357. {
  358. id: 1,
  359. orderNo: 'ORDER001',
  360. state: 0,
  361. payState: 2,
  362. remark: '测试订单',
  363. user: { username: 'testuser' },
  364. userPhone: '13800138000',
  365. recevierName: '张三',
  366. receiverMobile: '13800138001',
  367. amount: 100.00,
  368. payAmount: 100.00,
  369. freightAmount: 0.00,
  370. orderType: 1,
  371. payType: 1,
  372. address: '北京市朝阳区',
  373. createdAt: '2024-01-01T00:00:00Z',
  374. },
  375. ],
  376. pagination: {
  377. total: 1,
  378. page: 1,
  379. pageSize: 10,
  380. },
  381. };
  382. // Mock successful data load
  383. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  384. // Re-render with data
  385. renderWithProviders(<OrderManagement />);
  386. await waitFor(() => {
  387. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  388. });
  389. // Click edit button using test ID
  390. const editButtons = screen.getAllByTestId('order-edit-button');
  391. fireEvent.click(editButtons[0]);
  392. await waitFor(() => {
  393. expect(screen.getByText('编辑订单')).toBeInTheDocument();
  394. });
  395. // Mock update error
  396. (adminOrderClient[':id']['$put'] as any).mockRejectedValue(new Error('Update failed'));
  397. // Submit form using test ID
  398. const saveButton = screen.getByTestId('order-save-button');
  399. fireEvent.click(saveButton);
  400. await waitFor(() => {
  401. expect(toast.error).toHaveBeenCalledWith('更新失败,请重试');
  402. });
  403. });
  404. });