import { describe, it, expect } from 'vitest'; import { computeLayout } from './align'; // items: [{ id, naturalTop, noteHeight }] in document order. // naturalTop = paragraph's top with previously applied spacers subtracted (README algorithm). const items = [ { id: 'p1', naturalTop: 0, noteHeight: 300 }, // tall note over a short paragraph { id: 'p2', naturalTop: 100, noteHeight: 50 }, { id: 'p3', naturalTop: 200, noteHeight: 50 }, ]; describe('computeLayout', () => { it('pins note tops to paragraph tops and pushes later paragraphs down', () => { const { tops, spacers } = computeLayout(items, 26); expect(tops.p1).toBe(0); // p1's note ends at 300; +26 gap => p2 must start at 326; naturally at 100 => spacer 226 expect(spacers.p1).toBe(226); expect(tops.p2).toBe(326); // p2's note ends at 376; +26 => 402; p3 naturally at 200+226=426 >= 402 => no spacer expect(spacers.p2).toBe(0); expect(tops.p3).toBe(426); expect(spacers.p3).toBe(0); }); it('is idempotent: same inputs give identical outputs', () => { const a = computeLayout(items, 26); const b = computeLayout(items, 26); expect(b).toEqual(a); }); it('no spacers when notes are short', () => { const { spacers } = computeLayout( [{ id: 'p1', naturalTop: 0, noteHeight: 40 }, { id: 'p2', naturalTop: 100, noteHeight: 40 }], 26); expect(spacers.p1).toBe(0); }); it('empty items returns empty objects without crashing', () => { expect(computeLayout([], 26)).toEqual({ tops: {}, spacers: {} }); }); });