File size: 1,556 Bytes
28add9e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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: {} });
  });
});