import { describe, expect, it } from 'vitest'; import { stackingOffset, parsePath, pointAt, NO_OFFSET } from './stacking.js'; describe('parsePath', () => { it('measures a straight line', () => { const path = parsePath('M 0 0 L 10 0'); expect(path.length).toBeCloseTo(10); }); it('measures a cubic curve', () => { const path = parsePath('M 0 0 C 20 -20 40 -20 60 0'); // Longer than the chord (60) but finite. expect(path.length).toBeGreaterThan(60); expect(path.length).toBeLessThan(80); }); it('handles relative commands', () => { const path = parsePath('m 0 0 l 10 0 l 0 10'); expect(path.length).toBeCloseTo(20); }); it('supports h/v/z', () => { const path = parsePath('M 0 0 H 10 V 10 Z'); // 10 right + 10 down + the diagonal back to the start (closes the triangle). expect(path.length).toBeCloseTo(10 + 10 + Math.sqrt(200)); }); }); describe('pointAt', () => { it('returns the start at distance 0 and end at full length', () => { const path = parsePath('M 0 0 L 10 0'); expect(pointAt(path, 0)).toMatchObject({ x: 0, y: 0 }); const end = pointAt(path, path.length); expect(end.x).toBeCloseTo(10); expect(end.y).toBeCloseTo(0); }); it('interpolates along the path', () => { const path = parsePath('M 0 0 L 10 0'); const mid = pointAt(path, 5); expect(mid.x).toBeCloseTo(5); expect(mid.angle).toBeCloseTo(0); }); }); describe('stackingOffset', () => { it('returns no offset without a curve', () => { expect(stackingOffset(undefined, 0, 3)).toBe(NO_OFFSET); expect(stackingOffset({ limit: 5 }, 0, 3)).toBe(NO_OFFSET); }); it('spreads parts evenly along a straight curve', () => { const offset = stackingOffset({ curve: 'M 0 0 L 100 0' }, 1, 3); // step = length / max(steps=1, 2) = 50; part 1 at 50. expect(offset.x).toBeCloseTo(50); expect(offset.y).toBeCloseTo(0); }); it('aligns to center', () => { const offset = stackingOffset({ curve: 'M 0 0 L 100 0', align: 'center' }, 0, 3); // span = 50 * 2 = 100; centered start = (100 - 100)/2 = 0. expect(offset.x).toBeCloseTo(0); }); it('aligns to end', () => { const offset = stackingOffset({ curve: 'M 0 0 L 100 0', align: 'end' }, 2, 3); // start = 100 - 100 = 0; part 2 at 100. expect(offset.x).toBeCloseTo(100); }); it('respects a positive limit (first n)', () => { const offset = stackingOffset({ curve: 'M 0 0 L 100 0', limit: 2 }, 2, 4); // Part 2 is beyond the first 2 shown -> not placed. expect(offset).toBe(NO_OFFSET); }); it('respects a negative limit (last n)', () => { // Last 2 of 4 are indices 2,3. Part 2 is the first shown. const offset = stackingOffset({ curve: 'M 0 0 L 100 0', limit: -2 }, 2, 4); expect(offset.x).toBeCloseTo(0); }); it('uses steps to densify the curve', () => { // steps=4, 3 parts -> step = 100 / max(4, 2) = 25. const offset = stackingOffset({ curve: 'M 0 0 L 100 0', steps: 4 }, 1, 3); expect(offset.x).toBeCloseTo(25); }); });