@@ -788,4 +788,171 @@ test.describe('Vue Node Link Interaction', () => {
788788      targetSlot : 2 
789789    } ) 
790790  } ) 
791+ 
792+   test . describe ( 'Release actions (Shift-drop)' ,  ( )  =>  { 
793+     test ( 'Context menu opens and endpoint is pinned on Shift-drop' ,  async  ( { 
794+       comfyPage, 
795+       comfyMouse
796+     } )  =>  { 
797+       await  comfyPage . setSetting ( 
798+         'Comfy.LinkRelease.ActionShift' , 
799+         'context menu' 
800+       ) 
801+ 
802+       const  samplerNode  =  ( await  comfyPage . getNodeRefsByType ( 'KSampler' ) ) [ 0 ] 
803+       expect ( samplerNode ) . toBeTruthy ( ) 
804+ 
805+       const  outputCenter  =  await  getSlotCenter ( 
806+         comfyPage . page , 
807+         samplerNode . id , 
808+         0 , 
809+         false 
810+       ) 
811+ 
812+       const  dropPos  =  {  x : outputCenter . x  +  180 ,  y : outputCenter . y  -  140  } 
813+ 
814+       await  comfyMouse . move ( outputCenter ) 
815+       await  comfyPage . page . keyboard . down ( 'Shift' ) 
816+       try  { 
817+         await  comfyMouse . drag ( dropPos ) 
818+         await  comfyMouse . drop ( ) 
819+       }  finally  { 
820+         await  comfyPage . page . keyboard . up ( 'Shift' ) . catch ( ( )  =>  { } ) 
821+       } 
822+ 
823+       // Context menu should be visible 
824+       const  contextMenu  =  comfyPage . page . locator ( '.litecontextmenu' ) 
825+       await  expect ( contextMenu ) . toBeVisible ( ) 
826+ 
827+       // Pinned endpoint should not change with mouse movement while menu is open 
828+       const  before  =  await  comfyPage . page . evaluate ( ( )  =>  { 
829+         const  snap  =  window [ 'app' ] ?. canvas ?. linkConnector ?. state ?. snapLinksPos 
830+         return  Array . isArray ( snap )  ? [ snap [ 0 ] ,  snap [ 1 ] ]  : null 
831+       } ) 
832+       expect ( before ) . not . toBeNull ( ) 
833+ 
834+       // Move mouse elsewhere and verify snap position is unchanged 
835+       await  comfyMouse . move ( {  x : dropPos . x  +  160 ,  y : dropPos . y  +  100  } ) 
836+       const  after  =  await  comfyPage . page . evaluate ( ( )  =>  { 
837+         const  snap  =  window [ 'app' ] ?. canvas ?. linkConnector ?. state ?. snapLinksPos 
838+         return  Array . isArray ( snap )  ? [ snap [ 0 ] ,  snap [ 1 ] ]  : null 
839+       } ) 
840+       expect ( after ) . toEqual ( before ) 
841+     } ) 
842+ 
843+     test ( 'Context menu -> Search pre-filters by link type and connects after selection' ,  async  ( { 
844+       comfyPage, 
845+       comfyMouse
846+     } )  =>  { 
847+       await  comfyPage . setSetting ( 
848+         'Comfy.LinkRelease.ActionShift' , 
849+         'context menu' 
850+       ) 
851+       await  comfyPage . setSetting ( 'Comfy.NodeSearchBoxImpl' ,  'default' ) 
852+ 
853+       const  samplerNode  =  ( await  comfyPage . getNodeRefsByType ( 'KSampler' ) ) [ 0 ] 
854+       expect ( samplerNode ) . toBeTruthy ( ) 
855+ 
856+       const  outputCenter  =  await  getSlotCenter ( 
857+         comfyPage . page , 
858+         samplerNode . id , 
859+         0 , 
860+         false 
861+       ) 
862+       const  dropPos  =  {  x : outputCenter . x  +  200 ,  y : outputCenter . y  -  120  } 
863+ 
864+       await  comfyMouse . move ( outputCenter ) 
865+       await  comfyPage . page . keyboard . down ( 'Shift' ) 
866+       try  { 
867+         await  comfyMouse . drag ( dropPos ) 
868+         await  comfyMouse . drop ( ) 
869+       }  finally  { 
870+         await  comfyPage . page . keyboard . up ( 'Shift' ) . catch ( ( )  =>  { } ) 
871+       } 
872+ 
873+       // Open Search from the context menu 
874+       await  comfyPage . clickContextMenuItem ( 'Search' ) 
875+ 
876+       // Search box opens with prefilled type filter based on link type (LATENT) 
877+       await  expect ( comfyPage . searchBox . input ) . toBeVisible ( ) 
878+       const  chips  =  comfyPage . searchBox . filterChips 
879+       // Ensure at least one filter chip exists and it matches the link type 
880+       const  chipCount  =  await  chips . count ( ) 
881+       expect ( chipCount ) . toBeGreaterThan ( 0 ) 
882+       await  expect ( chips . first ( ) ) . toContainText ( 'LATENT' ) 
883+ 
884+       // Choose a compatible node and verify it auto-connects 
885+       await  comfyPage . searchBox . fillAndSelectFirstNode ( 'VAEDecode' ) 
886+       await  comfyPage . nextFrame ( ) 
887+ 
888+       // KSampler output should now have an outgoing link 
889+       const  samplerOutput  =  await  samplerNode . getOutput ( 0 ) 
890+       expect ( await  samplerOutput . getLinkCount ( ) ) . toBe ( 1 ) 
891+ 
892+       // One of the VAEDecode nodes should have an incoming link on input[0] 
893+       const  vaeNodes  =  await  comfyPage . getNodeRefsByType ( 'VAEDecode' ) 
894+       let  linked  =  false 
895+       for  ( const  vae  of  vaeNodes )  { 
896+         const  details  =  await  getInputLinkDetails ( comfyPage . page ,  vae . id ,  0 ) 
897+         if  ( details )  { 
898+           expect ( details . originId ) . toBe ( samplerNode . id ) 
899+           linked  =  true 
900+           break 
901+         } 
902+       } 
903+       expect ( linked ) . toBe ( true ) 
904+     } ) 
905+ 
906+     test ( 'Search box opens on Shift-drop and connects after selection' ,  async  ( { 
907+       comfyPage, 
908+       comfyMouse
909+     } )  =>  { 
910+       await  comfyPage . setSetting ( 'Comfy.LinkRelease.ActionShift' ,  'search box' ) 
911+ 
912+       const  samplerNode  =  ( await  comfyPage . getNodeRefsByType ( 'KSampler' ) ) [ 0 ] 
913+       expect ( samplerNode ) . toBeTruthy ( ) 
914+ 
915+       const  outputCenter  =  await  getSlotCenter ( 
916+         comfyPage . page , 
917+         samplerNode . id , 
918+         0 , 
919+         false 
920+       ) 
921+       const  dropPos  =  {  x : outputCenter . x  +  140 ,  y : outputCenter . y  -  100  } 
922+ 
923+       await  comfyMouse . move ( outputCenter ) 
924+       await  comfyPage . page . keyboard . down ( 'Shift' ) 
925+       try  { 
926+         await  comfyMouse . drag ( dropPos ) 
927+         await  comfyMouse . drop ( ) 
928+       }  finally  { 
929+         await  comfyPage . page . keyboard . up ( 'Shift' ) . catch ( ( )  =>  { } ) 
930+       } 
931+ 
932+       // Search box should open directly 
933+       await  expect ( comfyPage . searchBox . input ) . toBeVisible ( ) 
934+       await  expect ( comfyPage . searchBox . filterChips . first ( ) ) . toContainText ( 
935+         'LATENT' 
936+       ) 
937+ 
938+       // Select a compatible node and verify connection 
939+       await  comfyPage . searchBox . fillAndSelectFirstNode ( 'VAEDecode' ) 
940+       await  comfyPage . nextFrame ( ) 
941+ 
942+       const  samplerOutput  =  await  samplerNode . getOutput ( 0 ) 
943+       expect ( await  samplerOutput . getLinkCount ( ) ) . toBe ( 1 ) 
944+ 
945+       const  vaeNodes  =  await  comfyPage . getNodeRefsByType ( 'VAEDecode' ) 
946+       let  linked  =  false 
947+       for  ( const  vae  of  vaeNodes )  { 
948+         const  details  =  await  getInputLinkDetails ( comfyPage . page ,  vae . id ,  0 ) 
949+         if  ( details )  { 
950+           expect ( details . originId ) . toBe ( samplerNode . id ) 
951+           linked  =  true 
952+           break 
953+         } 
954+       } 
955+       expect ( linked ) . toBe ( true ) 
956+     } ) 
957+   } ) 
791958} ) 
0 commit comments