feat: 添加管理后台页面和功能,优化测试和性能配置

refactor: 重构页面导航和滚动逻辑,提升用户体验

test: 更新测试配置和用例,增加覆盖率和稳定性

perf: 优化性能指标和阈值,适应开发环境需求

ci: 添加Lighthouse CI工作流,集成性能测试

docs: 更新API文档和健康检查端点

fix: 修复登录页面和表单提交问题

style: 调整响应式布局和可访问性改进

chore: 更新依赖项和脚本配置
This commit is contained in:
张翔
2026-03-24 10:11:30 +08:00
parent 08978d38c8
commit f5dec95a83
85 changed files with 12331 additions and 1408 deletions
+134
View File
@@ -0,0 +1,134 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useNews } from './use-news';
import { contentService } from '@/lib/api/services';
import { NewsItem } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useNews', () => {
const mockNewsData: NewsItem[] = [
{
id: '1',
title: '测试新闻1',
excerpt: '测试摘要1',
content: '测试内容1',
date: '2024-01-01',
category: '公司新闻',
slug: 'test-news-1',
},
{
id: '2',
title: '测试新闻2',
excerpt: '测试摘要2',
content: '测试内容2',
date: '2024-01-02',
category: '产品发布',
slug: 'test-news-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取新闻数据', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result } = renderHook(() => useNews());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual(mockNewsData);
expect(result.current.error).toBeNull();
expect(contentService.getNews).toHaveBeenCalledWith(undefined, undefined, 'desc');
});
it('应该支持分类筛选', async () => {
const categories = ['公司新闻'];
(contentService.getNews as jest.Mock).mockResolvedValue([mockNewsData[0]]);
const { result } = renderHook(() => useNews(categories));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([mockNewsData[0]]);
expect(contentService.getNews).toHaveBeenCalledWith(categories, undefined, 'desc');
});
it('应该支持数量限制', async () => {
const limit = 1;
(contentService.getNews as jest.Mock).mockResolvedValue([mockNewsData[0]]);
const { result } = renderHook(() => useNews(undefined, limit));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([mockNewsData[0]]);
expect(contentService.getNews).toHaveBeenCalledWith(undefined, limit, 'desc');
});
it('应该支持排序', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result } = renderHook(() => useNews(undefined, undefined, 'asc'));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getNews).toHaveBeenCalledWith(undefined, undefined, 'asc');
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取新闻失败');
(contentService.getNews as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useNews());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.news).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getNews as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useNews());
expect(result.current.loading).toBe(true);
expect(result.current.news).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在参数变化时重新获取数据', async () => {
(contentService.getNews as jest.Mock).mockResolvedValue(mockNewsData);
const { result, rerender } = renderHook(
({ categories }) => useNews(categories),
{ initialProps: { categories: ['公司新闻'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getNews).toHaveBeenCalledTimes(1);
rerender({ categories: ['产品发布'] });
await waitFor(() => {
expect(contentService.getNews).toHaveBeenCalledTimes(2);
});
});
});
+121
View File
@@ -0,0 +1,121 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useProducts } from './use-products';
import { contentService } from '@/lib/api/services';
import { Product } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useProducts', () => {
const mockProductsData: Product[] = [
{
id: '1',
title: '测试产品1',
description: '测试描述1',
category: '软件产品',
features: ['功能1', '功能2'],
benefits: ['优势1', '优势2'],
slug: 'test-product-1',
},
{
id: '2',
title: '测试产品2',
description: '测试描述2',
category: '云服务',
features: ['功能3', '功能4'],
benefits: ['优势3', '优势4'],
slug: 'test-product-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取产品数据', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual(mockProductsData);
expect(result.current.error).toBeNull();
expect(contentService.getProducts).toHaveBeenCalledWith(undefined);
});
it('应该支持精选产品筛选', async () => {
const featuredIds = ['1'];
(contentService.getProducts as jest.Mock).mockResolvedValue([mockProductsData[0]]);
const { result } = renderHook(() => useProducts(featuredIds));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual([mockProductsData[0]]);
expect(contentService.getProducts).toHaveBeenCalledWith(featuredIds);
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取产品失败');
(contentService.getProducts as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useProducts());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getProducts as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useProducts());
expect(result.current.loading).toBe(true);
expect(result.current.products).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在featuredIds变化时重新获取数据', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result, rerender } = renderHook(
({ featuredIds }) => useProducts(featuredIds),
{ initialProps: { featuredIds: ['1'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getProducts).toHaveBeenCalledTimes(1);
rerender({ featuredIds: ['2'] });
await waitFor(() => {
expect(contentService.getProducts).toHaveBeenCalledTimes(2);
});
});
it('应该处理空数组featuredIds', async () => {
(contentService.getProducts as jest.Mock).mockResolvedValue(mockProductsData);
const { result } = renderHook(() => useProducts([]));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.products).toEqual(mockProductsData);
expect(contentService.getProducts).toHaveBeenCalledWith([]);
});
});
+121
View File
@@ -0,0 +1,121 @@
import { renderHook, waitFor } from '@testing-library/react';
import { useServices } from './use-services';
import { contentService } from '@/lib/api/services';
import { Service } from '@/lib/api/types';
jest.mock('@/lib/api/services');
describe('useServices', () => {
const mockServicesData: Service[] = [
{
id: '1',
title: '测试服务1',
description: '测试描述1',
icon: 'Code',
features: ['功能1', '功能2'],
benefits: ['优势1', '优势2'],
slug: 'test-service-1',
},
{
id: '2',
title: '测试服务2',
description: '测试描述2',
icon: 'Cloud',
features: ['功能3', '功能4'],
benefits: ['优势3', '优势4'],
slug: 'test-service-2',
},
];
beforeEach(() => {
jest.clearAllMocks();
});
it('应该成功获取服务数据', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result } = renderHook(() => useServices());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual(mockServicesData);
expect(result.current.error).toBeNull();
expect(contentService.getServices).toHaveBeenCalledWith(undefined);
});
it('应该支持服务ID筛选', async () => {
const ids = ['1'];
(contentService.getServices as jest.Mock).mockResolvedValue([mockServicesData[0]]);
const { result } = renderHook(() => useServices(ids));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual([mockServicesData[0]]);
expect(contentService.getServices).toHaveBeenCalledWith(ids);
});
it('应该处理获取失败的情况', async () => {
const error = new Error('获取服务失败');
(contentService.getServices as jest.Mock).mockRejectedValue(error);
const { result } = renderHook(() => useServices());
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual([]);
expect(result.current.error).toEqual(error);
});
it('应该在加载时设置loading状态', () => {
(contentService.getServices as jest.Mock).mockImplementation(
() => new Promise(() => {})
);
const { result } = renderHook(() => useServices());
expect(result.current.loading).toBe(true);
expect(result.current.services).toEqual([]);
expect(result.current.error).toBeNull();
});
it('应该在ids变化时重新获取数据', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result, rerender } = renderHook(
({ ids }) => useServices(ids),
{ initialProps: { ids: ['1'] } }
);
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(contentService.getServices).toHaveBeenCalledTimes(1);
rerender({ ids: ['2'] });
await waitFor(() => {
expect(contentService.getServices).toHaveBeenCalledTimes(2);
});
});
it('应该处理空数组ids', async () => {
(contentService.getServices as jest.Mock).mockResolvedValue(mockServicesData);
const { result } = renderHook(() => useServices([]));
await waitFor(() => {
expect(result.current.loading).toBe(false);
});
expect(result.current.services).toEqual(mockServicesData);
expect(contentService.getServices).toHaveBeenCalledWith([]);
});
});