Skip to content

Commit ecd6d57

Browse files
author
Shipra Gupta
committed
test: add comprehensive touch interaction tests for menu submenus
1 parent bec8838 commit ecd6d57

File tree

1 file changed

+199
-0
lines changed

1 file changed

+199
-0
lines changed

1st-gen/packages/menu/test/submenu.test.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)