beacon / frontend /src /lib /align.test.js
kiyer's picture
feat: pure note-alignment layout math
28add9e
Raw
History Blame Contribute Delete
1.56 kB
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: {} });
});
});