Skip to content

Commit

Permalink
feat: runPerRouteGuards
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Dec 21, 2020
1 parent 824985c commit 01c0393
Showing 1 changed file with 57 additions and 7 deletions.
64 changes: 57 additions & 7 deletions src/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import {
createRouter,
RouteLocationRaw,
Router,
RouteRecordRaw,
START_LOCATION,
} from 'vue-router'

/**
Expand All @@ -19,6 +21,8 @@ export interface RouterMock extends Router {
returnValue: Error | boolean | RouteLocationRaw | undefined
): void

// NOTE: we could automatically wait for a tick inside getPendingNavigation(), that would require access to the wrapper, unless directly using nextTick from vue works. We could allow an optional parameter `eager: true` to not wait for a tick. Waiting one tick by default is likely to be more useful than not.

/**
* Returns a Promise of the pending navigation. Resolves right away if there
* isn't any.
Expand All @@ -29,7 +33,22 @@ export interface RouterMock extends Router {
/**
* Creates a router mock instance
*/
export function createRouterMock(): RouterMock {
export function createRouterMock({
// TODO: test
/**
* Override the starting location before each test. Defaults to
* START_LOCATION.
*/
initialLocation = START_LOCATION as RouteLocationRaw,
/**
* Run in-component guards. Defaults to false
*/
runInComponentGuards = false,
/**
* Run per-route guards. Defaults to false
*/
runPerRouteGuards = false,
} = {}): RouterMock {
const router = createRouter({
history: createMemoryHistory(),
routes: [
Expand All @@ -40,22 +59,48 @@ export function createRouterMock(): RouterMock {
],
})

const { push } = router
const { push, addRoute, replace } = router

const pushMock = jest.fn((to) => {
const addRouteMock = jest.fn(
(
parentRecordName: Required<RouteRecordRaw>['name'] | RouteRecordRaw,
record?: RouteRecordRaw
) => {
record = record || (parentRecordName as RouteRecordRaw)

if (!runPerRouteGuards) {
// remove existing records to force our own router.beforeEach and easier
// way to mock navigation guard returns.
delete record.beforeEnter
}

// @ts-ignore: this should be valid
return addRoute(parentRecordName, record)
}
)

const pushMock = jest.fn((to: RouteLocationRaw) => {
return consumeNextReturn(to)
})

const replaceMock = jest.fn((to) => {
return consumeNextReturn({ ...to, replace: true })
const replaceMock = jest.fn((to: RouteLocationRaw) => {
return consumeNextReturn(to, { replace: true })
})

router.push = pushMock
router.replace = replaceMock
router.addRoute = addRouteMock

beforeEach(() => {
pushMock.mockClear()
replaceMock.mockClear()
addRouteMock.mockClear()

nextReturn = undefined
router.currentRoute.value =
initialLocation === START_LOCATION
? START_LOCATION
: router.resolve(initialLocation)
})

let nextReturn: Error | boolean | RouteLocationRaw | undefined = undefined
Expand All @@ -66,15 +111,20 @@ export function createRouterMock(): RouterMock {
nextReturn = returnValue
}

function consumeNextReturn(to: RouteLocationRaw) {
function consumeNextReturn(
to: RouteLocationRaw,
options: { replace?: boolean } = {}
) {
if (nextReturn != null) {
const removeGuard = router.beforeEach(() => {
const value = nextReturn
removeGuard()
nextReturn = undefined
return value
})
pendingNavigation = push(to)
// TODO: should we avoid all in component guards by deleting them here?

pendingNavigation = (options.replace ? replace : push)(to)
pendingNavigation
.catch(() => {})
.finally(() => {
Expand Down

0 comments on commit 01c0393

Please sign in to comment.