/** * 个人中心编辑资料功能单元测试 * 测试头像和昵称编辑功能 */ import { render, screen, waitFor, fireEvent } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import ProfilePage from '@/pages/profile/index' import { creditBalanceClient } from '@/api' import { useAuth } from '@/utils/auth' import Taro from '@tarojs/taro' // Mock API客户端 jest.mock('@/api', () => ({ creditBalanceClient: { me: { $get: jest.fn(), }, }, authClient: { me: { $get: jest.fn(), $put: jest.fn(), }, }, })) // Mock 认证hook jest.mock('@/utils/auth', () => ({ useAuth: jest.fn(), })) // Mock TDesign组件 - 复用现有测试中的mock jest.mock('@/components/tdesign/user-center-card', () => ({ __esModule: true, default: ({ avatar, nickname, isLoggedIn, onUserEdit, className }: any) => (
{avatar}
{nickname}
{isLoggedIn ? '已登录' : '未登录'}
), })) jest.mock('@/components/tdesign/order-group', () => ({ __esModule: true, default: ({ orderTagInfos, title, desc, onTopClick, onItemClick }: any) => (
{title}
{desc}
{orderTagInfos.map((item: any, index: number) => ( ))}
), })) jest.mock('@/components/tdesign/cell-group', () => ({ __esModule: true, default: ({ children }: any) => (
{children}
), })) jest.mock('@/components/tdesign/cell', () => ({ __esModule: true, default: ({ title, bordered, onClick, noteSlot }: any) => (
{title}
{noteSlot}
), })) jest.mock('@/components/tdesign/popup', () => ({ __esModule: true, default: ({ visible, placement, onClose, children }: any) => ( visible ? (
{children}
) : null ), })) jest.mock('@/components/tdesign/icon', () => ({ __esModule: true, default: ({ name, size, color }: any) => (
图标
), })) // Mock AvatarUpload组件 jest.mock('@/components/ui/avatar-upload', () => ({ AvatarUpload: ({ currentAvatar, onUploadSuccess, onUploadError, editable }: any) => (
头像
), })) // Mock Taro组件 - 输入框 jest.mock('@tarojs/components', () => ({ View: ({ children, ...props }: any) =>
{children}
, Text: ({ children, ...props }: any) => {children}, ScrollView: ({ children, ...props }: any) =>
{children}
, Input: ({ value, onInput, placeholder, maxlength, className }: any) => ( onInput?.({ detail: { value: e.target.value } })} placeholder={placeholder} maxLength={maxlength} className={className} /> ), })) // Mock Button组件 jest.mock('@/components/ui/button', () => ({ Button: ({ variant, size, className, onClick, children }: any) => ( ), })) // 创建测试QueryClient const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false }, mutations: { retry: false }, }, }) // Mock CartContext jest.mock('@/contexts/CartContext', () => ({ CartProvider: ({ children }: { children: React.ReactNode }) => <>{children}, useCart: () => ({ cart: { items: [], totalAmount: 0, totalCount: 0, }, addToCart: jest.fn(), removeFromCart: jest.fn(), updateQuantity: jest.fn(), clearCart: jest.fn(), isInCart: jest.fn(), getItemQuantity: jest.fn(), isLoading: false, }), })) // 测试包装器 const TestWrapper = ({ children }: { children: React.ReactNode }) => ( {children} ) // 测试数据工厂 const createTestUser = (overrides = {}) => ({ id: 1, username: '测试用户', avatarFile: { id: 123, fullUrl: 'https://example.com/avatar.jpg' }, ...overrides, }) const createTestCreditBalance = (overrides = {}) => ({ totalLimit: 1000, usedAmount: 200, availableAmount: 800, isEnabled: true, ...overrides, }) describe('个人中心编辑资料功能测试', () => { beforeEach(() => { jest.clearAllMocks() // 设置默认认证状态 ;(useAuth as jest.Mock).mockReturnValue({ user: createTestUser(), logout: jest.fn(), isLoading: false, updateUser: jest.fn(), refreshUser: jest.fn(), }) // 设置默认额度查询 ;(creditBalanceClient.me.$get as jest.Mock).mockResolvedValue({ status: 200, json: () => Promise.resolve(createTestCreditBalance({ usedAmount: 0 })), }) }) test('应该正确渲染个人中心页面', async () => { render( ) // 验证页面标题 await waitFor(() => { expect(screen.getByText('个人中心')).toBeInTheDocument() }) // 验证用户信息显示 expect(screen.getByTestId('user-center-card')).toBeInTheDocument() expect(screen.getByTestId('nickname')).toHaveTextContent('测试用户') expect(screen.getByTestId('avatar')).toHaveTextContent('https://example.com/avatar.jpg') }) test('应该打开编辑资料弹窗并显示当前头像和昵称', async () => { render( ) // 点击编辑按钮打开弹窗 fireEvent.click(screen.getByTestId('edit-button')) // 验证弹窗显示 expect(screen.getByTestId('popup')).toBeInTheDocument() // 验证头像显示正确 const avatarImage = screen.getByTestId('avatar-image') as HTMLImageElement expect(avatarImage.src).toBe('https://example.com/avatar.jpg') // 验证昵称输入框显示正确 const nicknameInput = screen.getByTestId('nickname-input') as HTMLInputElement expect(nicknameInput.value).toBe('测试用户') }) test('头像上传成功应该调用updateUser和refreshUser', async () => { const mockUpdateUser = jest.fn() const mockRefreshUser = jest.fn() // 设置模拟函数 ;(useAuth as jest.Mock).mockReturnValue({ user: createTestUser(), logout: jest.fn(), isLoading: false, updateUser: mockUpdateUser, refreshUser: mockRefreshUser, }) // 设置mock返回值,确保异步流程正常执行 mockUpdateUser.mockResolvedValue(createTestUser()) mockRefreshUser.mockResolvedValue(createTestUser()) render( ) // 打开编辑弹窗 fireEvent.click(screen.getByTestId('edit-button')) // 点击上传按钮(模拟上传成功) fireEvent.click(screen.getByTestId('upload-button')) // 验证updateUser被调用,参数包含新的fileId await waitFor(() => { expect(mockUpdateUser).toHaveBeenCalledWith({ avatarFileId: 456 }) }) // 验证refreshUser被调用 await waitFor(() => { expect(mockRefreshUser).toHaveBeenCalled() }) }) test('头像上传失败应该显示错误提示', async () => { // Mock Taro.showToast const mockShowToast = jest.fn() ;(Taro as any).showToast = mockShowToast render( ) // 打开编辑弹窗 fireEvent.click(screen.getByTestId('edit-button')) // 点击模拟上传失败按钮 fireEvent.click(screen.getByTestId('upload-error-button')) // 验证错误提示被调用 expect(mockShowToast).toHaveBeenCalledWith({ title: '模拟上传失败', icon: 'none', duration: 3000, }) }) test('昵称修改应该调用updateUser和refreshUser', async () => { const mockUpdateUser = jest.fn() const mockRefreshUser = jest.fn() // 设置模拟函数 ;(useAuth as jest.Mock).mockReturnValue({ user: createTestUser(), logout: jest.fn(), isLoading: false, updateUser: mockUpdateUser, refreshUser: mockRefreshUser, }) // 设置mock返回值,确保异步流程正常执行 mockUpdateUser.mockResolvedValue(createTestUser()) mockRefreshUser.mockResolvedValue(createTestUser()) render( ) // 打开编辑弹窗 fireEvent.click(screen.getByTestId('edit-button')) // 修改昵称输入框 const nicknameInput = screen.getByTestId('nickname-input') fireEvent.change(nicknameInput, { target: { value: '新昵称' } }) // 点击保存按钮 fireEvent.click(screen.getByTestId('button-default')) // 验证updateUser被调用,参数包含新昵称 await waitFor(() => { expect(mockUpdateUser).toHaveBeenCalledWith({ username: '新昵称' }) }) // 验证refreshUser被调用 await waitFor(() => { expect(mockRefreshUser).toHaveBeenCalled() }) }) test('同时修改头像和昵称应该调用updateUser包含两个字段', async () => { const mockUpdateUser = jest.fn() const mockRefreshUser = jest.fn() // 设置模拟函数 ;(useAuth as jest.Mock).mockReturnValue({ user: createTestUser(), logout: jest.fn(), isLoading: false, updateUser: mockUpdateUser, refreshUser: mockRefreshUser, }) // 设置mock返回值,确保异步流程正常执行 mockUpdateUser.mockResolvedValue(createTestUser()) mockRefreshUser.mockResolvedValue(createTestUser()) render( ) // 打开编辑弹窗 fireEvent.click(screen.getByTestId('edit-button')) // 上传新头像 fireEvent.click(screen.getByTestId('upload-button')) // 修改昵称 const nicknameInput = screen.getByTestId('nickname-input') fireEvent.change(nicknameInput, { target: { value: '新昵称' } }) // 点击保存按钮 fireEvent.click(screen.getByTestId('button-default')) // 验证updateUser被调用,参数包含两个字段 await waitFor(() => { expect(mockUpdateUser).toHaveBeenCalledWith({ avatarFileId: 456, username: '新昵称', }) }) // 验证refreshUser被调用 await waitFor(() => { expect(mockRefreshUser).toHaveBeenCalled() }) }) })