From 821e606011f135325e1816f0e36786a199ce3053 Mon Sep 17 00:00:00 2001 From: Justin Charles Date: Mon, 6 Jan 2025 19:36:08 +0530 Subject: [PATCH] Tests added for turtleactions Signed-off-by: Justin Charles --- js/turtleactions/DrumActions.js | 1 + js/turtleactions/MeterActions.js | 1 + js/turtleactions/PitchActions.js | 1 + js/turtleactions/ToneActions.js | 1 + js/turtleactions/VolumeActions.js | 1 + .../__tests__/DrumActions.test.js | 117 +++++++++++ .../__tests__/MeterActions.test.js | 159 ++++++++++++++ .../__tests__/PitchActions.test.js | 166 +++++++++++++++ .../__tests__/ToneActions.test.js | 197 ++++++++++++++++++ .../__tests__/VolumeActions.test.js | 147 +++++++++++++ 10 files changed, 791 insertions(+) create mode 100644 js/turtleactions/__tests__/DrumActions.test.js create mode 100644 js/turtleactions/__tests__/MeterActions.test.js create mode 100644 js/turtleactions/__tests__/PitchActions.test.js create mode 100644 js/turtleactions/__tests__/ToneActions.test.js create mode 100644 js/turtleactions/__tests__/VolumeActions.test.js diff --git a/js/turtleactions/DrumActions.js b/js/turtleactions/DrumActions.js index ab9e451f74..25bf7bf7b3 100644 --- a/js/turtleactions/DrumActions.js +++ b/js/turtleactions/DrumActions.js @@ -230,3 +230,4 @@ function setupDrumActions(activity) { } }; } +module.exports = setupDrumActions; \ No newline at end of file diff --git a/js/turtleactions/MeterActions.js b/js/turtleactions/MeterActions.js index 46bade792c..4517bdb123 100644 --- a/js/turtleactions/MeterActions.js +++ b/js/turtleactions/MeterActions.js @@ -369,3 +369,4 @@ function setupMeterActions(activity) { } }; } +module.exports = setupMeterActions; \ No newline at end of file diff --git a/js/turtleactions/PitchActions.js b/js/turtleactions/PitchActions.js index daa954e8fa..08101f209f 100644 --- a/js/turtleactions/PitchActions.js +++ b/js/turtleactions/PitchActions.js @@ -642,3 +642,4 @@ function setupPitchActions(activity) { } }; } +module.exports = setupPitchActions; \ No newline at end of file diff --git a/js/turtleactions/ToneActions.js b/js/turtleactions/ToneActions.js index 7d2527e6aa..4a15b0963b 100644 --- a/js/turtleactions/ToneActions.js +++ b/js/turtleactions/ToneActions.js @@ -488,3 +488,4 @@ function setupToneActions(activity) { } }; } +module.exports = setupToneActions; \ No newline at end of file diff --git a/js/turtleactions/VolumeActions.js b/js/turtleactions/VolumeActions.js index a111c90683..2ebfbf2284 100644 --- a/js/turtleactions/VolumeActions.js +++ b/js/turtleactions/VolumeActions.js @@ -293,3 +293,4 @@ function setupVolumeActions(activity) { } }; } +module.exports = setupVolumeActions; \ No newline at end of file diff --git a/js/turtleactions/__tests__/DrumActions.test.js b/js/turtleactions/__tests__/DrumActions.test.js new file mode 100644 index 0000000000..da1ad1bc5e --- /dev/null +++ b/js/turtleactions/__tests__/DrumActions.test.js @@ -0,0 +1,117 @@ +const setupDrumActions = require('../DrumActions'); + +describe('setupDrumActions', () => { + let activity; + let turtle; + let targetTurtle; + + beforeAll(() => { + global.Singer = { + DrumActions: {}, + processNote: jest.fn(), + }; + global.DEFAULTDRUM = 'defaultDrum'; + global.DRUMNAMES = { drum1: ['d1', 'drum1'], drum2: ['d2', 'drum2'] }; + global.NOISENAMES = { noise1: ['n1', 'noise1'] }; + global.DEFAULTVOLUME = 100; + global.last = jest.fn(array => array[array.length - 1]); + global._ = jest.fn(msg => msg); + }); + + beforeEach(() => { + activity = { + turtles: { + ithTurtle: jest.fn(), + }, + blocks: { + blockList: { 1: {} }, + }, + logo: { + setDispatchBlock: jest.fn(), + setTurtleListener: jest.fn(), + clearNoteParams: jest.fn(), + inRhythmRuler: false, + rhythmRuler: { Drums: [], Rulers: [] }, + }, + errorMsg: jest.fn(), + }; + + targetTurtle = { + singer: { + drumStyle: [], + inNoteBlock: [], + noteDrums: {}, + synthVolume: {}, + crescendoInitialVolume: {}, + noteBeatValues: {}, + beatFactor: 1, + pushedNote: false, + }, + }; + + activity.turtles.ithTurtle.mockReturnValue(targetTurtle); + setupDrumActions(activity); + }); + + it('should return the correct drum name', () => { + const result = Singer.DrumActions.GetDrumname('d1'); + expect(result).toBe('drum1'); + + const result2 = Singer.DrumActions.GetDrumname('unknown'); + expect(result2).toBe(DEFAULTDRUM); + }); + + it('should play a standalone drum sound', () => { + if (!targetTurtle.singer.noteDrums[1]) targetTurtle.singer.noteDrums[1] = []; + Singer.DrumActions.playDrum('d1', 0, 1); + expect(Singer.processNote).toHaveBeenCalledWith( + activity, + expect.any(Number), + false, + expect.anything(), + 0, + expect.any(Function) + ); + expect(activity.logo.clearNoteParams).toHaveBeenCalledWith(targetTurtle, 1, []); + expect(targetTurtle.singer.inNoteBlock).toContain(1); + expect(targetTurtle.singer.noteDrums[1]).toContain('drum1'); + expect(targetTurtle.singer.pushedNote).toBe(true); + }); + + + it('should add a drum to an existing note block', () => { + targetTurtle.singer.inNoteBlock.push(2); + targetTurtle.singer.noteDrums[2] = []; + Singer.DrumActions.playDrum('d1', 0, 1); + expect(targetTurtle.singer.noteDrums[2]).toContain('drum1'); + }); + + it('should set the drum style and add a listener', () => { + Singer.DrumActions.setDrum('d1', 0, 1); + expect(targetTurtle.singer.drumStyle).toContain('drum1'); + expect(activity.logo.setDispatchBlock).toHaveBeenCalledWith(1, 0, '_setdrum_0'); + expect(activity.logo.setTurtleListener).toHaveBeenCalledWith(0, '_setdrum_0', expect.any(Function)); + }); + + it('should map pitch to drum', () => { + Singer.DrumActions.mapPitchToDrum('d1', 0, 1); + expect(targetTurtle.singer.drumStyle).toContain('drum1'); + expect(activity.logo.setDispatchBlock).toHaveBeenCalledWith(1, 0, '_mapdrum_0'); + expect(activity.logo.setTurtleListener).toHaveBeenCalledWith(0, '_mapdrum_0', expect.any(Function)); + }); + + it('should play noise in a note block', () => { + targetTurtle.singer.inNoteBlock.push(2); + targetTurtle.singer.noteDrums[2] = []; + targetTurtle.singer.noteBeatValues[2] = []; + Singer.DrumActions.playNoise('n1', 0, 1); + expect(targetTurtle.singer.noteDrums[2]).toContain('noise1'); + expect(targetTurtle.singer.noteBeatValues[2]).toContain(1); + expect(targetTurtle.singer.pushedNote).toBe(true); + }); + + it('should throw an error for standalone noise block', () => { + Singer.DrumActions.playNoise('n1', 0, 1); + expect(activity.errorMsg).toHaveBeenCalledWith('Noise Block: Did you mean to use a Note block?', 1); + }); +}); diff --git a/js/turtleactions/__tests__/MeterActions.test.js b/js/turtleactions/__tests__/MeterActions.test.js new file mode 100644 index 0000000000..9a829017e0 --- /dev/null +++ b/js/turtleactions/__tests__/MeterActions.test.js @@ -0,0 +1,159 @@ +const setupMeterActions = require('../MeterActions'); + +describe('setupMeterActions', () => { + let activity; + let targetTurtle; + + beforeAll(() => { + global.Singer = { + MeterActions: {}, + RhythmActions: { + getNoteValue: jest.fn(() => 1), + }, + }; + global.TONEBPM = 240; + global.Queue = jest.fn((action, duration, blk) => ({ action, duration, blk })); + global.last = jest.fn((array) => array[array.length - 1]); + global._ = jest.fn((msg) => msg); + global.rationalToFraction = jest.fn((value) => [value * 4, 4]); + }); + + + beforeEach(() => { + activity = { + turtles: { + ithTurtle: jest.fn(), + turtleList: [], + addTurtle: jest.fn(), + }, + blocks: { + blockList: { 1: {} }, + }, + logo: { + actions: {}, + setDispatchBlock: jest.fn(), + setTurtleListener: jest.fn(), + initTurtle: jest.fn(), + prepSynths: jest.fn(), + runFromBlockNow: jest.fn(), + notation: { + notationMeter: jest.fn(), + notationPickup: jest.fn(), + }, + }, + errorMsg: jest.fn(), + stage: { + dispatchEvent: jest.fn(), + }, + }; + + targetTurtle = { + singer: { + beatsPerMeasure: 0, + noteValuePerBeat: 0, + beatList: [], + defaultStrongBeats: false, + bpm: [], + notesPlayed: [0, 1], + pickup: 0, + drift: 0, + }, + queue: [], + parentFlowQueue: [], + unhighlightQueue: [], + parameterQueue: [], + }; + + activity.turtles.ithTurtle.mockReturnValue(targetTurtle); + + setupMeterActions(activity); + }); + + it('should set the meter correctly', () => { + Singer.MeterActions.setMeter(4, 4, 0); + expect(targetTurtle.singer.beatsPerMeasure).toBe(4); + expect(targetTurtle.singer.noteValuePerBeat).toBe(1 / 4); + expect(activity.logo.notation.notationMeter).toHaveBeenCalledWith(0, 4, 1 / 4); + }); + + it('should set the pickup value correctly', () => { + Singer.MeterActions.setPickup(2, 0); + expect(targetTurtle.singer.pickup).toBe(2); + expect(activity.logo.notation.notationPickup).toHaveBeenCalledWith(0, 2); + }); + + it('should set BPM within range', () => { + Singer.MeterActions.setBPM(120, 0.25, 0, 1); + expect(targetTurtle.singer.bpm).toContain(120); + }); + + it('should handle BPM below range', () => { + Singer.MeterActions.setBPM(10, 0.25, 0, 1); + expect(activity.errorMsg).toHaveBeenCalledWith('1/4 beats per minute must be greater than 30', 1); + expect(targetTurtle.singer.bpm).toContain(30); + }); + + it('should set the master BPM correctly', () => { + Singer.MeterActions.setMasterBPM(100, 0.25, 1); + expect(Singer.masterBPM).toBe(100); + expect(Singer.defaultBPMFactor).toBe(TONEBPM / 100); + }); + + it('should set a listener for every beat', () => { + activity.turtles.turtleList = [{ companionTurtle: null }]; + activity.turtles.addTurtle = jest.fn(); + activity.logo.prepSynths = jest.fn(); + targetTurtle.id = 0; + + Singer.MeterActions.onEveryBeatDo('testAction', false, null, 0, 1); + + expect(activity.turtles.addTurtle).toHaveBeenCalled(); + expect(activity.logo.setTurtleListener).toHaveBeenCalledWith( + 1, + '__everybeat_1__', + expect.any(Function) + ); + }); + + it('should set a listener for every note', () => { + Singer.MeterActions.onEveryNoteDo('testAction', false, null, 0, 1); + expect(activity.logo.setTurtleListener).toHaveBeenCalled(); + }); + + it('should calculate the current meter correctly', () => { + targetTurtle.singer.beatsPerMeasure = 4; + targetTurtle.singer.noteValuePerBeat = 0.25; + const meter = Singer.MeterActions.getCurrentMeter(0); + expect(meter).toBe('4:0.25'); + }); + + it('should calculate the BPM correctly', () => { + targetTurtle.singer.bpm.push(120); + const bpm = Singer.MeterActions.getBPM(0); + expect(bpm).toBe(120); + }); + + it('should get the beat factor correctly', () => { + targetTurtle.singer.noteValuePerBeat = 0.25; + const factor = Singer.MeterActions.getBeatFactor(0); + expect(factor).toBe(0.25); + }); + + it('should calculate whole notes played correctly', () => { + targetTurtle.singer.notesPlayed = [4, 1]; + const wholeNotes = Singer.MeterActions.getWholeNotesPlayed(0); + expect(wholeNotes).toBe(4); + }); + + it('should return the correct meter format', () => { + targetTurtle.singer.beatsPerMeasure = 4; + targetTurtle.singer.noteValuePerBeat = 1; + + const meter = Singer.MeterActions.getCurrentMeter(0); + expect(meter).toBe('4:1'); + }); + + + + +}); \ No newline at end of file diff --git a/js/turtleactions/__tests__/PitchActions.test.js b/js/turtleactions/__tests__/PitchActions.test.js new file mode 100644 index 0000000000..08acee2dbe --- /dev/null +++ b/js/turtleactions/__tests__/PitchActions.test.js @@ -0,0 +1,166 @@ +const setupPitchActions = require('../PitchActions'); + +describe('setupPitchActions', () => { + let activity; + let turtle; + let targetTurtle; + + beforeAll(() => { + global.Singer = { + PitchActions: {}, + processPitch: jest.fn(), + addScalarTransposition: jest.fn(() => ['C', 4]), + }; + global.keySignatureToMode = jest.fn(() => ['C', 'major']); + global.isCustomTemperament = jest.fn(() => false); + global.ACCIDENTALNAMES = ['sharp', 'flat']; + global.ACCIDENTALVALUES = [1, -1]; + global.NANERRORMSG = 'Not a number error'; + global.NOTESFLAT = ['C', 'Db', 'D', 'Eb', 'E', 'F', 'Gb', 'G', 'Ab', 'A', 'Bb', 'B']; + global.NOTESSHARP = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; + global.NOTESTEP = { C: 0, D: 2, E: 4, F: 5, G: 7, A: 9, B: 11 }; + global.MUSICALMODES = { + major: [2, 2, 1, 2, 2, 2, 1], + minor: [2, 1, 2, 2, 1, 2, 2], + }; + global.FLAT = 'b'; + global.SHARP = '#'; + global.nthDegreeToPitch = jest.fn(() => 'C'); + global.pitchToNumber = jest.fn(() => 0); + global.getStepSizeUp = jest.fn(() => 2); + global.getStepSizeDown = jest.fn(() => -2); + global.getNote = jest.fn(); + global.calcOctave = jest.fn(() => 4); + global.frequencyToPitch = jest.fn(() => ['C', 4, 0]); + global.numberToPitch = jest.fn(() => ['C', 4]); + global._ = jest.fn(msg => msg); + }); + + beforeEach(() => { + activity = { + turtles: { + ithTurtle: jest.fn(), + }, + blocks: { + blockList: { 1: {} }, + }, + logo: { + synth: { + inTemperament: 'equal', + startingPitch: 'C4', + }, + setDispatchBlock: jest.fn(), + setTurtleListener: jest.fn(), + }, + errorMsg: jest.fn(), + }; + + targetTurtle = { + singer: { + justCounting: [], + pitchNumberOffset: 0, + currentOctave: 4, + lastNotePlayed: ['C4', 4], + inNoteBlock: [], + lastNotePlayed: null, + pitchNumberOffset: 0, + scalarTransposition: 0, + transposition: 0, + scalarTranspositionValues: [], + transpositionValues: [], + invertList: [], + }, + }; + + activity.turtles.ithTurtle.mockReturnValue(targetTurtle); + setupPitchActions(activity); + }); + + it('should play a pitch', () => { + Singer.PitchActions.playPitch('C', 4, 0, 0, 1); + expect(Singer.processPitch).toHaveBeenCalledWith(activity, 'C', 4, 0, 0, 1); + }); + + it('should step pitch correctly', () => { + Singer.PitchActions.stepPitch(2, 0, 1); + expect(global.Singer.addScalarTransposition).toHaveBeenCalledWith( + activity.logo, + 0, + 'G', + 4, + 2 + ); + }); + + it('should play nth modal pitch', () => { + const mockTurtle = { singer: { keySignature: 'C major' } }; + activity.turtles.ithTurtle.mockReturnValue(mockTurtle); + + Singer.PitchActions.playNthModalPitch(3, 4, 0, 1); + + expect(global.keySignatureToMode).toHaveBeenCalledWith('C major'); + expect(global.nthDegreeToPitch).toHaveBeenCalledWith('C major', 3); + expect(activity.errorMsg).not.toHaveBeenCalled(); + }); + + + it('should play pitch number', () => { + Singer.PitchActions.playPitchNumber(5, 0, 1); + expect(Singer.processPitch).toHaveBeenCalled(); + }); + + it('should play hertz', () => { + Singer.PitchActions.playHertz(440, 0, 1); + expect(Singer.processPitch).toHaveBeenCalledWith(activity, 'C', 4, 0, 0, 1); + }); + + it('should set accidental', () => { + Singer.PitchActions.setAccidental('sharp', 0, 1); + expect(activity.logo.setDispatchBlock).toHaveBeenCalled(); + expect(activity.logo.setTurtleListener).toHaveBeenCalled(); + }); + + it('should set scalar transpose', () => { + Singer.PitchActions.setScalarTranspose(2, 0, 1); + expect(activity.logo.setDispatchBlock).toHaveBeenCalled(); + expect(activity.logo.setTurtleListener).toHaveBeenCalled(); + }); + + it('should set semitone transpose', () => { + Singer.PitchActions.setSemitoneTranspose(2, 0, 1); + expect(activity.logo.setDispatchBlock).toHaveBeenCalled(); + expect(activity.logo.setTurtleListener).toHaveBeenCalled(); + }); + + it('should set register', () => { + Singer.PitchActions.setRegister(5, 0); + expect(targetTurtle.singer.register).toBe(5); + }); + + it('should invert notes correctly', () => { + Singer.PitchActions.invert('C', 4, 'even', 0, 1); + expect(targetTurtle.singer.invertList).toContainEqual(['C', 4, 'even']); + }); + + it('should calculate delta pitch correctly', () => { + targetTurtle.singer.previousNotePlayed = ['C4']; + targetTurtle.singer.lastNotePlayed = ['D4']; + const delta = Singer.PitchActions.deltaPitch('deltapitch', 0); + expect(delta).toBeDefined(); + }); + + it('should return consonant step size', () => { + const stepSize = Singer.PitchActions.consonantStepSize('up', 0); + expect(stepSize).toBeDefined(); + }); + + it('should set pitch number offset', () => { + Singer.PitchActions.setPitchNumberOffset('C', 4, 0); + expect(targetTurtle.singer.pitchNumberOffset).toBeDefined(); + }); + + it('should convert number to pitch', () => { + const pitch = Singer.PitchActions.numToPitch(3, 'pitch', 0); + expect(pitch).toBe('C'); + }); +}); diff --git a/js/turtleactions/__tests__/ToneActions.test.js b/js/turtleactions/__tests__/ToneActions.test.js new file mode 100644 index 0000000000..4859f661a4 --- /dev/null +++ b/js/turtleactions/__tests__/ToneActions.test.js @@ -0,0 +1,197 @@ +const setupToneActions = require('../ToneActions'); + +describe('setupToneActions', () => { + let activity; + let targetTurtle; + + beforeAll(() => { + global.Singer = { + ToneActions: {}, + }; + global.instrumentsEffects = { + 0: { + 'default-voice': { + vibratoActive: false, + vibratoIntensity: [], + vibratoFrequency: 0, + }, + }, + }; + global.VOICENAMES = { + Piano: ['piano', 'grand-piano'], + Violin: ['violin', 'acoustic-violin'], + }; + global.CUSTOMSAMPLES = {}; + global.DEFAULTVOICE = 'default-voice'; + global.last = jest.fn(array => array[array.length - 1]); + global._ = jest.fn(msg => msg); + global.instrumentsEffects = {}; + }); + + beforeEach(() => { + activity = { + turtles: { + ithTurtle: jest.fn(), + }, + blocks: { + blockList: { 1: {} }, + }, + logo: { + setDispatchBlock: jest.fn(), + setTurtleListener: jest.fn(), + synth: { + loadSynth: jest.fn(), + createSynth: jest.fn(), + }, + phraseMaker: { + _instrumentName: null, + }, + notation: { + notationSwing: jest.fn(), + notationVoices: jest.fn(), + notationBeginHarmonics: jest.fn(), + notationEndHarmonics: jest.fn(), + }, + timbre: { + instrumentName: 'default-voice', + FMSynthParams: [], + AMSynthParams: [], + duoSynthParams: [], + osc: [], + fmSynthParamvals: {}, + amSynthParamvals: {}, + duoSynthParamVals: {}, + FMSynthesizer: [], + AMSynthesizer: [], + duoSynthesizer: [], + vibratoEffect: [], + vibratoParams: [], + }, + inTimbre: true, + stopTurtle: false, + }, + errorMsg: jest.fn(), + }; + + targetTurtle = { + singer: { + instrumentNames: [], + synthVolume: { 'default-voice': [1] }, + crescendoInitialVolume: { 'default-voice': [1] }, + vibratoIntensity: [], + vibratoRate: [], + chorusRate: [], + delayTime: [], + chorusDepth: [], + rate: [], + octaves: [], + baseFrequency: [], + tremoloFrequency: [], + tremoloDepth: [], + distortionAmount: [], + inHarmonic: [], + partials: [], + }, + }; + + global.instrumentsEffects = { + 0: { + 'default-voice': { + vibratoActive: false, + vibratoIntensity: [], + vibratoFrequency: [], + }, + }, + }; + activity.turtles.ithTurtle.mockReturnValue(targetTurtle); + setupToneActions(activity); + }); + + it('should set timbre correctly', () => { + Singer.ToneActions.setTimbre('piano', 0, 1); + expect(targetTurtle.singer.instrumentNames).toContain('grand-piano'); + expect(activity.logo.synth.loadSynth).toHaveBeenCalledWith(0, 'grand-piano'); + }); + + it('should handle custom sample timbres correctly', () => { + const customSample = ['custom1', 'sample1', 'sample2', 'sample3']; + Singer.ToneActions.setTimbre(customSample, 0, 1); + expect(global.CUSTOMSAMPLES['customsample_custom1']).toEqual(['sample1', 'sample2', 'sample3']); + expect(targetTurtle.singer.instrumentNames).toContain('customsample_custom1'); + }); + + it('should apply vibrato correctly', () => { + const intensity = 50; + const rate = 10; + const blk = 1; + + Singer.ToneActions.doVibrato(intensity, rate, 0, blk); + + expect(targetTurtle.singer.vibratoIntensity).toContain(intensity / 100); + expect(targetTurtle.singer.vibratoRate).toContain(1 / rate); + expect(activity.logo.timbre.vibratoEffect).toContain(blk); // Ensure vibratoEffect is updated + expect(activity.logo.timbre.vibratoParams).toContain(intensity); // Ensure vibratoParams is updated + expect(global.instrumentsEffects[0]['default-voice'].vibratoActive).toBe(true); // Ensure vibratoActive is true + }); + + it('should show error for invalid vibrato intensity', () => { + Singer.ToneActions.doVibrato(150, 5, 0, 1); + expect(activity.errorMsg).toHaveBeenCalledWith('Vibrato intensity must be between 1 and 100.', 1); + expect(activity.logo.stopTurtle).toBe(true); + }); + + it('should apply chorus effect correctly', () => { + Singer.ToneActions.doChorus(1.5, 20, 50, 0, 1); + expect(targetTurtle.singer.chorusRate).toContain(1.5); + expect(targetTurtle.singer.delayTime).toContain(20); + expect(targetTurtle.singer.chorusDepth).toContain(0.5); + }); + + it('should apply phaser effect correctly', () => { + Singer.ToneActions.doPhaser(2, 3, 100, 0, 1); + expect(targetTurtle.singer.rate).toContain(2); + expect(targetTurtle.singer.octaves).toContain(3); + expect(targetTurtle.singer.baseFrequency).toContain(100); + }); + + it('should apply tremolo effect correctly', () => { + Singer.ToneActions.doTremolo(5, 50, 0, 1); + expect(targetTurtle.singer.tremoloFrequency).toContain(5); + expect(targetTurtle.singer.tremoloDepth).toContain(0.5); + }); + + it('should apply distortion effect correctly', () => { + Singer.ToneActions.doDistortion(50, 0, 1); + expect(targetTurtle.singer.distortionAmount).toContain(0.5); + }); + + it('should apply harmonic effect correctly', () => { + const blk = 1; + const harmonic = 2; // Adjust the harmonic to match the expected output + + Singer.ToneActions.doHarmonic(harmonic, 0, blk); + + expect(activity.logo.notation.notationBeginHarmonics).toHaveBeenCalledWith(0); // Verify the harmonics start + expect(targetTurtle.singer.partials).toHaveLength(1); + expect(targetTurtle.singer.partials[0]).toEqual([0, 0, 1]); // Ensure harmonics array matches expected output + }); + + + + + it('should define FM synth correctly', () => { + Singer.ToneActions.defFMSynth(10, 0, 1); + expect(activity.logo.timbre.FMSynthParams).toContain(10); + }); + + it('should define AM synth correctly', () => { + Singer.ToneActions.defAMSynth(5, 0, 1); + expect(activity.logo.timbre.AMSynthParams).toContain(5); + }); + + it('should define Duo synth correctly', () => { + Singer.ToneActions.defDuoSynth(10, 20, 0, 1); + expect(activity.logo.timbre.duoSynthParams).toContain(10); + expect(activity.logo.timbre.duoSynthParams).toContain(0.2); + }); +}); diff --git a/js/turtleactions/__tests__/VolumeActions.test.js b/js/turtleactions/__tests__/VolumeActions.test.js new file mode 100644 index 0000000000..22c61abfa3 --- /dev/null +++ b/js/turtleactions/__tests__/VolumeActions.test.js @@ -0,0 +1,147 @@ +const setupVolumeActions = require('../VolumeActions'); + +describe('setupVolumeActions', () => { + let activity; + let targetTurtle; + + beforeAll(() => { + global.Singer = { + VolumeActions: {}, + setSynthVolume: jest.fn(), + setMasterVolume: jest.fn(), + masterVolume: [], + }; + global.instruments = { + 0: { synth1: { connect: jest.fn() } }, + }; + global.Tone = { + Panner: jest.fn(() => ({ + toDestination: jest.fn(), + pan: { value: 0 }, + })), + }; + global.last = jest.fn(array => array[array.length - 1]); + global._ = jest.fn(msg => msg); + global.VOICENAMES = {}; + global.DRUMNAMES = {}; + global.DEFAULTVOLUME = 50; + global.DEFAULTVOICE = 'default'; + }); + + beforeEach(() => { + activity = { + turtles: { + ithTurtle: jest.fn(), + singer: { + synthVolume: { default: [DEFAULTVOLUME] }, + crescendoInitialVolume: { default: [DEFAULTVOLUME] }, + crescendoDelta: [], + inCrescendo: [], + instrumentNames: [], + suppressOutput: false, + justCounting: [], + panner: new Tone.Panner(), + }, + }, + blocks: { + blockList: { 1: {} }, + }, + logo: { + setDispatchBlock: jest.fn(), + setTurtleListener: jest.fn(), + synth: { + loadSynth: jest.fn(), + }, + notation: { + notationBeginArticulation: jest.fn(), + notationEndArticulation: jest.fn(), + notationEndCrescendo: jest.fn(), + }, + }, + errorMsg: jest.fn(), + }; + + targetTurtle = { + singer: { + synthVolume: { default: [DEFAULTVOLUME] }, + crescendoInitialVolume: { default: [DEFAULTVOLUME] }, + synthVolume: {}, + crescendoDelta: [], + crescendoInitialVolume: {}, + inCrescendo: [], + instrumentNames: [], + suppressOutput: false, + justCounting: [], + panner: new Tone.Panner(), + }, + }; + + activity.turtles.ithTurtle.mockReturnValue(targetTurtle); + setupVolumeActions(activity); + }); + + it('should set master volume correctly', () => { + Singer.VolumeActions.setMasterVolume(80, 0, 1); + expect(Singer.masterVolume).toContain(80); + expect(Singer.setMasterVolume).toHaveBeenCalledWith(activity.logo, 80); + }); + + it('should handle out-of-range master volume', () => { + Singer.VolumeActions.setMasterVolume(120, 0, 1); + expect(Singer.masterVolume).toContain(100); + expect(activity.errorMsg).not.toHaveBeenCalled(); + + Singer.VolumeActions.setMasterVolume(-10, 0, 1); + expect(Singer.masterVolume).toContain(0); + expect(activity.errorMsg).toHaveBeenCalledWith(_('Setting volume to 0.'), 1); + }); + + it('should set synth volume correctly', () => { + targetTurtle.singer.synthVolume['default'] = [DEFAULTVOLUME]; + Singer.VolumeActions.setSynthVolume('default', 70, 0); + + expect(targetTurtle.singer.synthVolume['default']).toContain(70); + expect(Singer.setSynthVolume).toHaveBeenCalledWith(activity.logo, 0, 'default', 70); + }); + + it('should apply crescendo correctly', () => { + // Ensure initial values are correctly set + targetTurtle.singer.synthVolume['default'] = [DEFAULTVOLUME]; + targetTurtle.singer.crescendoInitialVolume['default'] = [DEFAULTVOLUME]; + + // Call the crescendo function + Singer.VolumeActions.doCrescendo('crescendo', 10, 0, 1); + + // Assert the updates to synthVolume + expect(targetTurtle.singer.synthVolume['default']).toHaveLength(2); + expect(targetTurtle.singer.synthVolume['default'][1]).toBe(DEFAULTVOLUME); + + // Assert the updates to crescendoInitialVolume + expect(targetTurtle.singer.crescendoInitialVolume['default']).toHaveLength(2); + expect(targetTurtle.singer.crescendoInitialVolume['default'][1]).toBe(DEFAULTVOLUME); + + // Assert other properties + expect(targetTurtle.singer.crescendoDelta).toContain(10); + expect(activity.logo.setTurtleListener).toHaveBeenCalled(); + }); + + + it('should set panning correctly', () => { + Singer.VolumeActions.setPanning(50, 0); + expect(targetTurtle.singer.panner.pan.value).toBe(0.5); + expect(Tone.Panner).toHaveBeenCalled(); + }); + + it('should retrieve the correct master volume', () => { + Singer.masterVolume.push(60); + + expect(Singer.VolumeActions.masterVolume).toBe(60); + }); + + it('should retrieve the correct synth volume', () => { + targetTurtle.singer.synthVolume['default'] = [DEFAULTVOLUME, 70]; + + const volume = Singer.VolumeActions.getSynthVolume('default', 0); + expect(volume).toBe(70); + }); +});