@@ -518,10 +518,10 @@ test.describe("MIDI Studio V2", () => {
518518 await expect ( octaveNoteBlock ( page , "lead" ) . first ( ) ) . toHaveClass ( / m i d i - s t u d i o - v 2 _ _ g r i d - c e l l - - l a n e - s e l e c t e d / ) ;
519519 await expect ( octaveNoteBlock ( page , "chords" ) . first ( ) ) . toHaveClass ( / m i d i - s t u d i o - v 2 _ _ g r i d - c e l l - - l a n e - d i m m e d / ) ;
520520 await expect ( page . locator ( ".midi-studio-v2__octave-row-label[data-octave='5']" ) ) . not . toHaveCount ( 0 ) ;
521- await expect ( octaveCell ( page , "G4" , 0 ) ) . toContainText ( "G4 ") ;
521+ await expect ( octaveCell ( page , "G4" , 0 ) ) . toHaveText ( " ") ;
522522 await expect ( octaveCell ( page , "G4" , 0 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
523523 await selectInstrumentRow ( page , "bass" ) ;
524- await expect ( octaveCell ( page , "G2" , 0 ) ) . toContainText ( "G2 ") ;
524+ await expect ( octaveCell ( page , "G2" , 0 ) ) . toHaveText ( " ") ;
525525 await expect ( octaveCell ( page , "G2" , 0 ) ) . toHaveAttribute ( "data-note-lanes" , / b a s s / ) ;
526526 await instrumentRow ( page , "bass" ) . locator ( "[data-toggle-instrument-visibility='bass']" ) . click ( ) ;
527527 await expect ( octaveNoteBlock ( page , "bass" ) ) . toHaveCount ( 0 ) ;
@@ -530,7 +530,7 @@ test.describe("MIDI Studio V2", () => {
530530 await expect ( octaveNoteBlock ( page , "bass" ) . first ( ) ) . toBeVisible ( ) ;
531531 await selectInstrumentRow ( page , "lead" ) ;
532532 await octaveCell ( page , "C5" , 1 ) . click ( ) ;
533- await expect ( octaveCell ( page , "C5" , 1 ) ) . toHaveText ( "C5 " ) ;
533+ await expect ( octaveCell ( page , "C5" , 1 ) ) . toHaveText ( "" ) ;
534534 await expect ( octaveCell ( page , "C5" , 1 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
535535 expect ( await page . evaluate ( ( ) => window . __midiStudioV2App . lastInstrumentGridResult . timeline . some ( ( event ) => event . lane === "lead" && event . value === "C5" && event . stepIndex === 1 ) ) ) . toBe ( true ) ;
536536
@@ -550,10 +550,10 @@ test.describe("MIDI Studio V2", () => {
550550 await expect ( page . locator ( "#playButton" ) ) . toBeEnabled ( ) ;
551551
552552 await page . locator ( '[data-song-id="frog-hop-nursery-rhyme"]' ) . click ( ) ;
553- await expect ( octaveCell ( page , "C5" , 0 ) ) . toContainText ( "C5 ") ;
553+ await expect ( octaveCell ( page , "C5" , 0 ) ) . toHaveText ( " ") ;
554554 await expect ( octaveCell ( page , "C5" , 0 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
555555 await page . locator ( '[data-song-id="quiet-village-pad"]' ) . click ( ) ;
556- await expect ( octaveCell ( page , "D5" , 0 ) ) . toContainText ( "D5 ") ;
556+ await expect ( octaveCell ( page , "D5" , 0 ) ) . toHaveText ( " ") ;
557557 await expect ( octaveCell ( page , "D5" , 0 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
558558
559559 await selectMidiStudioTab ( page , "midi-import" ) ;
@@ -566,10 +566,10 @@ test.describe("MIDI Studio V2", () => {
566566 await expect ( page . locator ( "#songList" ) ) . toContainText ( "Local Import" ) ;
567567 await selectMidiStudioTab ( page , "studio" ) ;
568568 await expect ( instrumentRow ( page , "track-2-ch-1" ) ) . toBeVisible ( ) ;
569- await expect ( octaveCell ( page , "C4" , 0 ) ) . toContainText ( "C4 ") ;
569+ await expect ( octaveCell ( page , "C4" , 0 ) ) . toHaveText ( " ") ;
570570 await expect ( octaveCell ( page , "C4" , 0 ) ) . toHaveAttribute ( "data-note-lanes" , / t r a c k - 2 - c h - 1 / ) ;
571571 await octaveCell ( page , "D4" , 1 ) . click ( ) ;
572- await expect ( octaveCell ( page , "D4" , 1 ) ) . toHaveText ( "D4 " ) ;
572+ await expect ( octaveCell ( page , "D4" , 1 ) ) . toHaveText ( "" ) ;
573573 await expect ( octaveCell ( page , "D4" , 1 ) ) . toHaveAttribute ( "data-note-lanes" , / t r a c k - 2 - c h - 1 / ) ;
574574 expect ( await page . evaluate ( ( ) => window . __midiStudioV2App . selectedSong ( ) . studioArrangement . lanes [ "track-2-ch-1" ] ) ) . toContain ( "D4" ) ;
575575 await page . evaluate ( ( ) => {
@@ -726,8 +726,8 @@ test.describe("MIDI Studio V2", () => {
726726 . map ( ( event ) => event . value ) , chordStep ) ;
727727 await octaveCell ( page , "C5" , chordStep ) . click ( ) ;
728728 await octaveCell ( page , "E5" , chordStep ) . click ( ) ;
729- await expect ( octaveCell ( page , "C5" , chordStep ) ) . toHaveText ( "C5 " ) ;
730- await expect ( octaveCell ( page , "E5" , chordStep ) ) . toHaveText ( "E5 " ) ;
729+ await expect ( octaveCell ( page , "C5" , chordStep ) ) . toHaveText ( "" ) ;
730+ await expect ( octaveCell ( page , "E5" , chordStep ) ) . toHaveText ( "" ) ;
731731 await octaveCell ( page , "G5" , chordStep ) . click ( ) ;
732732 const leadChordValues = await page . evaluate ( ( stepIndex ) => window . __midiStudioV2App . lastInstrumentGridResult . timeline
733733 . filter ( ( event ) => event . lane === "lead" && event . kind === "note" && event . stepIndex === stepIndex )
@@ -819,8 +819,8 @@ test.describe("MIDI Studio V2", () => {
819819 expect ( playbackEvidence . oscillatorStartsAtSameTime ) . toBeGreaterThanOrEqual ( 3 ) ;
820820 expect ( playbackEvidence . bufferStartsAtSameTime ) . toBeGreaterThanOrEqual ( 2 ) ;
821821 await page . waitForFunction ( ( ) => window . __midiStudioV2App . instrumentGrid . playheadStep > 0 ) ;
822- const activeStep = await page . evaluate ( ( ) => window . __midiStudioV2App . instrumentGrid . playheadStep ) ;
823- await expect ( page . locator ( ` .midi-studio-v2__grid-cell--timing-header.midi-studio-v2__grid-cell--playhead-active[ data-step-index=" ${ activeStep } "]` ) ) . toHaveCount ( 1 ) ;
822+ await expect ( page . locator ( ".midi-studio-v2__grid-cell--timing-header.midi-studio-v2__grid-cell--playhead-active" ) ) . toHaveCount ( 1 ) ;
823+ expect ( Number ( await page . locator ( " .midi-studio-v2__grid-cell--timing-header.midi-studio-v2__grid-cell--playhead-active" ) . getAttribute ( " data-step-index" ) ) ) . toBeGreaterThan ( 0 ) ;
824824 await page . locator ( "#stopButton" ) . click ( ) ;
825825 await expect ( page . locator ( "#playButton" ) ) . toBeEnabled ( ) ;
826826 await page . locator ( "#playButton" ) . click ( ) ;
@@ -842,7 +842,7 @@ test.describe("MIDI Studio V2", () => {
842842 await expect ( octaveCell ( page , "C6" , 2 ) ) . not . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
843843
844844 await octaveCell ( page , "C6" , 2 ) . click ( ) ;
845- await expect ( octaveCell ( page , "C6" , 2 ) ) . toHaveText ( "C6 " ) ;
845+ await expect ( octaveCell ( page , "C6" , 2 ) ) . toHaveText ( "" ) ;
846846 await expect ( octaveCell ( page , "C6" , 2 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
847847 await expect ( octaveCell ( page , "C6" , 2 ) ) . toHaveClass ( / m i d i - s t u d i o - v 2 _ _ g r i d - c e l l - - n o t e - s e l e c t e d / ) ;
848848 await octaveCell ( page , "C6" , 2 ) . click ( ) ;
@@ -851,7 +851,7 @@ test.describe("MIDI Studio V2", () => {
851851
852852 await octaveCell ( page , "C6" , 3 ) . dragTo ( octaveCell ( page , "C6" , 5 ) ) ;
853853 for ( const stepIndex of [ 3 , 4 , 5 ] ) {
854- await expect ( octaveCell ( page , "C6" , stepIndex ) ) . toHaveText ( "C6 " ) ;
854+ await expect ( octaveCell ( page , "C6" , stepIndex ) ) . toHaveText ( "" ) ;
855855 await expect ( octaveCell ( page , "C6" , stepIndex ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
856856 }
857857 await octaveCell ( page , "C6" , 5 ) . dragTo ( octaveCell ( page , "C6" , 7 ) ) ;
@@ -924,6 +924,93 @@ test.describe("MIDI Studio V2", () => {
924924 }
925925 } ) ;
926926
927+ test ( "octave timeline freezes compact headers and note labels while active cells stay textless" , async ( { page } ) => {
928+ const server = await openMidiStudioForImport ( page ) ;
929+ try {
930+ await page . locator ( "#toolImportManifestInput" ) . setInputFiles ( uatManifestPath ) ;
931+ await selectInstrumentRow ( page , "lead" ) ;
932+
933+ await expect ( page . locator ( ".midi-studio-v2__timing-header-row-1.midi-studio-v2__note-table-instrument-header" ) ) . toHaveText ( "Octave" ) ;
934+ await expect ( page . locator ( ".midi-studio-v2__timing-header-row-1.midi-studio-v2__timing-axis-header" ) ) . toHaveText ( "Bar" ) ;
935+ await expect ( page . locator ( ".midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-instrument-header" ) ) . toHaveText ( "" ) ;
936+ await expect ( page . locator ( ".midi-studio-v2__timing-header-row-2.midi-studio-v2__timing-axis-header" ) ) . toHaveText ( "Beat" ) ;
937+ await expect ( page . locator ( '.midi-studio-v2__timing-header-row-1.midi-studio-v2__note-table-column-header[data-step-index="0"]' ) ) . toHaveText ( "1" ) ;
938+ await expect ( page . locator ( '.midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header[data-step-index="0"]' ) ) . toHaveText ( "1" ) ;
939+ await expect ( page . locator ( '.midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header[data-step-index="1"]' ) ) . toHaveText ( "2" ) ;
940+ await expect ( page . locator ( '.midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header[data-step-index="2"]' ) ) . toHaveText ( "3" ) ;
941+ await expect ( page . locator ( '.midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header[data-step-index="3"]' ) ) . toHaveText ( "4" ) ;
942+
943+ const output = page . locator ( "#instrumentGridOutput" ) ;
944+ const gridLayout = await output . evaluate ( ( element ) => {
945+ const firstNote = element . querySelector ( '.midi-studio-v2__octave-note-cell[data-step-index="0"]' ) ;
946+ return {
947+ columnWidth : firstNote . getBoundingClientRect ( ) . width ,
948+ columnTemplate : getComputedStyle ( element . querySelector ( ".midi-studio-v2__octave-timeline" ) ) . gridTemplateColumns
949+ } ;
950+ } ) ;
951+ expect ( gridLayout . columnWidth ) . toBeLessThanOrEqual ( 32 ) ;
952+ expect ( gridLayout . columnTemplate ) . toContain ( "28.8px" ) ;
953+
954+ await output . evaluate ( ( element ) => {
955+ element . style . maxHeight = "150px" ;
956+ element . scrollTop = 140 ;
957+ element . dispatchEvent ( new Event ( "scroll" ) ) ;
958+ } ) ;
959+ const verticalFreeze = await output . evaluate ( ( element ) => {
960+ const outputRect = element . getBoundingClientRect ( ) ;
961+ const row1 = element . querySelector ( ".midi-studio-v2__timing-header-row-1.midi-studio-v2__note-table-column-header" ) . getBoundingClientRect ( ) ;
962+ const row2 = element . querySelector ( ".midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header" ) . getBoundingClientRect ( ) ;
963+ return {
964+ row1TopDelta : Math . abs ( row1 . top - outputRect . top ) ,
965+ row2BelowRow1 : row2 . top >= row1 . bottom - 1 ,
966+ scrollTop : Math . round ( element . scrollTop )
967+ } ;
968+ } ) ;
969+ expect ( verticalFreeze . scrollTop ) . toBeGreaterThan ( 0 ) ;
970+ expect ( verticalFreeze . row1TopDelta ) . toBeLessThanOrEqual ( 2 ) ;
971+ expect ( verticalFreeze . row2BelowRow1 ) . toBe ( true ) ;
972+
973+ await output . evaluate ( ( element ) => {
974+ element . scrollLeft = 220 ;
975+ element . dispatchEvent ( new Event ( "scroll" ) ) ;
976+ } ) ;
977+ const horizontalFreeze = await output . evaluate ( ( element ) => {
978+ const outputRect = element . getBoundingClientRect ( ) ;
979+ const header = element . querySelector ( '.midi-studio-v2__grid-cell--beat-header[data-step-index="6"]' ) . getBoundingClientRect ( ) ;
980+ const bodyCell = element . querySelector ( '.midi-studio-v2__octave-note-cell[data-row-token="C5"][data-step-index="6"]' ) . getBoundingClientRect ( ) ;
981+ const label = element . querySelector ( '.midi-studio-v2__octave-row-label[data-octave-row="C5"]' ) . getBoundingClientRect ( ) ;
982+ return {
983+ headerBodyDelta : Math . abs ( header . left - bodyCell . left ) ,
984+ labelLeftDelta : Math . abs ( label . left - outputRect . left ) ,
985+ labelRowDelta : Math . abs ( label . top - bodyCell . top ) ,
986+ scrollLeft : Math . round ( element . scrollLeft )
987+ } ;
988+ } ) ;
989+ expect ( horizontalFreeze . scrollLeft ) . toBeGreaterThan ( 0 ) ;
990+ expect ( horizontalFreeze . headerBodyDelta ) . toBeLessThanOrEqual ( 1 ) ;
991+ expect ( horizontalFreeze . labelLeftDelta ) . toBeLessThanOrEqual ( 2 ) ;
992+ expect ( horizontalFreeze . labelRowDelta ) . toBeLessThanOrEqual ( 1 ) ;
993+
994+ await octaveCell ( page , "C6" , 10 ) . click ( ) ;
995+ await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveText ( "" ) ;
996+ await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
997+ await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveAttribute ( "data-note-values" , / C 6 / ) ;
998+ await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveClass ( / m i d i - s t u d i o - v 2 _ _ g r i d - c e l l - - l a n e - s e l e c t e d / ) ;
999+ const activeHighlight = await octaveCell ( page , "C6" , 10 ) . evaluate ( ( cell ) => {
1000+ const marker = getComputedStyle ( cell , "::before" ) ;
1001+ return {
1002+ content : marker . content ,
1003+ width : marker . width
1004+ } ;
1005+ } ) ;
1006+ expect ( activeHighlight . content ) . not . toBe ( "none" ) ;
1007+ expect ( Number . parseFloat ( activeHighlight . width ) ) . toBeGreaterThan ( 8 ) ;
1008+ } finally {
1009+ await workspaceV2CoverageReporter . stop ( page ) ;
1010+ await server . close ( ) ;
1011+ }
1012+ } ) ;
1013+
9271014 test ( "imports UAT manifest and plays the upbeat multi-instrument studio arrangement" , async ( { page } ) => {
9281015 const server = await openMidiStudioForImport ( page ) ;
9291016 try {
0 commit comments