Skip to content

Commit 621f4fa

Browse files
authored
fix(vue): using router.go now shows correct view (#23773)
resolves #22563
1 parent 6b18a89 commit 621f4fa

File tree

10 files changed

+402
-41
lines changed

10 files changed

+402
-41
lines changed

packages/vue-router/__tests__/locationHistory.spec.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@ describe('Location History', () => {
1515
locationHistory.add({ pathname: '/home' });
1616
locationHistory.add({ pathname: '/login', routerAction: 'replace' });
1717

18-
const current = locationHistory.current();
18+
const current = locationHistory.last();
1919
expect(current.pathname).toEqual('/login');
2020
});
2121

2222
it('should correctly pop an item from location history', () => {
2323
locationHistory.add({ pathname: '/home' });
2424
locationHistory.add({ pathname: '/login', routerAction: 'pop' });
2525

26-
const current = locationHistory.current();
26+
const current = locationHistory.last();
2727
expect(current.pathname).toEqual('/login');
2828
expect(locationHistory.canGoBack(1)).toEqual(false);
2929
});
@@ -33,7 +33,7 @@ describe('Location History', () => {
3333
locationHistory.add({ pathname: '/login' });
3434
locationHistory.add({ pathname: '/logout', routerDirection: 'root' });
3535

36-
const current = locationHistory.current();
36+
const current = locationHistory.last();
3737
expect(current.pathname).toEqual('/logout');
3838
expect(locationHistory.canGoBack(1)).toEqual(false);
3939
});
@@ -42,12 +42,12 @@ describe('Location History', () => {
4242
locationHistory.add({ id: '1', pathname: '/tabs/tab1', tab: 'tab1' });
4343
locationHistory.add({ id: '2', pathname: '/tabs/tab2' });
4444

45-
const current = { ...locationHistory.current() };
45+
const current = { ...locationHistory.last() };
4646
current.tab = 'tab2';
4747

4848
locationHistory.update(current);
4949

50-
const getCurrentAgain = locationHistory.current();
50+
const getCurrentAgain = locationHistory.last();
5151
expect(getCurrentAgain.tab).toEqual('tab2');
5252
});
5353

@@ -73,7 +73,7 @@ describe('Location History', () => {
7373
locationHistory.add({ pathname: '/home' });
7474
locationHistory.add({ pathname: '/login' });
7575

76-
const current = locationHistory.current();
76+
const current = locationHistory.last();
7777
expect(current.pathname).toEqual('/login');
7878

7979
const previous = locationHistory.previous();

packages/vue-router/__tests__/viewStacks.spec.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,39 @@ describe('View Stacks', () => {
102102

103103
const viewItemsAgain = viewStacks.getViewStack(2);
104104
expect(viewItemsAgain).toEqual(undefined);
105-
})
105+
});
106+
107+
it('should unmount orphaned views', () => {
108+
const itemA = createRegisteredViewItem(viewStacks, 1, '/home/1', true);
109+
const itemB = createRegisteredViewItem(viewStacks, 1, '/home/2', true);
110+
const itemC = createRegisteredViewItem(viewStacks, 1, '/home/3', true);
111+
const itemD = createRegisteredViewItem(viewStacks, 1, '/home/4', true);
112+
113+
viewStacks.unmountLeavingViews(1, itemA, itemD);
114+
115+
expect(itemB.mount).toEqual(false);
116+
expect(itemB.ionPageElement).toEqual(undefined);
117+
expect(itemB.ionRoute).toEqual(false);
118+
119+
expect(itemC.mount).toEqual(false);
120+
expect(itemC.ionPageElement).toEqual(undefined);
121+
expect(itemC.ionRoute).toEqual(false);
122+
});
123+
124+
it('should remount intermediary views', () => {
125+
const itemA = createRegisteredViewItem(viewStacks);
126+
const itemB = createRegisteredViewItem(viewStacks);
127+
const itemC = createRegisteredViewItem(viewStacks);
128+
const itemD = createRegisteredViewItem(viewStacks);
129+
130+
viewStacks.mountIntermediaryViews(1, itemD, itemA);
131+
132+
expect(itemB.mount).toEqual(true);
133+
expect(itemC.mount).toEqual(true);
134+
});
106135
})
107136

