@@ -5,15 +5,96 @@ import {
55 createRouter ,
66 createMemoryHistory ,
77 onBeforeRouteLeave ,
8+ type RouteRecordRaw ,
89} from '../../src'
9- import { createApp , defineComponent } from 'vue'
10+ import { createApp , defineComponent , onActivated , onDeactivated } from 'vue'
11+ import { mount } from '@vue/test-utils'
1012import { vi , describe , expect , it } from 'vitest'
1113
1214const component = {
1315 template : '<div>Generic</div>' ,
1416}
1517
1618describe ( 'onBeforeRouteLeave' , ( ) => {
19+ it ( 'triggers when shared KeepAlive component is reactivated for a different route' , async ( ) => {
20+ const routeLeaveSpy = vi . fn ( )
21+ const activatedSpy = vi . fn ( )
22+ const deactivatedSpy = vi . fn ( )
23+ const setupSpy = vi . fn ( ( ) => {
24+ onBeforeRouteLeave ( routeLeaveSpy )
25+ onActivated ( activatedSpy )
26+ onDeactivated ( deactivatedSpy )
27+ return { }
28+ } )
29+
30+ // A shared component used by multiple routes (simulates list pages)
31+ const SharedComponent = defineComponent ( {
32+ template : '<div>Shared: {{ $route.path }}</div>' ,
33+ setup : setupSpy ,
34+ } )
35+
36+ // A different component (simulates detail page)
37+ const DetailComponent = defineComponent ( {
38+ template : '<div>Detail</div>' ,
39+ } )
40+
41+ const routes : RouteRecordRaw [ ] = [
42+ { path : '/' , component } ,
43+ { path : '/a' , component : SharedComponent } ,
44+ { path : '/other' , component : DetailComponent } ,
45+ { path : '/b' , component : SharedComponent } ,
46+ ]
47+
48+ const router = createRouter ( {
49+ history : createMemoryHistory ( ) ,
50+ routes,
51+ } )
52+
53+ const wrapper = mount (
54+ {
55+ template : `
56+ <router-view v-slot="{ Component }">
57+ <keep-alive>
58+ <component :is="Component" />
59+ </keep-alive>
60+ </router-view>
61+ ` ,
62+ } ,
63+ {
64+ global : {
65+ plugins : [ router ] ,
66+ } ,
67+ }
68+ )
69+ await router . isReady ( )
70+
71+ // Step 1: Navigate to /a - component mounts and registers guard with /a's record
72+ await router . push ( '/a' )
73+ expect ( routeLeaveSpy ) . not . toHaveBeenCalled ( )
74+ expect ( setupSpy ) . toHaveBeenCalledTimes ( 1 )
75+ expect ( activatedSpy ) . toHaveBeenCalledTimes ( 1 )
76+
77+ // Step 2: Navigate to another route so SharedComponent is deactivated (kept alive)
78+ // Leave guard is called when leaving /a
79+ await router . push ( '/other' )
80+ expect ( deactivatedSpy ) . toHaveBeenCalledTimes ( 1 )
81+ expect ( routeLeaveSpy ) . toHaveBeenCalledTimes ( 1 ) // called when leaving /a
82+
83+ // Step 3: Navigate to /b - SharedComponent is reactivated for a DIFFERENT route
84+ // The guard should be re-registered with /b's record
85+ await router . push ( '/b' )
86+ expect ( activatedSpy ) . toHaveBeenCalledTimes ( 2 )
87+ expect ( setupSpy ) . toHaveBeenCalledTimes ( 1 ) // still only mounted once (kept alive)
88+
89+ // Step 4: Leave /b - onBeforeRouteLeave SHOULD be triggered
90+ // BUG (before fix): The guard was registered with /a's record, not /b's record
91+ // So leaving /b would not trigger the guard
92+ await router . push ( '/' )
93+ expect ( routeLeaveSpy ) . toHaveBeenCalledTimes ( 2 ) // called again when leaving /b
94+
95+ wrapper . unmount ( )
96+ } )
97+
1798 it ( 'removes guards when leaving the route' , async ( ) => {
1899 const spy = vi . fn ( )
19100 const WithLeave = defineComponent ( {
0 commit comments