| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595 |
- /**
- * 个人中心页面组件测试
- */
- 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 }) => (
- <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 FAQDialog 组件
- jest.mock('@/components/FAQDialog', () => ({
- FAQDialog: jest.fn(({ open, onOpenChange }) => (
- <div data-testid="faq-dialog" data-open={open}>
- <button
- data-testid="faq-dialog-close"
- onClick={() => onOpenChange(false)}
- >
- 关闭常见问题
- </button>
- <div data-testid="faq-content">常见问题内容</div>
- </div>
- ))
- }))
- // 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 (
- <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'
- // 初始化 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 检查页面标题
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击联系客服按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击联系客服按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击联系客服按钮
- const customerServiceButton = screen.getByTestId('customer-service-button')
- fireEvent.click(customerServiceButton)
- // 检查异常处理
- await waitFor(() => {
- expect(taroMock.showToast).toHaveBeenCalledWith({
- title: '客服功能异常,请稍后重试',
- icon: 'none'
- })
- })
- })
- test('应该处理其他功能按钮点击', () => {
- render(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击编辑资料按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击头像上传成功按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击头像上传失败按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击退出登录按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击退出登录按钮
- const logoutButton = screen.getByText('退出登录')
- fireEvent.click(logoutButton)
- // 检查取消退出登录
- await waitFor(() => {
- expect(taroMock.showModal).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')
- })
- test('应该显示默认头像当用户无头像时', () => {
- // 模拟用户无头像的情况
- const mockUseAuth = jest.requireMock('@/utils/auth').useAuth
- mockUseAuth.mockImplementation(() => ({
- user: {
- ...mockUser,
- avatarFile: null
- },
- logout: mockLogout,
- isLoading: false,
- updateUser: mockUpdateUser
- }))
- render(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 检查默认头像路径
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 检查用户名显示为"普通用户"
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 点击头像上传成功按钮
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 初始状态下弹窗应该是关闭的
- 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(
- <Wrapper>
- <ProfilePage />
- </Wrapper>
- )
- // 打开常见问题弹窗
- const faqButton = screen.getByTestId('faq-button')
- fireEvent.click(faqButton)
- // 检查弹窗内容是否正确显示
- const faqContent = screen.getByTestId('faq-content')
- expect(faqContent).toBeInTheDocument()
- expect(faqContent).toHaveTextContent('常见问题内容')
- })
- })
|