order-management.integration.test.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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. },
  88. ],
  89. pagination: {
  90. total: 1,
  91. page: 1,
  92. pageSize: 10,
  93. },
  94. };
  95. // Mock initial order list
  96. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  97. renderWithProviders(<OrderManagement />);
  98. // Wait for initial data to load
  99. await waitFor(() => {
  100. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  101. expect(screen.getByText('testuser')).toBeInTheDocument();
  102. expect(screen.getByText('张三')).toBeInTheDocument();
  103. // Use getAllByText for amount since there are multiple elements with the same text
  104. const amountElements = screen.getAllByText('¥100.00');
  105. expect(amountElements.length).toBeGreaterThan(0);
  106. });
  107. // Verify API was called with correct parameters
  108. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  109. query: {
  110. page: 1,
  111. pageSize: 10,
  112. keyword: '',
  113. filters: undefined,
  114. },
  115. });
  116. });
  117. it('应该处理订单搜索功能', async () => {
  118. const mockOrders = {
  119. data: [],
  120. pagination: { total: 0, page: 1, pageSize: 10 },
  121. };
  122. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  123. renderWithProviders(<OrderManagement />);
  124. // Wait for search elements to be available (search area is always available even during loading)
  125. await waitFor(() => {
  126. expect(screen.getByTestId('order-search-input')).toBeInTheDocument();
  127. expect(screen.getByTestId('order-search-button')).toBeInTheDocument();
  128. });
  129. // Test search
  130. const searchInput = screen.getByTestId('order-search-input');
  131. fireEvent.change(searchInput, { target: { value: 'ORDER001' } });
  132. const searchButton = screen.getByTestId('order-search-button');
  133. fireEvent.click(searchButton);
  134. await waitFor(() => {
  135. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  136. query: {
  137. page: 1,
  138. pageSize: 10,
  139. keyword: 'ORDER001',
  140. filters: undefined,
  141. },
  142. });
  143. });
  144. });
  145. it('应该处理订单状态过滤', async () => {
  146. const mockOrders = {
  147. data: [],
  148. pagination: { total: 0, page: 1, pageSize: 10 },
  149. };
  150. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  151. renderWithProviders(<OrderManagement />);
  152. // Wait for component to load
  153. await waitFor(() => {
  154. expect(screen.getByTestId('order-status-select')).toBeInTheDocument();
  155. });
  156. // Test status filter
  157. const statusSelect = screen.getByTestId('order-status-select');
  158. fireEvent.click(statusSelect);
  159. const statusOption = screen.getByText('已发货');
  160. fireEvent.click(statusOption);
  161. await waitFor(() => {
  162. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  163. query: {
  164. page: 1,
  165. pageSize: 10,
  166. keyword: '',
  167. filters: expect.stringContaining('"state":1'),
  168. },
  169. });
  170. });
  171. });
  172. it('应该处理支付状态过滤', async () => {
  173. const mockOrders = {
  174. data: [],
  175. pagination: { total: 0, page: 1, pageSize: 10 },
  176. };
  177. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  178. renderWithProviders(<OrderManagement />);
  179. // Wait for component to load
  180. await waitFor(() => {
  181. expect(screen.getByTestId('order-pay-status-select')).toBeInTheDocument();
  182. });
  183. // Test pay status filter
  184. const payStatusSelect = screen.getByTestId('order-pay-status-select');
  185. fireEvent.click(payStatusSelect);
  186. const payStatusOption = screen.getByText('支付成功');
  187. fireEvent.click(payStatusOption);
  188. await waitFor(() => {
  189. expect(adminOrderClient.$get).toHaveBeenCalledWith({
  190. query: {
  191. page: 1,
  192. pageSize: 10,
  193. keyword: '',
  194. filters: expect.stringContaining('"payState":2'),
  195. },
  196. });
  197. });
  198. });
  199. it('应该处理订单编辑功能', async () => {
  200. const { toast } = await import('sonner');
  201. const mockOrders = {
  202. data: [
  203. {
  204. id: 1,
  205. orderNo: 'ORDER001',
  206. state: 0,
  207. payState: 2,
  208. remark: '原始备注',
  209. user: { username: 'testuser' },
  210. userPhone: '13800138000',
  211. recevierName: '张三',
  212. receiverMobile: '13800138001',
  213. amount: 100.00,
  214. payAmount: 100.00,
  215. freightAmount: 0.00,
  216. orderType: 1,
  217. payType: 1,
  218. address: '北京市朝阳区',
  219. createdAt: '2024-01-01T00:00:00Z',
  220. },
  221. ],
  222. pagination: {
  223. total: 1,
  224. page: 1,
  225. pageSize: 10,
  226. },
  227. };
  228. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  229. renderWithProviders(<OrderManagement />);
  230. // Wait for data to load
  231. await waitFor(() => {
  232. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  233. });
  234. // Click edit button using test ID
  235. const editButtons = screen.getAllByTestId('order-edit-button');
  236. fireEvent.click(editButtons[0]);
  237. // Wait for edit modal to open and form elements to be available
  238. await waitFor(() => {
  239. expect(screen.getByText('编辑订单')).toBeInTheDocument();
  240. expect(screen.getByTestId('edit-order-status-select')).toBeInTheDocument();
  241. expect(screen.getByTestId('edit-remark-textarea')).toBeInTheDocument();
  242. }, { timeout: 5000 });
  243. // Change order status
  244. const statusSelect = screen.getByTestId('edit-order-status-select');
  245. fireEvent.click(statusSelect);
  246. // Use getAllByText and select the first option
  247. const newStatusOptions = screen.getAllByText('已发货');
  248. fireEvent.click(newStatusOptions[0]);
  249. // Change remark
  250. const remarkTextarea = screen.getByTestId('edit-remark-textarea');
  251. fireEvent.change(remarkTextarea, { target: { value: '更新后的备注' } });
  252. // Mock successful update
  253. (adminOrderClient[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
  254. // Submit form using test ID
  255. const saveButton = screen.getByTestId('order-save-button');
  256. fireEvent.click(saveButton);
  257. await waitFor(() => {
  258. expect(adminOrderClient[':id']['$put']).toHaveBeenCalledWith({
  259. param: { id: '1' },
  260. json: {
  261. state: 0,
  262. payState: 2,
  263. remark: '更新后的备注',
  264. },
  265. });
  266. expect(toast.success).toHaveBeenCalledWith('订单更新成功');
  267. });
  268. });
  269. it('应该处理订单详情查看', async () => {
  270. const mockOrders = {
  271. data: [
  272. {
  273. id: 1,
  274. orderNo: 'ORDER001',
  275. state: 0,
  276. payState: 2,
  277. remark: '测试订单',
  278. user: { username: 'testuser' },
  279. userPhone: '13800138000',
  280. recevierName: '张三',
  281. receiverMobile: '13800138001',
  282. amount: 100.00,
  283. payAmount: 100.00,
  284. freightAmount: 0.00,
  285. orderType: 1,
  286. payType: 1,
  287. address: '北京市朝阳区',
  288. createdAt: '2024-01-01T00:00:00Z',
  289. },
  290. ],
  291. pagination: {
  292. total: 1,
  293. page: 1,
  294. pageSize: 10,
  295. },
  296. };
  297. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  298. renderWithProviders(<OrderManagement />);
  299. // Wait for data to load
  300. await waitFor(() => {
  301. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  302. });
  303. // Click view details button using test ID
  304. const viewButtons = screen.getAllByTestId('order-view-button');
  305. fireEvent.click(viewButtons[0]);
  306. // Wait for detail modal to open
  307. await waitFor(() => {
  308. expect(screen.getByText('订单详情')).toBeInTheDocument();
  309. }, { timeout: 5000 });
  310. // Check modal content
  311. expect(screen.getByText('订单详情')).toBeInTheDocument();
  312. // Use getAllByText for ORDER001 since there are multiple elements with the same text
  313. const orderNoElements = screen.getAllByText('ORDER001');
  314. expect(orderNoElements.length).toBeGreaterThan(0);
  315. // Use getAllByText for 实物订单 since there are multiple elements with the same text
  316. const orderTypeElements = screen.getAllByText('实物订单');
  317. expect(orderTypeElements.length).toBeGreaterThan(0);
  318. // Use getAllByText for amount since there are multiple elements with the same text
  319. const amountElements = screen.getAllByText('¥100.00');
  320. expect(amountElements.length).toBeGreaterThan(0);
  321. expect(screen.getByText('测试订单')).toBeInTheDocument();
  322. });
  323. it('应该优雅处理API错误', async () => {
  324. const { toast } = await import('sonner');
  325. // Mock API error
  326. (adminOrderClient.$get as any).mockRejectedValue(new Error('API Error'));
  327. renderWithProviders(<OrderManagement />);
  328. // Should handle error without crashing
  329. await waitFor(() => {
  330. expect(screen.getByText('订单管理')).toBeInTheDocument();
  331. });
  332. // Test edit error scenario
  333. const mockOrders = {
  334. data: [
  335. {
  336. id: 1,
  337. orderNo: 'ORDER001',
  338. state: 0,
  339. payState: 2,
  340. remark: '测试订单',
  341. user: { username: 'testuser' },
  342. userPhone: '13800138000',
  343. recevierName: '张三',
  344. receiverMobile: '13800138001',
  345. amount: 100.00,
  346. payAmount: 100.00,
  347. freightAmount: 0.00,
  348. orderType: 1,
  349. payType: 1,
  350. address: '北京市朝阳区',
  351. createdAt: '2024-01-01T00:00:00Z',
  352. },
  353. ],
  354. pagination: {
  355. total: 1,
  356. page: 1,
  357. pageSize: 10,
  358. },
  359. };
  360. // Mock successful data load
  361. (adminOrderClient.$get as any).mockResolvedValue(createMockResponse(200, mockOrders));
  362. // Re-render with data
  363. renderWithProviders(<OrderManagement />);
  364. await waitFor(() => {
  365. expect(screen.getByText('ORDER001')).toBeInTheDocument();
  366. });
  367. // Click edit button using test ID
  368. const editButtons = screen.getAllByTestId('order-edit-button');
  369. fireEvent.click(editButtons[0]);
  370. await waitFor(() => {
  371. expect(screen.getByText('编辑订单')).toBeInTheDocument();
  372. });
  373. // Mock update error
  374. (adminOrderClient[':id']['$put'] as any).mockRejectedValue(new Error('Update failed'));
  375. // Submit form using test ID
  376. const saveButton = screen.getByTestId('order-save-button');
  377. fireEvent.click(saveButton);
  378. await waitFor(() => {
  379. expect(toast.error).toHaveBeenCalledWith('更新失败,请重试');
  380. });
  381. });
  382. });