import { describe, it, expect, jest } from '@jest/globals'; import { render, screen } from '@testing-library/react'; import '@testing-library/jest-dom'; import HeroParticleField from './hero-particle-field'; jest.mock('@/hooks/use-reduced-motion', () => ({ useReducedMotion: jest.fn(() => false), })); describe('HeroParticleField - 动态粒子层(入场浮现后静止)', () => { it('renders an aria-hidden, non-interactive canvas layer', () => { render(); const canvas = screen.getByTestId('hero-particle-field'); expect(canvas.tagName).toBe('CANVAS'); expect(canvas).toHaveAttribute('aria-hidden', 'true'); expect(canvas.className).toContain('pointer-events-none'); }); it('does not throw under reduced motion (degrades to a single static frame)', () => { const { useReducedMotion } = require('@/hooks/use-reduced-motion'); (useReducedMotion as jest.Mock).mockReturnValue(true); expect(() => render()).not.toThrow(); // 静态降级下仍渲染 canvas(无 rAF 循环,仅一次绘制) expect(screen.getByTestId('hero-particle-field').tagName).toBe('CANVAS'); }); it('bails safely when no 2d context is available (jsdom / SSR guard)', () => { const ctxSpy = jest .spyOn(HTMLCanvasElement.prototype, 'getContext') .mockReturnValue(null as any); expect(() => render()).not.toThrow(); ctxSpy.mockRestore(); }); });