import { render } from '@testing-library/react'; import { describe, it, expect, afterEach } from 'vitest'; import { HiloConductor } from '../HiloConductor'; describe('', () => { const matchMediaOriginal = window.matchMedia; afterEach(() => { window.matchMedia = matchMediaOriginal; }); it('is decorative: hidden from assistive tech', () => { const { container } = render(); const svg = container.querySelector('svg'); expect(svg).toHaveAttribute('aria-hidden', 'true'); expect(svg).toHaveAttribute('focusable', 'false'); }); it('draws a single continuous thread', () => { const { container } = render(); expect(container.querySelectorAll('path')).toHaveLength(1); }); it('travels with the document instead of sticking to the viewport', () => { const { container } = render(); // `fixed` would read as an ornament pinned to the window; the thread // belongs to the page and must scroll with it. expect(container.firstElementChild?.className).toContain('absolute'); expect(container.firstElementChild?.className).not.toContain('fixed'); }); it('stays out of the way of pointer input', () => { const { container } = render(); expect(container.firstElementChild?.className).toContain('pointer-events-none'); }); it('sits in the margin band beside the column, never over it', () => { const { container } = render(); const svg = container.querySelector('svg'); // `right-full` (`right: 100%`) anchors the thread's right edge to the // left edge of the `max-w-content` column it shares a parent with — // structurally outside the column, not merely visually offset within it. // Read via getAttribute: on an SVGElement, `.className` is an // SVGAnimatedString, not a plain string. expect(svg?.getAttribute('class')).toContain('right-full'); }); it('declares a stacking level that beats section roots but stays under the header', () => { const { container } = render(); const wrapperClass = container.firstElementChild?.className ?? ''; // Section roots never set z-index (auto), so any explicit positive value // paints above them regardless of whether the section itself happens to // be `position: relative` or `static` — that inconsistency was the bug. // The sticky header is z-50 and must still win. expect(wrapperClass).toMatch(/\bz-\d+\b/); expect(wrapperClass).not.toContain('z-50'); }); it('is hidden below the breakpoint that guarantees a safe margin', () => { const { container } = render(); const wrapperClass = container.firstElementChild?.className ?? ''; // No safe band exists under the content column's own breakpoint; hiding // it there is what keeps it from ever creeping over the text. expect(wrapperClass).toContain('hidden'); expect(wrapperClass).toContain('2xl:block'); }); it('renders a genuine plain path under reduced motion, not a neutralised motion.path', () => { window.matchMedia = (query: string) => ({ matches: true, media: query, onchange: null, addEventListener: () => {}, removeEventListener: () => {}, addListener: () => {}, removeListener: () => {}, dispatchEvent: () => false, }) as unknown as MediaQueryList; const { container } = render(); const path = container.querySelector('path'); // motion.path stamps stroke-dasharray/stroke-dashoffset onto the DOM even // without initial/animate — the dash-scaffolding trap that leaves the // thread invisible for reduced-motion users. A real plain never // gets them, so their absence proves the branches weren't collapsed back // into one. expect(path).not.toHaveAttribute('stroke-dasharray'); expect(path).not.toHaveAttribute('stroke-dashoffset'); }); });