Skip to content

Commit

Permalink
feat: add navigation duplicated failure
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Apr 24, 2020
1 parent c1f38b7 commit 9570416
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 40 deletions.
24 changes: 24 additions & 0 deletions __tests__/errors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,30 @@ describe('Errors & Navigation failures', () => {
)
})

it('Duplicated navigation triggers afterEach', async () => {
let expectedFailure = expect.objectContaining({
type: NavigationFailureType.duplicated,
to: expect.objectContaining({ path: '/' }),
from: expect.objectContaining({ path: '/' }),
})

const { router } = createRouter()

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

await expect(router.push('/')).resolves.toEqual(expectedFailure)
expect(afterEach).toHaveBeenCalledTimes(2)
expect(onError).toHaveBeenCalledTimes(0)

expect(afterEach).toHaveBeenCalledWith(
expect.any(Object),
expect.any(Object),
expectedFailure
)
})

it('next("/location") triggers afterEach', async () => {
await testNavigation(
((to, from, next) => {
Expand Down
16 changes: 9 additions & 7 deletions src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ export const enum ErrorTypes {
NAVIGATION_GUARD_REDIRECT,
NAVIGATION_ABORTED,
NAVIGATION_CANCELLED,
// Using string enums because error codes are exposed to developers
// and number enums could collide with other error codes in runtime
// MATCHER_NOT_FOUND = 'MATCHER_NOT_FOUND',
// NAVIGATION_GUARD_REDIRECT = 'NAVIGATION_GUARD_REDIRECT',
// NAVIGATION_ABORTED = 'NAVIGATION_ABORTED',
// NAVIGATION_CANCELLED = 'NAVIGATION_CANCELLED',
NAVIGATION_DUPLICATED,
}

interface RouterErrorBase extends Error {
Expand All @@ -31,9 +26,13 @@ export interface MatcherError extends RouterErrorBase {
export enum NavigationFailureType {
cancelled = ErrorTypes.NAVIGATION_CANCELLED,
aborted = ErrorTypes.NAVIGATION_ABORTED,
duplicated = ErrorTypes.NAVIGATION_DUPLICATED,
}
export interface NavigationFailure extends RouterErrorBase {
type: ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_CANCELLED
type:
| ErrorTypes.NAVIGATION_CANCELLED
| ErrorTypes.NAVIGATION_ABORTED
| ErrorTypes.NAVIGATION_DUPLICATED
from: RouteLocationNormalized
to: RouteLocationNormalized
}
Expand Down Expand Up @@ -67,6 +66,9 @@ const ErrorTypeMessages = {
[ErrorTypes.NAVIGATION_CANCELLED]({ from, to }: NavigationFailure) {
return `Navigation cancelled from "${from.fullPath}" to "${to.fullPath}" with a new \`push\` or \`replace\``
},
[ErrorTypes.NAVIGATION_DUPLICATED]({ from, to }: NavigationFailure) {
return `Avoided redundant navigation to current location: "${from.fullPath}"`
},
}

// Possible internal errors
Expand Down
71 changes: 38 additions & 33 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,9 +304,6 @@ export function createRouter({
// to could be a string where `replace` is a function
const replace = (to as RouteLocationOptions).replace === true

// TODO: create navigation failure
if (!force && isSameRouteLocation(from, targetLocation)) return

const lastMatched =
targetLocation.matched[targetLocation.matched.length - 1]
if (lastMatched && 'redirect' in lastMatched) {
Expand Down Expand Up @@ -334,36 +331,44 @@ export function createRouter({
toLocation.redirectedFrom = redirectedFrom
let failure: NavigationFailure | void

// trigger all guards, throw if navigation is rejected
try {
await navigate(toLocation, from)
} catch (error) {
// a more recent navigation took place
if (pendingLocation !== toLocation) {
failure = createRouterError<NavigationFailure>(
ErrorTypes.NAVIGATION_CANCELLED,
{
from,
to: toLocation,
}
)
} else if (error.type === ErrorTypes.NAVIGATION_ABORTED) {
failure = error as NavigationFailure
} else if (error.type === ErrorTypes.NAVIGATION_GUARD_REDIRECT) {
// preserve the original redirectedFrom if any
return pushWithRedirect(
// keep options
{
...locationAsObject((error as NavigationRedirectError).to),
state: data,
force,
replace,
},
redirectedFrom || toLocation
)
} else {
// unknown error, throws
triggerError(error, true)
if (!force && isSameRouteLocation(from, targetLocation))
failure = createRouterError<NavigationFailure>(
ErrorTypes.NAVIGATION_DUPLICATED,
{ to: toLocation, from }
)

if (!failure) {
// trigger all guards, throw if navigation is rejected
try {
await navigate(toLocation, from)
} catch (error) {
// a more recent navigation took place
if (pendingLocation !== toLocation) {
failure = createRouterError<NavigationFailure>(
ErrorTypes.NAVIGATION_CANCELLED,
{
from,
to: toLocation,
}
)
} else if (error.type === ErrorTypes.NAVIGATION_ABORTED) {
failure = error as NavigationFailure
} else if (error.type === ErrorTypes.NAVIGATION_GUARD_REDIRECT) {
// preserve the original redirectedFrom if any
return pushWithRedirect(
// keep options
{
...locationAsObject((error as NavigationRedirectError).to),
state: data,
force,
replace,
},
redirectedFrom || toLocation
)
} else {
// unknown error, throws
triggerError(error, true)
}
}
}

Expand Down

0 comments on commit 9570416

Please sign in to comment.