order-page.test.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. /**
  2. * 订单页面组件测试
  3. */
  4. import React from 'react'
  5. import { render, screen, fireEvent, waitFor } from '@testing-library/react'
  6. import '@testing-library/jest-dom'
  7. import OrderPage from '@/pages/order/index'
  8. // Mock Taro相关API
  9. const mockNavigateTo = jest.fn()
  10. const mockShowToast = jest.fn()
  11. const mockUseRouter = jest.fn()
  12. jest.mock('@tarojs/taro', () => ({
  13. useRouter: () => mockUseRouter(),
  14. navigateTo: mockNavigateTo,
  15. showToast: mockShowToast,
  16. requestPayment: jest.fn(),
  17. getSystemInfoSync: () => ({
  18. statusBarHeight: 20
  19. }),
  20. getMenuButtonBoundingClientRect: () => ({
  21. width: 87,
  22. height: 32,
  23. top: 48,
  24. right: 314,
  25. bottom: 80,
  26. left: 227
  27. }),
  28. navigateBack: jest.fn()
  29. }))
  30. // Mock React Query
  31. const mockUseQuery = jest.fn()
  32. const mockUseMutation = jest.fn()
  33. jest.mock('@tanstack/react-query', () => ({
  34. useQuery: (options: any) => mockUseQuery(options),
  35. useMutation: (options: any) => mockUseMutation(options)
  36. }))
  37. // Mock cn工具函数
  38. jest.mock('@/utils/cn', () => ({
  39. cn: (...inputs: any[]) => inputs.join(' ')
  40. }))
  41. // Mock platform工具
  42. jest.mock('@/utils/platform', () => ({
  43. isWeapp: () => false
  44. }))
  45. // Mock navbar组件
  46. // jest.mock('@/components/ui/navbar', () => ({
  47. // Navbar: ({ children }: any) => <div data-testid="navbar">{children}</div>,
  48. // NavbarPresets: {
  49. // primary: {
  50. // backgroundColor: 'bg-primary-600',
  51. // textColor: 'text-white'
  52. // }
  53. // }
  54. // }))
  55. // Mock API客户端
  56. jest.mock('@/api', () => ({
  57. orderClient: {
  58. $post: jest.fn()
  59. },
  60. paymentClient: {
  61. $post: jest.fn()
  62. },
  63. routeClient: {
  64. ':id': {
  65. $get: jest.fn()
  66. }
  67. },
  68. passengerClient: {
  69. $get: jest.fn()
  70. }
  71. }))
  72. describe('OrderPage', () => {
  73. const mockRouteData = {
  74. id: 1,
  75. name: '测试路线',
  76. pickupPoint: '上车地点',
  77. dropoffPoint: '下车地点',
  78. departureTime: '2025-10-24 10:00:00',
  79. price: 100,
  80. vehicleType: '商务车',
  81. travelMode: 'charter',
  82. availableSeats: 10
  83. }
  84. const mockPassengers = [
  85. {
  86. id: 1,
  87. name: '张三',
  88. idType: '身份证',
  89. idNumber: '110101199001011234',
  90. phone: '13800138000'
  91. }
  92. ]
  93. beforeEach(() => {
  94. mockUseRouter.mockReturnValue({
  95. params: {
  96. routeId: '1',
  97. activityName: '测试活动',
  98. type: 'business-charter'
  99. }
  100. })
  101. mockUseQuery.mockImplementation((options) => {
  102. if (options.queryKey?.[0] === 'route') {
  103. return {
  104. data: mockRouteData,
  105. isLoading: false
  106. }
  107. }
  108. if (options.queryKey?.[0] === 'passengers') {
  109. return {
  110. data: mockPassengers,
  111. isLoading: false
  112. }
  113. }
  114. return { data: null, isLoading: false }
  115. })
  116. mockUseMutation.mockImplementation((options) => ({
  117. mutateAsync: options.mutationFn,
  118. isPending: false
  119. }))
  120. mockNavigateTo.mockClear()
  121. mockShowToast.mockClear()
  122. })
  123. it('should render order page correctly', () => {
  124. render(<OrderPage />)
  125. expect(screen.getByTestId('order-navbar')).toBeInTheDocument()
  126. expect(screen.getByTestId('activity-name')).toHaveTextContent('测试活动')
  127. expect(screen.getByTestId('service-type')).toHaveTextContent('包车服务')
  128. expect(screen.getByTestId('price-per-unit')).toHaveTextContent('¥100/车')
  129. })
  130. it('should show loading state', () => {
  131. mockUseQuery.mockImplementation((options) => {
  132. if (options.queryKey?.[0] === 'route') {
  133. return { data: null, isLoading: true }
  134. }
  135. return { data: null, isLoading: false }
  136. })
  137. render(<OrderPage />)
  138. expect(screen.getByText('加载中...')).toBeInTheDocument()
  139. })
  140. it('should handle phone number acquisition', async () => {
  141. render(<OrderPage />)
  142. const getPhoneButton = screen.getByTestId('get-phone-button')
  143. expect(getPhoneButton).toBeInTheDocument()
  144. // 这里可以模拟获取手机号的交互
  145. // 由于Taro API的限制,实际测试可能需要更复杂的模拟
  146. })
  147. it('should handle passenger selection', async () => {
  148. render(<OrderPage />)
  149. const addPassengerButton = screen.getByTestId('add-passenger-button')
  150. fireEvent.click(addPassengerButton)
  151. // 应该显示乘客选择器
  152. await waitFor(() => {
  153. expect(screen.getByText('选择乘车人')).toBeInTheDocument()
  154. })
  155. // 选择乘客
  156. const passengerCard = screen.getByText('张三')
  157. fireEvent.click(passengerCard)
  158. // 应该显示乘客已添加的提示
  159. await waitFor(() => {
  160. expect(mockShowToast).toHaveBeenCalledWith({
  161. title: '乘客添加成功',
  162. icon: 'success',
  163. duration: 1500
  164. })
  165. })
  166. })
  167. it('should validate payment prerequisites', async () => {
  168. const { mutateAsync: createOrderMutation } = mockUseMutation()
  169. render(<OrderPage />)
  170. const payButton = screen.getByTestId('pay-button')
  171. fireEvent.click(payButton)
  172. // 应该显示需要获取手机号的提示
  173. await waitFor(() => {
  174. expect(mockShowToast).toHaveBeenCalledWith({
  175. title: '请先获取手机号',
  176. icon: 'none',
  177. duration: 2000
  178. })
  179. })
  180. // 应该显示需要添加乘车人的提示
  181. // 这里需要模拟已获取手机号但未添加乘客的情况
  182. })
  183. it('should handle successful payment flow', async () => {
  184. // Mock成功的订单创建
  185. const mockOrderResponse = { id: 123 }
  186. const mockPaymentResponse = {
  187. timeStamp: '1234567890',
  188. nonceStr: 'abcdefghijklmnopqrstuvwxyz',
  189. package: 'prepay_id=wx1234567890',
  190. signType: 'RSA',
  191. paySign: 'abcdefghijklmnopqrstuvwxyz1234567890'
  192. }
  193. mockUseMutation.mockImplementation((options) => ({
  194. mutateAsync: async (data: any) => {
  195. if (options.mutationFn === expect.any(Function)) {
  196. if (data.routeId) {
  197. // 订单创建
  198. return mockOrderResponse
  199. } else if (data.orderId) {
  200. // 支付创建
  201. return mockPaymentResponse
  202. }
  203. }
  204. return null
  205. },
  206. isPending: false
  207. }))
  208. // Mock成功的微信支付
  209. const mockRequestPayment = require('@tarojs/taro').requestPayment
  210. mockRequestPayment.mockResolvedValue({})
  211. render(<OrderPage />)
  212. // 这里需要模拟已获取手机号和添加乘客的状态
  213. // 由于状态管理的复杂性,这个测试可能需要更详细的设置
  214. const payButton = screen.getByTestId('pay-button')
  215. fireEvent.click(payButton)
  216. // 应该调用订单创建和支付创建
  217. await waitFor(() => {
  218. expect(mockRequestPayment).toHaveBeenCalled()
  219. })
  220. // 应该跳转到支付成功页面
  221. await waitFor(() => {
  222. expect(mockNavigateTo).toHaveBeenCalledWith({
  223. url: '/pages/pay-success/index?orderId=123&totalPrice=100&passengerCount=0'
  224. })
  225. })
  226. })
  227. it('should handle payment failure', async () => {
  228. // Mock失败的订单创建
  229. mockUseMutation.mockImplementation((options) => ({
  230. mutateAsync: async () => {
  231. throw new Error('支付创建失败')
  232. },
  233. isPending: false
  234. }))
  235. render(<OrderPage />)
  236. const payButton = screen.getByTestId('pay-button')
  237. fireEvent.click(payButton)
  238. // 应该显示支付失败的提示
  239. await waitFor(() => {
  240. expect(mockShowToast).toHaveBeenCalledWith({
  241. title: '支付失败,请重试',
  242. icon: 'error',
  243. duration: 2000
  244. })
  245. })
  246. })
  247. it('should handle user cancellation', async () => {
  248. // Mock用户取消支付
  249. const mockRequestPayment = require('@tarojs/taro').requestPayment
  250. mockRequestPayment.mockRejectedValue({
  251. errMsg: 'requestPayment:fail cancel'
  252. })
  253. render(<OrderPage />)
  254. const payButton = screen.getByTestId('pay-button')
  255. fireEvent.click(payButton)
  256. // 应该显示支付已取消的提示
  257. await waitFor(() => {
  258. expect(mockShowToast).toHaveBeenCalledWith({
  259. title: '支付已取消',
  260. icon: 'none',
  261. duration: 2000
  262. })
  263. })
  264. })
  265. it('should calculate total price correctly', () => {
  266. render(<OrderPage />)
  267. // 检查总价计算
  268. // 包车模式下应该显示固定价格
  269. expect(screen.getByTestId('total-price')).toHaveTextContent('¥100')
  270. })
  271. it('should validate seat availability', async () => {
  272. // 测试拼车模式的座位验证
  273. mockUseRouter.mockReturnValue({
  274. params: {
  275. routeId: '1',
  276. activityName: '测试活动',
  277. type: 'carpool' // 拼车模式
  278. }
  279. })
  280. render(<OrderPage />)
  281. // 这里需要模拟超过座位数量的乘客添加
  282. // 然后测试支付时的验证逻辑
  283. })
  284. })