@@ -3051,6 +3051,134 @@ test.describe("MIDI Studio V2", () => {
30513051 }
30523052 } ) ;
30533053
3054+ test ( "keeps PR064 Song Sheet and Instrument Settings fields in responsive editable/read-only grids" , async ( { page } ) => {
3055+ await page . setViewportSize ( { width : 1600 , height : 900 } ) ;
3056+ const server = await openMidiStudioForImport ( page ) ;
3057+ try {
3058+ await page . locator ( "#toolImportManifestInput" ) . setInputFiles ( uatManifestPath ) ;
3059+ await selectMidiStudioTab ( page , "song-setup" ) ;
3060+
3061+ await page . locator ( "#songSheetSectionsInput" ) . fill ( "intro: C F\nbridge: Dm G\nloop: F G C C" ) ;
3062+ await page . locator ( "#songSheetLoopSectionsInput" ) . fill ( "bridge, loop" ) ;
3063+ await expect ( page . locator ( "#songSheetSummary [data-song-sheet-summary-field='bars'] dd" ) ) . toHaveText ( "8" ) ;
3064+ const songSheetLayout = await page . locator ( "#songSheetContent" ) . evaluate ( ( content ) => {
3065+ const columnCount = ( element ) => getComputedStyle ( element ) . gridTemplateColumns . split ( " " ) . filter ( Boolean ) . length ;
3066+ const fieldRows = ( fields ) => new Set ( fields . map ( ( field ) => Math . round ( field . getBoundingClientRect ( ) . top ) ) ) . size ;
3067+ const editableGrid = content . querySelector ( ".midi-studio-v2__song-sheet-grid" ) ;
3068+ const summary = content . querySelector ( "#songSheetSummary" ) ;
3069+ const editableFields = Array . from ( editableGrid . querySelectorAll ( "[data-midi-studio-field-state='editable']" ) ) ;
3070+ const readonlyFields = Array . from ( summary . querySelectorAll ( "[data-midi-studio-field-state='readonly']" ) ) ;
3071+ return {
3072+ editableBackgrounds : editableFields . map ( ( field ) => getComputedStyle ( field ) . backgroundColor ) ,
3073+ editableBorderStyles : editableFields . map ( ( field ) => getComputedStyle ( field ) . borderStyle ) ,
3074+ editableColumns : columnCount ( editableGrid ) ,
3075+ editableControls : editableGrid . querySelectorAll ( "textarea:not([readonly]):not([disabled])" ) . length ,
3076+ editableFieldRows : fieldRows ( editableFields ) ,
3077+ readonlyAria : readonlyFields . every ( ( field ) => field . getAttribute ( "aria-readonly" ) === "true" ) ,
3078+ readonlyBorderStyles : readonlyFields . map ( ( field ) => getComputedStyle ( field ) . borderStyle ) ,
3079+ readonlyControls : summary . querySelectorAll ( "input, select, textarea" ) . length ,
3080+ readonlyFields : readonlyFields . length ,
3081+ summaryColumns : columnCount ( summary ) ,
3082+ summaryFieldRows : fieldRows ( readonlyFields )
3083+ } ;
3084+ } ) ;
3085+ expect ( songSheetLayout . editableColumns ) . toBeGreaterThan ( 1 ) ;
3086+ expect ( songSheetLayout . summaryColumns ) . toBeGreaterThan ( 1 ) ;
3087+ expect ( songSheetLayout . editableFieldRows ) . toBe ( 1 ) ;
3088+ expect ( songSheetLayout . summaryFieldRows ) . toBeLessThan ( songSheetLayout . readonlyFields ) ;
3089+ expect ( songSheetLayout . editableControls ) . toBe ( 2 ) ;
3090+ expect ( songSheetLayout . readonlyControls ) . toBe ( 0 ) ;
3091+ expect ( songSheetLayout . readonlyFields ) . toBeGreaterThanOrEqual ( 6 ) ;
3092+ expect ( songSheetLayout . readonlyAria ) . toBe ( true ) ;
3093+ expect ( songSheetLayout . editableBorderStyles . every ( ( style ) => style === "solid" ) ) . toBe ( true ) ;
3094+ expect ( songSheetLayout . readonlyBorderStyles . every ( ( style ) => style === "dashed" ) ) . toBe ( true ) ;
3095+ expect ( new Set ( songSheetLayout . editableBackgrounds ) . size ) . toBe ( 1 ) ;
3096+
3097+ const songSheetCanonical = await page . evaluate ( ( ) => window . __midiStudioV2App . selectedSong ( ) . studioArrangement . songSheet ) ;
3098+ expect ( songSheetCanonical ) . toEqual ( {
3099+ loopSections : "bridge, loop" ,
3100+ sections : "intro: C F\nbridge: Dm G\nloop: F G C C"
3101+ } ) ;
3102+
3103+ await selectMidiStudioTab ( page , "instruments" ) ;
3104+ const editor = page . locator ( "#selectedInstrumentEditor" ) ;
3105+ await expect ( editor ) . toHaveAttribute ( "data-selected-instrument-id" , "lead" ) ;
3106+ const instrumentLayout = await editor . evaluate ( ( instrumentEditor ) => {
3107+ const columnCount = ( element ) => getComputedStyle ( element ) . gridTemplateColumns . split ( " " ) . filter ( Boolean ) . length ;
3108+ const buckets = Array . from ( instrumentEditor . querySelectorAll ( ".midi-studio-v2__instrument-editor-bucket" ) ) ;
3109+ const bucketRows = new Set ( buckets . map ( ( bucket ) => Math . round ( bucket . getBoundingClientRect ( ) . top ) ) ) . size ;
3110+ const identityBucket = instrumentEditor . querySelector ( "[data-instrument-editor-bucket='identity']" ) ;
3111+ const editableFields = Array . from ( instrumentEditor . querySelectorAll ( "[data-midi-studio-field-state='editable']" ) ) ;
3112+ const readonlyFields = Array . from ( instrumentEditor . querySelectorAll ( "[data-midi-studio-field-state='readonly']" ) ) ;
3113+ const unwiredFields = Array . from ( instrumentEditor . querySelectorAll ( "[data-midi-studio-field-state='unwired']" ) ) ;
3114+ const readonlyOutput = instrumentEditor . querySelector ( "[data-instrument-derived-field='audible-preview']" ) ;
3115+ return {
3116+ bucketRows,
3117+ buckets : buckets . length ,
3118+ editorColumns : columnCount ( instrumentEditor ) ,
3119+ editableBorderStyles : editableFields . map ( ( field ) => getComputedStyle ( field ) . borderStyle ) ,
3120+ editableControls : editableFields . map ( ( field ) => field . querySelector ( "input, select, textarea" ) ?. disabled === false ) ,
3121+ identityColumns : columnCount ( identityBucket ) ,
3122+ readonlyBorderStyles : readonlyFields . map ( ( field ) => getComputedStyle ( field ) . borderStyle ) ,
3123+ readonlyOutputText : readonlyOutput ?. textContent || "" ,
3124+ readonlyOutputs : readonlyFields . filter ( ( field ) => field . querySelector ( "output[aria-readonly='true']" ) ) . length ,
3125+ unwiredControls : unwiredFields . map ( ( field ) => {
3126+ const control = field . querySelector ( "[data-midi-studio-future-control]" ) ;
3127+ return {
3128+ borderStyle : getComputedStyle ( field ) . borderStyle ,
3129+ controlClass : control ?. classList . contains ( "midi-studio-v2__unwired-control" ) || false ,
3130+ disabled : control ?. disabled || false ,
3131+ status : control ?. dataset . midiStudioUnwired || "" ,
3132+ title : control ?. title || ""
3133+ } ;
3134+ } ) ,
3135+ unwiredFields : unwiredFields . length
3136+ } ;
3137+ } ) ;
3138+ expect ( instrumentLayout . editorColumns ) . toBeGreaterThan ( 1 ) ;
3139+ expect ( instrumentLayout . identityColumns ) . toBeGreaterThan ( 1 ) ;
3140+ expect ( instrumentLayout . bucketRows ) . toBeLessThan ( instrumentLayout . buckets ) ;
3141+ expect ( instrumentLayout . editableControls . every ( Boolean ) ) . toBe ( true ) ;
3142+ expect ( instrumentLayout . editableBorderStyles . every ( ( style ) => style === "solid" ) ) . toBe ( true ) ;
3143+ expect ( instrumentLayout . readonlyOutputs ) . toBeGreaterThanOrEqual ( 1 ) ;
3144+ expect ( instrumentLayout . readonlyOutputText ) . toContain ( "Lead" ) ;
3145+ expect ( instrumentLayout . readonlyBorderStyles . every ( ( style ) => style === "dashed" ) ) . toBe ( true ) ;
3146+ expect ( instrumentLayout . unwiredFields ) . toBeGreaterThanOrEqual ( 8 ) ;
3147+ expect ( instrumentLayout . unwiredControls . every ( ( control ) => control . borderStyle === "solid" && control . controlClass && control . disabled && control . status === "not-implemented" && control . title . includes ( "Not implemented:" ) ) ) . toBe ( true ) ;
3148+
3149+ await page . locator ( "#previewDisplayNameLeadInput" ) . fill ( "Lead Grid Voice" ) ;
3150+ await setInputValue ( page , "#previewVolumeLeadInput" , "0.75" ) ;
3151+ await instrumentTypeSelect ( page , "lead" ) . selectOption ( "Bass" ) ;
3152+ await instrumentSelect ( page , "lead" ) . selectOption ( "gm-electric-bass-finger" ) ;
3153+ await expect ( editor . locator ( "[data-instrument-derived-field='audible-preview']" ) ) . toHaveText ( "Synth Bass 1" ) ;
3154+ expect ( await page . evaluate ( ( ) => {
3155+ const settings = window . __midiStudioV2App . selectedSong ( ) . studioArrangement . previewLaneSettings ;
3156+ return {
3157+ displayName : settings . displayNames . lead ,
3158+ instrument : settings . instruments . lead ,
3159+ instrumentType : settings . instrumentTypes . lead ,
3160+ volume : settings . volumes . lead
3161+ } ;
3162+ } ) ) . toEqual ( {
3163+ displayName : "Lead Grid Voice" ,
3164+ instrument : "gm-electric-bass-finger" ,
3165+ instrumentType : "Bass" ,
3166+ volume : 0.75
3167+ } ) ;
3168+
3169+ await selectMidiStudioTab ( page , "studio" ) ;
3170+ await page . locator ( "#playButton" ) . click ( ) ;
3171+ await expect ( page . locator ( "#playButton" ) ) . toBeDisabled ( ) ;
3172+ await expect ( page . locator ( "#stopButton" ) ) . toBeEnabled ( ) ;
3173+ await page . locator ( "#stopButton" ) . click ( ) ;
3174+ await expect ( page . locator ( "#stopButton" ) ) . toBeDisabled ( ) ;
3175+ await expect ( page . locator ( "#playButton" ) ) . toBeEnabled ( ) ;
3176+ } finally {
3177+ await workspaceV2CoverageReporter . stop ( page ) ;
3178+ await server . close ( ) ;
3179+ }
3180+ } ) ;
3181+
30543182 test ( "derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song" , async ( { page } ) => {
30553183 const server = await openMidiStudioForImport ( page ) ;
30563184 try {
0 commit comments