|
@@ -0,0 +1,436 @@
|
|
|
|
|
+/**
|
|
|
|
|
+ * 个人中心编辑资料功能单元测试
|
|
|
|
|
+ * 测试头像和昵称编辑功能
|
|
|
|
|
+ */
|
|
|
|
|
+
|
|
|
|
|
+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) => (
|
|
|
|
|
+ <div data-testid="user-center-card" className={className}>
|
|
|
|
|
+ <div data-testid="avatar">{avatar}</div>
|
|
|
|
|
+ <div data-testid="nickname">{nickname}</div>
|
|
|
|
|
+ <div data-testid="is-logged-in">{isLoggedIn ? '已登录' : '未登录'}</div>
|
|
|
|
|
+ <button data-testid="edit-button" onClick={onUserEdit}>编辑</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/tdesign/order-group', () => ({
|
|
|
|
|
+ __esModule: true,
|
|
|
|
|
+ default: ({ orderTagInfos, title, desc, onTopClick, onItemClick }: any) => (
|
|
|
|
|
+ <div data-testid="order-group">
|
|
|
|
|
+ <div data-testid="order-title">{title}</div>
|
|
|
|
|
+ <div data-testid="order-desc">{desc}</div>
|
|
|
|
|
+ <button data-testid="top-click" onClick={onTopClick}>查看全部</button>
|
|
|
|
|
+ {orderTagInfos.map((item: any, index: number) => (
|
|
|
|
|
+ <button key={index} data-testid={`order-item-${index}`} onClick={() => onItemClick(item)}>
|
|
|
|
|
+ {item.title}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/tdesign/cell-group', () => ({
|
|
|
|
|
+ __esModule: true,
|
|
|
|
|
+ default: ({ children }: any) => (
|
|
|
|
|
+ <div data-testid="cell-group">{children}</div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/tdesign/cell', () => ({
|
|
|
|
|
+ __esModule: true,
|
|
|
|
|
+ default: ({ title, bordered, onClick, noteSlot }: any) => (
|
|
|
|
|
+ <div data-testid="cell" data-bordered={bordered}>
|
|
|
|
|
+ <div data-testid="cell-title">{title}</div>
|
|
|
|
|
+ <button data-testid="cell-click" onClick={onClick}>点击</button>
|
|
|
|
|
+ <div data-testid="cell-note">{noteSlot}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/tdesign/popup', () => ({
|
|
|
|
|
+ __esModule: true,
|
|
|
|
|
+ default: ({ visible, placement, onClose, children }: any) => (
|
|
|
|
|
+ visible ? (
|
|
|
|
|
+ <div data-testid="popup" data-placement={placement}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ <button data-testid="popup-close" onClick={onClose}>关闭</button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ) : null
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+jest.mock('@/components/tdesign/icon', () => ({
|
|
|
|
|
+ __esModule: true,
|
|
|
|
|
+ default: ({ name, size, color }: any) => (
|
|
|
|
|
+ <div data-testid="icon" data-name={name} data-size={size} data-color={color}>图标</div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock AvatarUpload组件
|
|
|
|
|
+jest.mock('@/components/ui/avatar-upload', () => ({
|
|
|
|
|
+ AvatarUpload: ({
|
|
|
|
|
+ currentAvatar,
|
|
|
|
|
+ onUploadSuccess,
|
|
|
|
|
+ onUploadError,
|
|
|
|
|
+ editable
|
|
|
|
|
+ }: any) => (
|
|
|
|
|
+ <div data-testid="avatar-upload" data-editable={editable}>
|
|
|
|
|
+ <img src={currentAvatar} alt="头像" data-testid="avatar-image" />
|
|
|
|
|
+ <button
|
|
|
|
|
+ data-testid="upload-button"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ // 模拟上传成功
|
|
|
|
|
+ if (editable) {
|
|
|
|
|
+ onUploadSuccess?.({
|
|
|
|
|
+ fileUrl: 'https://example.com/new-avatar.jpg',
|
|
|
|
|
+ fileId: 456,
|
|
|
|
|
+ fileKey: 'avatars/new-avatar.jpg',
|
|
|
|
|
+ bucketName: 'd8dai'
|
|
|
|
|
+ })
|
|
|
|
|
+ }
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 上传头像
|
|
|
|
|
+ </button>
|
|
|
|
|
+ <button
|
|
|
|
|
+ data-testid="upload-error-button"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ onUploadError?.(new Error('模拟上传失败'))
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ 模拟上传失败
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// Mock Taro组件 - 输入框
|
|
|
|
|
+jest.mock('@tarojs/components', () => ({
|
|
|
|
|
+ View: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
|
|
|
+ Text: ({ children, ...props }: any) => <span {...props}>{children}</span>,
|
|
|
|
|
+ ScrollView: ({ children, ...props }: any) => <div {...props}>{children}</div>,
|
|
|
|
|
+ Input: ({ value, onInput, placeholder, maxlength, className }: any) => (
|
|
|
|
|
+ <input
|
|
|
|
|
+ data-testid="nickname-input"
|
|
|
|
|
+ value={value}
|
|
|
|
|
+ onChange={(e) => 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) => (
|
|
|
|
|
+ <button
|
|
|
|
|
+ data-testid={`button-${variant}`}
|
|
|
|
|
+ data-size={size}
|
|
|
|
|
+ className={className}
|
|
|
|
|
+ onClick={onClick}
|
|
|
|
|
+ >
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </button>
|
|
|
|
|
+ ),
|
|
|
|
|
+}))
|
|
|
|
|
+
|
|
|
|
|
+// 创建测试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 }) => (
|
|
|
|
|
+ <QueryClientProvider client={createTestQueryClient()}>
|
|
|
|
|
+ {children}
|
|
|
|
|
+ </QueryClientProvider>
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+// 测试数据工厂
|
|
|
|
|
+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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 验证页面标题
|
|
|
|
|
+ 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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 点击编辑按钮打开弹窗
|
|
|
|
|
+ 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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 打开编辑弹窗
|
|
|
|
|
+ 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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 打开编辑弹窗
|
|
|
|
|
+ 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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 打开编辑弹窗
|
|
|
|
|
+ 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(
|
|
|
|
|
+ <TestWrapper>
|
|
|
|
|
+ <ProfilePage />
|
|
|
|
|
+ </TestWrapper>
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // 打开编辑弹窗
|
|
|
|
|
+ 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()
|
|
|
|
|
+ })
|
|
|
|
|
+ })
|
|
|
|
|
+})
|