ActivitySelectPage.test.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. import React from 'react'
  2. import { render, screen, fireEvent, waitFor } from '@testing-library/react'
  3. import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
  4. import ActivitySelectPage from '../../src/pages/select-activity/ActivitySelectPage'
  5. // 导入 Taro mock 函数
  6. import taroMock, { mockUseRouter, mockNavigateBack } from '../__mocks__/taroMock'
  7. // Mock API 客户端
  8. let mockRouteClient: any
  9. jest.mock('../../src/api', () => {
  10. mockRouteClient = {
  11. search: {
  12. $get: jest.fn()
  13. }
  14. }
  15. return {
  16. routeClient: mockRouteClient
  17. }
  18. })
  19. // Mock Navbar 组件
  20. jest.mock('@/components/ui/navbar', () => ({
  21. Navbar: jest.fn(({ title, leftIcon, onClickLeft, ...props }) => (
  22. <div data-testid="navbar">
  23. <div data-testid="navbar-title">{title}</div>
  24. <button data-testid="navbar-back" onClick={onClickLeft}>返回</button>
  25. </div>
  26. )),
  27. NavbarPresets: {
  28. primary: {}
  29. }
  30. }))
  31. // 创建测试用的 QueryClient
  32. const createTestQueryClient = () => new QueryClient({
  33. defaultOptions: {
  34. queries: {
  35. retry: false,
  36. },
  37. },
  38. })
  39. // 包装组件
  40. const Wrapper = ({ children }: { children: React.ReactNode }) => {
  41. const queryClient = createTestQueryClient()
  42. return (
  43. <QueryClientProvider client={queryClient}>
  44. {children}
  45. </QueryClientProvider>
  46. )
  47. }
  48. // 模拟活动数据
  49. const mockActivities = [
  50. {
  51. id: 1,
  52. name: '音乐节活动',
  53. description: '大型音乐节活动',
  54. startDate: '2025-11-01T10:00:00Z',
  55. endDate: '2025-11-01T22:00:00Z',
  56. venueLocationId: 1,
  57. venueLocation: {
  58. id: 1,
  59. name: '北京工人体育场',
  60. provinceId: 1,
  61. cityId: 11,
  62. districtId: 101,
  63. address: '北京市朝阳区工人体育场北路',
  64. latitude: '39.929985',
  65. longitude: '116.395645',
  66. province: {
  67. id: 1,
  68. name: '北京市',
  69. level: 1,
  70. code: '110000'
  71. },
  72. city: {
  73. id: 11,
  74. name: '北京市',
  75. level: 2,
  76. code: '110100'
  77. },
  78. district: {
  79. id: 101,
  80. name: '朝阳区',
  81. level: 3,
  82. code: '110105'
  83. }
  84. }
  85. },
  86. {
  87. id: 2,
  88. name: '体育赛事',
  89. description: '足球比赛',
  90. startDate: '2025-11-02T15:00:00Z',
  91. endDate: '2025-11-02T17:00:00Z',
  92. venueLocationId: 2,
  93. venueLocation: {
  94. id: 2,
  95. name: '上海体育场',
  96. provinceId: 2,
  97. cityId: 22,
  98. districtId: 202,
  99. address: '上海市徐汇区天钥桥路666号',
  100. latitude: '31.230416',
  101. longitude: '121.473701',
  102. province: {
  103. id: 2,
  104. name: '上海市',
  105. level: 1,
  106. code: '310000'
  107. },
  108. city: {
  109. id: 22,
  110. name: '上海市',
  111. level: 2,
  112. code: '310100'
  113. },
  114. district: {
  115. id: 202,
  116. name: '徐汇区',
  117. level: 3,
  118. code: '310104'
  119. }
  120. }
  121. }
  122. ]
  123. // 模拟路线数据
  124. const mockRoutes = [
  125. {
  126. id: 1,
  127. activityId: 1,
  128. routeType: 'departure',
  129. startLocation: {
  130. id: 1,
  131. name: '北京南站',
  132. provinceId: 1,
  133. cityId: 11,
  134. districtId: 101,
  135. province: { id: 1, name: '北京市' },
  136. city: { id: 11, name: '北京市' },
  137. district: { id: 101, name: '朝阳区' }
  138. },
  139. endLocation: {
  140. id: 2,
  141. name: '上海虹桥站',
  142. provinceId: 2,
  143. cityId: 22,
  144. districtId: 202,
  145. province: { id: 2, name: '上海市' },
  146. city: { id: 22, name: '上海市' },
  147. district: { id: 202, name: '徐汇区' }
  148. }
  149. },
  150. {
  151. id: 2,
  152. activityId: 2,
  153. routeType: 'return',
  154. startLocation: {
  155. id: 2,
  156. name: '上海虹桥站',
  157. provinceId: 2,
  158. cityId: 22,
  159. districtId: 202,
  160. province: { id: 2, name: '上海市' },
  161. city: { id: 22, name: '上海市' },
  162. district: { id: 202, name: '徐汇区' }
  163. },
  164. endLocation: {
  165. id: 1,
  166. name: '北京南站',
  167. provinceId: 1,
  168. cityId: 11,
  169. districtId: 101,
  170. province: { id: 1, name: '北京市' },
  171. city: { id: 11, name: '北京市' },
  172. district: { id: 101, name: '朝阳区' }
  173. }
  174. }
  175. ]
  176. describe('活动选择页面测试', () => {
  177. beforeEach(() => {
  178. jest.clearAllMocks()
  179. // 设置默认的路由参数
  180. mockUseRouter.mockReturnValue({
  181. params: {
  182. startAreaIds: JSON.stringify([1, 11, 101]),
  183. endAreaIds: JSON.stringify([2, 22, 202]),
  184. startAreaName: encodeURIComponent('北京市 北京市 朝阳区'),
  185. endAreaName: encodeURIComponent('上海市 上海市 徐汇区'),
  186. date: '2025-11-01',
  187. vehicleType: 'bus',
  188. travelMode: 'carpool'
  189. }
  190. })
  191. // 设置默认的API响应
  192. mockRouteClient.search.$get.mockResolvedValue({
  193. status: 200,
  194. json: async () => ({
  195. success: true,
  196. data: {
  197. routes: mockRoutes,
  198. activities: mockActivities
  199. }
  200. })
  201. })
  202. })
  203. test('应该正确渲染活动选择页面', async () => {
  204. render(
  205. <Wrapper>
  206. <ActivitySelectPage />
  207. </Wrapper>
  208. )
  209. // 检查导航栏
  210. await waitFor(() => {
  211. expect(screen.getByTestId('navbar')).toBeInTheDocument()
  212. expect(screen.getByTestId('navbar-title')).toHaveTextContent('选择活动')
  213. })
  214. // 检查头部信息
  215. expect(screen.getByText('北京市 北京市 朝阳区 → 上海市 上海市 徐汇区')).toBeInTheDocument()
  216. expect(screen.getByTestId('header-date')).toHaveTextContent('2025-11-01')
  217. // 检查页面标题
  218. expect(screen.getByText('选择观看活动')).toBeInTheDocument()
  219. expect(screen.getByText('系统已根据您选择的地区自动匹配相关活动')).toBeInTheDocument()
  220. // 检查去程活动区域
  221. expect(screen.getByText('去程活动')).toBeInTheDocument()
  222. expect(screen.getByText('前往上海市 上海市 徐汇区观看活动')).toBeInTheDocument()
  223. // 检查活动名称是否正确分配到对应的区域
  224. expect(screen.getByTestId('departure-activity-name-1')).toHaveTextContent('音乐节活动')
  225. expect(screen.getByTestId('return-activity-name-2')).toHaveTextContent('体育赛事')
  226. // 检查返程活动区域
  227. expect(screen.getByText('返程活动')).toBeInTheDocument()
  228. expect(screen.getByText('从北京市 北京市 朝阳区观看活动后返回')).toBeInTheDocument()
  229. })
  230. test('应该正确显示活动列表', async () => {
  231. render(
  232. <Wrapper>
  233. <ActivitySelectPage />
  234. </Wrapper>
  235. )
  236. // 等待数据加载完成
  237. await waitFor(() => {
  238. expect(screen.getByText('音乐节活动')).toBeInTheDocument()
  239. expect(screen.getByText('体育赛事')).toBeInTheDocument()
  240. })
  241. // 检查活动信息完整显示
  242. expect(screen.getByText('朝阳区 · 北京市 · 北京市')).toBeInTheDocument()
  243. expect(screen.getByText('北京市朝阳区工人体育场北路')).toBeInTheDocument()
  244. expect(screen.getByText('到达:上海市 上海市 徐汇区')).toBeInTheDocument()
  245. // 检查活动图片占位符已被注释掉,不显示
  246. expect(screen.queryByText('活动图片')).not.toBeInTheDocument()
  247. })
  248. test('应该正确处理活动选择', async () => {
  249. render(
  250. <Wrapper>
  251. <ActivitySelectPage />
  252. </Wrapper>
  253. )
  254. // 等待数据加载完成
  255. await waitFor(() => {
  256. expect(screen.getByText('音乐节活动')).toBeInTheDocument()
  257. })
  258. // 点击去程活动
  259. const activityItem = screen.getByTestId('departure-activity-1')
  260. fireEvent.click(activityItem)
  261. // 检查导航被调用
  262. expect(taroMock.navigateTo).toHaveBeenCalledWith({
  263. url: expect.stringContaining('pages/schedule-list/ScheduleListPage')
  264. })
  265. // 检查导航参数正确
  266. const navigateCall = taroMock.navigateTo.mock.calls[0][0]
  267. expect(navigateCall.url).toContain('startAreaIds=[1,11,101]')
  268. expect(navigateCall.url).toContain('endAreaIds=[2,22,202]')
  269. expect(navigateCall.url).toContain('date=2025-11-01')
  270. expect(navigateCall.url).toContain('vehicleType=bus')
  271. expect(navigateCall.url).toContain('travelMode=carpool')
  272. expect(navigateCall.url).toContain('activityId=1')
  273. expect(navigateCall.url).toContain('routeType=departure')
  274. })
  275. test('应该处理返程活动选择', async () => {
  276. render(
  277. <Wrapper>
  278. <ActivitySelectPage />
  279. </Wrapper>
  280. )
  281. // 等待数据加载完成
  282. await waitFor(() => {
  283. expect(screen.getByText('体育赛事')).toBeInTheDocument()
  284. })
  285. // 点击返程活动
  286. const returnActivity = screen.getByTestId('return-activity-2')
  287. fireEvent.click(returnActivity)
  288. // 检查导航被调用
  289. expect(taroMock.navigateTo).toHaveBeenCalledWith({
  290. url: expect.stringContaining('pages/schedule-list/ScheduleListPage')
  291. })
  292. // 检查导航参数正确
  293. const navigateCall = taroMock.navigateTo.mock.calls[0][0]
  294. expect(navigateCall.url).toContain('routeType=return')
  295. expect(navigateCall.url).toContain('activityId=2')
  296. })
  297. test('应该处理无活动的情况', async () => {
  298. // 模拟无活动数据
  299. mockRouteClient.search.$get.mockResolvedValue({
  300. status: 200,
  301. json: async () => ({
  302. success: true,
  303. data: {
  304. routes: [],
  305. activities: []
  306. }
  307. })
  308. })
  309. render(
  310. <Wrapper>
  311. <ActivitySelectPage />
  312. </Wrapper>
  313. )
  314. // 检查无活动状态显示
  315. await waitFor(() => {
  316. expect(screen.getByText('暂无去程活动')).toBeInTheDocument()
  317. expect(screen.getByText('上海市 上海市 徐汇区当前没有相关活动')).toBeInTheDocument()
  318. expect(screen.getByText('暂无返程活动')).toBeInTheDocument()
  319. expect(screen.getByText('北京市 北京市 朝阳区当前没有相关活动')).toBeInTheDocument()
  320. })
  321. })
  322. test('应该处理加载状态', () => {
  323. // 模拟API延迟
  324. mockRouteClient.search.$get.mockImplementation(() => new Promise(() => {}))
  325. render(
  326. <Wrapper>
  327. <ActivitySelectPage />
  328. </Wrapper>
  329. )
  330. // 检查加载状态显示
  331. expect(screen.getByText('正在加载活动')).toBeInTheDocument()
  332. expect(screen.getByText('请稍候...')).toBeInTheDocument()
  333. })
  334. test('应该处理API错误', async () => {
  335. // 模拟API错误
  336. mockRouteClient.search.$get.mockRejectedValue(new Error('网络错误'))
  337. render(
  338. <Wrapper>
  339. <ActivitySelectPage />
  340. </Wrapper>
  341. )
  342. // 检查无活动状态显示(因为API错误导致无数据)
  343. await waitFor(() => {
  344. expect(screen.getByText('暂无去程活动')).toBeInTheDocument()
  345. expect(screen.getByText('暂无返程活动')).toBeInTheDocument()
  346. })
  347. })
  348. test('应该验证布局优化效果', async () => {
  349. render(
  350. <Wrapper>
  351. <ActivitySelectPage />
  352. </Wrapper>
  353. )
  354. // 等待数据加载完成
  355. await waitFor(() => {
  356. expect(screen.getByText('音乐节活动')).toBeInTheDocument()
  357. })
  358. // 验证图片占位符已被注释掉,不显示
  359. expect(screen.queryByText('活动图片')).not.toBeInTheDocument()
  360. // 验证活动信息完整显示
  361. expect(screen.getByText('音乐节活动')).toBeInTheDocument()
  362. expect(screen.getByText('朝阳区 · 北京市 · 北京市')).toBeInTheDocument()
  363. expect(screen.getByText('北京市朝阳区工人体育场北路')).toBeInTheDocument()
  364. expect(screen.getByText('到达:上海市 上海市 徐汇区')).toBeInTheDocument()
  365. // 验证头部日期显示正确
  366. expect(screen.getByTestId('header-date')).toHaveTextContent('2025-11-01')
  367. })
  368. test('应该处理返回按钮', async () => {
  369. render(
  370. <Wrapper>
  371. <ActivitySelectPage />
  372. </Wrapper>
  373. )
  374. // 等待数据加载完成
  375. await waitFor(() => {
  376. expect(screen.getByTestId('navbar')).toBeInTheDocument()
  377. })
  378. // 点击返回按钮
  379. const backButton = screen.getByTestId('navbar-back')
  380. fireEvent.click(backButton)
  381. // 检查返回导航被调用
  382. expect(mockNavigateBack).toHaveBeenCalled()
  383. })
  384. test('应该处理不同的路由参数', async () => {
  385. // 设置不同的路由参数
  386. mockUseRouter.mockReturnValue({
  387. params: {
  388. startAreaIds: '[3,33,303]',
  389. endAreaIds: '[4,44,404]',
  390. startAreaName: encodeURIComponent('广州市 广东省 天河区'),
  391. endAreaName: encodeURIComponent('深圳市 广东省 福田区'),
  392. date: '2025-11-02',
  393. vehicleType: 'business',
  394. travelMode: 'charter'
  395. }
  396. })
  397. render(
  398. <Wrapper>
  399. <ActivitySelectPage />
  400. </Wrapper>
  401. )
  402. // 检查头部信息显示正确的地区
  403. await waitFor(() => {
  404. expect(screen.getByText('广州市 广东省 天河区 → 深圳市 广东省 福田区')).toBeInTheDocument()
  405. expect(screen.getByTestId('header-date')).toHaveTextContent('2025-11-02')
  406. })
  407. })
  408. })