108-
const createRegisteredViewItem = (viewStacks, outletId = '1', route = `/home/${counter++}`) => {
137+
const createRegisteredViewItem = (viewStacks, outletId = '1', route = `/home/${counter++}`, mount = false) => {
109138
const item = viewStacks.createViewItem(
110139
outletId,
111140
() => {},
@@ -115,10 +144,15 @@ const createRegisteredViewItem = (viewStacks, outletId = '1', route = `/home/${c
115144

116145
viewStacks.add(item);
117146

118-
const ionPage = document.createElement('div');
119-
ionPage.classList.add('ion-page');
147+
if (mount) {
148+
const ionPage = document.createElement('div');
149+
ionPage.classList.add('ion-page');
150+
151+
viewStacks.registerIonPage(item, ionPage);
120152

121-
viewStacks.registerIonPage(item, ionPage);
153+
item.mount = true;
154+
item.ionRoute = true;
155+
}
122156

123157
return item;
124158
}

packages/vue-router/src/locationHistory.ts

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,33 @@ export const createLocationHistory = () => {
102102

103103
return history;
104104
}
105-
const previous = () => locationHistory[locationHistory.length - 2] || current();
106-
const current = () => locationHistory[locationHistory.length - 1];
105+
106+
const size = () => locationHistory.length;
107+
108+
const updateByHistoryPosition = (routeInfo: RouteInfo) => {
109+
const existingRouteIndex = locationHistory.findIndex(r => r.position === routeInfo.position);
110+
if (existingRouteIndex === -1) return;
111+
112+
locationHistory[existingRouteIndex].pathname = routeInfo.pathname;
113+
}
114+
115+
/**
116+
* Finds and returns the location history item
117+
* given the state of browser's history API.
118+
* This is useful when jumping around in browser
119+
* history using router.go.
120+
*/
121+
const current = (initialHistory: number, currentHistory: number) => {
122+
/**
123+
* initialHistory does not always start at 0 if users navigated
124+
* to app from another website, so doing this math lets us
125+
* find the correct index in our locationHistory array.
126+
*/
127+
const index = currentHistory - initialHistory;
128+
return locationHistory[index] || last();
129+
}
130+
const previous = () => locationHistory[locationHistory.length - 2] || last();
131+
const last = () => locationHistory[locationHistory.length - 1];
107132
const canGoBack = (deep: number = 1) => locationHistory.length > deep;
108133

109134
const getFirstRouteInfoForTab = (tab: string): RouteInfo | undefined => {
@@ -122,23 +147,41 @@ export const createLocationHistory = () => {
122147
return undefined;
123148
}
124149

125-
const findLastLocation = (routeInfo: RouteInfo): RouteInfo | undefined => {
150+
/**
151+
* Finds and returns the previous view based upon
152+
* what originally pushed it (pushedByRoute).
153+
* When `delta` < -1 then we should just index into
154+
* to array because the previous view that we want is not
155+
* necessarily the view that pushed our current view.
156+
* Additionally, when jumping around in history, we
157+
* do not modify the locationHistory stack so we would
158+
* not update pushedByRoute anyways.
159+
*/
160+
const findLastLocation = (routeInfo: RouteInfo, delta: number = -1): RouteInfo | undefined => {
126161
const routeInfos = getTabsHistory(routeInfo.tab);
127162
if (routeInfos) {
128-
for (let i = routeInfos.length - 2; i >= 0; i--) {
129-
const ri = routeInfos[i];
130-
if (ri) {
131-
if (ri.pathname === routeInfo.pushedByRoute) {
132-
return ri;
163+
if (delta < -1) {
164+
return routeInfos[routeInfos.length - 1 + delta];
165+
} else {
166+
for (let i = routeInfos.length - 2; i >= 0; i--) {
167+
const ri = routeInfos[i];
168+
if (ri) {
169+
if (ri.pathname === routeInfo.pushedByRoute) {
170+
return ri;
171+
}
133172
}
134173
}
135174
}
136175
}
137-
for (let i = locationHistory.length - 2; i >= 0; i--) {
138-
const ri = locationHistory[i];
139-
if (ri) {
140-
if (ri.pathname === routeInfo.pushedByRoute) {
141-
return ri;
176+
if (delta < -1) {
177+
return locationHistory[locationHistory.length - 1 + delta];
178+
} else {
179+
for (let i = locationHistory.length - 2; i >= 0; i--) {
180+
const ri = locationHistory[i];
181+
if (ri) {
182+
if (ri.pathname === routeInfo.pushedByRoute) {
183+
return ri;
184+
}
142185
}
143186
}
144187
}
@@ -147,6 +190,9 @@ export const createLocationHistory = () => {
147190

148191
return {
149192
current,
193+
updateByHistoryPosition,
194+
size,
195+
last,
150196
previous,
151197
add,
152198
canGoBack,

packages/vue-router/src/router.ts

Lines changed: 65 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {
1818
import { AnimationBuilder } from '@ionic/vue';
1919

2020
export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) => {
21-
let currentNavigationInfo: NavigationInformation = { direction: undefined, action: undefined };
21+
let currentNavigationInfo: NavigationInformation = { direction: undefined, action: undefined, delta: undefined };
2222

2323
/**
2424
* Ionic Vue should only react to navigation
@@ -32,7 +32,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
3232
router.afterEach((to: RouteLocationNormalized, _: RouteLocationNormalized, failure?: NavigationFailure) => {
3333
if (failure) return;
3434

35-
const { direction, action } = currentNavigationInfo;
35+
const { direction, action, delta } = currentNavigationInfo;
3636

3737
/**
3838
* When calling router.replace, we are not informed
@@ -42,13 +42,26 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
4242
* We need to use opts.history rather than window.history
4343
* because window.history will be undefined when using SSR.
4444
*/
45+
46+
currentHistoryPosition = opts.history.state.position as number;
47+
4548
const replaceAction = opts.history.state.replaced ? 'replace' : undefined;
46-
handleHistoryChange(to, action || replaceAction, direction);
49+
handleHistoryChange(to, action || replaceAction, direction, delta);
4750

48-
currentNavigationInfo = { direction: undefined, action: undefined };
51+
currentNavigationInfo = { direction: undefined, action: undefined, delta: undefined };
4952
});
5053

5154
const locationHistory = createLocationHistory();
55+
56+
/**
57+
* Keeping track of the history position
58+
* allows us to determine if a user is pushing
59+
* new pages or updating history via the forward
60+
* and back browser buttons.
61+
*/
62+
const initialHistoryPosition = opts.history.state.position as number;
63+
let currentHistoryPosition = opts.history.state.position as number;
64+
5265
let currentRouteInfo: RouteInfo;
5366
let incomingRouteParams: RouteParams;
5467
let currentTab: string | undefined;
@@ -78,14 +91,21 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
7891
* router.beforeEach
7992
*/
8093
currentNavigationInfo = {
81-
action: info.type,
94+
delta: info.delta,
95+
96+
/**
97+
* Both the browser forward and backward actions
98+
* are considered "pop" actions, but when going forward
99+
* we want to make sure the forward animation is used.
100+
*/
101+
action: (info.type === 'pop' && info.delta >= 1) ? 'push' : info.type,
82102
direction: info.direction === '' ? 'forward' : info.direction
83103
};
84104
});
85105

86106
const handleNavigateBack = (defaultHref?: string, routerAnimation?: AnimationBuilder) => {
87107
// todo grab default back button href from config
88-
const routeInfo = locationHistory.current();
108+
const routeInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition);
89109
if (routeInfo && routeInfo.pushedByRoute) {
90110
const prevInfo = locationHistory.findLastLocation(routeInfo);
91111
if (prevInfo) {
@@ -131,16 +151,23 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
131151
}
132152

133153
// TODO RouteLocationNormalized
134-
const handleHistoryChange = (location: any, action?: RouteAction, direction?: RouteDirection) => {
154+
const handleHistoryChange = (
155+
location: any,
156+
action?: RouteAction,
157+
direction?: RouteDirection,
158+
delta?: number
159+
) => {
135160
let leavingLocationInfo: RouteInfo;
136161
if (incomingRouteParams) {
137162
if (incomingRouteParams.routerAction === 'replace') {
138163
leavingLocationInfo = locationHistory.previous();
164+
} else if (incomingRouteParams.routerAction === 'pop') {
165+
leavingLocationInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition + 1);
139166
} else {
140-
leavingLocationInfo = locationHistory.current();
167+
leavingLocationInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition - 1);
141168
}
142169
} else {
143-
leavingLocationInfo = locationHistory.current();
170+
leavingLocationInfo = currentRouteInfo;
144171
}
145172

146173
if (!leavingLocationInfo) {
@@ -160,9 +187,10 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
160187
tab: currentTab
161188
}
162189
} else if (action === 'pop') {
163-
const routeInfo = locationHistory.current();
190+
const routeInfo = locationHistory.current(initialHistoryPosition, currentHistoryPosition - delta);
191+
164192
if (routeInfo && routeInfo.pushedByRoute) {
165-
const prevRouteInfo = locationHistory.findLastLocation(routeInfo);
193+
const prevRouteInfo = locationHistory.findLastLocation(routeInfo, delta);
166194
incomingRouteParams = {
167195
...prevRouteInfo,
168196
routerAction: 'pop',
@@ -191,7 +219,6 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
191219
...incomingRouteParams,
192220
lastPathname: leavingLocationInfo.pathname
193221
}
194-
locationHistory.add(routeInfo);
195222

196223
} else {
197224
const isPushed = incomingRouteParams.routerAction === 'push' && incomingRouteParams.routerDirection === 'forward';
@@ -215,7 +242,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
215242
const lastRoute = locationHistory.getCurrentRouteInfoForTab(routeInfo.tab);
216243
routeInfo.pushedByRoute = lastRoute?.pushedByRoute;
217244
} else if (routeInfo.routerAction === 'replace') {
218-
const currentRouteInfo = locationHistory.current();
245+
const currentRouteInfo = locationHistory.last();
219246

220247
/**
221248
* If going from /home to /child, then replacing from
@@ -232,8 +259,27 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
232259
routeInfo.prevRouteLastPathname = currentRouteInfo?.lastPathname;
233260
}
234261

262+
}
263+
264+
routeInfo.position = currentHistoryPosition;
265+
const historySize = locationHistory.size();
266+
const historyDiff = currentHistoryPosition - initialHistoryPosition;
267+
268+
/**
269+
* If the size of location history is greater
270+
* than the difference between the current history
271+
* position and the initial history position
272+
* then we are guaranteed to already have a history
273+
* item for this route. In other words, a user
274+
* is navigating within the history without pushing
275+
* new items within the stack.
276+
*/
277+
if (historySize > historyDiff && routeInfo.tab === undefined) {
278+
locationHistory.updateByHistoryPosition(routeInfo);
279+
} else {
235280
locationHistory.add(routeInfo);
236281
}
282+
237283
currentRouteInfo = routeInfo;
238284
}
239285
incomingRouteParams = undefined;
@@ -301,7 +347,7 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
301347
const handleSetCurrentTab = (tab: string) => {
302348
currentTab = tab;
303349

304-
const ri = { ...locationHistory.current() };
350+
const ri = { ...locationHistory.last() };
305351
if (ri.tab !== tab) {
306352
ri.tab = tab;
307353
locationHistory.update(ri);
@@ -313,7 +359,12 @@ export const createIonRouter = (opts: IonicVueRouterOptions, router: Router) =>
313359
historyChangeListeners.push(cb);
314360
}
315361

362+
const getLeavingRouteInfo = () => {
363+
return locationHistory.current(initialHistoryPosition, currentHistoryPosition);
364+
}
365+
316366
return {
367+
getLeavingRouteInfo,
317368
handleNavigateBack,
318369
handleSetCurrentTab,
319370
getCurrentRouteInfo,

0 commit comments

Comments
 (0)