@@ -423,12 +423,14 @@ async function timelineScrollSnapshot(page) {
423423 return page . locator ( "#instrumentGridOutput" ) . evaluate ( ( output ) => {
424424 const firstHeader = output . querySelector ( ".midi-studio-v2__grid-cell--timing-header" ) ;
425425 const firstNoteCell = output . querySelector ( ".midi-studio-v2__octave-note-cell" ) ;
426+ const topScrollbar = output . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) ;
426427 return {
427428 headerLeft : Math . round ( firstHeader ?. getBoundingClientRect ( ) . left || 0 ) ,
428429 noteLeft : Math . round ( firstNoteCell ?. getBoundingClientRect ( ) . left || 0 ) ,
429430 scrollDataset : output . dataset . timelineScrollLeft || "" ,
430431 scrollLeft : Math . round ( output . scrollLeft ) ,
431- scrollTop : Math . round ( output . scrollTop )
432+ scrollTop : Math . round ( output . scrollTop ) ,
433+ topScrollLeft : Math . round ( topScrollbar ?. scrollLeft || 0 )
432434 } ;
433435 } ) ;
434436}
@@ -978,18 +980,70 @@ test.describe("MIDI Studio V2", () => {
978980 const firstNote = element . querySelector ( '.midi-studio-v2__octave-note-cell[data-step-index="0"]' ) ;
979981 const firstRow = element . querySelector ( '.midi-studio-v2__octave-note-cell[data-octave-row-index="0"][data-step-index="0"]' ) ;
980982 const secondRow = element . querySelector ( '.midi-studio-v2__octave-note-cell[data-octave-row-index="1"][data-step-index="0"]' ) ;
983+ const topScrollbar = element . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) ;
984+ const grid = element . querySelector ( ".midi-studio-v2__octave-timeline" ) ;
985+ const firstLabel = element . querySelector ( ".midi-studio-v2__octave-row-label" ) ;
981986 return {
982987 alternatingRowsDiffer : getComputedStyle ( firstRow ) . backgroundColor !== getComputedStyle ( secondRow ) . backgroundColor ,
983988 borderRightWidth : getComputedStyle ( firstNote ) . borderRightWidth ,
989+ cellCssWidth : getComputedStyle ( firstNote ) . width ,
984990 columnWidth : firstNote . getBoundingClientRect ( ) . width ,
985- columnTemplate : getComputedStyle ( element . querySelector ( ".midi-studio-v2__octave-timeline" ) ) . gridTemplateColumns
991+ columnTemplate : getComputedStyle ( grid ) . gridTemplateColumns ,
992+ containsGrid : grid ?. parentElement === element ,
993+ labelWidth : firstLabel . getBoundingClientRect ( ) . width ,
994+ topScrollbarHeight : topScrollbar . getBoundingClientRect ( ) . height
986995 } ;
987996 } ) ;
997+ expect ( gridLayout . containsGrid ) . toBe ( true ) ;
988998 expect ( gridLayout . alternatingRowsDiffer ) . toBe ( true ) ;
989999 expect ( Number . parseFloat ( gridLayout . borderRightWidth ) ) . toBeGreaterThanOrEqual ( 0.8 ) ;
9901000 expect ( Number . parseFloat ( gridLayout . borderRightWidth ) ) . toBeLessThanOrEqual ( 1 ) ;
991- expect ( gridLayout . columnWidth ) . toBeLessThanOrEqual ( 32 ) ;
992- expect ( gridLayout . columnTemplate ) . toContain ( "28.8px" ) ;
1001+ expect ( Number . parseFloat ( gridLayout . cellCssWidth ) ) . toBeGreaterThanOrEqual ( 0.8 ) ;
1002+ expect ( Number . parseFloat ( gridLayout . cellCssWidth ) ) . toBeLessThanOrEqual ( 1 ) ;
1003+ expect ( gridLayout . columnWidth ) . toBeLessThanOrEqual ( 1 ) ;
1004+ expect ( gridLayout . columnTemplate ) . toContain ( "1px" ) ;
1005+ expect ( gridLayout . labelWidth ) . toBeGreaterThan ( 40 ) ;
1006+ expect ( gridLayout . topScrollbarHeight ) . toBeGreaterThanOrEqual ( 12 ) ;
1007+
1008+ await output . evaluate ( ( element ) => {
1009+ element . style . maxWidth = "80px" ;
1010+ element . dispatchEvent ( new Event ( "scroll" ) ) ;
1011+ } ) ;
1012+ const scrollbars = await output . evaluate ( ( element ) => {
1013+ const topScrollbar = element . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) ;
1014+ const grid = element . querySelector ( ".midi-studio-v2__octave-timeline" ) ;
1015+ const topRect = topScrollbar . getBoundingClientRect ( ) ;
1016+ return {
1017+ bottomCanScroll : element . scrollWidth > element . clientWidth ,
1018+ bottomOverflowX : getComputedStyle ( element ) . overflowX ,
1019+ gridOverflowsOutput : grid . scrollWidth > element . clientWidth ,
1020+ topCanScroll : topScrollbar . scrollWidth > topScrollbar . clientWidth ,
1021+ topOverflowX : getComputedStyle ( topScrollbar ) . overflowX ,
1022+ topVisible : topRect . height >= 12 && topRect . width > 0
1023+ } ;
1024+ } ) ;
1025+ expect ( scrollbars . bottomCanScroll ) . toBe ( true ) ;
1026+ expect ( scrollbars . bottomOverflowX ) . toMatch ( / a u t o | s c r o l l / ) ;
1027+ expect ( scrollbars . gridOverflowsOutput ) . toBe ( true ) ;
1028+ expect ( scrollbars . topCanScroll ) . toBe ( true ) ;
1029+ expect ( scrollbars . topOverflowX ) . toMatch ( / a u t o | s c r o l l / ) ;
1030+ expect ( scrollbars . topVisible ) . toBe ( true ) ;
1031+
1032+ await output . evaluate ( ( element ) => {
1033+ const topScrollbar = element . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) ;
1034+ topScrollbar . scrollLeft = Math . min ( 14 , topScrollbar . scrollWidth - topScrollbar . clientWidth ) ;
1035+ topScrollbar . dispatchEvent ( new Event ( "scroll" ) ) ;
1036+ } ) ;
1037+ await expect . poll ( ( ) => timelineScrollSnapshot ( page ) ) . toEqual ( expect . objectContaining ( {
1038+ scrollLeft : await output . evaluate ( ( element ) => Math . round ( element . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) . scrollLeft ) )
1039+ } ) ) ;
1040+ await output . evaluate ( ( element ) => {
1041+ element . scrollLeft = Math . min ( 9 , element . scrollWidth - element . clientWidth ) ;
1042+ element . dispatchEvent ( new Event ( "scroll" ) ) ;
1043+ } ) ;
1044+ await expect . poll ( ( ) => timelineScrollSnapshot ( page ) ) . toEqual ( expect . objectContaining ( {
1045+ topScrollLeft : await output . evaluate ( ( element ) => Math . round ( element . scrollLeft ) )
1046+ } ) ) ;
9931047
9941048 await output . evaluate ( ( element ) => {
9951049 element . style . maxHeight = "150px" ;
@@ -1000,15 +1054,16 @@ test.describe("MIDI Studio V2", () => {
10001054 const outputRect = element . getBoundingClientRect ( ) ;
10011055 const row1 = element . querySelector ( ".midi-studio-v2__timing-header-row-1.midi-studio-v2__note-table-column-header" ) . getBoundingClientRect ( ) ;
10021056 const row2 = element . querySelector ( ".midi-studio-v2__timing-header-row-2.midi-studio-v2__note-table-column-header" ) . getBoundingClientRect ( ) ;
1057+ const topScrollbar = element . querySelector ( ".midi-studio-v2__timeline-scroll-proxy" ) . getBoundingClientRect ( ) ;
10031058 return {
10041059 headerGap : Math . round ( row2 . top - row1 . bottom ) ,
1005- row1TopDelta : Math . abs ( row1 . top - outputRect . top ) ,
1060+ row1BelowTopScrollbar : Math . abs ( row1 . top - topScrollbar . bottom ) ,
10061061 row2BelowRow1 : row2 . top >= row1 . bottom - 1 ,
10071062 scrollTop : Math . round ( element . scrollTop )
10081063 } ;
10091064 } ) ;
10101065 expect ( verticalFreeze . scrollTop ) . toBeGreaterThan ( 0 ) ;
1011- expect ( verticalFreeze . row1TopDelta ) . toBeLessThanOrEqual ( 2 ) ;
1066+ expect ( verticalFreeze . row1BelowTopScrollbar ) . toBeLessThanOrEqual ( 2 ) ;
10121067 expect ( verticalFreeze . headerGap ) . toBeLessThanOrEqual ( 1 ) ;
10131068 expect ( verticalFreeze . row2BelowRow1 ) . toBe ( true ) ;
10141069
@@ -1023,16 +1078,21 @@ test.describe("MIDI Studio V2", () => {
10231078 const label = element . querySelector ( '.midi-studio-v2__octave-row-label[data-octave-row="C5"]' ) . getBoundingClientRect ( ) ;
10241079 return {
10251080 headerBodyDelta : Math . abs ( header . left - bodyCell . left ) ,
1026- labelLeftDelta : Math . abs ( label . left - outputRect . left ) ,
1081+ labelVisibleInViewport : label . right > outputRect . left && label . left < outputRect . right ,
10271082 labelRowDelta : Math . abs ( label . top - bodyCell . top ) ,
10281083 scrollLeft : Math . round ( element . scrollLeft )
10291084 } ;
10301085 } ) ;
10311086 expect ( horizontalFreeze . scrollLeft ) . toBeGreaterThan ( 0 ) ;
10321087 expect ( horizontalFreeze . headerBodyDelta ) . toBeLessThanOrEqual ( 1 ) ;
1033- expect ( horizontalFreeze . labelLeftDelta ) . toBeLessThanOrEqual ( 2 ) ;
1088+ expect ( horizontalFreeze . labelVisibleInViewport ) . toBe ( true ) ;
10341089 expect ( horizontalFreeze . labelRowDelta ) . toBeLessThanOrEqual ( 1 ) ;
10351090
1091+ await output . evaluate ( ( element ) => {
1092+ element . style . maxWidth = "" ;
1093+ element . scrollLeft = 0 ;
1094+ element . dispatchEvent ( new Event ( "scroll" ) ) ;
1095+ } ) ;
10361096 await octaveCell ( page , "C6" , 10 ) . click ( ) ;
10371097 await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveText ( "" ) ;
10381098 await expect ( octaveCell ( page , "C6" , 10 ) ) . toHaveAttribute ( "data-note-lanes" , / l e a d / ) ;
@@ -1046,7 +1106,8 @@ test.describe("MIDI Studio V2", () => {
10461106 } ;
10471107 } ) ;
10481108 expect ( activeHighlight . content ) . not . toBe ( "none" ) ;
1049- expect ( Number . parseFloat ( activeHighlight . width ) ) . toBeGreaterThan ( 8 ) ;
1109+ expect ( Number . parseFloat ( activeHighlight . width ) ) . toBeGreaterThanOrEqual ( 0.8 ) ;
1110+ expect ( Number . parseFloat ( activeHighlight . width ) ) . toBeLessThanOrEqual ( 1 ) ;
10501111 } finally {
10511112 await workspaceV2CoverageReporter . stop ( page ) ;
10521113 await server . close ( ) ;
0 commit comments