| import React from 'react'; | |
| import { render, screen, waitFor, fireEvent } from '@testing-library/react'; | |
| import { MemoryRouter, useNavigate, useLocation } from 'react-router-dom'; | |
| import Exchange from './Exchange'; | |
| import { | |
| generateInviteCode, | |
| getUserInviteCodes, | |
| exchangeUpload, | |
| getUserInfo | |
| } from '../../api/personal'; | |
| // Mock API 调用 | |
| jest.mock('../../api/personal', () => ({ | |
| generateInviteCode: jest.fn(), | |
| getUserInviteCodes: jest.fn(), | |
| exchangeUpload: jest.fn(), | |
| getUserInfo: jest.fn() | |
| })); | |
| // Mock react-router-dom hooks | |
| jest.mock('react-router-dom', () => ({ | |
| ...jest.requireActual('react-router-dom'), | |
| useNavigate: jest.fn(), | |
| useLocation: jest.fn() | |
| })); | |
| describe('Exchange Component', () => { | |
| const mockNavigate = jest.fn(); | |
| const mockLocation = { | |
| pathname: '/personal/exchange', | |
| state: { dashboardTab: 'exchange' } | |
| }; | |
| const mockUserInfo = { | |
| magicPoints: 100, | |
| username: 'testuser' | |
| }; | |
| const mockInviteCodes = [ | |
| { code: 'ABCD-1234', isUsed: false }, | |
| { code: 'EFGH-5678', isUsed: true } | |
| ]; | |
| beforeEach(() => { | |
| useNavigate.mockReturnValue(mockNavigate); | |
| useLocation.mockReturnValue(mockLocation); | |
| jest.clearAllMocks(); | |
| // 设置默认 mock 返回值 | |
| getUserInfo.mockResolvedValue(mockUserInfo); | |
| getUserInviteCodes.mockResolvedValue(mockInviteCodes); | |
| generateInviteCode.mockResolvedValue({ code: 'NEW-CODE', isUsed: false }); | |
| exchangeUpload.mockResolvedValue({ success: true }); | |
| }); | |
| it('应该正确加载并显示用户信息和邀请码', async () => { | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| // 初始加载状态 | |
| expect(screen.getByText('加载中...')).toBeInTheDocument(); | |
| // 等待数据加载完成 | |
| await waitFor(() => { | |
| expect(screen.getByText('兑换区')).toBeInTheDocument(); | |
| expect(screen.getByText('当前魔力值: 100')).toBeInTheDocument(); | |
| expect(screen.getByText('ABCD-1234')).toBeInTheDocument(); | |
| expect(screen.getByText('EFGH-5678')).toBeInTheDocument(); | |
| expect(screen.getByText('可用')).toBeInTheDocument(); | |
| expect(screen.getByText('已使用')).toBeInTheDocument(); | |
| }); | |
| }); | |
| it('应该处理生成邀请码操作', async () => { | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| // 使用更精确的选择器定位按钮 | |
| const generateButtons = screen.getAllByRole('button', { name: '兑换邀请码' }); | |
| // 选择第一个按钮(或根据实际情况选择正确的按钮) | |
| fireEvent.click(generateButtons[0]); | |
| }); | |
| expect(generateInviteCode).toHaveBeenCalled(); | |
| await waitFor(() => { | |
| expect(getUserInfo).toHaveBeenCalledTimes(2); // 初始加载 + 生成后刷新 | |
| }); | |
| }); | |
| it('应该处理兑换上传量操作', async () => { | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| const input = screen.getByPlaceholderText('输入要兑换的魔力值'); | |
| const exchangeButton = screen.getByRole('button', { name: '兑换上传量' }); | |
| // 输入有效值 | |
| fireEvent.change(input, { target: { value: '50' } }); | |
| fireEvent.click(exchangeButton); | |
| }); | |
| expect(exchangeUpload).toHaveBeenCalledWith(50); | |
| await waitFor(() => { | |
| expect(getUserInfo).toHaveBeenCalledTimes(2); // 初始加载 + 兑换后刷新 | |
| }); | |
| }); | |
| it('应该处理返回按钮点击', async () => { | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| const backButton = screen.getByText(/返回个人中心/); | |
| fireEvent.click(backButton); | |
| expect(mockNavigate).toHaveBeenCalledWith('/personal', { | |
| state: { | |
| fromSubpage: true, | |
| dashboardTab: 'exchange' | |
| }, | |
| replace: true | |
| }); | |
| }); | |
| }); | |
| it('应该显示错误信息当API调用失败', async () => { | |
| getUserInfo.mockRejectedValueOnce(new Error('获取用户信息失败')); | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| expect(screen.getByText('错误: 获取用户信息失败')).toBeInTheDocument(); | |
| }); | |
| }); | |
| it('应该禁用兑换按钮当魔力值不足', async () => { | |
| getUserInfo.mockResolvedValueOnce({ magicPoints: 5 }); // 设置魔力值不足 | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| const inviteButtons = screen.getAllByRole('button', { name: '兑换邀请码' }); | |
| expect(inviteButtons[0]).toBeDisabled(); | |
| }); | |
| }); | |
| it('应该正确处理空邀请码列表', async () => { | |
| getUserInviteCodes.mockResolvedValueOnce([]); | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| await waitFor(() => { | |
| expect(screen.queryByText('我的邀请码')).not.toBeInTheDocument(); | |
| }); | |
| }); | |
| it('应该显示加载状态', async () => { | |
| // 延迟API响应以测试加载状态 | |
| getUserInfo.mockImplementation(() => new Promise(() => {})); | |
| render( | |
| <MemoryRouter> | |
| <Exchange /> | |
| </MemoryRouter> | |
| ); | |
| expect(screen.getByText('加载中...')).toBeInTheDocument(); | |
| }); | |
| }); |