@@ -689,4 +689,107 @@ describe('Routing', () => {
689689 expect ( wrapper . findComponent ( Page2 ) . exists ( ) ) . toBe ( false ) ;
690690 expect ( wrapper . findComponent ( Page3 ) . exists ( ) ) . toBe ( false ) ;
691691 } ) ;
692+
693+ // Verifies fix for https://github.com/ionic-team/ionic-framework/issues/29721
694+ it ( 'should keep the view stack intact after a navigation guard blocks going back' , async ( ) => {
695+ const createPage = ( id : string ) => ( {
696+ components : { IonPage } ,
697+ name : id ,
698+ template : `<ion-page data-page="${ id } "></ion-page>`
699+ } ) ;
700+
701+ const Home = createPage ( 'home' ) ;
702+ const Register = createPage ( 'register' ) ;
703+ const Profile = createPage ( 'profile' ) ;
704+
705+ let isLoggedIn = false ;
706+
707+ const router = createRouter ( {
708+ history : createWebHistory ( process . env . BASE_URL ) ,
709+ routes : [
710+ { path : '/' , redirect : '/home' } ,
711+ { path : '/home' , component : Home } ,
712+ { path : '/register' , component : Register } ,
713+ { path : '/profile' , component : Profile }
714+ ]
715+ } ) ;
716+
717+ /*
718+ * Leaving the authenticated route while still logged in is blocked, which
719+ * aborts the navigation. An aborted back navigation used to leave stale
720+ * navigation info behind, which then made the next navigation look like
721+ * history traversal.
722+ */
723+ router . beforeEach ( ( to , from ) => {
724+ if ( from . path === '/profile' && to . path !== '/profile' && isLoggedIn ) {
725+ return false ;
726+ }
727+
728+ return true ;
729+ } ) ;
730+
731+ router . push ( '/home' ) ;
732+ await router . isReady ( ) ;
733+ const wrapper = mount ( IonRouterOutlet , {
734+ global : {
735+ plugins : [ router , IonicVue ]
736+ }
737+ } ) ;
738+
739+ /*
740+ * Ionic keeps previously visited pages mounted so they can be animated back
741+ * to, hiding the inactive ones with `ion-page-hidden`. Asserting on the
742+ * whole stack therefore catches both a wrong visible page and a page that
743+ * was destroyed when it should have been kept.
744+ */
745+ const viewStack = ( ) =>
746+ wrapper . findAll ( '.ion-page' ) . map ( ( page ) => ( {
747+ id : page . attributes ( 'data-page' ) ,
748+ hidden : page . classes ( 'ion-page-hidden' )
749+ } ) ) ;
750+
751+ router . push ( '/register' ) ;
752+ await waitForRouter ( ) ;
753+
754+ isLoggedIn = true ;
755+ router . replace ( '/profile' ) ;
756+ await waitForRouter ( ) ;
757+
758+ expect ( viewStack ( ) ) . toEqual ( [
759+ { id : 'home' , hidden : true } ,
760+ { id : 'profile' , hidden : false }
761+ ] ) ;
762+
763+ // The guard blocks this, so the stack should be untouched.
764+ router . back ( ) ;
765+ await waitForRouter ( ) ;
766+
767+ expect ( viewStack ( ) ) . toEqual ( [
768+ { id : 'home' , hidden : true } ,
769+ { id : 'profile' , hidden : false }
770+ ] ) ;
771+
772+ /*
773+ * Logging out is a push, so Profile stays in the stack behind Home. Before
774+ * the fix the stale delta from the blocked back navigation made this look
775+ * like history traversal, which destroyed the Profile view.
776+ */
777+ isLoggedIn = false ;
778+ router . push ( '/home' ) ;
779+ await waitForRouter ( ) ;
780+
781+ expect ( viewStack ( ) ) . toEqual ( [
782+ { id : 'home' , hidden : false } ,
783+ { id : 'profile' , hidden : true }
784+ ] ) ;
785+
786+ isLoggedIn = true ;
787+ router . push ( '/profile' ) ;
788+ await waitForRouter ( ) ;
789+
790+ expect ( viewStack ( ) ) . toEqual ( [
791+ { id : 'home' , hidden : true } ,
792+ { id : 'profile' , hidden : false }
793+ ] ) ;
794+ } ) ;
692795} ) ;
0 commit comments