import { render } from '@testing-library/react';
import { describe, it, expect, afterEach } from 'vitest';
import { HiloConductor } from '../HiloConductor';
import { BARRAS_SIMBOLO, VIEWBOX_SIMBOLO } from '@/lib/simbolo';
const preferirMenosMovimiento = () => {
window.matchMedia = (query: string) =>
({
matches: true,
media: query,
onchange: null,
addEventListener: () => {},
removeEventListener: () => {},
addListener: () => {},
removeListener: () => {},
dispatchEvent: () => false,
}) as unknown as MediaQueryList;
};
const simboloFinal = (container: HTMLElement) =>
Array.from(container.querySelectorAll('svg')).find(
(svg) => svg.getAttribute('viewBox') === VIEWBOX_SIMBOLO,
);
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('runs down the centre of the column, not in a side gutter', () => {
const { container } = render();
const cls = container.querySelector('svg')?.getAttribute('class') ?? '';
// `left-1/2 -translate-x-1/2` anchors the thread to the column's centre;
// the old `right-full` banished it to the left gutter, which is why the
// thread read as off to the side instead of as the page's spine.
expect(cls).toContain('left-1/2');
expect(cls).toContain('-translate-x-1/2');
expect(cls).not.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('renders on every breakpoint, not only on very wide screens', () => {
const { container } = render();
const wrapperClass = container.firstElementChild?.className ?? '';
// The thread is the brand's through-line; it belongs in the middle on a
// phone too. Legibility is handled by layering it behind the text, not by
// hiding it below a breakpoint.
expect(wrapperClass).not.toContain('hidden');
expect(wrapperClass).not.toContain('2xl:block');
});
it('keeps its declared weight on screen despite the non-uniform viewBox scale', () => {
const { container } = render();
// The band is stretched with preserveAspectRatio="none": y over the whole
// document, x over a couple hundred pixels. That squeezes the stroke of a
// near-vertical line by the x factor alone, so a declared 1.5 rendered as
// well under 1 real pixel — a ghost hairline. non-scaling-stroke pins the
// width to the value we actually chose.
expect(container.querySelector('path')).toHaveAttribute('vector-effect', 'non-scaling-stroke');
});
it('sizes the band with an explicit height, never a top+bottom pair', () => {
const { container } = render();
const cls = container.querySelector('svg')?.getAttribute('class') ?? '';
// An