Skip to content

Commit

Permalink
feat(guards): next callback beforeRouteEnter
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jul 2, 2020
1 parent e4b3fbe commit d9dad0b
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 153 deletions.
19 changes: 18 additions & 1 deletion __tests__/RouterView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const routes = createRoutes({
{
components: { default: components.Home },
instances: {},
enterCallbacks: [],
path: '/',
props,
},
Expand All @@ -56,6 +57,7 @@ const routes = createRoutes({
{
components: { default: components.Foo },
instances: {},
enterCallbacks: [],
path: '/foo',
props,
},
Expand All @@ -73,12 +75,14 @@ const routes = createRoutes({
{
components: { default: components.Nested },
instances: {},
enterCallbacks: [],
path: '/',
props,
},
{
components: { default: components.Foo },
instances: {},
enterCallbacks: [],
path: 'a',
props,
},
Expand All @@ -96,18 +100,21 @@ const routes = createRoutes({
{
components: { default: components.Nested },
instances: {},
enterCallbacks: [],
path: '/',
props,
},
{
components: { default: components.Nested },
instances: {},
enterCallbacks: [],
path: 'a',
props,
},
{
components: { default: components.Foo },
instances: {},
enterCallbacks: [],
path: 'b',
props,
},
Expand All @@ -122,7 +129,13 @@ const routes = createRoutes({
hash: '',
meta: {},
matched: [
{ components: { foo: components.Foo }, instances: {}, path: '/', props },
{
components: { foo: components.Foo },
instances: {},
enterCallbacks: [],
path: '/',
props,
},
],
},
withParams: {
Expand All @@ -138,6 +151,7 @@ const routes = createRoutes({
components: { default: components.User },

instances: {},
enterCallbacks: [],
path: '/users/:id',
props: { default: true },
},
Expand All @@ -156,6 +170,7 @@ const routes = createRoutes({
components: { default: components.WithProps },

instances: {},
enterCallbacks: [],
path: '/props/:id',
props: { default: { id: 'foo', other: 'fixed' } },
},
Expand All @@ -175,6 +190,7 @@ const routes = createRoutes({
components: { default: components.WithProps },

instances: {},
enterCallbacks: [],
path: '/props/:id',
props: {
default: (to: RouteLocationNormalized) => ({
Expand Down Expand Up @@ -247,6 +263,7 @@ describe('RouterView', () => {
{
components: { default: components.User },
instances: {},
enterCallbacks: [],
path: '/users/:id',
props,
},
Expand Down
23 changes: 13 additions & 10 deletions __tests__/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,17 @@ describe('Errors & Navigation failures', () => {
// should hang
let navigationPromise = router.push('/foo')

expect(afterEach).toHaveBeenCalledTimes(0)
await expect(router.push('/')).resolves.toEqual(undefined)
expect(afterEach).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledTimes(0)

resolve()
await navigationPromise
expect(afterEach).toHaveBeenCalledTimes(2)
expect(onError).toHaveBeenCalledTimes(0)

expect(afterEach).toHaveBeenLastCalledWith(
expect(afterEach).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ path: '/foo' }),
from,
expect.objectContaining({ type: NavigationFailureType.cancelled })
Expand Down Expand Up @@ -159,18 +160,12 @@ describe('Errors & Navigation failures', () => {
let navigationPromise = router.push('/bar')

// goes from /foo to /
expect(afterEach).toHaveBeenCalledTimes(0)
history.go(-1)

await tick()

expect(afterEach).toHaveBeenCalledTimes(1)
expect(onError).toHaveBeenCalledTimes(0)
expect(afterEach).toHaveBeenLastCalledWith(
expect.objectContaining({ path: '/' }),
from,
undefined
)

resolve()
await expect(navigationPromise).resolves.toEqual(
expect.objectContaining({ type: NavigationFailureType.cancelled })
Expand All @@ -179,11 +174,19 @@ describe('Errors & Navigation failures', () => {
expect(afterEach).toHaveBeenCalledTimes(2)
expect(onError).toHaveBeenCalledTimes(0)

expect(afterEach).toHaveBeenLastCalledWith(
expect(afterEach).toHaveBeenNthCalledWith(
1,
expect.objectContaining({ path: '/bar' }),
from,
expect.objectContaining({ type: NavigationFailureType.cancelled })
)

expect(afterEach).toHaveBeenNthCalledWith(
2,
expect.objectContaining({ path: '/' }),
from,
undefined
)
})

it('next(false) triggers afterEach with history.back', async () => {
Expand Down
30 changes: 0 additions & 30 deletions __tests__/guards/beforeRouteEnter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,34 +201,4 @@ describe('beforeRouteEnter', () => {
await p
expect(router.currentRoute.value.fullPath).toBe('/foo')
})

// TODO: wait until we have something working with keep-alive and transition first
it.skip('calls next callback', async done => {
const router = createRouter({ routes })
beforeRouteEnter.mockImplementationOnce((to, from, next) => {
next(vm => {
expect(router.currentRoute.value.fullPath).toBe('/foo')
expect(vm).toBeTruthy()
done()
})
})

await router.push('/')
await router.push('/guard/2')
})

it.skip('calls next callback after waiting', async done => {
const [promise, resolve] = fakePromise()
const router = createRouter({ routes })
beforeRouteEnter.mockImplementationOnce(async (to, from, next) => {
await promise
next(vm => {
expect(router.currentRoute.value.fullPath).toBe('/foo')
expect(vm).toBeTruthy()
done()
})
})
router.push('/foo')
resolve()
})
})
6 changes: 3 additions & 3 deletions __tests__/matcher/records.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ describe('normalizeRouteRecord', () => {
path: '/home',
component: {},
})
expect(record).toEqual({
expect(record).toMatchObject({
beforeEnter: undefined,
children: [],
aliasOf: undefined,
Expand All @@ -31,7 +31,7 @@ describe('normalizeRouteRecord', () => {
name: 'name',
component: {},
})
expect(record).toEqual({
expect(record).toMatchObject({
beforeEnter,
children: [{ path: '/child' }],
components: { default: {} },
Expand Down Expand Up @@ -73,7 +73,7 @@ describe('normalizeRouteRecord', () => {
name: 'name',
components: { one: {} },
})
expect(record).toEqual({
expect(record).toMatchObject({
beforeEnter,
children: [{ path: '/child' }],
components: { one: {} },
Expand Down
1 change: 1 addition & 0 deletions __tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export interface RouteRecordViewLoose
> {
leaveGuards?: any
instances: Record<string, any>
enterCallbacks: Function[]
props: Record<string, _RouteRecordProps>
aliasOf: RouteRecordViewLoose | undefined
}
Expand Down
10 changes: 10 additions & 0 deletions e2e/keep-alive/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,19 @@ const Foo: RouteComponent = { template: '<div class="foo">foo</div>' }

const WithGuards: RouteComponent = {
template: `<div>
<p>Enter Count <span id="enter-count">{{ enterCount }}</span></p>
<p>Update Count <span id="update-count">{{ updateCount }}</span></p>
<p>Leave Count <span id="leave-count">{{ leaveCount }}</span></p>
<button id="change-query" @click="changeQuery">Change query</button>
<button id="reset" @click="reset">Reset</button>
</div>`,

beforeRouteEnter(to, from, next) {
next(vm => {
;(vm as any).enterCount++
})
},

beforeRouteUpdate(to, from, next) {
this.updateCount++
next()
Expand All @@ -37,11 +44,13 @@ const WithGuards: RouteComponent = {
},

setup() {
const enterCount = ref(0)
const updateCount = ref(0)
const leaveCount = ref(0)
const router = useRouter()

function reset() {
enterCount.value = 0
updateCount.value = 0
leaveCount.value = 0
}
Expand All @@ -52,6 +61,7 @@ const WithGuards: RouteComponent = {
return {
reset,
changeQuery,
enterCount,
updateCount,
leaveCount,
}
Expand Down
3 changes: 3 additions & 0 deletions e2e/specs/keep-alive.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@ module.exports = {
.assert.containsText('#counter', '1')

.click('li:nth-child(3) a')
.assert.containsText('#enter-count', '1')
.assert.containsText('#update-count', '0')
.click('#change-query')
.assert.containsText('#enter-count', '1')
.assert.containsText('#update-count', '1')
.back()
.assert.containsText('#update-count', '2')
.assert.containsText('#leave-count', '0')
.back()
.assert.containsText('#counter', '1')
.forward()
.assert.containsText('#enter-count', '2')
.assert.containsText('#update-count', '2')
.assert.containsText('#leave-count', '1')

Expand Down
4 changes: 3 additions & 1 deletion src/RouterView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ export const RouterViewImpl = defineComponent({
const currentName = props.name
const onVnodeMounted = () => {
matchedRoute.instances[currentName] = viewRef.value
// TODO: trigger beforeRouteEnter hooks
matchedRoute.enterCallbacks.forEach(callback =>
callback(viewRef.value!)
)
}
const onVnodeUnmounted = () => {
// remove the instance reference to prevent leak
Expand Down
51 changes: 43 additions & 8 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,25 @@ import {
RouteLocationNormalized,
} from './types'
import { assign } from './utils'
import { PolySymbol } from './injectionSymbols'

/**
* order is important to make it backwards compatible with v3
* Flags so we can combine them when checking for multiple errors
*/
export const enum ErrorTypes {
MATCHER_NOT_FOUND = 0,
NAVIGATION_GUARD_REDIRECT = 1,
NAVIGATION_ABORTED = 2,
NAVIGATION_CANCELLED = 3,
NAVIGATION_DUPLICATED = 4,
// they must be literals to be used as values so we can't write
// 1 << 2
MATCHER_NOT_FOUND = 1,
NAVIGATION_GUARD_REDIRECT = 2,
NAVIGATION_ABORTED = 4,
NAVIGATION_CANCELLED = 8,
NAVIGATION_DUPLICATED = 16,
}

const NavigationFailureSymbol = PolySymbol(
__DEV__ ? 'navigation failure' : 'nf'
)

interface RouterErrorBase extends Error {
type: ErrorTypes
}
Expand Down Expand Up @@ -85,14 +92,42 @@ export function createRouterError<E extends RouterError>(
if (__DEV__ || !__BROWSER__) {
return assign(
new Error(ErrorTypeMessages[type](params as any)),
{ type },
{
type,
[NavigationFailureSymbol]: true,
} as { type: typeof type },
params
) as E
} else {
return assign(new Error(), { type }, params) as E
return assign(
new Error(),
{
type,
[NavigationFailureSymbol]: true,
} as { type: typeof type },
params
) as E
}
}

export function isNavigationFailure(
error: any,
type: ErrorTypes.NAVIGATION_GUARD_REDIRECT
): error is NavigationRedirectError
export function isNavigationFailure(
error: any,
type: ErrorTypes
): error is NavigationFailure
export function isNavigationFailure(
error: any,
type?: number
): error is NavigationFailure {
return (
NavigationFailureSymbol in error &&
(type == null || !!((error as NavigationFailure).type & type))
)
}

const propertiesToLog = ['params', 'query', 'hash'] as const

function stringifyRoute(to: RouteLocationRaw): string {
Expand Down
1 change: 1 addition & 0 deletions src/matcher/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ export function normalizeRouteRecord(
instances: {},
leaveGuards: [],
updateGuards: [],
enterCallbacks: [],
components:
'components' in record
? record.components || {}
Expand Down

0 comments on commit d9dad0b

Please sign in to comment.