import { describe, expect, it } from 'vitest'; import { type LabelBox, stackLabels, type WantedLabel } from '../../src/viewport/labelRows'; const H = 12; const STEP = 13; function label(id: string, x: number, y: number, w = 40): WantedLabel { return { id, x, y, w, h: H }; } function overlap(a: LabelBox, b: LabelBox): boolean { return a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h; } describe('stackLabels', () => { it('leaves names that do not touch on their own row', () => { const tops = stackLabels([label('a', 0, 0), label('b', 100, 0), label('c', 0, 20)], [], STEP); expect([...tops]).toEqual([ ['a', 0], ['b', 0], ['c', 20], ]); }); it('moves the lower of two colliding names a row down', () => { const tops = stackLabels([label('low', 10, 4), label('high', 0, 0)], [], STEP); expect(tops.get('high')).toBe(0); expect(tops.get('low')).toBe(4 + STEP); }); it('stacks names on one spot row by row: down, up, two down, two up', () => { const names = ['a', 'b', 'c', 'd', 'e'].map((id) => label(id, 0, 0)); const tops = stackLabels(names, [], STEP); expect(names.map((n) => tops.get(n.id))).toEqual([0, STEP, -STEP, 2 * STEP, -2 * STEP]); }); it('keeps names off icons they would cover', () => { // Covers the row above, the name's own and the next one down: two rows down is the first free. const icon = { x: 20, y: -6, w: 18, h: 30 }; const tops = stackLabels([label('a', 0, 0)], [icon], STEP); const box = { ...label('a', 0, 0), y: tops.get('a')! }; expect(overlap(box, icon)).toBe(false); expect(tops.get('a')).toBe(2 * STEP); }); it('leaves no two names overlapping in a crowd', () => { const names = Array.from({ length: 30 }, (_, k) => label(`n${k}`, (k * 7) % 50, (k * 5) % 30, 30 + (k % 4) * 10), ); const tops = stackLabels(names, [], STEP); const boxes = names.map((n) => ({ ...n, y: tops.get(n.id)! })); for (let i = 0; i < boxes.length; i++) for (let j = i + 1; j < boxes.length; j++) expect(overlap(boxes[i]!, boxes[j]!)).toBe(false); // Only up or down: every name stays where its icon wants it across. for (const box of boxes) expect(box.x).toBe(names.find((n) => n.id === box.id)!.x); }); });