@@ -620,6 +620,88 @@ Am F`);
620620 }
621621 } ) ;
622622
623+ test ( "renders timing ruler, section navigation, and loop region visualization" , async ( { page } ) => {
624+ const server = await openMidiStudio ( page ) ;
625+ try {
626+ await fillInstrumentGrid ( page , {
627+ bass : "" ,
628+ chords : "Am F C G | Dm G C Am | F G Am C | C G F Am | G F C Am" ,
629+ drums : "" ,
630+ lead : "" ,
631+ pad : "" ,
632+ sections : "intro:1, loop:1, bridge:1, boss:1, victory:1"
633+ } ) ;
634+ await page . locator ( "#normalizeInstrumentGridButton" ) . click ( ) ;
635+ await expect ( page . locator ( "#instrumentGridOutput" ) ) . toContainText ( "Bar 1" ) ;
636+ await expect ( page . locator ( "#instrumentGridOutput" ) ) . toContainText ( "Bar 5" ) ;
637+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--bar" ) ) . toHaveCount ( 5 ) ;
638+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--ruler" ) . first ( ) ) . toContainText ( "1" ) ;
639+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) ) . toHaveAttribute ( "data-section" , "intro" ) ;
640+ await expect ( page . locator ( "#instrumentGridSectionSelect" ) ) . toContainText ( "bridge" ) ;
641+ await page . locator ( "#instrumentGridLoopStartSelect" ) . selectOption ( "loop" ) ;
642+ await page . locator ( "#instrumentGridLoopEndSelect" ) . selectOption ( "boss" ) ;
643+ expect ( await page . locator ( ".midi-studio-v2__grid-cell--loop-region" ) . count ( ) ) . toBeGreaterThan ( 0 ) ;
644+ await page . locator ( '[data-section-preset="boss"]' ) . click ( ) ;
645+ await expect ( page . locator ( "#instrumentGridSectionSelect" ) ) . toHaveValue ( "boss" ) ;
646+ await page . locator ( "#jumpToSectionButton" ) . click ( ) ;
647+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) ) . toHaveAttribute ( "data-section" , "boss" ) ;
648+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K T i m i n g p l a y h e a d j u m p e d t o s e c t i o n b o s s \. / ) ;
649+ } finally {
650+ await workspaceV2CoverageReporter . stop ( page ) ;
651+ await server . close ( ) ;
652+ }
653+ } ) ;
654+
655+ test ( "animates timing-only playhead by subdivision and preserves generated manual cells" , async ( { page } ) => {
656+ const server = await openMidiStudio ( page ) ;
657+ try {
658+ await fillInstrumentGrid ( page , {
659+ bass : "" ,
660+ beats : "2" ,
661+ chords : "Am - F - | C - G -" ,
662+ drums : "" ,
663+ lead : "" ,
664+ pad : "" ,
665+ subdivision : "2"
666+ } ) ;
667+ await page . locator ( "#generateBassFromChordsButton" ) . click ( ) ;
668+ await page . locator ( "#instrumentGridBassInput" ) . fill ( "A2 E2 F2 - | C2 - G2 -" ) ;
669+ await page . locator ( "#normalizeInstrumentGridButton" ) . click ( ) ;
670+ await expect ( page . locator ( '[data-lane="bass"][data-source="generated"]' ) ) . toHaveCount ( 4 ) ;
671+ await expect ( page . locator ( '[data-lane="bass"][data-source="manual"]' ) ) . toHaveCount ( 1 ) ;
672+ await page . locator ( "#instrumentGridSectionSelect" ) . selectOption ( "intro" ) ;
673+ const beforeStep = await page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) . getAttribute ( "data-step-index" ) ;
674+ await page . locator ( "#playSectionButton" ) . click ( ) ;
675+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / W A R N L i v e p l a y b a c k s y n t h e s i s n o t i m p l e m e n t e d \. P l a y i n g t i m i n g - p r e v i e w p l a y h e a d o n l y ; n o a u d i o p l a y b a c k w a s s t a r t e d \. / ) ;
676+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) ) . not . toHaveAttribute ( "data-step-index" , beforeStep || "" ) ;
677+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) ) . toHaveAttribute ( "data-beat" , / 1 | 2 / ) ;
678+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--playhead-active" ) ) . toHaveAttribute ( "data-subdivision-step" , / 1 | 2 / ) ;
679+ await page . locator ( "#stopTimingPreviewButton" ) . click ( ) ;
680+ await expect ( page . locator ( "#instrumentGridTransportState" ) ) . toContainText ( "Timing preview stopped." ) ;
681+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / O K T i m i n g p r e v i e w s t o p p e d \. / ) ;
682+ } finally {
683+ await workspaceV2CoverageReporter . stop ( page ) ;
684+ await server . close ( ) ;
685+ }
686+ } ) ;
687+
688+ test ( "reports invalid section and invalid loop handling" , async ( { page } ) => {
689+ const server = await openMidiStudio ( page ) ;
690+ try {
691+ await fillInstrumentGrid ( page ) ;
692+ await page . locator ( "#normalizeInstrumentGridButton" ) . click ( ) ;
693+ await page . locator ( '[data-section-preset="bridge"]' ) . click ( ) ;
694+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L I n s t r u m e n t g r i d s e c t i o n n o t f o u n d : B r i d g e \. N o r m a l i z e a s e c t i o n m a p c o n t a i n i n g t h a t l a b e l o r c h o o s e a l i s t e d c u s t o m s e c t i o n \. / ) ;
695+ await page . locator ( "#instrumentGridLoopStartSelect" ) . selectOption ( "loop" ) ;
696+ await page . locator ( "#instrumentGridLoopEndSelect" ) . selectOption ( "intro" ) ;
697+ await page . locator ( "#playLoopButton" ) . click ( ) ;
698+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / F A I L I n s t r u m e n t g r i d l o o p r e j e c t e d : I n v a l i d l o o p r e g i o n : l o o p s t a r t s a f t e r i n t r o \. / ) ;
699+ } finally {
700+ await workspaceV2CoverageReporter . stop ( page ) ;
701+ await server . close ( ) ;
702+ }
703+ } ) ;
704+
623705 test ( "generates bass pad arpeggio and drum lanes from chord grid" , async ( { page } ) => {
624706 const server = await openMidiStudio ( page ) ;
625707 try {
0 commit comments