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');
});
});