'use strict'; /** * WAVE D1 motion primitives. * * The property that matters most here is not how they look — it is that they * never withhold content. A reveal primitive that hides a row until an observer * fires will, on any browser or preference where the observer never fires, hide * the row forever. */ const m = require('../../web/src/lib/motion'); describe('reduced motion is honoured, not softened', () => { it('assumes reduced motion server-side', () => { expect(m.prefersReducedMotion()).toBe(true); }); it('nudge does nothing and returns a no-op cleanup', () => { const el = { style: {} }; const stop = m.nudge(el); expect(el.style.transform).toBeUndefined(); expect(typeof stop).toBe('function'); expect(() => stop()).not.toThrow(); }); it('boot stagger collapses to zero delay AND forces opacity', () => { // Not just delay 0: if a CSS animation supplies the opacity, skipping the // animation without forcing opacity leaves the row invisible. expect(m.bootStagger(9)).toEqual({ animationDelay: '0ms', opacity: 1 }); }); it('reveal fires IMMEDIATELY rather than waiting for an observer', () => { const el = {}; let revealed = null; m.revealOnIntersect(el, (x) => { revealed = x; }); expect(revealed).toBe(el); }); }); describe('the stagger cannot make content late', () => { it('is capped, so a long board does not delay its tail', () => { // Uncapped, row 40 would wait 1.1s and the stagger becomes the latency it // exists to disguise. const orig = m.prefersReducedMotion; expect(m.STAGGER_CAP_MS).toBeLessThanOrEqual(300); expect(m.STAGGER_STEP_MS * 40).toBeGreaterThan(m.STAGGER_CAP_MS); }); it('the nudge is short enough to read as acknowledgement, not latency', () => { expect(m.NUDGE_MS).toBeLessThanOrEqual(220); }); }); describe('row hover is pointer-only by construction', () => { it('exposes handlers rather than CSS, so touch cannot stick a hover state', () => { const h = m.rowHover(); expect(typeof h.onMouseEnter).toBe('function'); expect(typeof h.onMouseLeave).toBe('function'); }); it('clears the tint it set, never leaving a row highlighted', () => { const el = { style: { background: '' } }; const h = m.rowHover(); h.onMouseEnter({ currentTarget: el }); expect(el.style.background).toBeTruthy(); h.onMouseLeave({ currentTarget: el }); expect(el.style.background).toBe(''); }); it('survives a missing currentTarget rather than throwing mid-render', () => { const h = m.rowHover(); expect(() => h.onMouseEnter({})).not.toThrow(); expect(() => h.onMouseLeave({})).not.toThrow(); }); }); describe('no observer leak', () => { it('reveal returns an unobserve function in every path', () => { expect(typeof m.revealOnIntersect(null, () => {})).toBe('function'); expect(typeof m.revealOnIntersect({}, () => {})).toBe('function'); }); });