新增 hero-particle-field 组件作为 Hero L3 层:品牌红点缀、Canvas + rAF 仅在 ~600ms 入场窗运行后冻结为静态帧,遵守「无持续循环动画」动效铁律; prefers-reduced-motion 退化为一次性静态帧。固定种子 PRNG 保证视觉快照可复现。 同步更新 home-content-v15 接入与单测、home 视觉基线。 Co-Authored-By: 阿睿 <workbuddy@tencent.com>
35 lines
1.4 KiB
TypeScript
35 lines
1.4 KiB
TypeScript
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(<HeroParticleField />);
|
|
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(<HeroParticleField />)).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(<HeroParticleField />)).not.toThrow();
|
|
ctxSpy.mockRestore();
|
|
});
|
|
});
|