@@ -601,6 +601,324 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
601601 expect ( ionChange ) . toHaveReceivedEventTimes ( 1 ) ;
602602 } ) ;
603603
604+ test ( 'should fire ionChange exactly once when confirming a popover value with Space' , async ( {
605+ page,
606+ skip,
607+ } , testInfo ) => {
608+ // TODO (ROU-5437)
609+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
610+
611+ testInfo . annotations . push ( {
612+ type : 'issue' ,
613+ description : 'https://github.com/ionic-team/ionic-framework/issues/30561' ,
614+ } ) ;
615+
616+ await page . setContent (
617+ `
618+ <ion-app>
619+ <ion-select aria-label="Fruit" interface="popover">
620+ <ion-select-option value="apple">Apple</ion-select-option>
621+ <ion-select-option value="banana">Banana</ion-select-option>
622+ </ion-select>
623+ </ion-app>
624+ ` ,
625+ config
626+ ) ;
627+
628+ const ionPopoverDidPresent = await page . spyOnEvent ( 'ionPopoverDidPresent' ) ;
629+ const ionPopoverDidDismiss = await page . spyOnEvent ( 'ionPopoverDidDismiss' ) ;
630+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
631+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
632+
633+ await select . click ( ) ;
634+ await ionPopoverDidPresent . next ( ) ;
635+
636+ const popover = page . locator ( 'ion-popover' ) ;
637+ const secondRadio = popover . locator ( 'ion-radio' ) . nth ( 1 ) ;
638+
639+ await secondRadio . focus ( ) ;
640+ await page . keyboard . press ( 'Space' ) ;
641+
642+ await ionChange . next ( ) ;
643+ await ionPopoverDidDismiss . next ( ) ;
644+
645+ expect ( ionChange ) . toHaveReceivedEventDetail ( { value : 'banana' } ) ;
646+ expect ( ionChange ) . toHaveReceivedEventTimes ( 1 ) ;
647+ await expect ( popover ) . not . toBeVisible ( ) ;
648+ } ) ;
649+
650+ test ( 'should not fire ionChange when confirming the already-selected popover option with Enter' , async ( {
651+ page,
652+ skip,
653+ } , testInfo ) => {
654+ // TODO (ROU-5437)
655+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
656+
657+ testInfo . annotations . push ( {
658+ type : 'issue' ,
659+ description : 'https://github.com/ionic-team/ionic-framework/issues/26789' ,
660+ } ) ;
661+
662+ await page . setContent (
663+ `
664+ <ion-app>
665+ <ion-select aria-label="Fruit" interface="popover" value="apple">
666+ <ion-select-option value="apple">Apple</ion-select-option>
667+ <ion-select-option value="banana">Banana</ion-select-option>
668+ </ion-select>
669+ </ion-app>
670+ ` ,
671+ config
672+ ) ;
673+
674+ const ionPopoverDidPresent = await page . spyOnEvent ( 'ionPopoverDidPresent' ) ;
675+ const ionPopoverDidDismiss = await page . spyOnEvent ( 'ionPopoverDidDismiss' ) ;
676+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
677+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
678+
679+ await select . click ( ) ;
680+ await ionPopoverDidPresent . next ( ) ;
681+
682+ const popover = page . locator ( 'ion-popover' ) ;
683+ const selectedRadio = popover . locator ( 'ion-radio' ) . nth ( 0 ) ;
684+
685+ await selectedRadio . focus ( ) ;
686+ await page . keyboard . press ( 'Enter' ) ;
687+
688+ await ionPopoverDidDismiss . next ( ) ;
689+
690+ expect ( ionChange ) . toHaveReceivedEventTimes ( 0 ) ;
691+ await expect ( popover ) . not . toBeVisible ( ) ;
692+ await expect ( select ) . toHaveJSProperty ( 'value' , 'apple' ) ;
693+ } ) ;
694+
695+ test ( 'should not fire ionChange when confirming the already-selected popover option with Space' , async ( {
696+ page,
697+ skip,
698+ } , testInfo ) => {
699+ // TODO (ROU-5437)
700+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
701+
702+ testInfo . annotations . push ( {
703+ type : 'issue' ,
704+ description : 'https://github.com/ionic-team/ionic-framework/issues/26789' ,
705+ } ) ;
706+
707+ await page . setContent (
708+ `
709+ <ion-app>
710+ <ion-select aria-label="Fruit" interface="popover" value="apple">
711+ <ion-select-option value="apple">Apple</ion-select-option>
712+ <ion-select-option value="banana">Banana</ion-select-option>
713+ </ion-select>
714+ </ion-app>
715+ ` ,
716+ config
717+ ) ;
718+
719+ const ionPopoverDidPresent = await page . spyOnEvent ( 'ionPopoverDidPresent' ) ;
720+ const ionPopoverDidDismiss = await page . spyOnEvent ( 'ionPopoverDidDismiss' ) ;
721+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
722+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
723+
724+ await select . click ( ) ;
725+ await ionPopoverDidPresent . next ( ) ;
726+
727+ const popover = page . locator ( 'ion-popover' ) ;
728+ const selectedRadio = popover . locator ( 'ion-radio' ) . nth ( 0 ) ;
729+
730+ await selectedRadio . focus ( ) ;
731+ await page . keyboard . press ( 'Space' ) ;
732+
733+ await ionPopoverDidDismiss . next ( ) ;
734+
735+ expect ( ionChange ) . toHaveReceivedEventTimes ( 0 ) ;
736+ await expect ( popover ) . not . toBeVisible ( ) ;
737+ await expect ( select ) . toHaveJSProperty ( 'value' , 'apple' ) ;
738+ } ) ;
739+
740+ test ( 'should fire ionChange exactly once when confirming a modal value with Enter' , async ( {
741+ page,
742+ skip,
743+ } , testInfo ) => {
744+ // TODO (ROU-5437)
745+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
746+
747+ testInfo . annotations . push ( {
748+ type : 'issue' ,
749+ description : 'https://github.com/ionic-team/ionic-framework/issues/30561' ,
750+ } ) ;
751+
752+ await page . setContent (
753+ `
754+ <ion-app>
755+ <ion-select aria-label="Fruit" interface="modal">
756+ <ion-select-option value="apple">Apple</ion-select-option>
757+ <ion-select-option value="banana">Banana</ion-select-option>
758+ </ion-select>
759+ </ion-app>
760+ ` ,
761+ config
762+ ) ;
763+
764+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
765+ const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
766+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
767+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
768+
769+ await select . click ( ) ;
770+ await ionModalDidPresent . next ( ) ;
771+
772+ const modal = page . locator ( 'ion-modal' ) ;
773+ const secondRadio = modal . locator ( 'ion-radio' ) . nth ( 1 ) ;
774+
775+ await secondRadio . focus ( ) ;
776+ await page . keyboard . press ( 'Enter' ) ;
777+
778+ await ionChange . next ( ) ;
779+ await ionModalDidDismiss . next ( ) ;
780+
781+ expect ( ionChange ) . toHaveReceivedEventDetail ( { value : 'banana' } ) ;
782+ expect ( ionChange ) . toHaveReceivedEventTimes ( 1 ) ;
783+ await expect ( modal ) . not . toBeVisible ( ) ;
784+ } ) ;
785+
786+ test ( 'should fire ionChange exactly once when confirming a modal value with Space' , async ( {
787+ page,
788+ skip,
789+ } , testInfo ) => {
790+ // TODO (ROU-5437)
791+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
792+
793+ testInfo . annotations . push ( {
794+ type : 'issue' ,
795+ description : 'https://github.com/ionic-team/ionic-framework/issues/30561' ,
796+ } ) ;
797+
798+ await page . setContent (
799+ `
800+ <ion-app>
801+ <ion-select aria-label="Fruit" interface="modal">
802+ <ion-select-option value="apple">Apple</ion-select-option>
803+ <ion-select-option value="banana">Banana</ion-select-option>
804+ </ion-select>
805+ </ion-app>
806+ ` ,
807+ config
808+ ) ;
809+
810+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
811+ const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
812+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
813+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
814+
815+ await select . click ( ) ;
816+ await ionModalDidPresent . next ( ) ;
817+
818+ const modal = page . locator ( 'ion-modal' ) ;
819+ const secondRadio = modal . locator ( 'ion-radio' ) . nth ( 1 ) ;
820+
821+ await secondRadio . focus ( ) ;
822+ await page . keyboard . press ( 'Space' ) ;
823+
824+ await ionChange . next ( ) ;
825+ await ionModalDidDismiss . next ( ) ;
826+
827+ expect ( ionChange ) . toHaveReceivedEventDetail ( { value : 'banana' } ) ;
828+ expect ( ionChange ) . toHaveReceivedEventTimes ( 1 ) ;
829+ await expect ( modal ) . not . toBeVisible ( ) ;
830+ } ) ;
831+
832+ test ( 'should not fire ionChange when confirming the already-selected modal option with Enter' , async ( {
833+ page,
834+ skip,
835+ } , testInfo ) => {
836+ // TODO (ROU-5437)
837+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
838+
839+ testInfo . annotations . push ( {
840+ type : 'issue' ,
841+ description : 'https://github.com/ionic-team/ionic-framework/issues/26789' ,
842+ } ) ;
843+
844+ await page . setContent (
845+ `
846+ <ion-app>
847+ <ion-select aria-label="Fruit" interface="modal" value="apple">
848+ <ion-select-option value="apple">Apple</ion-select-option>
849+ <ion-select-option value="banana">Banana</ion-select-option>
850+ </ion-select>
851+ </ion-app>
852+ ` ,
853+ config
854+ ) ;
855+
856+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
857+ const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
858+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
859+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
860+
861+ await select . click ( ) ;
862+ await ionModalDidPresent . next ( ) ;
863+
864+ const modal = page . locator ( 'ion-modal' ) ;
865+ const selectedRadio = modal . locator ( 'ion-radio' ) . nth ( 0 ) ;
866+
867+ await selectedRadio . focus ( ) ;
868+ await page . keyboard . press ( 'Enter' ) ;
869+
870+ await ionModalDidDismiss . next ( ) ;
871+
872+ expect ( ionChange ) . toHaveReceivedEventTimes ( 0 ) ;
873+ await expect ( modal ) . not . toBeVisible ( ) ;
874+ await expect ( select ) . toHaveJSProperty ( 'value' , 'apple' ) ;
875+ } ) ;
876+
877+ test ( 'should not fire ionChange when confirming the already-selected modal option with Space' , async ( {
878+ page,
879+ skip,
880+ } , testInfo ) => {
881+ // TODO (ROU-5437)
882+ skip . browser ( 'webkit' , 'Safari 16 only allows text fields and pop-up menus to be focused.' ) ;
883+
884+ testInfo . annotations . push ( {
885+ type : 'issue' ,
886+ description : 'https://github.com/ionic-team/ionic-framework/issues/26789' ,
887+ } ) ;
888+
889+ await page . setContent (
890+ `
891+ <ion-app>
892+ <ion-select aria-label="Fruit" interface="modal" value="apple">
893+ <ion-select-option value="apple">Apple</ion-select-option>
894+ <ion-select-option value="banana">Banana</ion-select-option>
895+ </ion-select>
896+ </ion-app>
897+ ` ,
898+ config
899+ ) ;
900+
901+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
902+ const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
903+ const select = page . locator ( 'ion-select' ) as E2ELocator ;
904+ const ionChange = await select . spyOnEvent ( 'ionChange' ) ;
905+
906+ await select . click ( ) ;
907+ await ionModalDidPresent . next ( ) ;
908+
909+ const modal = page . locator ( 'ion-modal' ) ;
910+ const selectedRadio = modal . locator ( 'ion-radio' ) . nth ( 0 ) ;
911+
912+ await selectedRadio . focus ( ) ;
913+ await page . keyboard . press ( 'Space' ) ;
914+
915+ await ionModalDidDismiss . next ( ) ;
916+
917+ expect ( ionChange ) . toHaveReceivedEventTimes ( 0 ) ;
918+ await expect ( modal ) . not . toBeVisible ( ) ;
919+ await expect ( select ) . toHaveJSProperty ( 'value' , 'apple' ) ;
920+ } ) ;
921+
604922 test ( 'should fire ionChange when confirming multiple values from a popover' , async ( { page } ) => {
605923 await page . setContent (
606924 `
0 commit comments