fix(align): remove whole auto run between manual anchors on edit

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Haiku 5.5 committed 2026-10-30 06:30:48 +01:00
1 parent 1ff29f94cd
commit ca5942956a
2 files changed
+40 -18

No files matched your search

+30 -9
View File
@@ -108,17 +108,32 @@ describe("addManual", () => {
expect(addManual([], { midiSec: 2, audioSec: 2.5 })).toEqual([M(2, 2.5)]);
});
test("removes only the auto immediate neighbours, keeps manual ones and farther autos", () => {
// M0 | A2 | A4 | M8 ; insert manual at 6 -> A4 (immediate left, auto) goes; A2 stays (not adjacent); M8 stays
test("removes the whole contiguous auto run up to the nearest manual on each side", () => {
// M0 | A2 | A4 | M8 ; insert manual at 6 -> both autos between M0 and M8 go (the run stops at manuals)
const base = [M(0, 0), A(2, 2), A(4, 4.5), M(8, 8)];
expect(addManual(base, { midiSec: 6, audioSec: 6.2 })).toEqual([
M(0, 0),
A(2, 2),
M(6, 6.2),
M(8, 8),
expect(addManual(base, { midiSec: 6, audioSec: 6.2 })).toEqual([M(0, 0), M(6, 6.2), M(8, 8)]);
});
test("autos every 2 s: adding a manual at 20 s removes autos between manuals at 10 s and 30 s only", () => {
const autos = [2, 4, 6, 8, 12, 14, 16, 18, 22, 24, 26, 28].map((t) => A(t, t));
const base = [...autos.slice(0, 4), M(10, 10), M(30, 30), ...autos.slice(4)].sort((x, y) => x.midiSec - y.midiSec);
// autos 12..28 sit between the manuals at 10 and 30 and go; 2..8 (before 10) are kept
expect(addManual(base, { midiSec: 20, audioSec: 20 })).toEqual([
A(2, 2), A(4, 4), A(6, 6), A(8, 8), M(10, 10), M(20, 20), M(30, 30),
]);
});
test("moving a manual anchor across several autos succeeds and removes the crossed autos", () => {
// M0 | A10 | A20 | M30 | A40 | M60 ; move M30 -> midi 45 (crosses A40, stays before M60)
const base = [M(0, 0), A(10, 10), A(20, 20), M(30, 30), A(40, 40), M(60, 60)];
expect(moveAnchor(base, 3, { midiSec: 45, audioSec: 45 })).toEqual([M(0, 0), M(45, 45), M(60, 60)]);
});
test("auto runs are removed even when they are the only anchors besides the moved one", () => {
const base = [A(2, 2), A(4, 4), A(6, 6)];
expect(addManual(base, { midiSec: 5, audioSec: 5 })).toEqual([M(5, 5)]);
});
test("keeps manual neighbours untouched", () => {
const base = [M(0, 0), M(8, 8)];
expect(addManual(base, { midiSec: 4, audioSec: 4 })).toEqual([M(0, 0), M(4, 4), M(8, 8)]);
@@ -158,9 +173,15 @@ describe("moveAnchor", () => {
expect(() => moveAnchor(base, 1, { midiSec: 4, audioSec: 8.5 })).toThrow();
});
test("throws when moved past an auto neighbour", () => {
test("moving past an auto neighbour is allowed; the crossed auto is removed", () => {
const base = [M(0, 0), A(4, 4), M(8, 8)];
expect(() => moveAnchor(base, 2, { midiSec: 3, audioSec: 3 })).toThrow();
expect(moveAnchor(base, 2, { midiSec: 3, audioSec: 3 })).toEqual([M(0, 0), M(3, 3)]);
});
test("crossing a manual neighbour in audio time throws even with an auto between", () => {
const base = [M(0, 0), A(4, 4), M(8, 8), A(12, 12), M(16, 16)];
// Moving M(8) to audio 17 puts it past its right manual neighbour M(16) (audio 16); autos between are ignored.
expect(() => moveAnchor(base, 2, { midiSec: 10, audioSec: 17 })).toThrow();
});
test("throws on a bad index", () => {