/**
* 个人中心编辑资料功能单元测试
* 测试头像和昵称编辑功能
*/
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) => (
),
}))
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()
})
})
})