@@ -26,7 +26,11 @@ global.NOTATIONROUNDDOWN = 4;
2626global . NOTATIONINSIDECHORD = 5 ; // deprecated
2727global . NOTATIONSTACCATO = 6 ;
2828
29- global . frequencyToPitch = jest . fn ( freq => [ "G♯" , "4" ] ) ;
29+ global . frequencyToPitch = jest . fn ( freq => {
30+ if ( freq === 440 ) return [ "A" , "4" ] ;
31+ return [ "G♯" , "4" ] ;
32+ } ) ;
33+
3034global . toFraction = jest . fn ( num => [ 1 , 1 ] ) ;
3135
3236const {
@@ -44,7 +48,7 @@ describe("getABCHeader", () => {
4448 } ) ;
4549} ) ;
4650
47- describe ( "processABCNotes" , ( ) => {
51+ describe ( "processABCNotes - Basic Note Processing " , ( ) => {
4852 let logo ;
4953
5054 beforeEach ( ( ) => {
@@ -66,8 +70,250 @@ describe("processABCNotes", () => {
6670 processABCNotes ( logo , "0" ) ;
6771 expect ( logo . notationNotes [ "0" ] ) . toBe ( "G^4 G^4 F4 F4 G^2 G^8 " ) ;
6872 } ) ;
73+
74+ it ( "should insert a newline after every 8 notes" , ( ) => {
75+ const notes = [ ] ;
76+ // Add 9 notes
77+ for ( let i = 0 ; i < 9 ; i ++ ) {
78+ notes . push ( [ [ "C4" ] , 4 , 0 , null , null , - 1 , false ] ) ;
79+ }
80+ logo . notation . notationStaging [ "0" ] = notes ;
81+
82+ processABCNotes ( logo , "0" ) ;
83+ // Check if newline exists in the output
84+ expect ( logo . notationNotes [ "0" ] ) . toMatch ( / \n / ) ;
85+ } ) ;
86+ } ) ;
87+ describe ( "processABCNotes - Advanced Note Handling" , ( ) => {
88+ let logo ;
89+
90+ beforeEach ( ( ) => {
91+ logo = { notationNotes : { "0" : "" } , notation : { notationStaging : { "0" : [ ] } } } ;
92+ } ) ;
93+
94+ it ( "should handle frequency (number) inputs" , ( ) => {
95+ logo . notation . notationStaging [ "0" ] = [
96+ [ [ 440 ] , 4 , 0 , null , null , - 1 , false ] // 440Hz -> A4
97+ ] ;
98+ processABCNotes ( logo , "0" ) ;
99+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "A4" ) ;
100+ expect ( global . frequencyToPitch ) . toHaveBeenCalledWith ( 440 ) ;
101+ } ) ;
102+
103+ it ( "should handle staccato and dots" , ( ) => {
104+ logo . notation . notationStaging [ "0" ] = [ [ [ "C4" ] , 4 , 2 , null , null , - 1 , true ] ] ;
105+ processABCNotes ( logo , "0" ) ;
106+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "C4.." ) ;
107+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "." ) ;
108+ } ) ;
109+
110+ it ( "should convert durations using the map and fallback to string" , ( ) => {
111+ logo . notation . notationStaging [ "0" ] = [
112+ [ [ "C4" ] , 64 , 0 , null , null , - 1 , false ] , // Map: 1/4
113+ [ [ "D4" ] , 32 , 0 , null , null , - 1 , false ] , // Map: 1/2
114+ [ [ "E4" ] , 1 , 0 , null , null , - 1 , false ] , // Map: 16
115+ [ [ "F4" ] , 5 , 0 , null , null , - 1 , false ] // No map: "5"
116+ ] ;
117+ processABCNotes ( logo , "0" ) ;
118+ const output = logo . notationNotes [ "0" ] ;
119+ expect ( output ) . toContain ( "C1/4" ) ;
120+ expect ( output ) . toContain ( "D1/2" ) ;
121+ expect ( output ) . toContain ( "E16" ) ;
122+ expect ( output ) . toContain ( "F5" ) ;
123+ } ) ;
124+ } ) ;
125+ describe ( "processABCNotes - Control Strings" , ( ) => {
126+ let logo ;
127+ beforeEach ( ( ) => {
128+ logo = { notationNotes : { "0" : "" } , notation : { notationStaging : { "0" : [ ] } } } ;
129+ } ) ;
130+
131+ it ( "should handle all string commands correctly" , ( ) => {
132+ logo . notation . notationStaging [ "0" ] = [
133+ "break" ,
134+ [ [ "C4" ] , 4 , 0 , null , null , - 1 , false ] ,
135+ "break" ,
136+ "begin articulation" ,
137+ [ [ "D4" ] , 4 , 0 , null , null , - 1 , false ] ,
138+ "end articulation" ,
139+ "begin crescendo" ,
140+ "end crescendo" ,
141+ "begin decrescendo" ,
142+ "end decrescendo" ,
143+ "begin slur" ,
144+ [ [ "E4" ] , 4 , 0 , null , null , - 1 , false ] ,
145+ "end slur" ,
146+ "tie" ,
147+ "voice one" ,
148+ "voice two" ,
149+ "voice three" ,
150+ "voice four" ,
151+ "one voice" ,
152+ "unknown command"
153+ ] ;
154+
155+ processABCNotes ( logo , "0" ) ;
156+ const out = logo . notationNotes [ "0" ] ;
157+
158+ expect ( out ) . toContain ( "\n" ) ;
159+ expect ( out ) . toContain ( "!<(!" ) ;
160+ expect ( out ) . toContain ( "!<)!" ) ;
161+ expect ( out ) . toContain ( "!>(!" ) ;
162+ expect ( out ) . toContain ( "V:1" ) ;
163+ expect ( out ) . toContain ( "V:2" ) ;
164+ expect ( out ) . toContain ( "V:3" ) ;
165+ expect ( out ) . toContain ( "V:4" ) ;
166+ expect ( out ) . toContain ( "unknown command" ) ;
167+ } ) ;
168+
169+ it ( "should handle meter command" , ( ) => {
170+ logo . notation . notationStaging [ "0" ] = [ "meter" , "4" , "4" ] ;
171+ processABCNotes ( logo , "0" ) ;
172+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "M:4/4" ) ;
173+ } ) ;
174+
175+ it ( "should handle pickup command" , ( ) => {
176+ logo . notation . notationStaging [ "0" ] = [ "pickup" , "8" ] ;
177+ processABCNotes ( logo , "0" ) ;
178+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "K: pickup=8" ) ;
179+ } ) ;
180+ } ) ;
181+
182+ describe ( "processABCNotes - Chords" , ( ) => {
183+ let logo ;
184+ beforeEach ( ( ) => {
185+ logo = { notationNotes : { "0" : "" } , notation : { notationStaging : { "0" : [ ] } } } ;
186+ } ) ;
187+
188+ it ( "should handle chords correctly (Start, Middle, End)" , ( ) => {
189+ const chordID = 123 ;
190+ logo . notation . notationStaging [ "0" ] = [
191+ [ [ "C4" ] , 4 , 0 , null , null , chordID , false ] ,
192+ [ [ "E4" ] , 4 , 0 , null , null , chordID , false ] ,
193+ [ [ "G4" ] , 4 , 0 , null , null , chordID , false ] ,
194+ [ [ "A4" ] , 4 , 0 , null , null , 999 , false ]
195+ ] ;
196+
197+ processABCNotes ( logo , "0" ) ;
198+ const out = logo . notationNotes [ "0" ] ;
199+ expect ( out ) . toContain ( "C4 [C" ) ;
200+ expect ( out ) . toContain ( "E4" ) ;
201+ expect ( out ) . toContain ( "G4 G]4" ) ;
202+ } ) ;
203+
204+ it ( "should handle articulation inside chords" , ( ) => {
205+ const chordID = 55 ;
206+ logo . notation . notationStaging [ "0" ] = [
207+ "begin articulation" ,
208+ [ [ "C4" ] , 4 , 0 , null , null , chordID , false ] ,
209+ [ [ "E4" ] , 4 , 0 , null , null , chordID , false ] ,
210+ "end articulation"
211+ ] ;
212+
213+ processABCNotes ( logo , "0" ) ;
214+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "C4 [C" ) ;
215+ } ) ;
69216} ) ;
70217
218+ describe ( "processABCNotes - Tuplet Handling" , ( ) => {
219+ let logo ;
220+ beforeEach ( ( ) => {
221+ logo = { notationNotes : { "0" : "" } , notation : { notationStaging : { "0" : [ ] } } } ;
222+ } ) ;
223+
224+ it ( "should process standard tuplets correctly" , ( ) => {
225+ logo . notation . notationStaging [ "0" ] = [
226+ [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
227+ [ [ "F4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
228+ [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ]
229+ ] ;
230+
231+ processABCNotes ( logo , "0" ) ;
232+ expect ( logo . notationNotes [ "0" ] ) . toBe ( "(1:1G^ 2G^ 2G^ 2 " ) ;
233+ } ) ;
234+
235+ it ( "should handle array of notes (chords) inside tuplets" , ( ) => {
236+ logo . notation . notationStaging [ "0" ] = [ [ [ "C4" , "E4" ] , 4 , 0 , 1 , 1 , - 1 , false ] ] ;
237+
238+ processABCNotes ( logo , "0" ) ;
239+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "[C E ]" ) ;
240+ } ) ;
241+
242+ it ( "should handle staccato inside tuplets" , ( ) => {
243+ logo . notation . notationStaging [ "0" ] = [ [ [ "C4" , "E4" ] , 4 , 0 , 1 , 1 , - 1 , true ] ] ;
244+
245+ processABCNotes ( logo , "0" ) ;
246+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "." ) ;
247+ } ) ;
248+
249+ it ( "should handle incomplete/mixed tuplets logic" , ( ) => {
250+ logo . notation . notationStaging [ "0" ] = [
251+ [ [ "A4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
252+ [ [ "B4" ] , 4 , 0 , 3 , 2 , - 1 , false ]
253+ ] ;
254+ processABCNotes ( logo , "0" ) ;
255+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "(" ) ;
256+ } ) ;
257+
258+ it ( "should handle tuplet with matching chord IDs (skip logic)" , ( ) => {
259+ logo . notation . notationStaging [ "0" ] = [
260+ [ [ "A4" ] , 4 , 0 , 2 , 2 , 100 , false ] ,
261+ [ [ "B4" ] , 4 , 0 , 2 , 2 , 100 , false ] ,
262+ [ [ "C4" ] , 4 , 0 , 2 , 2 , - 1 , false ]
263+ ] ;
264+ processABCNotes ( logo , "0" ) ;
265+ expect ( logo . notationNotes [ "0" ] ) . not . toBe ( "" ) ;
266+ } ) ;
267+ } ) ;
268+
269+ describe ( "processABCNotes - Edge Cases for 100% Coverage" , ( ) => {
270+ let logo ;
271+
272+ beforeEach ( ( ) => {
273+ logo = { notationNotes : { "0" : "" } , notation : { notationStaging : { "0" : [ ] } } } ;
274+ } ) ;
275+
276+ it ( "should handle array of notes in NOTATIONNOTE field" , ( ) => {
277+ logo . notation . notationStaging [ "0" ] = [ [ [ "C4" , "E4" ] , 4 , 0 , null , null , - 1 , false ] ] ;
278+ processABCNotes ( logo , "0" ) ;
279+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "C4" ) ;
280+ } ) ;
281+ it ( "should handle incomplete tuplets with different tuplet values" , ( ) => {
282+ logo . notation . notationStaging [ "0" ] = [
283+ [ [ "A4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
284+ [ [ "B4" ] , 4 , 0 , 5 , 2 , - 1 , false ]
285+ ] ;
286+ processABCNotes ( logo , "0" ) ;
287+ expect ( logo . notationNotes [ "0" ] ) . toContain ( "(" ) ;
288+ } ) ;
289+ it ( "should handle closing parenthesis in notation staging" , ( ) => {
290+ logo . notation . notationStaging [ "0" ] = [
291+ [ [ "C4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
292+ [ [ "D4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
293+ [ [ "E4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
294+ ")"
295+ ] ;
296+ processABCNotes ( logo , "0" ) ;
297+ expect ( logo . notationNotes [ "0" ] ) . not . toBe ( "" ) ;
298+ } ) ;
299+ it ( "should handle chords with multiple notes outside tuplets" , ( ) => {
300+ logo . notation . notationStaging [ "0" ] = [ [ [ "C4" , "E4" , "G4" ] , 4 , 0 , null , null , - 1 , false ] ] ;
301+ processABCNotes ( logo , "0" ) ;
302+ const out = logo . notationNotes [ "0" ] ;
303+ expect ( out ) . toContain ( "[" ) ;
304+ expect ( out ) . toContain ( "]" ) ;
305+ } ) ;
306+ it ( "should handle dots when closing chords" , ( ) => {
307+ const chordID = 456 ;
308+ logo . notation . notationStaging [ "0" ] = [
309+ [ [ "C4" ] , 4 , 2 , null , null , chordID , false ] ,
310+ [ [ "E4" ] , 4 , 2 , null , null , chordID , false ] ,
311+ [ [ "G4" ] , 4 , 0 , null , null , - 1 , false ]
312+ ] ;
313+ processABCNotes ( logo , "0" ) ;
314+ expect ( logo . notationNotes [ "0" ] ) . toContain ( " " ) ;
315+ } ) ;
316+ } ) ;
71317describe ( "saveAbcOutput" , ( ) => {
72318 let activity ;
73319
@@ -78,11 +324,7 @@ describe("saveAbcOutput", () => {
78324 notationNotes : { "0" : "" } ,
79325 notation : {
80326 notationStaging : {
81- "0" : [
82- [ [ "G♯4" ] , 4 , 0 , null , null , - 1 , false ] ,
83- [ [ "F4" ] , 4 , 0 , null , null , - 1 , false ] ,
84- [ [ "G♯4" ] , 2 , 0 , null , null , - 1 , false ]
85- ]
327+ "0" : [ [ [ "G♯4" ] , 4 , 0 , null , null , - 1 , false ] ]
86328 }
87329 }
88330 } ,
@@ -96,18 +338,15 @@ describe("saveAbcOutput", () => {
96338 } ;
97339 } ) ;
98340
99- it ( "should generate the correct ABC notation output" , ( ) => {
100- const expectedOutput =
101- "X:1\n" +
102- "T:Music Blocks composition\n" +
103- "C:Mr. Mouse\n" +
104- "L:1/16\n" +
105- "M:C\n" +
106- "K:CMAJOR\n" +
107- "G^4 G^4 F4 F4 G^2 G^8 \n" ;
341+ it ( "should generate the correct ABC notation output with key signature replacements" , ( ) => {
342+ activity . turtles . ithTurtle = ( ) => ( {
343+ singer : { keySignature : "B ♭ major" }
344+ } ) ;
108345
109346 const result = saveAbcOutput ( activity ) ;
110- expect ( result ) . toBe ( expectedOutput ) ;
347+
348+ expect ( result ) . toContain ( "K:Bb MAJOR" ) ;
349+ expect ( result ) . toContain ( "b" ) ;
111350 } ) ;
112351} ) ;
113352
@@ -118,9 +357,9 @@ describe("processABCNotes - Tuplet Handling", () => {
118357 notation : {
119358 notationStaging : {
120359 "0" : [
121- [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ] , // Tuplet
122- [ [ "F4" ] , 4 , 0 , 3 , 2 , - 1 , false ] , // Tuplet
123- [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ] // Tuplet
360+ [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
361+ [ [ "F4" ] , 4 , 0 , 3 , 2 , - 1 , false ] ,
362+ [ [ "G♯4" ] , 4 , 0 , 3 , 2 , - 1 , false ]
124363 ]
125364 }
126365 }
0 commit comments