import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import { ScrollProgress } from './scroll-progress'; // Mock useReducedMotion jest.mock('@/hooks/use-reduced-motion', () => ({ useReducedMotion: () => false, })); describe('ScrollProgress', () => { beforeEach(() => { jest.clearAllMocks(); // Mock document/scroll geometry for predictable progress calculation Object.defineProperty(document.documentElement, 'scrollHeight', { value: 1000, configurable: true, }); Object.defineProperty(window, 'innerHeight', { value: 500, configurable: true, }); Object.defineProperty(window, 'scrollY', { value: 0, configurable: true, writable: true, }); }); it('should always render the progress container', () => { const { container } = render(); // ScrollProgress 始终渲染(带 aria-hidden="true") expect(container.firstChild).not.toBeNull(); }); it('should have aria-hidden="true" for decorative purposes', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper).toHaveAttribute('aria-hidden', 'true'); }); it('should apply fixed positioning class', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper).toHaveClass('fixed'); expect(wrapper).toHaveClass('pointer-events-none'); }); it('should render inner progress bar div', () => { const { container } = render(); const innerBar = (container.firstChild as HTMLElement).firstChild as HTMLElement; expect(innerBar).toBeInTheDocument(); // 默认 progress 为 0,使用 transform scaleX 实现宽度变化 expect(innerBar.style.width).toBe('100%'); expect(innerBar.style.transform).toBe('scaleX(0)'); expect(innerBar.style.transformOrigin).toBe('left'); }); it('should apply default height of 2px', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper.style.height).toBe('2px'); }); it('should support custom height', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper.style.height).toBe('4px'); }); it('should apply default top position', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper.style.top).toBe('0px'); }); it('should support bottom position', () => { const { container } = render(); const wrapper = container.firstChild as HTMLElement; expect(wrapper.style.bottom).toBe('0px'); }); it('should use brand color by default', () => { const { container } = render(); const innerBar = (container.firstChild as HTMLElement).firstChild as HTMLElement; expect(innerBar.style.background).toBe('var(--color-brand)'); }); it('should support custom color', () => { const { container } = render(); const innerBar = (container.firstChild as HTMLElement).firstChild as HTMLElement; // jsdom 将 #ff0000 转换为 rgb(255, 0, 0) expect(innerBar.style.background).toContain('255'); expect(innerBar.style.background).toContain('0'); }); });