/**
* 个人中心页面组件测试
*/
import React from 'react'
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import ProfilePage from '../../src/pages/profile/index'
// 导入 Taro mock 函数
import taroMock from '../../tests/__mocks__/taroMock'
// Mock TabBarLayout 组件
jest.mock('@/layouts/tab-bar-layout', () => ({
TabBarLayout: jest.fn(({ children, activeKey, className }) => (
{children}
))
}))
// Mock Navbar 组件
jest.mock('@/components/ui/navbar', () => ({
Navbar: jest.fn(({ title, rightIcon, onClickRight, leftIcon, backgroundColor, textColor, border }) => (
{title}
))
}))
// Mock AvatarUpload 组件
jest.mock('@/components/ui/avatar-upload', () => ({
AvatarUpload: jest.fn(({ currentAvatar, onUploadSuccess, onUploadError, size, editable, className }) => (
))
}))
// Mock Button 组件
jest.mock('@/components/ui/button', () => ({
Button: jest.fn(({ children, variant, size, onClick, className }) => (
))
}))
// Mock FAQDialog 组件
jest.mock('@/components/FAQDialog', () => ({
FAQDialog: jest.fn(({ open, onOpenChange }) => (
常见问题内容
))
}))
// 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 React Query hooks
const mockUseQuery = jest.fn()
const mockUseMutation = jest.fn()
jest.mock('@tanstack/react-query', () => {
const actual = jest.requireActual('@tanstack/react-query')
return {
...actual,
useQuery: (options: any) => mockUseQuery(options),
useMutation: (options: any) => mockUseMutation(options)
}
})
// 创建测试用的 QueryClient
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
})
// 包装组件
const Wrapper = ({ children }: { children: React.ReactNode }) => {
const queryClient = createTestQueryClient()
return (
{children}
)
}
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'
// 初始化 React Query mock
mockUseQuery.mockImplementation(() => ({
data: null,
isLoading: false
}))
mockUseMutation.mockImplementation((options) => ({
mutateAsync: options.mutationFn,
isPending: false
}))
// 重置所有 mock 调用记录
taroMock.showToast.mockClear()
taroMock.openCustomerServiceChat.mockClear()
taroMock.navigateTo.mockClear()
taroMock.showLoading.mockClear()
taroMock.hideLoading.mockClear()
taroMock.showModal.mockClear()
taroMock.reLaunch.mockClear()
})
test('应该正确渲染个人中心页面', () => {
render(
)
// 检查页面标题
expect(screen.getByText('个人中心')).toBeInTheDocument()
// 检查用户信息
expect(screen.getByText('普通用户')).toBeInTheDocument()
expect(screen.getByText('ID: 1')).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 () => {
taroMock.openCustomerServiceChat.mockImplementation((options) => {
options.success()
})
render(
)
// 点击联系客服按钮
const customerServiceButton = screen.getByTestId('customer-service-button')
fireEvent.click(customerServiceButton)
// 检查微信客服API被正确调用
await waitFor(() => {
expect(taroMock.openCustomerServiceChat).toHaveBeenCalledWith({
extInfo: {
url: 'https://work.weixin.qq.com/kfid/kfc5f4d729bc3c893d7'
},
corpId: 'wwc6d7911e2d23b7fb',
success: expect.any(Function),
fail: expect.any(Function)
})
})
})
test('应该处理联系客服功能 - 失败场景', async () => {
taroMock.openCustomerServiceChat.mockImplementation((options) => {
options.fail({ errMsg: '客服功能不可用' })
})
render(
)
// 点击联系客服按钮
const customerServiceButton = screen.getByTestId('customer-service-button')
fireEvent.click(customerServiceButton)
// 检查错误提示显示
await waitFor(() => {
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '客服功能暂不可用,请稍后重试',
icon: 'none'
})
})
})
test('应该处理联系客服功能 - 异常场景', async () => {
taroMock.openCustomerServiceChat.mockImplementation(() => {
throw new Error('API调用异常')
})
render(
)
// 点击联系客服按钮
const customerServiceButton = screen.getByTestId('customer-service-button')
fireEvent.click(customerServiceButton)
// 检查异常处理
await waitFor(() => {
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '客服功能异常,请稍后重试',
icon: 'none'
})
})
})
test('应该处理其他功能按钮点击', () => {
render(
)
// 点击编辑资料按钮
const editProfileButton = screen.getByTestId('edit-profile-button')
fireEvent.click(editProfileButton)
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '功能开发中...',
icon: 'none'
})
// 点击乘车人管理按钮
const passengersButton = screen.getByTestId('passengers-button')
fireEvent.click(passengersButton)
expect(taroMock.navigateTo).toHaveBeenCalledWith({
url: '/pages/passengers/passengers'
})
// 点击设置按钮
const settingsButton = screen.getByTestId('settings-button')
fireEvent.click(settingsButton)
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '功能开发中...',
icon: 'none'
})
// 点击常见问题按钮 - 现在应该打开弹窗而不是显示Toast
const faqButton = screen.getByTestId('faq-button')
fireEvent.click(faqButton)
// 检查弹窗状态变为打开
const faqDialog = screen.getByTestId('faq-dialog')
expect(faqDialog).toHaveAttribute('data-open', 'true')
// 点击意见反馈按钮
const feedbackButton = screen.getByTestId('feedback-button')
fireEvent.click(feedbackButton)
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '意见反馈功能开发中...',
icon: 'none'
})
})
test('应该处理头像上传功能', async () => {
render(
)
// 点击头像上传成功按钮
const uploadButton = screen.getByTestId('avatar-upload-button')
fireEvent.click(uploadButton)
// 检查上传成功处理
await waitFor(() => {
expect(taroMock.showLoading).toHaveBeenCalledWith({ title: '更新头像...' })
expect(taroMock.hideLoading).toHaveBeenCalled()
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '头像更新成功',
icon: 'success'
})
expect(mockUpdateUser).toHaveBeenCalledWith({
...mockUser,
avatarFileId: 'test-file-id'
})
})
})
test('应该处理头像上传失败', async () => {
render(
)
// 点击头像上传失败按钮
const uploadErrorButton = screen.getByTestId('avatar-upload-error-button')
fireEvent.click(uploadErrorButton)
// 检查上传失败处理
await waitFor(() => {
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '上传失败,请重试',
icon: 'none'
})
})
})
// 退出登录功能暂时被注释掉,跳过相关测试
test.skip('应该处理退出登录', async () => {
taroMock.showModal.mockImplementation((options) => {
options.success({ confirm: true })
})
render(
)
// 点击退出登录按钮
const logoutButton = screen.getByText('退出登录')
fireEvent.click(logoutButton)
// 检查退出登录流程
await waitFor(() => {
expect(taroMock.showModal).toHaveBeenCalledWith({
title: '退出登录',
content: '确定要退出登录吗?',
success: expect.any(Function)
})
expect(taroMock.showLoading).toHaveBeenCalledWith({ title: '退出中...' })
expect(mockLogout).toHaveBeenCalled()
expect(taroMock.hideLoading).toHaveBeenCalled()
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '已退出登录',
icon: 'success',
duration: 1500
})
})
})
test.skip('应该处理退出登录取消', async () => {
taroMock.showModal.mockImplementation((options) => {
options.success({ confirm: false })
})
render(
)
// 点击退出登录按钮
const logoutButton = screen.getByText('退出登录')
fireEvent.click(logoutButton)
// 检查取消退出登录
await waitFor(() => {
expect(taroMock.showModal).toHaveBeenCalled()
expect(mockLogout).not.toHaveBeenCalled()
})
})
test('应该正确使用TabBarLayout', () => {
render(
)
// 检查TabBarLayout是否正确使用
const tabBarLayout = screen.getByTestId('tab-bar-layout')
expect(tabBarLayout).toHaveAttribute('data-active-key', 'profile')
})
test('应该正确使用Navbar', () => {
render(
)
// 检查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')
})
test('应该显示默认头像当用户无头像时', () => {
// 模拟用户无头像的情况
const mockUseAuth = jest.requireMock('@/utils/auth').useAuth
mockUseAuth.mockImplementation(() => ({
user: {
...mockUser,
avatarFile: null
},
logout: mockLogout,
isLoading: false,
updateUser: mockUpdateUser
}))
render(
)
// 检查默认头像路径
const avatarUpload = screen.getByTestId('avatar-upload')
expect(avatarUpload).toHaveAttribute('data-current-avatar', '/images/default_avatar.jpg')
})
test('应该显示默认用户名', () => {
// 模拟用户无用户名的情况
const mockUseAuth = jest.requireMock('@/utils/auth').useAuth
mockUseAuth.mockImplementation(() => ({
user: {
...mockUser,
username: ''
},
logout: mockLogout,
isLoading: false,
updateUser: mockUpdateUser
}))
render(
)
// 检查用户名显示为"普通用户"
expect(screen.getByText('普通用户')).toBeInTheDocument()
})
test('头像上传功能应该在默认头像状态下正常工作', async () => {
// 模拟用户无头像的情况
const mockUseAuth = jest.requireMock('@/utils/auth').useAuth
mockUseAuth.mockImplementation(() => ({
user: {
...mockUser,
avatarFile: null
},
logout: mockLogout,
isLoading: false,
updateUser: mockUpdateUser
}))
render(
)
// 点击头像上传成功按钮
const uploadButton = screen.getByTestId('avatar-upload-button')
fireEvent.click(uploadButton)
// 检查上传成功处理
await waitFor(() => {
expect(taroMock.showLoading).toHaveBeenCalledWith({ title: '更新头像...' })
expect(taroMock.hideLoading).toHaveBeenCalled()
expect(taroMock.showToast).toHaveBeenCalledWith({
title: '头像更新成功',
icon: 'success'
})
expect(mockUpdateUser).toHaveBeenCalledWith({
...mockUser,
avatarFile: null,
avatarFileId: 'test-file-id'
})
})
})
test('应该正确处理常见问题弹窗的打开和关闭', () => {
render(
)
// 初始状态下弹窗应该是关闭的
const faqDialog = screen.getByTestId('faq-dialog')
expect(faqDialog).toHaveAttribute('data-open', 'false')
// 点击常见问题按钮打开弹窗
const faqButton = screen.getByTestId('faq-button')
fireEvent.click(faqButton)
// 检查弹窗状态变为打开
expect(faqDialog).toHaveAttribute('data-open', 'true')
// 点击关闭按钮关闭弹窗
const closeButton = screen.getByTestId('faq-dialog-close')
fireEvent.click(closeButton)
// 检查弹窗状态变为关闭
expect(faqDialog).toHaveAttribute('data-open', 'false')
})
test('常见问题弹窗应该显示正确的内容', () => {
render(
)
// 打开常见问题弹窗
const faqButton = screen.getByTestId('faq-button')
fireEvent.click(faqButton)
// 检查弹窗内容是否正确显示
const faqContent = screen.getByTestId('faq-content')
expect(faqContent).toBeInTheDocument()
expect(faqContent).toHaveTextContent('常见问题内容')
})
})