fix(test): 修复测试环境问题
ci/woodpecker/push/woodpecker Pipeline failed

1. jest.setup.js:
   - 添加 Request/Response/Headers 全局对象 mock
   - 解决 'Request is not defined' 错误

2. .eslintrc.json:
   - 将 jest.setup.js 添加到忽略列表

3. shared-mocks.tsx:
   - 添加 ArrowUp 图标 mock

4. back-to-top.test.tsx:
   - 重写测试使用 import 语法
   - 使用 fireEvent.scroll 触发滚动事件
   - 修复组件渲染测试
This commit is contained in:
张翔
2026-03-29 14:50:09 +08:00
parent e0ca8235c8
commit 7cbb7a9ac8
12 changed files with 2289 additions and 20 deletions
+1
View File
@@ -84,6 +84,7 @@ export const mockLucideReact = () => {
jest.mock('lucide-react', () => ({
ArrowRight: () => <span data-testid="arrow-right" />,
ArrowLeft: () => <span data-testid="arrow-left" />,
ArrowUp: () => <span data-testid="arrow-up" />,
Shield: () => <span data-testid="shield-icon" />,
Zap: () => <span data-testid="zap-icon" />,
Award: () => <span data-testid="award-icon" />,
+43 -19
View File
@@ -1,43 +1,58 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import React from 'react';
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import { BackToTop } from './back-to-top';
// Mock useReducedMotion
jest.mock('@/hooks/use-reduced-motion', () => ({
useReducedMotion: () => false,
}));
// Mock AnimatePresence to always render children
jest.mock('framer-motion', () => ({
...jest.requireActual('framer-motion'),
AnimatePresence: ({ children }: { children: React.ReactNode }) => <>{children}</>,
motion: {
button: ({ children, ...props }: React.ComponentProps<'button'>) => <button {...props}>{children}</button>,
},
useAnimation: () => ({
start: jest.fn(),
stop: jest.fn(),
}),
useMotionValue: () => ({
get: jest.fn(),
set: jest.fn(),
}),
}));
jest.mock('lucide-react', () => ({
ArrowUp: () => <span data-testid="arrow-up-icon" />,
}));
describe('BackToTop', () => {
let scrollYValue = 0;
const originalScrollY = Object.getOwnPropertyDescriptor(window, 'scrollY');
const originalScrollTo = window.scrollTo;
beforeEach(() => {
jest.clearAllMocks();
scrollYValue = 0;
Object.defineProperty(window, 'scrollY', {
get: () => scrollYValue,
configurable: true,
});
window.scrollTo = jest.fn();
window.addEventListener = jest.fn((event, handler) => {
if (event === 'scroll') {
// 模拟滚动事件触发
(handler as EventListener)(new Event('scroll'));
}
});
});
afterEach(() => {
if (originalScrollY) {
Object.defineProperty(window, 'scrollY', originalScrollY);
}
window.scrollTo = originalScrollTo;
});
it('should not render when scroll position is less than 500px', () => {
scrollYValue = 0;
const { container } = render(<BackToTop />);
expect(container.firstChild).toBeNull();
expect(container.querySelector('button')).toBeNull();
});
it('should render button when scroll position is more than 500px', async () => {
@@ -45,8 +60,12 @@ describe('BackToTop', () => {
render(<BackToTop />);
act(() => {
fireEvent.scroll(window);
});
await waitFor(() => {
const button = screen.getByRole('button', { name: /返回顶部/i });
const button = screen.queryByRole('button', { name: /返回顶部/i });
expect(button).toBeInTheDocument();
});
});
@@ -56,11 +75,14 @@ describe('BackToTop', () => {
render(<BackToTop />);
await waitFor(() => {
const button = screen.getByRole('button', { name: /返回顶部/i });
fireEvent.click(button);
act(() => {
fireEvent.scroll(window);
});
const button = await screen.findByRole('button', { name: /返回顶部/i });
fireEvent.click(button);
expect(window.scrollTo).toHaveBeenCalledWith({
top: 0,
behavior: 'smooth',
@@ -72,10 +94,12 @@ describe('BackToTop', () => {
render(<BackToTop />);
await waitFor(() => {
const button = screen.getByRole('button');
expect(button).toHaveAttribute('aria-label', '返回顶部');
expect(button).toHaveAttribute('title', '返回顶部');
act(() => {
fireEvent.scroll(window);
});
const button = await screen.findByRole('button');
expect(button).toHaveAttribute('aria-label', '返回顶部');
expect(button).toHaveAttribute('title', '返回顶部');
});
});