DREW | ae420b2 | 2025-06-02 14:07:20 +0800 | [diff] [blame^] | 1 | import React from 'react'; |
| 2 | import { render, screen, fireEvent, waitFor } from '@testing-library/react'; |
| 3 | import { MemoryRouter, Route, Routes } from 'react-router-dom'; |
| 4 | import RequestDetail from './RequestDetail'; |
| 5 | import { isFormData } from 'axios'; |
| 6 | |
| 7 | const mockNavigate = jest.fn(); |
| 8 | |
| 9 | |
| 10 | // 明确 mock API 模块 |
| 11 | jest.mock('../api/requestPost', () => ({ |
| 12 | getRequestPostDetail: jest.fn(), |
| 13 | addRequestPostComment: jest.fn(), |
| 14 | likeRequestPost: jest.fn(), |
| 15 | deleteRequestPost: jest.fn() |
| 16 | })); |
| 17 | |
| 18 | jest.mock('../api/requestComment', () => ({ |
| 19 | likeRequestPostComment: jest.fn(), |
| 20 | getRequestCommentReplies: jest.fn(), |
| 21 | addRequestCommentReply: jest.fn(), |
| 22 | deleteRequestComment: jest.fn() |
| 23 | })); |
| 24 | |
| 25 | jest.mock('react-router-dom', () => ({ |
| 26 | ...jest.requireActual('react-router-dom'), |
| 27 | useNavigate: () => mockNavigate, |
| 28 | useLocation: jest.fn() |
| 29 | })); |
| 30 | |
| 31 | // 导入 mock 后的 API |
| 32 | import * as requestPostApi from '../api/requestPost'; |
| 33 | import * as requestCommentApi from '../api/requestComment'; |
| 34 | |
| 35 | describe('RequestDetail 组件', () => { |
| 36 | const mockPost = { |
| 37 | id: '1', |
| 38 | title: '测试求种帖', |
| 39 | content: '这是一个测试求种内容', |
| 40 | authorId: 'user1', |
| 41 | createTime: '2023-01-01T00:00:00Z', |
| 42 | likeCount: 5, |
| 43 | replyCount: 3, |
| 44 | isSolved: false, |
| 45 | }; |
| 46 | |
| 47 | const mockComments = [ |
| 48 | { |
| 49 | id: 'c1', |
| 50 | content: '测试评论1', |
| 51 | authorId: 'user2', |
| 52 | createTime: '2023-01-01T01:00:00Z', |
| 53 | likeCount: 2, |
| 54 | replies: [ |
| 55 | { |
| 56 | id: 'c1r1', |
| 57 | content: '测试回复1', |
| 58 | authorId: 'user3', |
| 59 | createTime: '2023-01-01T02:00:00Z', |
| 60 | likeCount: 1, |
| 61 | } |
| 62 | ] |
| 63 | } |
| 64 | ]; |
| 65 | |
| 66 | beforeEach(() => { |
| 67 | jest.clearAllMocks(); |
| 68 | |
| 69 | requestPostApi.getRequestPostDetail.mockResolvedValue({ |
| 70 | data: { |
| 71 | code: 200, |
| 72 | data: { |
| 73 | post: mockPost, |
| 74 | comments: mockComments, |
| 75 | } |
| 76 | } |
| 77 | }); |
| 78 | |
| 79 | // 模拟 useLocation |
| 80 | require('react-router-dom').useLocation.mockReturnValue({ |
| 81 | state: { fromTab: 'request' } |
| 82 | }); |
| 83 | |
| 84 | // 模拟 localStorage |
| 85 | Storage.prototype.getItem = jest.fn((key) => { |
| 86 | if (key === 'username') return 'testuser'; |
| 87 | return null; |
| 88 | }); |
| 89 | }); |
| 90 | |
| 91 | const renderComponent = () => { |
| 92 | return render( |
| 93 | <MemoryRouter initialEntries={['/request/1']}> |
| 94 | <Routes> |
| 95 | <Route path="/request/:id" element={<RequestDetail />} /> |
| 96 | {/* 添加一个模拟的求助列表路由用于导航测试 */} |
| 97 | <Route path="/dashboard/request" element={<div>求助列表</div>} /> |
| 98 | </Routes> |
| 99 | </MemoryRouter> |
| 100 | ); |
| 101 | }; |
| 102 | |
| 103 | it('应该正确加载和显示求助帖详情', async () => { |
| 104 | renderComponent(); |
| 105 | |
| 106 | // 检查加载状态 |
| 107 | expect(screen.getByText('加载中...')).toBeInTheDocument(); |
| 108 | |
| 109 | // 增加超时时间 |
| 110 | await waitFor(() => { |
| 111 | expect(screen.getByText(mockPost.title)).toBeInTheDocument(); |
| 112 | }, { timeout: 3000 }); |
| 113 | |
| 114 | // 等待数据加载完成 |
| 115 | await waitFor(() => { |
| 116 | expect(screen.getByText(mockPost.title)).toBeInTheDocument(); |
| 117 | expect(screen.getByText(mockPost.content)).toBeInTheDocument(); |
| 118 | expect(screen.getByText(mockPost.authorId)).toBeInTheDocument(); |
| 119 | }); |
| 120 | }); |
| 121 | |
| 122 | it('应该能够点赞帖子', async () => { |
| 123 | renderComponent(); |
| 124 | |
| 125 | await waitFor(() => { |
| 126 | expect(screen.getByText(mockPost.title)).toBeInTheDocument(); |
| 127 | }); |
| 128 | |
| 129 | const likeButton = screen.getByRole('button', { name: /点赞 \(\d+\)/ }); |
| 130 | fireEvent.click(likeButton); |
| 131 | |
| 132 | await waitFor(() => { |
| 133 | expect(requestPostApi.likeRequestPost).toHaveBeenCalledWith('1'); |
| 134 | }); |
| 135 | }); |
| 136 | |
| 137 | it('应该能够提交评论', async () => { |
| 138 | renderComponent(); |
| 139 | |
| 140 | await waitFor(() => { |
| 141 | expect(screen.getByText(mockPost.title)).toBeInTheDocument(); |
| 142 | }); |
| 143 | |
| 144 | const commentInput = screen.getByPlaceholderText('写下你的评论...'); |
| 145 | fireEvent.change(commentInput, { target: { value: '新评论' } }); |
| 146 | |
| 147 | const submitButton = screen.getByRole('button', { name: '发表评论' }); |
| 148 | fireEvent.click(submitButton); |
| 149 | |
| 150 | await waitFor(() => { |
| 151 | expect(requestPostApi.addRequestPostComment).toHaveBeenCalled(); |
| 152 | }); |
| 153 | }); |
| 154 | |
| 155 | it('应该能够点赞评论', async () => { |
| 156 | renderComponent(); |
| 157 | |
| 158 | await waitFor(() => { |
| 159 | expect(screen.getByText(mockComments[0].content)).toBeInTheDocument(); |
| 160 | }); |
| 161 | |
| 162 | const likeButtons = screen.getAllByRole('button', { name: /👍 \(\d+\)/ }); |
| 163 | fireEvent.click(likeButtons[0]); |
| 164 | |
| 165 | await waitFor(() => { |
| 166 | expect(requestCommentApi.likeRequestPostComment).toHaveBeenCalledWith('c1'); |
| 167 | }); |
| 168 | }); |
| 169 | |
| 170 | it('应该能够打开和关闭回复模态框', async () => { |
| 171 | renderComponent(); |
| 172 | |
| 173 | await waitFor(() => { |
| 174 | expect(screen.getByText(mockComments[0].content)).toBeInTheDocument(); |
| 175 | }); |
| 176 | |
| 177 | // 点击回复按钮 |
| 178 | const replyButtons = screen.getAllByRole('button', { name: '回复' }); |
| 179 | fireEvent.click(replyButtons[0]); |
| 180 | |
| 181 | // 检查模态框是否打开 |
| 182 | await waitFor(() => { |
| 183 | expect(screen.getByText(`回复 @${mockComments[0].authorId}`)).toBeInTheDocument(); |
| 184 | }); |
| 185 | |
| 186 | // 点击关闭按钮 |
| 187 | const closeButton = screen.getByRole('button', { name: '×' }); |
| 188 | fireEvent.click(closeButton); |
| 189 | |
| 190 | // 检查模态框是否关闭 |
| 191 | await waitFor(() => { |
| 192 | expect(screen.queryByText(`回复 @${mockComments[0].authorId}`)).not.toBeInTheDocument(); |
| 193 | }); |
| 194 | }); |
| 195 | |
| 196 | it('应该能够返回求助区', async () => { |
| 197 | renderComponent(); |
| 198 | |
| 199 | await waitFor(() => { |
| 200 | expect(screen.getByText(mockPost.title)).toBeInTheDocument(); |
| 201 | }); |
| 202 | |
| 203 | const backButton = screen.getByRole('button', { name: /返回/i }); |
| 204 | fireEvent.click(backButton); |
| 205 | |
| 206 | expect(mockNavigate).toHaveBeenCalledWith('/dashboard/request'); |
| 207 | |
| 208 | }); |
| 209 | }); |