添加了postsPanel作为通用帖子显示板,增加了对jest测试的配置,添加了论坛主页,设定了论坛全局框架,设定了论坛基础主题色及主题切换、字号切换逻辑

Change-Id: I9fad0cf577088adb00c9850d405ccd23e6072413
diff --git a/test/login.test.tsx b/test/login.test.tsx
index 936d9fb..b342387 100644
--- a/test/login.test.tsx
+++ b/test/login.test.tsx
@@ -33,8 +33,9 @@
 describe('Login Component', () => {
     const mockDispatch = jest.fn();
     const mockNavigate = jest.fn();
-    const mockRefresh = jest.fn();
-  
+    const mockPostRefresh = jest.fn();
+    const mockGetRefresh = jest.fn();
+
     beforeEach(() => {
       // 初始化模拟函数返回值
       mockUseAppDispatch.mockReturnValue(mockDispatch);
@@ -42,12 +43,28 @@
       mockUseSelector.mockImplementation((selector) => selector({ user: { userName: '' } }));
       
       // 默认模拟 useApi 返回正常状态
-      mockUseApi.mockReturnValue({
+      mockUseApi
+      .mockReturnValueOnce({
         data: { token: 'mock-token' },
         loading: false,
         error: null,
-        refresh: mockRefresh,
-      });
+        refresh: mockPostRefresh,
+      })
+      .mockReturnValueOnce({
+        data:{
+            'userId' : '001',
+            'userName' : 'san3yuan',
+            'role' : 'manager',
+            'uploadTraffic' : 0,
+            'downloadTraffic': 0,
+            'downloadPoints' : 0,
+            'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+        },
+        loading: false,
+        error: null,
+        refresh: mockGetRefresh,
+      })
+      ;
     });
   
     afterEach(() => {
@@ -67,26 +84,47 @@
     // 测试 2: 登录成功流程
     it('点击登录按钮触发API请求、Redux更新和导航', async () => {
       // 模拟 API 返回有效数据
-      mockRefresh.mockResolvedValue({ token: 'mock-token' }); // 关键:必须返回 Promise
-      mockUseApi.mockReturnValue({
-        data: { token: 'mock-token' },
-        loading: false,
-        error: null,
-        refresh: mockRefresh,
-      });
+      mockPostRefresh.mockResolvedValue({ token: 'mock-token' });
+      mockGetRefresh.mockResolvedValue({
+        'userId' : '001',
+        'userName' : 'san3yuan',
+        'role' : 'manager',
+        'uploadTraffic' : 0,
+        'downloadTraffic': 0,
+        'downloadPoints' : 0,
+        'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+    });
     
       render(<Login />);
-      
+      jest.useFakeTimers();
       fireEvent.click(screen.getByText('登录'));
-    
+      jest.runAllTimers();
       await waitFor(() => {
         // 验证 dispatch 调用
-        expect(mockDispatch).toHaveBeenCalledWith({
+        expect(mockPostRefresh).toHaveBeenCalled();
+      
+        // 验证Redux更新
+        expect(mockDispatch).toHaveBeenNthCalledWith(1, {
           type: 'user/login',
-          payload: { token: 'mock-token' },
+          payload: { token: 'mock-token' }
+        });
+
+        // 验证第二次API调用(用户信息)
+        expect(mockGetRefresh).toHaveBeenCalled();
+
+        // 验证用户信息更新
+        expect(mockDispatch).toHaveBeenNthCalledWith(2, {
+          type: 'user/getUserInfo',
+          payload: {
+            'userId' : '001',
+            'userName' : 'san3yuan',
+            'role' : 'manager',
+            'uploadTraffic' : 0,
+            'downloadTraffic': 0,
+            'downloadPoints' : 0,
+            'avatar' : 'https://www.w3school.com.cn/i/photo/tulip.jpg',
+        }
         });
       });
-    });
-  
-  
+    });  
   });
\ No newline at end of file
diff --git a/test/postsPanel.test.tsx b/test/postsPanel.test.tsx
new file mode 100644
index 0000000..cd753fc
--- /dev/null
+++ b/test/postsPanel.test.tsx
@@ -0,0 +1,75 @@
+import React from 'react';
+import { render, screen, waitFor } from '@testing-library/react';
+import '@testing-library/jest-dom';
+import { useApi } from '@/hooks/request';
+import PostsPanel from '@/components/postsPanel/postsPanel';
+
+// 模拟 useApi
+jest.mock('@/hooks/request', () => ({
+    useApi: jest.fn(() => ({
+        data: [], // 默认返回空数组
+        loading: false,
+        error: null,
+    })),
+}));
+describe('PostsPanel Component', () => {
+    const mockUseApi = useApi as jest.MockedFunction<typeof useApi>;
+
+    it('renders the component with a title', () => {
+        // 渲染组件
+        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+        // 验证标题是否正确渲染
+        expect(screen.getByText('热门帖子')).toBeInTheDocument();
+        expect(screen.getByText('更多')).toBeInTheDocument();
+    });
+
+    it('renders posts when data is available', async () => {
+        // 模拟 API 返回数据
+        mockUseApi.mockReturnValue({
+            data: [
+                { title: 'Post 1', date: '2025-04-01' },
+                { title: 'Post 2', date: '2025-04-02' },
+            ],
+        });
+
+        // 渲染组件
+        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+        // 验证数据是否正确渲染
+        await waitFor(() => {
+            expect(screen.getByText('Post 1')).toBeInTheDocument();
+            expect(screen.getByText('2025-04-01')).toBeInTheDocument();
+            expect(screen.getByText('Post 2')).toBeInTheDocument();
+            expect(screen.getByText('2025-04-02')).toBeInTheDocument();
+        });
+    });
+
+    it('renders a message when no data is available', async () => {
+        // 模拟 API 返回空数据
+        mockUseApi.mockReturnValue({
+            data: [],
+        });
+
+        // 渲染组件
+        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+        // 验证无数据时的提示信息
+        await waitFor(() => {
+            expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
+        });
+    });
+
+    it('handles loading state', () => {
+        // 模拟加载状态
+        mockUseApi.mockReturnValue({
+            data: null,
+        });
+
+        // 渲染组件
+        render(<PostsPanel name="热门帖子" url="/api/posts" limit={5} />);
+
+        // 验证组件是否正确渲染(可以根据需求添加加载状态的测试)
+        expect(screen.getByText('未查询到相关记录')).toBeInTheDocument();
+    });
+});
\ No newline at end of file
diff --git a/test/selfStatus.test.tsx b/test/selfStatus.test.tsx
new file mode 100644
index 0000000..4bd690e
--- /dev/null
+++ b/test/selfStatus.test.tsx
@@ -0,0 +1,77 @@
+import SelfStatus from '@/components/selfStatus/selfStatus';
+import { render, screen } from '@testing-library/react';
+import { useSelector } from 'react-redux';
+import React from 'react';
+import '@testing-library/jest-dom';
+import { useAppSelector } from '@/hooks/store';
+
+jest.mock('@/hooks/request', () => ({
+    useApi: jest.fn(),
+  }));
+  // 模拟所有外部依赖
+  jest.mock('@/hooks/store', () => ({
+    useAppDispatch: jest.fn(),
+    useAppSelector: jest.fn(),
+  }));
+  
+  jest.mock('react-router', () => ({
+    useNavigate: jest.fn(),
+  }));
+  
+  jest.mock('react-redux', () => ({
+    useSelector: jest.fn(),
+  }));
+
+
+describe('SelfStatus Component', () => {
+    it('renders correctly', () => {
+        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+            user: {
+                userId: '001',
+                userName: 'san3yuan',
+                role: 'manager',
+                uploadTraffic: 0,
+                downloadTraffic: 0,
+                downloadPoints: 0,
+            },
+            setting: {
+                theme: 'light',
+            },
+        }));
+        
+        render(<SelfStatus />);
+        
+        expect(screen.getByText('san3yuan')).toBeInTheDocument();
+        expect(screen.getByText('用户组: manager')).toBeInTheDocument();
+        expect(screen.getByText('上传量: 0')).toBeInTheDocument();
+        expect(screen.getByText('下载量: 0')).toBeInTheDocument();
+        expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
+    })
+    it('calculates and displays share ratio correctly', () => {
+        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+            user: {
+                uploadTraffic: 100,
+                downloadTraffic: 50,
+            },
+        }));
+        render(<SelfStatus />);
+        expect(screen.getByText('分享率: 2.00')).toBeInTheDocument();
+    });
+    it('handles empty data gracefully', () => {
+        (useAppSelector as jest.Mock).mockImplementation((selector) => selector({
+            user: {
+                userName: '',
+                role: '',
+                uploadTraffic: null,
+                downloadTraffic: null,
+                downloadPoints: null,
+            },
+        }));
+        render(<SelfStatus />);
+        expect(screen.getByText('用户组: N/A')).toBeInTheDocument();
+        expect(screen.getByText('上传量: 0')).toBeInTheDocument();
+        expect(screen.getByText('下载量: 0')).toBeInTheDocument();        
+        expect(screen.getByText('分享率: N/A')).toBeInTheDocument();
+        expect(screen.getByText('下载积分: 0')).toBeInTheDocument();
+    });
+});
\ No newline at end of file