|
|
@@ -0,0 +1,448 @@
|
|
|
+import React from 'react'
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
|
+import ProfilePage from '../../src/pages/profile/index'
|
|
|
+
|
|
|
+// Mock Taro
|
|
|
+const mockShowToast = jest.fn()
|
|
|
+const mockShowLoading = jest.fn()
|
|
|
+const mockHideLoading = jest.fn()
|
|
|
+const mockShowModal = jest.fn()
|
|
|
+const mockNavigateTo = jest.fn()
|
|
|
+const mockReLaunch = jest.fn()
|
|
|
+
|
|
|
+jest.mock('@tarojs/taro', () => ({
|
|
|
+ showToast: mockShowToast,
|
|
|
+ showLoading: mockShowLoading,
|
|
|
+ hideLoading: mockHideLoading,
|
|
|
+ showModal: mockShowModal,
|
|
|
+ navigateTo: mockNavigateTo,
|
|
|
+ reLaunch: mockReLaunch,
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock 微信客服API
|
|
|
+const mockOpenCustomerServiceChat = jest.fn()
|
|
|
+
|
|
|
+// 更新Taro mock以包含openCustomerServiceChat
|
|
|
+jest.mock('@tarojs/taro', () => ({
|
|
|
+ showToast: mockShowToast,
|
|
|
+ showLoading: mockShowLoading,
|
|
|
+ hideLoading: mockHideLoading,
|
|
|
+ showModal: mockShowModal,
|
|
|
+ navigateTo: mockNavigateTo,
|
|
|
+ reLaunch: mockReLaunch,
|
|
|
+ openCustomerServiceChat: mockOpenCustomerServiceChat,
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock TabBarLayout 组件
|
|
|
+jest.mock('@/layouts/tab-bar-layout', () => ({
|
|
|
+ TabBarLayout: jest.fn(({ children, activeKey, className }) => (
|
|
|
+ <div data-testid="tab-bar-layout" data-active-key={activeKey} className={className}>
|
|
|
+ {children}
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock Navbar 组件
|
|
|
+jest.mock('@/components/ui/navbar', () => ({
|
|
|
+ Navbar: jest.fn(({ title, rightIcon, onClickRight, leftIcon, backgroundColor, textColor, border }) => (
|
|
|
+ <div
|
|
|
+ data-testid="navbar"
|
|
|
+ data-title={title}
|
|
|
+ data-right-icon={rightIcon}
|
|
|
+ data-left-icon={leftIcon}
|
|
|
+ data-background-color={backgroundColor}
|
|
|
+ data-text-color={textColor}
|
|
|
+ data-border={border}
|
|
|
+ >
|
|
|
+ <button data-testid="navbar-right-button" onClick={onClickRight}>
|
|
|
+ {rightIcon}
|
|
|
+ </button>
|
|
|
+ <h1>{title}</h1>
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock AvatarUpload 组件
|
|
|
+jest.mock('@/components/ui/avatar-upload', () => ({
|
|
|
+ AvatarUpload: jest.fn(({ currentAvatar, onUploadSuccess, onUploadError, size, editable, className }) => (
|
|
|
+ <div
|
|
|
+ data-testid="avatar-upload"
|
|
|
+ data-current-avatar={currentAvatar}
|
|
|
+ data-size={size}
|
|
|
+ data-editable={editable}
|
|
|
+ className={className}
|
|
|
+ >
|
|
|
+ <button
|
|
|
+ data-testid="avatar-upload-button"
|
|
|
+ onClick={() => onUploadSuccess({ fileId: 'test-file-id', fullUrl: 'https://example.com/avatar.jpg' })}
|
|
|
+ >
|
|
|
+ 上传头像
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ data-testid="avatar-upload-error-button"
|
|
|
+ onClick={() => onUploadError(new Error('Upload failed'))}
|
|
|
+ >
|
|
|
+ 上传失败
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock Button 组件
|
|
|
+jest.mock('@/components/ui/button', () => ({
|
|
|
+ Button: jest.fn(({ children, variant, size, onClick, className }) => (
|
|
|
+ <button
|
|
|
+ data-testid="button"
|
|
|
+ data-variant={variant}
|
|
|
+ data-size={size}
|
|
|
+ className={className}
|
|
|
+ onClick={onClick}
|
|
|
+ >
|
|
|
+ {children}
|
|
|
+ </button>
|
|
|
+ ))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock useAuth hook
|
|
|
+const mockUser = {
|
|
|
+ id: 1,
|
|
|
+ username: '测试用户',
|
|
|
+ avatarFile: {
|
|
|
+ fullUrl: 'https://example.com/avatar.jpg'
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const mockLogout = jest.fn()
|
|
|
+const mockUpdateUser = jest.fn()
|
|
|
+
|
|
|
+jest.mock('@/utils/auth', () => ({
|
|
|
+ useAuth: jest.fn(() => ({
|
|
|
+ user: mockUser,
|
|
|
+ logout: mockLogout,
|
|
|
+ isLoading: false,
|
|
|
+ updateUser: mockUpdateUser
|
|
|
+ }))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock cn utility
|
|
|
+jest.mock('@/utils/cn', () => ({
|
|
|
+ cn: jest.fn((...args) => args.join(' '))
|
|
|
+}))
|
|
|
+
|
|
|
+// Mock CSS imports
|
|
|
+jest.mock('../../src/pages/profile/index.css', () => ({}))
|
|
|
+
|
|
|
+// 创建测试用的 QueryClient
|
|
|
+const createTestQueryClient = () => new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: {
|
|
|
+ retry: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+})
|
|
|
+
|
|
|
+// 包装组件
|
|
|
+const Wrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
+ const queryClient = createTestQueryClient()
|
|
|
+ return (
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
+ {children}
|
|
|
+ </QueryClientProvider>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+describe('个人中心页面测试', () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ jest.clearAllMocks()
|
|
|
+
|
|
|
+ // 设置环境变量
|
|
|
+ process.env.TARO_APP_WX_CORP_ID = 'wwc6d7911e2d23b7fb'
|
|
|
+ process.env.TARO_APP_WX_KEFU_URL = 'https://work.weixin.qq.com/kfid/kfc5f4d729bc3c893d7'
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该正确渲染个人中心页面', () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 检查页面标题
|
|
|
+ expect(screen.getByText('个人中心')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 检查用户信息
|
|
|
+ expect(screen.getByText('测试用户')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('ID: 0001')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 检查功能菜单
|
|
|
+ expect(screen.getByText('我的服务')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('编辑资料')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('乘车人管理')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('设置')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('隐私政策')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('帮助与反馈')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 检查客服与帮助区域
|
|
|
+ expect(screen.getByText('客服与帮助')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('联系客服')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('7x24小时在线客服')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('常见问题')).toBeInTheDocument()
|
|
|
+ expect(screen.getByText('意见反馈')).toBeInTheDocument()
|
|
|
+
|
|
|
+ // 检查版本信息
|
|
|
+ expect(screen.getByText('去看出行 v1.0.0')).toBeInTheDocument()
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理联系客服功能 - 成功场景', async () => {
|
|
|
+ mockOpenCustomerServiceChat.mockImplementation((options) => {
|
|
|
+ options.success()
|
|
|
+ })
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击联系客服按钮
|
|
|
+ const customerServiceButton = screen.getByText('联系客服')
|
|
|
+ fireEvent.click(customerServiceButton)
|
|
|
+
|
|
|
+ // 检查微信客服API被正确调用
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockOpenCustomerServiceChat).toHaveBeenCalledWith({
|
|
|
+ extInfo: {
|
|
|
+ url: 'https://work.weixin.qq.com/kfid/kfc5f4d729bc3c893d7'
|
|
|
+ },
|
|
|
+ corpId: 'wwc6d7911e2d23b7fb',
|
|
|
+ success: expect.any(Function),
|
|
|
+ fail: expect.any(Function)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理联系客服功能 - 失败场景', async () => {
|
|
|
+ mockOpenCustomerServiceChat.mockImplementation((options) => {
|
|
|
+ options.fail({ errMsg: '客服功能不可用' })
|
|
|
+ })
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击联系客服按钮
|
|
|
+ const customerServiceButton = screen.getByText('联系客服')
|
|
|
+ fireEvent.click(customerServiceButton)
|
|
|
+
|
|
|
+ // 检查错误提示显示
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '客服功能暂不可用,请稍后重试',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理联系客服功能 - 异常场景', async () => {
|
|
|
+ mockOpenCustomerServiceChat.mockImplementation(() => {
|
|
|
+ throw new Error('API调用异常')
|
|
|
+ })
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击联系客服按钮
|
|
|
+ const customerServiceButton = screen.getByText('联系客服')
|
|
|
+ fireEvent.click(customerServiceButton)
|
|
|
+
|
|
|
+ // 检查异常处理
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '客服功能异常,请稍后重试',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理其他功能按钮点击', () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击编辑资料按钮
|
|
|
+ const editProfileButton = screen.getByText('编辑资料')
|
|
|
+ fireEvent.click(editProfileButton)
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '功能开发中...',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击乘车人管理按钮
|
|
|
+ const passengersButton = screen.getByText('乘车人管理')
|
|
|
+ fireEvent.click(passengersButton)
|
|
|
+ expect(mockNavigateTo).toHaveBeenCalledWith({
|
|
|
+ url: '/pages/passengers/passengers'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击设置按钮
|
|
|
+ const settingsButton = screen.getByText('设置')
|
|
|
+ fireEvent.click(settingsButton)
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '功能开发中...',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击常见问题按钮
|
|
|
+ const faqButton = screen.getByText('常见问题')
|
|
|
+ fireEvent.click(faqButton)
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '常见问题功能开发中...',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+
|
|
|
+ // 点击意见反馈按钮
|
|
|
+ const feedbackButton = screen.getByText('意见反馈')
|
|
|
+ fireEvent.click(feedbackButton)
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '意见反馈功能开发中...',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理头像上传功能', async () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击头像上传成功按钮
|
|
|
+ const uploadButton = screen.getByTestId('avatar-upload-button')
|
|
|
+ fireEvent.click(uploadButton)
|
|
|
+
|
|
|
+ // 检查上传成功处理
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowLoading).toHaveBeenCalledWith({ title: '更新头像...' })
|
|
|
+ expect(mockHideLoading).toHaveBeenCalled()
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '头像更新成功',
|
|
|
+ icon: 'success'
|
|
|
+ })
|
|
|
+ expect(mockUpdateUser).toHaveBeenCalledWith({
|
|
|
+ ...mockUser,
|
|
|
+ avatarFileId: 'test-file-id'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理头像上传失败', async () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击头像上传失败按钮
|
|
|
+ const uploadErrorButton = screen.getByTestId('avatar-upload-error-button')
|
|
|
+ fireEvent.click(uploadErrorButton)
|
|
|
+
|
|
|
+ // 检查上传失败处理
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '上传失败,请重试',
|
|
|
+ icon: 'none'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理退出登录', async () => {
|
|
|
+ mockShowModal.mockImplementation((options) => {
|
|
|
+ options.success({ confirm: true })
|
|
|
+ })
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击退出登录按钮
|
|
|
+ const logoutButton = screen.getByText('退出登录')
|
|
|
+ fireEvent.click(logoutButton)
|
|
|
+
|
|
|
+ // 检查退出登录流程
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowModal).toHaveBeenCalledWith({
|
|
|
+ title: '退出登录',
|
|
|
+ content: '确定要退出登录吗?',
|
|
|
+ success: expect.any(Function)
|
|
|
+ })
|
|
|
+ expect(mockShowLoading).toHaveBeenCalledWith({ title: '退出中...' })
|
|
|
+ expect(mockLogout).toHaveBeenCalled()
|
|
|
+ expect(mockHideLoading).toHaveBeenCalled()
|
|
|
+ expect(mockShowToast).toHaveBeenCalledWith({
|
|
|
+ title: '已退出登录',
|
|
|
+ icon: 'success',
|
|
|
+ duration: 1500
|
|
|
+ })
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该处理退出登录取消', async () => {
|
|
|
+ mockShowModal.mockImplementation((options) => {
|
|
|
+ options.success({ confirm: false })
|
|
|
+ })
|
|
|
+
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 点击退出登录按钮
|
|
|
+ const logoutButton = screen.getByText('退出登录')
|
|
|
+ fireEvent.click(logoutButton)
|
|
|
+
|
|
|
+ // 检查取消退出登录
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockShowModal).toHaveBeenCalled()
|
|
|
+ expect(mockLogout).not.toHaveBeenCalled()
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该正确使用TabBarLayout', () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 检查TabBarLayout是否正确使用
|
|
|
+ const tabBarLayout = screen.getByTestId('tab-bar-layout')
|
|
|
+ expect(tabBarLayout).toHaveAttribute('data-active-key', 'profile')
|
|
|
+ })
|
|
|
+
|
|
|
+ test('应该正确使用Navbar', () => {
|
|
|
+ render(
|
|
|
+ <Wrapper>
|
|
|
+ <ProfilePage />
|
|
|
+ </Wrapper>
|
|
|
+ )
|
|
|
+
|
|
|
+ // 检查Navbar是否正确使用
|
|
|
+ const navbar = screen.getByTestId('navbar')
|
|
|
+ expect(navbar).toHaveAttribute('data-title', '个人中心')
|
|
|
+ expect(navbar).toHaveAttribute('data-right-icon', 'i-heroicons-cog-6-tooth-20-solid')
|
|
|
+ expect(navbar).toHaveAttribute('data-background-color', 'bg-primary')
|
|
|
+ expect(navbar).toHaveAttribute('data-text-color', 'text-white')
|
|
|
+ expect(navbar).toHaveAttribute('data-border', 'false')
|
|
|
+ })
|
|
|
+})
|