From ffb625a124d960f655056c7a84962536dceec6b4 Mon Sep 17 00:00:00 2001 From: dazzatronus Date: Wed, 11 Mar 2026 10:20:44 +1100 Subject: [PATCH] fix: add sentinel value to track positions for insert-after-last insertion --- .../interaction/interaction-calculations.ts | 1 + tests/interaction-calculations.test.ts | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/components/timeline/interaction/interaction-calculations.ts b/src/components/timeline/interaction/interaction-calculations.ts index 56f3ddf..a078304 100644 --- a/src/components/timeline/interaction/interaction-calculations.ts +++ b/src/components/timeline/interaction/interaction-calculations.ts @@ -93,6 +93,7 @@ export function buildTrackYPositions(tracks: readonly TrackState[]): number[] { positions.push(y); y += getTrackHeight(track.primaryAssetType); } + positions.push(y); // sentinel: total height for "insert after last track" return positions; } diff --git a/tests/interaction-calculations.test.ts b/tests/interaction-calculations.test.ts index 37979eb..abced86 100644 --- a/tests/interaction-calculations.test.ts +++ b/tests/interaction-calculations.test.ts @@ -118,13 +118,13 @@ describe("formatDragTime", () => { describe("Track Y Position Calculations", () => { describe("buildTrackYPositions", () => { - it("returns empty array for no tracks", () => { - expect(buildTrackYPositions([])).toEqual([]); + it("returns sentinel-only array for no tracks", () => { + expect(buildTrackYPositions([])).toEqual([0]); }); it("calculates positions for image tracks (72px height)", () => { const tracks: TrackState[] = [createMockTrack([createMockClip(0, 1)], "image"), createMockTrack([createMockClip(0, 1, "image", 1)], "image")]; - expect(buildTrackYPositions(tracks)).toEqual([0, 72]); + expect(buildTrackYPositions(tracks)).toEqual([0, 72, 144]); }); it("calculates positions for audio tracks (48px height)", () => { @@ -132,7 +132,7 @@ describe("Track Y Position Calculations", () => { createMockTrack([createMockClip(0, 1, "audio")], "audio"), createMockTrack([createMockClip(0, 1, "audio", 1)], "audio") ]; - expect(buildTrackYPositions(tracks)).toEqual([0, 48]); + expect(buildTrackYPositions(tracks)).toEqual([0, 48, 96]); }); it("handles mixed track types", () => { @@ -141,20 +141,25 @@ describe("Track Y Position Calculations", () => { createMockTrack([createMockClip(0, 1, "audio", 1)], "audio") // 48px ]; const positions = buildTrackYPositions(tracks); - expect(positions).toEqual([0, 72]); // Second track starts at 72 + expect(positions).toEqual([0, 72, 120]); // Sentinel at 72 + 48 = 120 }); }); describe("getTrackYPosition", () => { it("returns correct position from cache", () => { - const cache = [0, 60, 120]; + const cache = [0, 60, 120, 180]; // includes sentinel expect(getTrackYPosition(0, cache)).toBe(0); expect(getTrackYPosition(1, cache)).toBe(60); expect(getTrackYPosition(2, cache)).toBe(120); }); + it("returns sentinel position for insert-after-last index", () => { + const cache = [0, 60, 120]; // 2 tracks + sentinel at 120 + expect(getTrackYPosition(2, cache)).toBe(120); + }); + it("returns 0 for out of bounds index", () => { - const cache = [0, 60]; + const cache = [0, 60, 120]; // 2 tracks + sentinel expect(getTrackYPosition(5, cache)).toBe(0); }); });