Skip to content

Commit 797f55d

Browse files
committed
feat: add deprecation warning for next() callback in navigation guards
1 parent e69ce52 commit 797f55d

File tree

4 files changed

+99
-15
lines changed

4 files changed

+99
-15
lines changed

packages/router/__tests__/guards/guardToPromiseFn.spec.ts

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { ErrorTypes } from '../../src/errors'
44
import { mockWarn } from '../vitest-mock-warn'
55
import { vi, describe, expect, it } from 'vitest'
66

7+
const NEXT_DEPRECATION_MESSAGE =
8+
'The `next()` callback in navigation guards is deprecated. Return the value instead of calling `next(value)`.'
9+
710
// stub those two
811
const to = START_LOCATION_NORMALIZED
912
const from = {
@@ -15,28 +18,31 @@ const from = {
1518
describe('guardToPromiseFn', () => {
1619
mockWarn()
1720
it('calls the guard with to, from and, next', async () => {
18-
expect.assertions(2)
21+
expect.assertions(3)
1922
const spy = vi.fn((to, from, next) => next())
2023
await expect(guardToPromiseFn(spy, to, from)()).resolves.toEqual(undefined)
2124
expect(spy).toHaveBeenCalledWith(to, from, expect.any(Function))
25+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
2226
})
2327

2428
it('resolves if next is called with no arguments', async () => {
25-
expect.assertions(1)
29+
expect.assertions(2)
2630
await expect(
2731
guardToPromiseFn((to, from, next) => next(), to, from)()
2832
).resolves.toEqual(undefined)
33+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
2934
})
3035

3136
it('resolves if next is called with true', async () => {
32-
expect.assertions(1)
37+
expect.assertions(2)
3338
await expect(
3439
guardToPromiseFn((to, from, next) => next(true), to, from)()
3540
).resolves.toEqual(undefined)
41+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
3642
})
3743

3844
it('rejects if next is called with false', async () => {
39-
expect.assertions(1)
45+
expect.assertions(2)
4046
try {
4147
await guardToPromiseFn((to, from, next) => next(false), to, from)()
4248
} catch (err) {
@@ -46,10 +52,11 @@ describe('guardToPromiseFn', () => {
4652
type: ErrorTypes.NAVIGATION_ABORTED,
4753
})
4854
}
55+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
4956
})
5057

5158
it('rejects if next is called with a string location', async () => {
52-
expect.assertions(1)
59+
expect.assertions(2)
5360
try {
5461
await guardToPromiseFn((to, from, next) => next('/new'), to, from)()
5562
} catch (err) {
@@ -59,10 +66,11 @@ describe('guardToPromiseFn', () => {
5966
type: ErrorTypes.NAVIGATION_GUARD_REDIRECT,
6067
})
6168
}
69+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
6270
})
6371

6472
it('rejects if next is called with an object location', async () => {
65-
expect.assertions(1)
73+
expect.assertions(2)
6674
let redirectTo = { path: '/new' }
6775
try {
6876
await guardToPromiseFn((to, from, next) => next(redirectTo), to, from)()
@@ -73,14 +81,16 @@ describe('guardToPromiseFn', () => {
7381
type: ErrorTypes.NAVIGATION_GUARD_REDIRECT,
7482
})
7583
}
84+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
7685
})
7786

7887
it('rejects if next is called with an error', async () => {
79-
expect.assertions(1)
88+
expect.assertions(2)
8089
let error = new Error('nope')
8190
await expect(
8291
guardToPromiseFn((to, from, next) => next(error), to, from)()
8392
).rejects.toBe(error)
93+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
8494
})
8595

8696
it('rejects if guard rejects a Promise', async () => {
@@ -265,7 +275,7 @@ describe('guardToPromiseFn', () => {
265275
})
266276

267277
it('does not warn if guard returns undefined', async () => {
268-
expect.assertions(2)
278+
expect.assertions(3)
269279
await expect(
270280
guardToPromiseFn(
271281
(to, from, next) => {
@@ -278,5 +288,38 @@ describe('guardToPromiseFn', () => {
278288
).resolves.toEqual(undefined)
279289

280290
expect('callback was never called').not.toHaveBeenWarned()
291+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
292+
})
293+
294+
describe('next() callback deprecation', () => {
295+
it('warns when guard calls next()', async () => {
296+
await guardToPromiseFn((to, from, next) => next(), to, from)()
297+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
298+
})
299+
300+
it('warns only once per guard run when next() is called multiple times', async () => {
301+
await expect(
302+
guardToPromiseFn(
303+
(to, from, next) => {
304+
next()
305+
next()
306+
},
307+
to,
308+
from
309+
)()
310+
).resolves.toEqual(undefined)
311+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarnedTimes(1)
312+
expect('called more than once').toHaveBeenWarned()
313+
})
314+
315+
it('does not warn when guard returns value without next parameter', async () => {
316+
await guardToPromiseFn((to, from) => true, to, from)()
317+
expect(NEXT_DEPRECATION_MESSAGE).not.toHaveBeenWarned()
318+
})
319+
320+
it('does not warn when guard returns undefined without next parameter', async () => {
321+
await guardToPromiseFn((to, from) => {}, to, from)()
322+
expect(NEXT_DEPRECATION_MESSAGE).not.toHaveBeenWarned()
323+
})
281324
})
282325
})

packages/router/__tests__/warnings.spec.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import { mockWarn } from './vitest-mock-warn'
1010

1111
let component = defineComponent({})
1212

13+
const NEXT_DEPRECATION_MESSAGE =
14+
'The `next()` callback in navigation guards is deprecated. Return the value instead of calling `next(value)`.'
15+
1316
describe('warnings', () => {
1417
mockWarn()
1518
it('warns on missing name and path for redirect', async () => {
@@ -119,7 +122,7 @@ describe('warnings', () => {
119122
})
120123

121124
it('warns if next is called multiple times in one navigation guard', async () => {
122-
expect.assertions(3)
125+
expect.assertions(4)
123126
let router = createRouter({
124127
history: createMemoryHistory(),
125128
routes: [
@@ -130,14 +133,15 @@ describe('warnings', () => {
130133

131134
router.beforeEach((to, from, next) => {
132135
next()
133-
expect('').not.toHaveBeenWarned()
136+
expect('called more than once').not.toHaveBeenWarned()
134137
next()
135138
expect('called more than once').toHaveBeenWarnedTimes(1)
136139
next()
137140
expect('called more than once').toHaveBeenWarnedTimes(1)
138141
})
139142

140143
await router.push('/b')
144+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
141145
})
142146

143147
it('warns if a non valid function is passed as a component', async () => {
@@ -289,6 +293,7 @@ describe('warnings', () => {
289293
expect(
290294
'It should be called exactly one time in each navigation guard'
291295
).toHaveBeenWarned()
296+
expect(NEXT_DEPRECATION_MESSAGE).toHaveBeenWarned()
292297
})
293298

294299
it('warns when discarding params', () => {

packages/router/src/navigationGuards.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,9 @@ export function guardToPromiseFn(
199199
record && record.instances[name!],
200200
to,
201201
from,
202-
__DEV__ ? canOnlyBeCalledOnce(next, to, from) : next
202+
__DEV__
203+
? withDeprecationWarning(canOnlyBeCalledOnce(next, to, from))
204+
: next
203205
)
204206
)
205207
let guardCall = Promise.resolve(guardReturn)
@@ -231,6 +233,28 @@ export function guardToPromiseFn(
231233
})
232234
}
233235

236+
/**
237+
* Wraps the next callback to warn when it is used. Dev-only: when __DEV__ is
238+
* false (production builds), this branch is dead code and is stripped from the
239+
* bundle.
240+
*
241+
* @internal
242+
*/
243+
function withDeprecationWarning(
244+
next: NavigationGuardNext
245+
): NavigationGuardNext {
246+
let warned = false
247+
return function (this: any) {
248+
if (!warned) {
249+
warned = true
250+
warn(
251+
'The `next()` callback in navigation guards is deprecated. Return the value instead of calling `next(value)`.'
252+
)
253+
}
254+
return next.apply(this, arguments as any)
255+
}
256+
}
257+
234258
function canOnlyBeCalledOnce(
235259
next: NavigationGuardNext,
236260
to: RouteLocationNormalized,

packages/router/src/typed-routes/navigation-guards.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export interface NavigationGuardWithThis<T> {
2424
this: T,
2525
to: RouteLocationNormalized,
2626
from: RouteLocationNormalizedLoaded,
27-
// intentionally not typed to make people use the return
27+
/**
28+
* @deprecated Return a value from the guard instead of calling `next(value)`.
29+
* The callback will be removed in a future version of Vue Router.
30+
*/
2831
next: NavigationGuardNext
2932
): _Awaitable<NavigationGuardReturn>
3033
}
@@ -40,7 +43,10 @@ export interface _NavigationGuardResolved {
4043
this: undefined,
4144
to: RouteLocationNormalizedLoaded,
4245
from: RouteLocationNormalizedLoaded,
43-
// intentionally not typed to make people use the return
46+
/**
47+
* @deprecated Return a value from the guard instead of calling `next(value)`.
48+
* The callback will be removed in a future version of Vue Router.
49+
*/
4450
next: NavigationGuardNext
4551
): _Awaitable<NavigationGuardReturn>
4652
}
@@ -52,7 +58,10 @@ export interface NavigationGuard {
5258
(
5359
to: RouteLocationNormalized,
5460
from: RouteLocationNormalizedLoaded,
55-
// intentionally not typed to make people use the return
61+
/**
62+
* @deprecated Return a value from the guard instead of calling `next(value)`.
63+
* The callback will be removed in a future version of Vue Router.
64+
*/
5665
next: NavigationGuardNext
5766
): _Awaitable<NavigationGuardReturn>
5867
}
@@ -69,7 +78,10 @@ export interface NavigationHookAfter {
6978
}
7079

7180
/**
72-
* `next()` callback passed to navigation guards.
81+
* Callback passed to navigation guards to continue or abort the navigation.
82+
*
83+
* @deprecated Prefer returning a value from the guard instead of calling
84+
* `next(value)`. The callback will be removed in a future version of Vue Router.
7385
*/
7486
export interface NavigationGuardNext {
7587
(): void

0 commit comments

Comments
 (0)