Skip to content

Commit 4e93846

Browse files
dennybiasiolliJ-Sek
authored andcommitted
fix(router): replace next() deprecated in Vue Router v5 (#22643)
Co-authored-by: J-Sek <J-Sek@users.noreply.github.com> resolves #22632
1 parent 0e6a9c6 commit 4e93846

File tree

4 files changed

+118
-15
lines changed

4 files changed

+118
-15
lines changed

packages/docs/src/main.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,11 +147,11 @@ app.config.warnHandler = (err, vm, info) => {
147147
console.warn(err, vm, info)
148148
}
149149

150-
router.beforeEach((to, from, next) => {
150+
router.beforeEach((to, from) => {
151151
if (to.meta.locale !== from.meta.locale) {
152152
localeStore.locale = to.meta.locale as string
153153
}
154-
return to.path.endsWith('/') ? next() : next(`${trailingSlash(to.path)}` + to.hash)
154+
if (!to.path.endsWith('/')) return `${trailingSlash(to.path)}` + to.hash
155155
})
156156
router.afterEach((to, from) => {
157157
if (to.meta.locale !== from.meta.locale && from.meta.locale === 'eo-UY') {

packages/vuetify/src/components/VDialog/__test__/VDialog.spec.browser.tsx

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import { VDialog } from '../VDialog'
33

44
// Utilities
5-
import { commands, render, screen, userEvent } from '@test'
6-
import { nextTick, ref } from 'vue'
5+
import { commands, render, screen, userEvent, wait } from '@test'
6+
import { h, nextTick, ref } from 'vue'
7+
import { createMemoryHistory, createRouter } from 'vue-router'
78

89
// Tests
910
describe('VDialog', () => {
@@ -88,4 +89,106 @@ describe('VDialog', () => {
8889
await userEvent.tab()
8990
await expect.poll(() => document.activeElement).toBe(first)
9091
})
92+
93+
describe('routing back', () => {
94+
function createTestRouter () {
95+
return createRouter({
96+
history: createMemoryHistory(),
97+
routes: [
98+
{ path: '/', component: { setup: () => () => h('h1', 'home') } },
99+
{ path: '/page1', component: { setup: () => () => h('h1', 'page1') } },
100+
{ path: '/page2', component: { setup: () => () => h('h1', 'page2') } },
101+
{ path: '/page3', component: { setup: () => () => h('h1', 'page3') } },
102+
],
103+
})
104+
}
105+
106+
async function simulateBackNavigation (router: ReturnType<typeof createTestRouter>) {
107+
router.back()
108+
for (let i = 0; i < 10; i++) await nextTick() // flush microtasks
109+
window.dispatchEvent(new PopStateEvent('popstate', { state: {} }))
110+
await wait()
111+
}
112+
113+
it('should block back with persistent dialog, allow after close, and block again when reopened', async () => {
114+
const router = createTestRouter()
115+
await router.push('/page1')
116+
await router.push('/page2')
117+
await router.push('/page3')
118+
119+
const model = ref(true)
120+
render(() => (
121+
<VDialog v-model={ model.value } persistent data-testid="dialog">
122+
<div data-testid="content">Content</div>
123+
</VDialog>
124+
), { global: { plugins: [router] } })
125+
126+
await expect(screen.findByTestId('dialog')).resolves.toBeVisible()
127+
await wait()
128+
129+
// 1st back: persistent dialog blocks navigation
130+
await simulateBackNavigation(router)
131+
expect(model.value).toBeTruthy()
132+
expect(router.currentRoute.value.path).toBe('/page3')
133+
134+
// close the dialog
135+
model.value = false
136+
await nextTick()
137+
138+
// 2nd back: no dialog blocking, navigation proceeds
139+
await simulateBackNavigation(router)
140+
expect(router.currentRoute.value.path).toBe('/page2')
141+
142+
// reopen
143+
model.value = true
144+
await nextTick()
145+
await expect(screen.findByTestId('dialog')).resolves.toBeVisible()
146+
await wait()
147+
148+
// 3rd back: persistent dialog blocks again
149+
await simulateBackNavigation(router)
150+
expect(model.value).toBeTruthy()
151+
expect(router.currentRoute.value.path).toBe('/page2')
152+
})
153+
154+
it('should close non-persistent dialog on back and block navigation', async () => {
155+
const router = createTestRouter()
156+
await router.push('/page1')
157+
await router.push('/page2')
158+
await router.push('/page3')
159+
160+
const model = ref(false)
161+
render(() => (
162+
<VDialog v-model={ model.value } data-testid="dialog">
163+
<div data-testid="content">Content</div>
164+
</VDialog>
165+
), { global: { plugins: [router] } })
166+
167+
// Open dialog
168+
model.value = true
169+
await nextTick()
170+
await expect(screen.findByTestId('dialog')).resolves.toBeVisible()
171+
await wait()
172+
173+
// 1st back: dialog closes but route stays (navigation blocked)
174+
await simulateBackNavigation(router)
175+
await expect.poll(() => model.value).toBeFalsy()
176+
expect(router.currentRoute.value.path).toBe('/page3')
177+
178+
// 2nd back: no dialog, navigation proceeds
179+
await simulateBackNavigation(router)
180+
expect(router.currentRoute.value.path).toBe('/page2')
181+
182+
// reopen
183+
model.value = true
184+
await nextTick()
185+
await expect(screen.findByTestId('dialog')).resolves.toBeVisible()
186+
await wait()
187+
188+
// 3rd back: dialog closes again, route stays
189+
await simulateBackNavigation(router)
190+
await expect.poll(() => model.value).toBeFalsy()
191+
expect(router.currentRoute.value.path).toBe('/page2')
192+
})
193+
})
91194
})

packages/vuetify/src/components/VOverlay/VOverlay.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,14 +243,13 @@ export const VOverlay = genericComponent<OverlaySlots>()({
243243

244244
const router = useRouter()
245245
useToggleScope(() => props.closeOnBack, () => {
246-
useBackButton(router, next => {
246+
useBackButton(router, () => {
247247
if (globalTop.value && isActive.value) {
248-
next(false)
249248
if (!props.persistent) isActive.value = false
250249
else animateClick()
251-
} else {
252-
next()
250+
return false
253251
}
252+
return undefined
254253
})
255254
})
256255

packages/vuetify/src/composables/router.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { PropType, Ref, SetupContext } from 'vue'
1313
import type {
1414
RouterLink as _RouterLink,
1515
useLink as _useLink,
16-
NavigationGuardNext,
16+
NavigationGuardReturn,
1717
RouteLocation,
1818
RouteLocationNormalizedLoaded,
1919
RouteLocationRaw,
@@ -118,21 +118,22 @@ export const makeRouterProps = propsFactory({
118118
}, 'router')
119119

120120
let inTransition = false
121-
export function useBackButton (router: Router | undefined, cb: (next: NavigationGuardNext) => void) {
121+
export function useBackButton (router: Router | undefined, cb: () => NavigationGuardReturn) {
122122
let popped = false
123123
let removeBefore: (() => void) | undefined
124124
let removeAfter: (() => void) | undefined
125125

126126
if (IN_BROWSER && router?.beforeEach) {
127127
nextTick(() => {
128128
window.addEventListener('popstate', onPopstate)
129-
removeBefore = router.beforeEach((to, from, next) => {
129+
removeBefore = router.beforeEach(() => {
130130
if (!inTransition) {
131-
setTimeout(() => popped ? cb(next) : next())
132-
} else {
133-
popped ? cb(next) : next()
131+
inTransition = true
132+
return new Promise<NavigationGuardReturn>(resolve => {
133+
setTimeout(() => resolve(popped ? cb() : undefined))
134+
})
134135
}
135-
inTransition = true
136+
return popped ? cb() : undefined
136137
})
137138
removeAfter = router?.afterEach(() => {
138139
inTransition = false

0 commit comments

Comments
 (0)