@@ -887,4 +887,203 @@ describe('Submenu', () => {
887887 expect ( submenu . scrollTop ) . to . equal ( 50 ) ;
888888 expect ( submenu . scrollTop ) . to . not . equal ( initialScrollTop ) ;
889889 } ) ;
890+ describe ( 'touch interactions' , ( ) => {
891+ beforeEach ( async function ( ) {
892+ this . el = await fixture < Menu > ( html `
893+ < sp-menu >
894+ < sp-menu-item > No submenu</ sp-menu-item >
895+ < sp-menu-item class ="root ">
896+ Has submenu
897+ < sp-menu slot ="submenu ">
898+ < sp-menu-item class ="submenu-item-1 ">
899+ One
900+ </ sp-menu-item >
901+ < sp-menu-item class ="submenu-item-2 ">
902+ Two
903+ </ sp-menu-item >
904+ < sp-menu-item class ="submenu-item-3 ">
905+ Three
906+ </ sp-menu-item >
907+ </ sp-menu >
908+ </ sp-menu-item >
909+ </ sp-menu >
910+ ` ) ;
911+ await elementUpdated ( this . el ) ;
912+ this . rootItem = this . el . querySelector ( '.root' ) as MenuItem ;
913+ await elementUpdated ( this . rootItem ) ;
914+ } ) ;
915+
916+ it ( 'does not open submenu on touch pointerenter' , async function ( ) {
917+ expect ( this . rootItem . open ) . to . be . false ;
918+
919+ // Simulate touch pointerenter event
920+ this . rootItem . dispatchEvent (
921+ new PointerEvent ( 'pointerenter' , {
922+ bubbles : true ,
923+ pointerType : 'touch' ,
924+ } )
925+ ) ;
926+
927+ // Wait to ensure submenu doesn't open
928+ await aTimeout ( 150 ) ;
929+
930+ expect ( this . rootItem . open ) . to . be . false ;
931+ } ) ;
932+
933+ it ( 'does not close submenu on touch pointerleave' , async function ( ) {
934+ // First open the submenu via click
935+ expect ( this . rootItem . open ) . to . be . false ;
936+
937+ const opened = oneEvent ( this . rootItem , 'sp-opened' ) ;
938+ this . rootItem . click ( ) ;
939+ await opened ;
940+
941+ expect ( this . rootItem . open ) . to . be . true ;
942+
943+ // Simulate touch pointerleave event
944+ this . rootItem . dispatchEvent (
945+ new PointerEvent ( 'pointerleave' , {
946+ bubbles : true ,
947+ pointerType : 'touch' ,
948+ } )
949+ ) ;
950+
951+ // Wait to ensure submenu doesn't close
952+ await aTimeout ( 150 ) ;
953+
954+ expect ( this . rootItem . open ) . to . be . true ;
955+ } ) ;
956+
957+ it ( 'opens submenu on touch click when closed' , async function ( ) {
958+ expect ( this . rootItem . open ) . to . be . false ;
959+
960+ // Track pointer type by dispatching pointerenter first
961+ this . rootItem . dispatchEvent (
962+ new PointerEvent ( 'pointerenter' , {
963+ bubbles : true ,
964+ pointerType : 'touch' ,
965+ } )
966+ ) ;
967+
968+ const opened = oneEvent ( this . rootItem , 'sp-opened' ) ;
969+ this . rootItem . click ( ) ;
970+ await opened ;
971+
972+ expect ( this . rootItem . open ) . to . be . true ;
973+ } ) ;
974+
975+ it ( 'closes submenu on touch click when open' , async function ( ) {
976+ // First open the submenu
977+ this . rootItem . dispatchEvent (
978+ new PointerEvent ( 'pointerenter' , {
979+ bubbles : true ,
980+ pointerType : 'touch' ,
981+ } )
982+ ) ;
983+
984+ const opened = oneEvent ( this . rootItem , 'sp-opened' ) ;
985+ this . rootItem . click ( ) ;
986+ await opened ;
987+
988+ expect ( this . rootItem . open ) . to . be . true ;
989+
990+ // Click again to close
991+ const closed = oneEvent ( this . rootItem , 'sp-closed' ) ;
992+ this . rootItem . click ( ) ;
993+ await closed ;
994+
995+ expect ( this . rootItem . open ) . to . be . false ;
996+ } ) ;
997+
998+ it ( 'mouse pointerenter still opens submenu' , async function ( ) {
999+ expect ( this . rootItem . open ) . to . be . false ;
1000+
1001+ const opened = oneEvent ( this . rootItem , 'sp-opened' ) ;
1002+ this . rootItem . dispatchEvent (
1003+ new PointerEvent ( 'pointerenter' , {
1004+ bubbles : true ,
1005+ pointerType : 'mouse' ,
1006+ } )
1007+ ) ;
1008+ await opened ;
1009+
1010+ expect ( this . rootItem . open ) . to . be . true ;
1011+ } ) ;
1012+
1013+ it ( 'mouse pointerleave still closes submenu' , async function ( ) {
1014+ // First open via mouse
1015+ const opened = oneEvent ( this . rootItem , 'sp-opened' ) ;
1016+ this . rootItem . dispatchEvent (
1017+ new PointerEvent ( 'pointerenter' , {
1018+ bubbles : true ,
1019+ pointerType : 'mouse' ,
1020+ } )
1021+ ) ;
1022+ await opened ;
1023+
1024+ expect ( this . rootItem . open ) . to . be . true ;
1025+
1026+ // Leave with mouse
1027+ const closed = oneEvent ( this . rootItem , 'sp-closed' ) ;
1028+ this . rootItem . dispatchEvent (
1029+ new PointerEvent ( 'pointerleave' , {
1030+ bubbles : true ,
1031+ pointerType : 'mouse' ,
1032+ } )
1033+ ) ;
1034+ await closed ;
1035+
1036+ expect ( this . rootItem . open ) . to . be . false ;
1037+ } ) ;
1038+
1039+ it ( 'closes sibling submenus on touch pointerenter' , async function ( ) {
1040+ // Create a second menu item with submenu
1041+ const el = await fixture < Menu > ( html `
1042+ < sp-menu >
1043+ < sp-menu-item class ="root-1 ">
1044+ First submenu
1045+ < sp-menu slot ="submenu ">
1046+ < sp-menu-item > Item A</ sp-menu-item >
1047+ </ sp-menu >
1048+ </ sp-menu-item >
1049+ < sp-menu-item class ="root-2 ">
1050+ Second submenu
1051+ < sp-menu slot ="submenu ">
1052+ < sp-menu-item > Item B</ sp-menu-item >
1053+ </ sp-menu >
1054+ </ sp-menu-item >
1055+ </ sp-menu >
1056+ ` ) ;
1057+ await elementUpdated ( el ) ;
1058+ const rootItem1 = el . querySelector ( '.root-1' ) as MenuItem ;
1059+ const rootItem2 = el . querySelector ( '.root-2' ) as MenuItem ;
1060+ await elementUpdated ( rootItem1 ) ;
1061+ await elementUpdated ( rootItem2 ) ;
1062+
1063+ // Open first submenu with mouse
1064+ const opened1 = oneEvent ( rootItem1 , 'sp-opened' ) ;
1065+ rootItem1 . dispatchEvent (
1066+ new PointerEvent ( 'pointerenter' , {
1067+ bubbles : true ,
1068+ pointerType : 'mouse' ,
1069+ } )
1070+ ) ;
1071+ await opened1 ;
1072+
1073+ expect ( rootItem1 . open ) . to . be . true ;
1074+ expect ( rootItem2 . open ) . to . be . false ;
1075+
1076+ // Hover second item with mouse should close first
1077+ const closed1 = oneEvent ( rootItem1 , 'sp-closed' ) ;
1078+ rootItem2 . dispatchEvent (
1079+ new PointerEvent ( 'pointerenter' , {
1080+ bubbles : true ,
1081+ pointerType : 'mouse' ,
1082+ } )
1083+ ) ;
1084+ await closed1 ;
1085+
1086+ expect ( rootItem1 . open ) . to . be . false ;
1087+ } ) ;
1088+ } ) ;
8901089} ) ;
0 commit comments