Skip to content

Commit 500fa45

Browse files
committed
test: add regression tests for RhythmActions timing invariants
1 parent cd58f16 commit 500fa45

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

js/turtleactions/__tests__/RhythmActions.test.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,33 @@ describe("setupRhythmActions", () => {
293293

294294
expect(value).toBe(0);
295295
});
296+
it("respects getNoteValue priority hierarchy", () => {
297+
// Simulate active note block
298+
targetTurtle.singer.inNoteBlock = [7];
299+
300+
// Provide ALL possible sources
301+
targetTurtle.singer.noteValue = { 7: 0.25 }; // highest priority
302+
targetTurtle.singer.lastNotePlayed = [null, 8]; // second priority
303+
targetTurtle.singer.notePitches = { 7: ["C"] }; // third priority
304+
targetTurtle.singer.noteBeat = { 7: 4 };
305+
306+
const value = Singer.RhythmActions.getNoteValue(0);
307+
308+
// noteValue = 0.25 -> internally inverted twice -> returns 0.25
309+
expect(value).toBe(0.25);
310+
});
311+
it("falls back to noteBeat when noteValue and lastNotePlayed are absent", () => {
312+
targetTurtle.singer.inNoteBlock = [5];
313+
314+
targetTurtle.singer.noteValue = {};
315+
targetTurtle.singer.lastNotePlayed = null;
316+
targetTurtle.singer.notePitches = { 5: ["C"] };
317+
targetTurtle.singer.noteBeat = { 5: 4 };
318+
319+
const value = Singer.RhythmActions.getNoteValue(0);
320+
321+
expect(value).toBe(0.25); // 1 / 4
322+
});
296323

297324
describe("doTie", () => {
298325
// Store original implementations to restore after tests
@@ -463,4 +490,65 @@ describe("setupRhythmActions", () => {
463490
);
464491
});
465492
});
493+
494+
it("restores beatFactor after dot and multiply lifecycle", () => {
495+
let dotListener, multiplyListener;
496+
497+
activity.logo.setTurtleListener = jest.fn((_, name, cb) => {
498+
if (name.includes("_dot_")) dotListener = cb;
499+
if (name.includes("_multiplybeat_")) multiplyListener = cb;
500+
});
501+
502+
targetTurtle.singer.beatFactor = 1;
503+
targetTurtle.singer.dotCount = 0;
504+
505+
Singer.RhythmActions.doRhythmicDot(1, 0, 1);
506+
Singer.RhythmActions.multiplyNoteValue(2, 0, 1);
507+
508+
const mutated = targetTurtle.singer.beatFactor;
509+
expect(mutated).not.toBe(1);
510+
511+
multiplyListener();
512+
dotListener();
513+
514+
expect(targetTurtle.singer.beatFactor).toBeCloseTo(1);
515+
});
516+
it("maintains stable beatFactor after repeated dots", () => {
517+
targetTurtle.singer.beatFactor = 1;
518+
targetTurtle.singer.dotCount = 0;
519+
520+
Singer.RhythmActions.doRhythmicDot(1, 0, 1);
521+
Singer.RhythmActions.doRhythmicDot(1, 0, 1);
522+
523+
expect(targetTurtle.singer.dotCount).toBe(2);
524+
expect(targetTurtle.singer.beatFactor).toBeGreaterThan(0);
525+
});
526+
it("treats osctime differently from note duration", () => {
527+
const enqueue = jest.fn();
528+
Singer.processNote.mockClear();
529+
530+
Singer.RhythmActions.playNote(2, "note", 0, 1, enqueue);
531+
let listener = activity.logo.setTurtleListener.mock.calls[0][2];
532+
listener();
533+
534+
const noteCall = Singer.processNote.mock.calls[0];
535+
536+
Singer.processNote.mockClear();
537+
538+
Singer.RhythmActions.playNote(500, "osctime", 0, 1, enqueue);
539+
listener = activity.logo.setTurtleListener.mock.calls[1][2];
540+
listener();
541+
542+
const oscCall = Singer.processNote.mock.calls[0];
543+
544+
expect(noteCall[2]).toBe(false);
545+
expect(oscCall[2]).toBe(true);
546+
});
547+
it("activates multipleVoices for nested notes", () => {
548+
targetTurtle.singer.inNoteBlock = [10];
549+
550+
Singer.RhythmActions.playNote(1, "note", 0, 1, jest.fn());
551+
552+
expect(targetTurtle.singer.multipleVoices).toBe(true);
553+
});
466554
});

0 commit comments

Comments
 (0)