@@ -184,6 +184,14 @@ async function openMidiStudio(page, routePayload = validManifest, midiRoutes = {
184184 return server ;
185185}
186186
187+ async function fillGuidedSongSheet ( page , { intro = "Am F" , key = "A minor" , loop = "Am F C G" , style = "retro-arcade" , tempo = "132" } = { } ) {
188+ await page . locator ( "#songSheetTempoInput" ) . fill ( tempo ) ;
189+ await page . locator ( "#songSheetKeyInput" ) . fill ( key ) ;
190+ await page . locator ( "#songSheetStyleInput" ) . fill ( style ) ;
191+ await page . locator ( "#songSheetIntroInput" ) . fill ( intro ) ;
192+ await page . locator ( "#songSheetLoopInput" ) . fill ( loop ) ;
193+ }
194+
187195test . describe ( "MIDI Studio V2" , ( ) => {
188196 test . afterAll ( async ( ) => {
189197 await workspaceV2CoverageReporter . writeReport ( ) ;
@@ -202,6 +210,11 @@ test.describe("MIDI Studio V2", () => {
202210 await expect ( page . locator ( "#directorPanel" ) ) . toContainText ( "heroic" ) ;
203211 await expect ( page . locator ( "#playButton" ) ) . toHaveText ( "Play Rendered Preview" ) ;
204212 await expect ( page . locator ( "#inspectMidiSourceButton" ) ) . toBeEnabled ( ) ;
213+ const renderedHeader = page . locator ( '.accordion-v2__header[aria-controls="renderedTargetsContent"]' ) ;
214+ await expect ( renderedHeader ) . toContainText ( "Rendered Export Targets" ) ;
215+ await expect ( renderedHeader . locator ( "#exportWavButton" ) ) . toBeVisible ( ) ;
216+ await expect ( renderedHeader . locator ( "#exportMp3Button" ) ) . toBeVisible ( ) ;
217+ await expect ( renderedHeader . locator ( "#exportOggButton" ) ) . toBeVisible ( ) ;
205218 await expect ( page . locator ( "#midiSourceDetails" ) ) . toContainText ( "No MIDI source inspected." ) ;
206219 await expect ( page . locator ( "#playbackState" ) ) . toContainText ( "Live MIDI synthesis: NOT IMPLEMENTED" ) ;
207220 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K L o a d e d 3 M I D I s o n g s / ) ;
@@ -229,6 +242,24 @@ test.describe("MIDI Studio V2", () => {
229242 }
230243 } ) ;
231244
245+ test ( "reports rendered export header action status without claiming files were written" , async ( { page } ) => {
246+ const server = await openMidiStudio ( page ) ;
247+ try {
248+ await page . locator ( "#exportWavButton" ) . click ( ) ;
249+ await page . locator ( "#exportMp3Button" ) . click ( ) ;
250+ await page . locator ( "#exportOggButton" ) . click ( ) ;
251+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N E x p o r t r e n d e r i n g n o t i m p l e m e n t e d f o r W A V \. P l a n n e d t a r g e t : a s s e t s \/ m u s i c \/ r e n d e r e d \/ t h e m e - m a i n \. w a v \. / ) ;
252+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N E x p o r t r e n d e r i n g n o t i m p l e m e n t e d f o r M P 3 \. P l a n n e d t a r g e t : a s s e t s \/ m u s i c \/ r e n d e r e d \/ t h e m e - m a i n \. m p 3 \. / ) ;
253+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N E x p o r t r e n d e r i n g n o t i m p l e m e n t e d f o r O G G \. P l a n n e d t a r g e t : a s s e t s \/ m u s i c \/ r e n d e r e d \/ t h e m e - m a i n \. o g g \. / ) ;
254+ await page . locator ( '[data-song-id="source-only"]' ) . click ( ) ;
255+ await page . locator ( "#exportWavButton" ) . click ( ) ;
256+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L M i s s i n g r e n d e r e d W A V e x p o r t t a r g e t f o r S o u r c e O n l y \. A d d m u s i c \. s o n g s \[ \] \. r e n d e r e d \. w a v b e f o r e e x p o r t i n g \. / ) ;
257+ } finally {
258+ await workspaceV2CoverageReporter . stop ( page ) ;
259+ await server . close ( ) ;
260+ }
261+ } ) ;
262+
232263 test ( "loads selected MIDI source metadata on request" , async ( { page } ) => {
233264 const server = await openMidiStudio ( page , validManifest , {
234265 "assets/music/midi/theme-main.mid" : validMidiBytes
@@ -335,18 +366,10 @@ test.describe("MIDI Studio V2", () => {
335366 }
336367 } ) ;
337368
338- test ( "parses a valid Song Sheet into section summary metadata" , async ( { page } ) => {
369+ test ( "parses guided Song Sheet fields into section summary metadata" , async ( { page } ) => {
339370 const server = await openMidiStudio ( page ) ;
340371 try {
341- await page . locator ( "#songSheetInput" ) . fill ( `tempo=132
342- key=A minor
343- style=retro-arcade
344-
345- [intro]
346- Am F
347-
348- [loop]
349- Am F C G` ) ;
372+ await fillGuidedSongSheet ( page ) ;
350373 await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
351374 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "intro: 2 bars, 2 chords" ) ;
352375 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "loop: 4 bars, 4 chords, loop" ) ;
@@ -370,38 +393,62 @@ Am F C G`);
370393 }
371394 } ) ;
372395
373- test ( "shows Song Sheet warnings for invalid chords and empty sections " , async ( { page } ) => {
396+ test ( "shows guided Song Sheet warnings for invalid chords and empty intro loop fields " , async ( { page } ) => {
374397 const server = await openMidiStudio ( page ) ;
375398 try {
376- await page . locator ( "#songSheetInput" ) . fill ( `tempo=120
377- key=C major
378- style=chip
379-
380- [loop]
381- C Hm G
382-
383- [break]` ) ;
399+ await fillGuidedSongSheet ( page , {
400+ intro : "" ,
401+ key : "C major" ,
402+ loop : "C Hm G" ,
403+ style : "chip" ,
404+ tempo : "120"
405+ } ) ;
384406 await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
385407 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( 'Invalid chord "Hm"' ) ;
386- await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Section break is empty." ) ;
408+ await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Section intro is empty." ) ;
387409 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "loop: 2 bars, 2 chords, loop" ) ;
388410 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N S o n g S h e e t p a r s e d w i t h w a r n i n g s : I n v a l i d c h o r d " H m " i n s e c t i o n l o o p / ) ;
389411 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K S o n g S h e e t p a r s e d : 2 s e c t i o n s , 2 b a r s , 2 c h o r d s \. / ) ;
412+ await page . locator ( "#songSheetLoopInput" ) . fill ( "" ) ;
413+ await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
414+ await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Section loop is empty." ) ;
415+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N S o n g S h e e t p a r s e d w i t h w a r n i n g s : S e c t i o n i n t r o i s e m p t y \. ; S e c t i o n l o o p i s e m p t y \. / ) ;
390416 } finally {
391417 await workspaceV2CoverageReporter . stop ( page ) ;
392418 await server . close ( ) ;
393419 }
394420 } ) ;
395421
396- test ( "rejects malformed Song Sheet syntax without partial section summary " , async ( { page } ) => {
422+ test ( "rejects invalid guided Song Sheet tempo and missing key before parser render " , async ( { page } ) => {
397423 const server = await openMidiStudio ( page ) ;
398424 try {
425+ await fillGuidedSongSheet ( page , { tempo : "-1" } ) ;
426+ await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
427+ await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Invalid tempo/BPM. Enter a positive number before parsing the guided Song Sheet." ) ;
428+ await expect ( page . locator ( "#songSheetSummary" ) ) . not . toContainText ( "intro:" ) ;
429+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L S o n g S h e e t r e j e c t e d : I n v a l i d t e m p o \/ B P M \. E n t e r a p o s i t i v e n u m b e r b e f o r e p a r s i n g t h e g u i d e d S o n g S h e e t \. / ) ;
430+ await page . locator ( "#songSheetTempoInput" ) . fill ( "132" ) ;
431+ await page . locator ( "#songSheetKeyInput" ) . fill ( "" ) ;
432+ await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
433+ await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Missing key. Enter a key before parsing the guided Song Sheet." ) ;
434+ await expect ( page . locator ( "#songSheetSummary" ) ) . not . toContainText ( "loop:" ) ;
435+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L S o n g S h e e t r e j e c t e d : M i s s i n g k e y \. E n t e r a k e y b e f o r e p a r s i n g t h e g u i d e d S o n g S h e e t \. / ) ;
436+ } finally {
437+ await workspaceV2CoverageReporter . stop ( page ) ;
438+ await server . close ( ) ;
439+ }
440+ } ) ;
441+
442+ test ( "rejects malformed raw Song Sheet syntax without partial section summary" , async ( { page } ) => {
443+ const server = await openMidiStudio ( page ) ;
444+ try {
445+ await page . locator ( ".midi-studio-v2__advanced-song-sheet summary" ) . click ( ) ;
399446 await page . locator ( "#songSheetInput" ) . fill ( `tempo:132
400447key=A minor
401448
402449[loop]
403450Am F` ) ;
404- await page . locator ( "#parseSongSheetButton " ) . click ( ) ;
451+ await page . locator ( "#parseRawSongSheetButton " ) . click ( ) ;
405452 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "Unsupported Song Sheet syntax on line 1: tempo:132" ) ;
406453 await expect ( page . locator ( "#songSheetSummary" ) ) . not . toContainText ( "loop:" ) ;
407454 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L S o n g S h e e t r e j e c t e d : U n s u p p o r t e d S o n g S h e e t s y n t a x o n l i n e 1 : t e m p o : 1 3 2 / ) ;
@@ -416,12 +463,7 @@ Am F`);
416463 "assets/music/midi/theme-main.mid" : validMidiBytes
417464 } ) ;
418465 try {
419- await page . locator ( "#songSheetInput" ) . fill ( `tempo=132
420- key=A minor
421- style=retro-arcade
422-
423- [loop]
424- Am F C G` ) ;
466+ await fillGuidedSongSheet ( page , { intro : "" , loop : "Am F C G" } ) ;
425467 await page . locator ( "#parseSongSheetButton" ) . click ( ) ;
426468 await expect ( page . locator ( "#songSheetSummary" ) ) . toContainText ( "loop: 4 bars, 4 chords, loop" ) ;
427469 await page . locator ( "#inspectMidiSourceButton" ) . click ( ) ;
@@ -535,6 +577,8 @@ Am F C G`);
535577 await expect ( page . locator ( "#songList" ) ) . toContainText ( "No MIDI songs loaded." ) ;
536578 await expect ( page . locator ( "#playButton" ) ) . toBeDisabled ( ) ;
537579 await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L M I D I S t u d i o V 2 p a y l o a d r e j e c t e d b e f o r e r e n d e r .* m u s i c \. s o n g s \[ 0 \] \. i d i s r e q u i r e d \. / ) ;
580+ await page . locator ( "#exportWavButton" ) . click ( ) ;
581+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L M i s s i n g M I D I s o n g f o r W A V e x p o r t \. L o a d o r s e l e c t a s o n g b e f o r e e x p o r t i n g \. / ) ;
538582 } finally {
539583 await workspaceV2CoverageReporter . stop ( page ) ;
540584 await server . close ( ) ;
0 commit comments