profile.test.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  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 ProfilePage from '../../src/pages/profile/index'
  5. // Mock Taro
  6. const mockShowToast = jest.fn()
  7. const mockShowLoading = jest.fn()
  8. const mockHideLoading = jest.fn()
  9. const mockShowModal = jest.fn()
  10. const mockNavigateTo = jest.fn()
  11. const mockReLaunch = jest.fn()
  12. jest.mock('@tarojs/taro', () => ({
  13. showToast: mockShowToast,
  14. showLoading: mockShowLoading,
  15. hideLoading: mockHideLoading,
  16. showModal: mockShowModal,
  17. navigateTo: mockNavigateTo,
  18. reLaunch: mockReLaunch,
  19. }))
  20. // Mock 微信客服API
  21. const mockOpenCustomerServiceChat = jest.fn()
  22. // 更新Taro mock以包含openCustomerServiceChat
  23. jest.mock('@tarojs/taro', () => ({
  24. showToast: mockShowToast,
  25. showLoading: mockShowLoading,
  26. hideLoading: mockHideLoading,
  27. showModal: mockShowModal,
  28. navigateTo: mockNavigateTo,
  29. reLaunch: mockReLaunch,
  30. openCustomerServiceChat: mockOpenCustomerServiceChat,
  31. }))
  32. // Mock TabBarLayout 组件
  33. jest.mock('@/layouts/tab-bar-layout', () => ({
  34. TabBarLayout: jest.fn(({ children, activeKey, className }) => (
  35. <div data-testid="tab-bar-layout" data-active-key={activeKey} className={className}>
  36. {children}
  37. </div>
  38. ))
  39. }))
  40. // Mock Navbar 组件
  41. jest.mock('@/components/ui/navbar', () => ({
  42. Navbar: jest.fn(({ title, rightIcon, onClickRight, leftIcon, backgroundColor, textColor, border }) => (
  43. <div
  44. data-testid="navbar"
  45. data-title={title}
  46. data-right-icon={rightIcon}
  47. data-left-icon={leftIcon}
  48. data-background-color={backgroundColor}
  49. data-text-color={textColor}
  50. data-border={border}
  51. >
  52. <button data-testid="navbar-right-button" onClick={onClickRight}>
  53. {rightIcon}
  54. </button>
  55. <h1>{title}</h1>
  56. </div>
  57. ))
  58. }))
  59. // Mock AvatarUpload 组件
  60. jest.mock('@/components/ui/avatar-upload', () => ({
  61. AvatarUpload: jest.fn(({ currentAvatar, onUploadSuccess, onUploadError, size, editable, className }) => (
  62. <div
  63. data-testid="avatar-upload"
  64. data-current-avatar={currentAvatar}
  65. data-size={size}
  66. data-editable={editable}
  67. className={className}
  68. >
  69. <button
  70. data-testid="avatar-upload-button"
  71. onClick={() => onUploadSuccess({ fileId: 'test-file-id', fullUrl: 'https://example.com/avatar.jpg' })}
  72. >
  73. 上传头像
  74. </button>
  75. <button
  76. data-testid="avatar-upload-error-button"
  77. onClick={() => onUploadError(new Error('Upload failed'))}
  78. >
  79. 上传失败
  80. </button>
  81. </div>
  82. ))
  83. }))
  84. // Mock Button 组件
  85. jest.mock('@/components/ui/button', () => ({
  86. Button: jest.fn(({ children, variant, size, onClick, className }) => (
  87. <button
  88. data-testid="button"
  89. data-variant={variant}
  90. data-size={size}
  91. className={className}
  92. onClick={onClick}
  93. >
  94. {children}
  95. </button>
  96. ))
  97. }))
  98. // Mock useAuth hook
  99. const mockUser = {
  100. id: 1,
  101. username: '测试用户',
  102. avatarFile: {
  103. fullUrl: 'https://example.com/avatar.jpg'
  104. }
  105. }
  106. const mockLogout = jest.fn()
  107. const mockUpdateUser = jest.fn()
  108. jest.mock('@/utils/auth', () => ({
  109. useAuth: jest.fn(() => ({
  110. user: mockUser,
  111. logout: mockLogout,
  112. isLoading: false,
  113. updateUser: mockUpdateUser
  114. }))
  115. }))
  116. // Mock cn utility
  117. jest.mock('@/utils/cn', () => ({
  118. cn: jest.fn((...args) => args.join(' '))
  119. }))
  120. // Mock CSS imports
  121. jest.mock('../../src/pages/profile/index.css', () => ({}))
  122. // 创建测试用的 QueryClient
  123. const createTestQueryClient = () => new QueryClient({
  124. defaultOptions: {
  125. queries: {
  126. retry: false,
  127. },
  128. },
  129. })
  130. // 包装组件
  131. const Wrapper = ({ children }: { children: React.ReactNode }) => {
  132. const queryClient = createTestQueryClient()
  133. return (
  134. <QueryClientProvider client={queryClient}>
  135. {children}
  136. </QueryClientProvider>
  137. )
  138. }
  139. describe('个人中心页面测试', () => {
  140. beforeEach(() => {
  141. jest.clearAllMocks()
  142. // 设置环境变量
  143. process.env.TARO_APP_WX_CORP_ID = 'wwc6d7911e2d23b7fb'
  144. process.env.TARO_APP_WX_KEFU_URL = 'https://work.weixin.qq.com/kfid/kfc5f4d729bc3c893d7'
  145. })
  146. test('应该正确渲染个人中心页面', () => {
  147. render(
  148. <Wrapper>
  149. <ProfilePage />
  150. </Wrapper>
  151. )
  152. // 检查页面标题
  153. expect(screen.getByText('个人中心')).toBeInTheDocument()
  154. // 检查用户信息
  155. expect(screen.getByText('测试用户')).toBeInTheDocument()
  156. expect(screen.getByText('ID: 0001')).toBeInTheDocument()
  157. // 检查功能菜单
  158. expect(screen.getByText('我的服务')).toBeInTheDocument()
  159. expect(screen.getByText('编辑资料')).toBeInTheDocument()
  160. expect(screen.getByText('乘车人管理')).toBeInTheDocument()
  161. expect(screen.getByText('设置')).toBeInTheDocument()
  162. expect(screen.getByText('隐私政策')).toBeInTheDocument()
  163. expect(screen.getByText('帮助与反馈')).toBeInTheDocument()
  164. // 检查客服与帮助区域
  165. expect(screen.getByText('客服与帮助')).toBeInTheDocument()
  166. expect(screen.getByText('联系客服')).toBeInTheDocument()
  167. expect(screen.getByText('7x24小时在线客服')).toBeInTheDocument()
  168. expect(screen.getByText('常见问题')).toBeInTheDocument()
  169. expect(screen.getByText('意见反馈')).toBeInTheDocument()
  170. // 检查版本信息
  171. expect(screen.getByText('去看出行 v1.0.0')).toBeInTheDocument()
  172. })
  173. test('应该处理联系客服功能 - 成功场景', async () => {
  174. mockOpenCustomerServiceChat.mockImplementation((options) => {
  175. options.success()
  176. })
  177. render(
  178. <Wrapper>
  179. <ProfilePage />
  180. </Wrapper>
  181. )
  182. // 点击联系客服按钮
  183. const customerServiceButton = screen.getByText('联系客服')
  184. fireEvent.click(customerServiceButton)
  185. // 检查微信客服API被正确调用
  186. await waitFor(() => {
  187. expect(mockOpenCustomerServiceChat).toHaveBeenCalledWith({
  188. extInfo: {
  189. url: 'https://work.weixin.qq.com/kfid/kfc5f4d729bc3c893d7'
  190. },
  191. corpId: 'wwc6d7911e2d23b7fb',
  192. success: expect.any(Function),
  193. fail: expect.any(Function)
  194. })
  195. })
  196. })
  197. test('应该处理联系客服功能 - 失败场景', async () => {
  198. mockOpenCustomerServiceChat.mockImplementation((options) => {
  199. options.fail({ errMsg: '客服功能不可用' })
  200. })
  201. render(
  202. <Wrapper>
  203. <ProfilePage />
  204. </Wrapper>
  205. )
  206. // 点击联系客服按钮
  207. const customerServiceButton = screen.getByText('联系客服')
  208. fireEvent.click(customerServiceButton)
  209. // 检查错误提示显示
  210. await waitFor(() => {
  211. expect(mockShowToast).toHaveBeenCalledWith({
  212. title: '客服功能暂不可用,请稍后重试',
  213. icon: 'none'
  214. })
  215. })
  216. })
  217. test('应该处理联系客服功能 - 异常场景', async () => {
  218. mockOpenCustomerServiceChat.mockImplementation(() => {
  219. throw new Error('API调用异常')
  220. })
  221. render(
  222. <Wrapper>
  223. <ProfilePage />
  224. </Wrapper>
  225. )
  226. // 点击联系客服按钮
  227. const customerServiceButton = screen.getByText('联系客服')
  228. fireEvent.click(customerServiceButton)
  229. // 检查异常处理
  230. await waitFor(() => {
  231. expect(mockShowToast).toHaveBeenCalledWith({
  232. title: '客服功能异常,请稍后重试',
  233. icon: 'none'
  234. })
  235. })
  236. })
  237. test('应该处理其他功能按钮点击', () => {
  238. render(
  239. <Wrapper>
  240. <ProfilePage />
  241. </Wrapper>
  242. )
  243. // 点击编辑资料按钮
  244. const editProfileButton = screen.getByText('编辑资料')
  245. fireEvent.click(editProfileButton)
  246. expect(mockShowToast).toHaveBeenCalledWith({
  247. title: '功能开发中...',
  248. icon: 'none'
  249. })
  250. // 点击乘车人管理按钮
  251. const passengersButton = screen.getByText('乘车人管理')
  252. fireEvent.click(passengersButton)
  253. expect(mockNavigateTo).toHaveBeenCalledWith({
  254. url: '/pages/passengers/passengers'
  255. })
  256. // 点击设置按钮
  257. const settingsButton = screen.getByText('设置')
  258. fireEvent.click(settingsButton)
  259. expect(mockShowToast).toHaveBeenCalledWith({
  260. title: '功能开发中...',
  261. icon: 'none'
  262. })
  263. // 点击常见问题按钮
  264. const faqButton = screen.getByText('常见问题')
  265. fireEvent.click(faqButton)
  266. expect(mockShowToast).toHaveBeenCalledWith({
  267. title: '常见问题功能开发中...',
  268. icon: 'none'
  269. })
  270. // 点击意见反馈按钮
  271. const feedbackButton = screen.getByText('意见反馈')
  272. fireEvent.click(feedbackButton)
  273. expect(mockShowToast).toHaveBeenCalledWith({
  274. title: '意见反馈功能开发中...',
  275. icon: 'none'
  276. })
  277. })
  278. test('应该处理头像上传功能', async () => {
  279. render(
  280. <Wrapper>
  281. <ProfilePage />
  282. </Wrapper>
  283. )
  284. // 点击头像上传成功按钮
  285. const uploadButton = screen.getByTestId('avatar-upload-button')
  286. fireEvent.click(uploadButton)
  287. // 检查上传成功处理
  288. await waitFor(() => {
  289. expect(mockShowLoading).toHaveBeenCalledWith({ title: '更新头像...' })
  290. expect(mockHideLoading).toHaveBeenCalled()
  291. expect(mockShowToast).toHaveBeenCalledWith({
  292. title: '头像更新成功',
  293. icon: 'success'
  294. })
  295. expect(mockUpdateUser).toHaveBeenCalledWith({
  296. ...mockUser,
  297. avatarFileId: 'test-file-id'
  298. })
  299. })
  300. })
  301. test('应该处理头像上传失败', async () => {
  302. render(
  303. <Wrapper>
  304. <ProfilePage />
  305. </Wrapper>
  306. )
  307. // 点击头像上传失败按钮
  308. const uploadErrorButton = screen.getByTestId('avatar-upload-error-button')
  309. fireEvent.click(uploadErrorButton)
  310. // 检查上传失败处理
  311. await waitFor(() => {
  312. expect(mockShowToast).toHaveBeenCalledWith({
  313. title: '上传失败,请重试',
  314. icon: 'none'
  315. })
  316. })
  317. })
  318. test('应该处理退出登录', async () => {
  319. mockShowModal.mockImplementation((options) => {
  320. options.success({ confirm: true })
  321. })
  322. render(
  323. <Wrapper>
  324. <ProfilePage />
  325. </Wrapper>
  326. )
  327. // 点击退出登录按钮
  328. const logoutButton = screen.getByText('退出登录')
  329. fireEvent.click(logoutButton)
  330. // 检查退出登录流程
  331. await waitFor(() => {
  332. expect(mockShowModal).toHaveBeenCalledWith({
  333. title: '退出登录',
  334. content: '确定要退出登录吗?',
  335. success: expect.any(Function)
  336. })
  337. expect(mockShowLoading).toHaveBeenCalledWith({ title: '退出中...' })
  338. expect(mockLogout).toHaveBeenCalled()
  339. expect(mockHideLoading).toHaveBeenCalled()
  340. expect(mockShowToast).toHaveBeenCalledWith({
  341. title: '已退出登录',
  342. icon: 'success',
  343. duration: 1500
  344. })
  345. })
  346. })
  347. test('应该处理退出登录取消', async () => {
  348. mockShowModal.mockImplementation((options) => {
  349. options.success({ confirm: false })
  350. })
  351. render(
  352. <Wrapper>
  353. <ProfilePage />
  354. </Wrapper>
  355. )
  356. // 点击退出登录按钮
  357. const logoutButton = screen.getByText('退出登录')
  358. fireEvent.click(logoutButton)
  359. // 检查取消退出登录
  360. await waitFor(() => {
  361. expect(mockShowModal).toHaveBeenCalled()
  362. expect(mockLogout).not.toHaveBeenCalled()
  363. })
  364. })
  365. test('应该正确使用TabBarLayout', () => {
  366. render(
  367. <Wrapper>
  368. <ProfilePage />
  369. </Wrapper>
  370. )
  371. // 检查TabBarLayout是否正确使用
  372. const tabBarLayout = screen.getByTestId('tab-bar-layout')
  373. expect(tabBarLayout).toHaveAttribute('data-active-key', 'profile')
  374. })
  375. test('应该正确使用Navbar', () => {
  376. render(
  377. <Wrapper>
  378. <ProfilePage />
  379. </Wrapper>
  380. )
  381. // 检查Navbar是否正确使用
  382. const navbar = screen.getByTestId('navbar')
  383. expect(navbar).toHaveAttribute('data-title', '个人中心')
  384. expect(navbar).toHaveAttribute('data-right-icon', 'i-heroicons-cog-6-tooth-20-solid')
  385. expect(navbar).toHaveAttribute('data-background-color', 'bg-primary')
  386. expect(navbar).toHaveAttribute('data-text-color', 'text-white')
  387. expect(navbar).toHaveAttribute('data-border', 'false')
  388. })
  389. })