@@ -515,6 +515,10 @@ async function selectInstrumentRow(page, lane) {
515515 await instrumentRow ( page , lane ) . locator ( `[data-lane-label="${ lane } "]` ) . click ( ) ;
516516}
517517
518+ function timelineQuickInstrumentRow ( page , lane ) {
519+ return page . locator ( `[data-quick-instrument-lane="${ lane } "]` ) ;
520+ }
521+
518522function instrumentTypeSelect ( page , lane ) {
519523 return instrumentRow ( page , lane ) . locator ( "[data-lane-instrument-type-select]" ) ;
520524}
@@ -2136,8 +2140,8 @@ test.describe("MIDI Studio V2", () => {
21362140 await expect ( page . locator ( "#songSheetKeyInput" ) ) . toHaveValue ( "G major" ) ;
21372141 await expect ( page . locator ( "#songSheetStyleInput" ) ) . toHaveJSProperty ( "tagName" , "SELECT" ) ;
21382142 await expect ( page . locator ( "#songSheetStyleInput" ) ) . toHaveValue ( "public-domain-reel" ) ;
2139- await expect ( page . locator ( "#songDetails [data-song-detail-field='tags']" ) ) . toHaveValue ( "uat, upbeat, traditional, public-domain" ) ;
2140- await expect ( page . locator ( "#songDetails [data-song-detail-field='usage']" ) ) . toHaveValue ( "uat, public-domain, preview-synth" ) ;
2143+ await expect ( page . locator ( "#songDetails [data-song-detail-field='tags']" ) ) . toHaveCount ( 0 ) ;
2144+ await expect ( page . locator ( "#songDetails [data-song-detail-field='usage']" ) ) . toHaveCount ( 0 ) ;
21412145 await expect ( page . locator ( "#songDetails [data-song-detail-field='notes']" ) ) . toHaveValue ( "Traditional Camptown Races style public-domain test arrangement with lead, bass, chords/pad, and drums." ) ;
21422146 await expect ( page . locator ( "#songSectionsLoopDetails [data-song-detail-field='sections']" ) ) . toHaveValue ( "verse:2, chorus:2" ) ;
21432147 await expect ( page . locator ( "#songSectionsLoopDetails [data-song-detail-field='loopEnabled']" ) ) . toBeChecked ( ) ;
@@ -2154,12 +2158,11 @@ test.describe("MIDI Studio V2", () => {
21542158 await expect ( page . locator ( "#songDetails [data-song-detail-field='instrumentSet']" ) ) . toHaveCount ( 0 ) ;
21552159
21562160 await page . locator ( "#songDetails [data-song-detail-field='name']" ) . fill ( "Edited UAT Reel" ) ;
2157- await page . locator ( "#songDetails [data-song-detail-field='id']" ) . fill ( "edited-uat-reel" ) ;
2161+ await expect ( page . locator ( "#songDetails [data-song-detail-field='id']" ) ) . toHaveJSProperty ( "readOnly" , true ) ;
2162+ await expect ( page . locator ( "#songDetails [data-song-detail-field='id']" ) ) . toHaveValue ( "editedUatReel" ) ;
21582163 await page . locator ( "#songSheetTempoInput" ) . fill ( "156" ) ;
21592164 await page . locator ( "#songSheetKeyInput" ) . selectOption ( "C major" ) ;
21602165 await page . locator ( "#songSheetStyleInput" ) . selectOption ( "chip" ) ;
2161- await page . locator ( "#songDetails [data-song-detail-field='tags']" ) . fill ( "uat, edited" ) ;
2162- await page . locator ( "#songDetails [data-song-detail-field='usage']" ) . fill ( "title, test" ) ;
21632166 await page . locator ( "#songDetails [data-song-detail-field='notes']" ) . fill ( "Edited notes from Song Setup." ) ;
21642167 await page . locator ( "#songSectionsLoopDetails [data-song-detail-field='loopEnabled']" ) . setChecked ( false ) ;
21652168 expect ( await page . evaluate ( ( ) => {
@@ -2172,21 +2175,17 @@ test.describe("MIDI Studio V2", () => {
21722175 name : song . name ,
21732176 notes : song . director . notes ,
21742177 style : song . studioArrangement . style ,
2175- tags : song . tags ,
2176- tempo : song . studioArrangement . tempo ,
2177- usage : song . director . usage
2178+ tempo : song . studioArrangement . tempo
21782179 } ;
21792180 } ) ) . toEqual ( {
2180- activeSongId : "edited-uat-reel " ,
2181- id : "edited-uat-reel " ,
2181+ activeSongId : "editedUatReel " ,
2182+ id : "editedUatReel " ,
21822183 key : "C major" ,
21832184 loopEnabled : false ,
21842185 name : "Edited UAT Reel" ,
21852186 notes : "Edited notes from Song Setup." ,
21862187 style : "chip" ,
2187- tags : [ "uat" , "edited" ] ,
2188- tempo : "156" ,
2189- usage : [ "title" , "test" ]
2188+ tempo : "156"
21902189 } ) ;
21912190
21922191 await page . locator ( '[data-song-id="frog-hop-nursery-rhyme"]' ) . click ( ) ;
@@ -2195,7 +2194,7 @@ test.describe("MIDI Studio V2", () => {
21952194 await expect ( page . locator ( "#songSheetTempoInput" ) ) . toHaveValue ( "132" ) ;
21962195 await expect ( page . locator ( "#songSheetKeyInput" ) ) . toHaveValue ( "C major" ) ;
21972196 await expect ( page . locator ( "#songSheetStyleInput" ) ) . toHaveValue ( "chip" ) ;
2198- await expect ( page . locator ( "#songDetails [data-song-detail-field='usage']" ) ) . toHaveValue ( "uat, frog-hop, nursery-rhyme" ) ;
2197+ await expect ( page . locator ( "#songDetails [data-song-detail-field='usage']" ) ) . toHaveCount ( 0 ) ;
21992198
22002199 await selectMidiStudioTab ( page , "instruments" ) ;
22012200 await expect ( page . locator ( "#instrumentListContent" ) ) . toBeVisible ( ) ;
@@ -2417,8 +2416,8 @@ test.describe("MIDI Studio V2", () => {
24172416
24182417 await selectMidiStudioTab ( page , "instruments" ) ;
24192418 await expect ( page . locator ( "#instrumentSettingsContent" ) ) . toBeVisible ( ) ;
2420- await expect ( page . locator ( "#playMiddleCButton " ) ) . toBeVisible ( ) ;
2421- await expect ( page . locator ( "#playMiddleCButton " ) ) . not . toHaveAttribute ( "data-midi-studio-unwired" ) ;
2419+ await expect ( page . locator ( "#instrumentAuditionKeyboard " ) ) . toBeVisible ( ) ;
2420+ await expect ( page . locator ( "#instrumentAuditionKeyboard " ) ) . not . toHaveAttribute ( "data-midi-studio-unwired" ) ;
24222421 await expect ( page . locator ( "#previewVolumeLeadInput" ) ) . toBeVisible ( ) ;
24232422 await expect ( page . locator ( "#previewPanLeadInput" ) ) . toBeVisible ( ) ;
24242423 const instrumentFutureControls = await page . locator ( '[data-midi-studio-tab-panel="instruments"] [data-midi-studio-future-control]' ) . evaluateAll ( ( controls ) => controls . map ( ( control ) => ( {
@@ -2446,14 +2445,16 @@ test.describe("MIDI Studio V2", () => {
24462445 await page . evaluate ( ( ) => {
24472446 window . __midiStudioPreviewSynthEvents = [ ] ;
24482447 } ) ;
2449- await page . locator ( "#playMiddleCButton " ) . click ( ) ;
2450- await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / A u d i t i o n e d m i d d l e C f o r L e a d / ) ;
2448+ await page . locator ( "#instrumentAuditionKeyboard [data-audition-note='C4'] " ) . click ( ) ;
2449+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / A u d i t i o n e d C 4 f o r L e a d / ) ;
24512450 expect ( await page . evaluate ( ( ) => window . __midiStudioPreviewSynthEvents . some ( ( event ) => event . action === "oscillator-start" ) ) ) . toBe ( true ) ;
24522451
24532452 await selectMidiStudioTab ( page , "studio" ) ;
24542453 await waitForCanvasRender ( page ) ;
2455- await clickCanvasCell ( page , "C6" , 2 ) ;
2456- expect ( await hasCanvasNote ( page , "lead" , "C6" , 2 ) ) . toBe ( true ) ;
2454+ const editableTarget = await emptyCanvasRun ( page , { lane : "lead" , length : 1 } ) ;
2455+ expect ( editableTarget ) . toBeTruthy ( ) ;
2456+ await clickCanvasCell ( page , editableTarget . rowToken , editableTarget . stepIndex ) ;
2457+ expect ( await hasCanvasNote ( page , "lead" , editableTarget . rowToken , editableTarget . stepIndex ) ) . toBe ( true ) ;
24572458 await page . locator ( "#playButton" ) . click ( ) ;
24582459 await expect ( page . locator ( "#playButton" ) ) . toBeDisabled ( ) ;
24592460 await expect ( page . locator ( "#stopButton" ) ) . toBeEnabled ( ) ;
@@ -2620,6 +2621,96 @@ test.describe("MIDI Studio V2", () => {
26202621 }
26212622 } ) ;
26222623
2624+ test ( "syncs PR060 song fields instrument selection and audition keyboard" , async ( { page } ) => {
2625+ const server = await openMidiStudioForImport ( page ) ;
2626+ try {
2627+ await page . locator ( "#toolImportManifestInput" ) . setInputFiles ( uatManifestPath ) ;
2628+
2629+ await selectMidiStudioTab ( page , "song-setup" ) ;
2630+ await expect ( page . locator ( "#songDetails [data-song-detail-field='tags']" ) ) . toHaveCount ( 0 ) ;
2631+ await expect ( page . locator ( "#songDetails [data-song-detail-field='usage']" ) ) . toHaveCount ( 0 ) ;
2632+ await expect ( page . locator ( "#songDetailsContent" ) ) . not . toContainText ( / \b T a g s \b | \b U s a g e \b / ) ;
2633+ await expect ( page . locator ( "#songDetails [data-song-detail-field='id']" ) ) . toHaveJSProperty ( "readOnly" , true ) ;
2634+ await page . locator ( "#songDetails [data-song-detail-field='name']" ) . fill ( "Camptown Races UAT Reel" ) ;
2635+ await expect ( page . locator ( "#songDetails [data-song-detail-field='id']" ) ) . toHaveValue ( "camptownRacesUatReel" ) ;
2636+ await page . locator ( "#songDetails [data-song-detail-field='name']" ) . fill ( "New Song 4" ) ;
2637+ await expect ( page . locator ( "#songDetails [data-song-detail-field='id']" ) ) . toHaveValue ( "newSong4" ) ;
2638+ expect ( await page . evaluate ( ( ) => ( {
2639+ activeSongId : window . __midiStudioV2App . payload . activeSongId ,
2640+ id : window . __midiStudioV2App . selectedSong ( ) . id ,
2641+ name : window . __midiStudioV2App . selectedSong ( ) . name
2642+ } ) ) ) . toEqual ( {
2643+ activeSongId : "newSong4" ,
2644+ id : "newSong4" ,
2645+ name : "New Song 4"
2646+ } ) ;
2647+
2648+ await page . locator ( "#songSheetIntroInput" ) . fill ( "C Am F G" ) ;
2649+ await page . locator ( "#songSheetLoopInput" ) . fill ( "F G C C" ) ;
2650+ await expect ( page . locator ( "#songSheetIntroInput" ) ) . toHaveValue ( "C Am F G" ) ;
2651+ await expect ( page . locator ( "#songSheetLoopInput" ) ) . toHaveValue ( "F G C C" ) ;
2652+ const songSheetLayout = await page . locator ( "#songSheetContent .midi-studio-v2__song-sheet-grid" ) . evaluate ( ( grid ) => ( {
2653+ columns : getComputedStyle ( grid ) . gridTemplateColumns . split ( " " ) . filter ( Boolean ) . length ,
2654+ disabledFields : Array . from ( grid . querySelectorAll ( "input, textarea, select" ) ) . filter ( ( field ) => field . disabled || field . readOnly ) . length
2655+ } ) ) ;
2656+ expect ( songSheetLayout . columns ) . toBeGreaterThan ( 1 ) ;
2657+ expect ( songSheetLayout . disabledFields ) . toBe ( 0 ) ;
2658+
2659+ await selectMidiStudioTab ( page , "studio" ) ;
2660+ await waitForCanvasRender ( page ) ;
2661+ await expect ( page . locator ( "#timelineInstrumentQuickList" ) ) . toBeVisible ( ) ;
2662+ await expect ( timelineQuickInstrumentRow ( page , "lead" ) ) . toBeVisible ( ) ;
2663+ await expect ( timelineQuickInstrumentRow ( page , "lead" ) . locator ( "[data-timeline-quick-mute='lead']" ) ) . toBeVisible ( ) ;
2664+ await expect ( timelineQuickInstrumentRow ( page , "lead" ) . locator ( "[data-timeline-quick-solo='lead']" ) ) . toBeVisible ( ) ;
2665+ await expect ( timelineQuickInstrumentRow ( page , "lead" ) . locator ( "[data-toggle-instrument-visibility='lead']" ) ) . toBeVisible ( ) ;
2666+ await expect ( page . locator ( "#timelineInstrumentQuickList select" ) ) . toHaveCount ( 0 ) ;
2667+
2668+ await timelineQuickInstrumentRow ( page , "bass" ) . click ( ) ;
2669+ expect ( await page . evaluate ( ( ) => ( {
2670+ hasOwnSelectedLane : Object . hasOwn ( window . __midiStudioV2App . instrumentGrid , "selectedLane" ) ,
2671+ selectedInstrumentId : window . __midiStudioV2App . instrumentGrid . selectedInstrumentId
2672+ } ) ) ) . toEqual ( {
2673+ hasOwnSelectedLane : false ,
2674+ selectedInstrumentId : "bass"
2675+ } ) ;
2676+ await selectMidiStudioTab ( page , "instruments" ) ;
2677+ await expect ( instrumentRow ( page , "bass" ) ) . toHaveClass ( / i s - s e l e c t e d / ) ;
2678+ await expect ( instrumentTypeSelect ( page , "bass" ) ) . toBeVisible ( ) ;
2679+ await expect ( instrumentSelect ( page , "bass" ) ) . toBeVisible ( ) ;
2680+ expect ( await instrumentTypeSelect ( page , "bass" ) . evaluate ( ( select ) => select . closest ( "[data-midi-studio-tab-panel]" ) ?. dataset . midiStudioTabPanel ) ) . toBe ( "instruments" ) ;
2681+ expect ( await instrumentSelect ( page , "bass" ) . evaluate ( ( select ) => select . closest ( "[data-midi-studio-tab-panel]" ) ?. dataset . midiStudioTabPanel ) ) . toBe ( "instruments" ) ;
2682+
2683+ const keyboard = page . locator ( "#instrumentAuditionKeyboard" ) ;
2684+ await expect ( keyboard ) . toBeVisible ( ) ;
2685+ await expect ( keyboard ) . not . toHaveAttribute ( "data-midi-studio-unwired" ) ;
2686+ await expect ( keyboard ) . toHaveAttribute ( "data-selected-lane" , "bass" ) ;
2687+ await expect ( keyboard ) . toHaveAttribute ( "data-octave-min" , "1" ) ;
2688+ await expect ( keyboard ) . toHaveAttribute ( "data-octave-max" , "3" ) ;
2689+ expect ( await keyboard . locator ( "[data-audition-note]" ) . count ( ) ) . toBe ( 36 ) ;
2690+ await page . evaluate ( ( ) => {
2691+ window . __midiStudioPreviewSynthEvents = [ ] ;
2692+ } ) ;
2693+ await keyboard . locator ( "[data-audition-note='C2']" ) . click ( ) ;
2694+ await expect ( page . locator ( "#statusLog" ) ) . toHaveValue ( / A u d i t i o n e d C 2 f o r B a s s / ) ;
2695+ expect ( await page . evaluate ( ( ) => window . __midiStudioPreviewSynthEvents . some ( ( event ) => event . action === "oscillator-start" ) ) ) . toBe ( true ) ;
2696+
2697+ await selectInstrumentRow ( page , "lead" ) ;
2698+ expect ( await page . evaluate ( ( ) => window . __midiStudioV2App . instrumentGrid . selectedInstrumentId ) ) . toBe ( "lead" ) ;
2699+ await selectMidiStudioTab ( page , "studio" ) ;
2700+ await expect ( timelineQuickInstrumentRow ( page , "lead" ) ) . toHaveClass ( / i s - s e l e c t e d / ) ;
2701+
2702+ await page . locator ( "#playButton" ) . click ( ) ;
2703+ await expect ( page . locator ( "#playButton" ) ) . toBeDisabled ( ) ;
2704+ await expect ( page . locator ( "#stopButton" ) ) . toBeEnabled ( ) ;
2705+ await page . locator ( "#stopButton" ) . click ( ) ;
2706+ await expect ( page . locator ( "#stopButton" ) ) . toBeDisabled ( ) ;
2707+ await expect ( page . locator ( "#playButton" ) ) . toBeEnabled ( ) ;
2708+ } finally {
2709+ await workspaceV2CoverageReporter . stop ( page ) ;
2710+ await server . close ( ) ;
2711+ }
2712+ } ) ;
2713+
26232714 test ( "derives primary song, instrument, grid, playback, and diagnostics views from the canonical selected song" , async ( { page } ) => {
26242715 const server = await openMidiStudioForImport ( page ) ;
26252716 try {
0 commit comments