Akane1217 | 3a7bb97 | 2025-06-01 01:05:27 +0800 | [diff] [blame] | 1 | import React from 'react';
|
| 2 | import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
| 3 | import { MemoryRouter, useNavigate, useLocation } from 'react-router-dom';
|
| 4 | import Exchange from './Exchange';
|
| 5 | import {
|
| 6 | generateInviteCode,
|
| 7 | getUserInviteCodes,
|
| 8 | exchangeUpload,
|
| 9 | getUserInfo
|
| 10 | } from '../../api/personal';
|
| 11 |
|
| 12 | // Mock API 调用
|
| 13 | jest.mock('../../api/personal', () => ({
|
| 14 | generateInviteCode: jest.fn(),
|
| 15 | getUserInviteCodes: jest.fn(),
|
| 16 | exchangeUpload: jest.fn(),
|
| 17 | getUserInfo: jest.fn()
|
| 18 | }));
|
| 19 |
|
| 20 | // Mock react-router-dom hooks
|
| 21 | jest.mock('react-router-dom', () => ({
|
| 22 | ...jest.requireActual('react-router-dom'),
|
| 23 | useNavigate: jest.fn(),
|
| 24 | useLocation: jest.fn()
|
| 25 | }));
|
| 26 |
|
| 27 | describe('Exchange Component', () => {
|
| 28 | const mockNavigate = jest.fn();
|
| 29 | const mockLocation = {
|
| 30 | pathname: '/personal/exchange',
|
| 31 | state: { dashboardTab: 'exchange' }
|
| 32 | };
|
| 33 |
|
| 34 | const mockUserInfo = {
|
| 35 | magicPoints: 100,
|
| 36 | username: 'testuser'
|
| 37 | };
|
| 38 |
|
| 39 | const mockInviteCodes = [
|
| 40 | { code: 'ABCD-1234', isUsed: false },
|
| 41 | { code: 'EFGH-5678', isUsed: true }
|
| 42 | ];
|
| 43 |
|
| 44 | beforeEach(() => {
|
| 45 | useNavigate.mockReturnValue(mockNavigate);
|
| 46 | useLocation.mockReturnValue(mockLocation);
|
| 47 | jest.clearAllMocks();
|
| 48 |
|
| 49 | // 设置默认 mock 返回值
|
| 50 | getUserInfo.mockResolvedValue(mockUserInfo);
|
| 51 | getUserInviteCodes.mockResolvedValue(mockInviteCodes);
|
| 52 | generateInviteCode.mockResolvedValue({ code: 'NEW-CODE', isUsed: false });
|
| 53 | exchangeUpload.mockResolvedValue({ success: true });
|
| 54 | });
|
| 55 |
|
| 56 | it('应该正确加载并显示用户信息和邀请码', async () => {
|
| 57 | render(
|
| 58 | <MemoryRouter>
|
| 59 | <Exchange />
|
| 60 | </MemoryRouter>
|
| 61 | );
|
| 62 |
|
| 63 | // 初始加载状态
|
| 64 | expect(screen.getByText('加载中...')).toBeInTheDocument();
|
| 65 |
|
| 66 | // 等待数据加载完成
|
| 67 | await waitFor(() => {
|
| 68 | expect(screen.getByText('兑换区')).toBeInTheDocument();
|
| 69 | expect(screen.getByText('当前魔力值: 100')).toBeInTheDocument();
|
| 70 | expect(screen.getByText('ABCD-1234')).toBeInTheDocument();
|
| 71 | expect(screen.getByText('EFGH-5678')).toBeInTheDocument();
|
| 72 | expect(screen.getByText('可用')).toBeInTheDocument();
|
| 73 | expect(screen.getByText('已使用')).toBeInTheDocument();
|
| 74 | });
|
| 75 | });
|
| 76 |
|
| 77 | it('应该处理生成邀请码操作', async () => {
|
| 78 | render(
|
| 79 | <MemoryRouter>
|
| 80 | <Exchange />
|
| 81 | </MemoryRouter>
|
| 82 | );
|
| 83 |
|
| 84 | await waitFor(() => {
|
| 85 | // 使用更精确的选择器定位按钮
|
| 86 | const generateButtons = screen.getAllByRole('button', { name: '兑换邀请码' });
|
| 87 | // 选择第一个按钮(或根据实际情况选择正确的按钮)
|
| 88 | fireEvent.click(generateButtons[0]);
|
| 89 | });
|
| 90 |
|
| 91 | expect(generateInviteCode).toHaveBeenCalled();
|
| 92 | await waitFor(() => {
|
| 93 | expect(getUserInfo).toHaveBeenCalledTimes(2); // 初始加载 + 生成后刷新
|
| 94 | });
|
| 95 | });
|
| 96 |
|
| 97 | it('应该处理兑换上传量操作', async () => {
|
| 98 | render(
|
| 99 | <MemoryRouter>
|
| 100 | <Exchange />
|
| 101 | </MemoryRouter>
|
| 102 | );
|
| 103 |
|
| 104 | await waitFor(() => {
|
| 105 | const input = screen.getByPlaceholderText('输入要兑换的魔力值');
|
| 106 | const exchangeButton = screen.getByRole('button', { name: '兑换上传量' });
|
| 107 |
|
| 108 | // 输入有效值
|
| 109 | fireEvent.change(input, { target: { value: '50' } });
|
| 110 | fireEvent.click(exchangeButton);
|
| 111 | });
|
| 112 |
|
| 113 | expect(exchangeUpload).toHaveBeenCalledWith(50);
|
| 114 | await waitFor(() => {
|
| 115 | expect(getUserInfo).toHaveBeenCalledTimes(2); // 初始加载 + 兑换后刷新
|
| 116 | });
|
| 117 | });
|
| 118 |
|
| 119 |
|
| 120 | it('应该处理返回按钮点击', async () => {
|
| 121 | render(
|
| 122 | <MemoryRouter>
|
| 123 | <Exchange />
|
| 124 | </MemoryRouter>
|
| 125 | );
|
| 126 |
|
| 127 | await waitFor(() => {
|
| 128 | const backButton = screen.getByText(/← 返回个人中心/);
|
| 129 | fireEvent.click(backButton);
|
| 130 |
|
| 131 | expect(mockNavigate).toHaveBeenCalledWith('/personal', {
|
| 132 | state: {
|
| 133 | fromSubpage: true,
|
| 134 | dashboardTab: 'exchange'
|
| 135 | },
|
| 136 | replace: true
|
| 137 | });
|
| 138 | });
|
| 139 | });
|
| 140 |
|
| 141 | it('应该显示错误信息当API调用失败', async () => {
|
| 142 | getUserInfo.mockRejectedValueOnce(new Error('获取用户信息失败'));
|
| 143 |
|
| 144 | render(
|
| 145 | <MemoryRouter>
|
| 146 | <Exchange />
|
| 147 | </MemoryRouter>
|
| 148 | );
|
| 149 |
|
| 150 | await waitFor(() => {
|
| 151 | expect(screen.getByText('错误: 获取用户信息失败')).toBeInTheDocument();
|
| 152 | });
|
| 153 | });
|
| 154 |
|
| 155 | it('应该禁用兑换按钮当魔力值不足', async () => {
|
| 156 | getUserInfo.mockResolvedValueOnce({ magicPoints: 5 }); // 设置魔力值不足
|
| 157 |
|
| 158 | render(
|
| 159 | <MemoryRouter>
|
| 160 | <Exchange />
|
| 161 | </MemoryRouter>
|
| 162 | );
|
| 163 |
|
| 164 | await waitFor(() => {
|
| 165 | const inviteButtons = screen.getAllByRole('button', { name: '兑换邀请码' });
|
| 166 | expect(inviteButtons[0]).toBeDisabled();
|
| 167 | });
|
| 168 | });
|
| 169 |
|
| 170 | it('应该正确处理空邀请码列表', async () => {
|
| 171 | getUserInviteCodes.mockResolvedValueOnce([]);
|
| 172 |
|
| 173 | render(
|
| 174 | <MemoryRouter>
|
| 175 | <Exchange />
|
| 176 | </MemoryRouter>
|
| 177 | );
|
| 178 |
|
| 179 | await waitFor(() => {
|
| 180 | expect(screen.queryByText('我的邀请码')).not.toBeInTheDocument();
|
| 181 | });
|
| 182 | });
|
| 183 |
|
| 184 | it('应该显示加载状态', async () => {
|
| 185 | // 延迟API响应以测试加载状态
|
| 186 | getUserInfo.mockImplementation(() => new Promise(() => {}));
|
| 187 |
|
| 188 | render(
|
| 189 | <MemoryRouter>
|
| 190 | <Exchange />
|
| 191 | </MemoryRouter>
|
| 192 | );
|
| 193 |
|
| 194 | expect(screen.getByText('加载中...')).toBeInTheDocument();
|
| 195 | });
|
| 196 | }); |