UNPKG

vue-router-mock

Version:

Easily test your components by mocking the router

253 lines (244 loc) 7.38 kB
import { RouterLink, RouterView, START_LOCATION, createMemoryHistory, createRouter, matchedRouteKey, routeLocationKey, routerKey, routerViewLocationKey } from "vue-router"; import { config } from "@vue/test-utils"; import { computed, defineComponent, nextTick, reactive, ref } from "vue"; //#region src/routeLocation.ts /** * Wraps a `router.currentRoute` properties using `reactive` and computed * properties to mimic `useRoute()` from vue-router. * * @param route router.currentRoute to wrap */ function createReactiveRouteLocation(route) { return reactive(Object.keys(route.value).reduce((newRoute, key) => { newRoute[key] = computed(() => route.value[key]); return newRoute; }, {})); } //#endregion //#region src/testers/jest.ts function getJestGlobal() { return typeof jest !== "undefined" && jest; } //#endregion //#region src/testers/sinon.ts function getSinonGlobal() { return typeof sinon !== "undefined" && sinon; } //#endregion //#region src/testers/vitest.ts function getVitestGlobal() { return typeof vi !== "undefined" && vi; } //#endregion //#region src/autoSpy.ts /** * Creates a spy on a function * * @param fn function to spy on * @returns [spy, mockClear] */ function createSpy(fn, spyFactory) { if (spyFactory) { const spy = spyFactory.create(fn); return [spy, () => spyFactory.reset(spy)]; } const sinon$1 = getSinonGlobal(); if (sinon$1) { const spy = sinon$1.spy(fn); return [spy, () => spy.resetHistory()]; } const jest$1 = getVitestGlobal() || getJestGlobal(); if (jest$1) { const spy = jest$1.fn(fn); return [spy, () => spy.mockClear()]; } console.error(`Couldn't detect a global spy (tried jest and sinon). Make sure to provide a "spy.create" option when creating the router mock.`); throw new Error("No Spy Available. See https://github.com/posva/vue-router-mock#testing-libraries"); } //#endregion //#region src/router.ts const EmptyView = defineComponent({ name: "RouterMockEmptyView", render: () => null }); /** * Creates a router mock instance * * @param options - options to initialize the router */ function createRouterMock(options = {}) { const router = createRouter({ history: createMemoryHistory(), routes: [{ path: "/:pathMatch(.*)*", component: EmptyView }], ...options }); router.onError(() => {}); let { runPerRouteGuards, removePerRouteGuards, runInComponentGuards, useRealNavigation, noUndeclaredRoutes, spy } = options; const initialLocation = options.initialLocation || START_LOCATION; const { push, addRoute, replace, beforeEach, beforeResolve, onError } = router; const [addRouteMock, addRouteMockClear] = createSpy((parentRecordName, record) => { record = record || parentRecordName; if (!runPerRouteGuards || removePerRouteGuards) delete record.beforeEnter; return addRoute(parentRecordName, record); }, spy); const [pushMock, pushMockClear] = createSpy((to) => { return consumeNextReturn(to); }, spy); const [replaceMock, replaceMockClear] = createSpy((to) => { return consumeNextReturn(to, { replace: true }); }, spy); router.push = pushMock; router.replace = replaceMock; router.addRoute = addRouteMock; let guardRemovers = []; router.beforeEach = (...args) => { const removeGuard = beforeEach(...args); guardRemovers.push(removeGuard); return removeGuard; }; router.beforeResolve = (...args) => { const removeGuard = beforeResolve(...args); guardRemovers.push(removeGuard); return removeGuard; }; let onErrorRemovers = []; router.onError = (...args) => { const removeOnError = onError(...args); onErrorRemovers.push(removeOnError); return removeOnError; }; function reset() { pushMockClear(); replaceMockClear(); addRouteMockClear(); guardRemovers.forEach((remove) => remove()); guardRemovers = []; onErrorRemovers.forEach((remove) => remove()); onErrorRemovers = []; nextReturn = void 0; router.currentRoute.value = initialLocation === START_LOCATION ? START_LOCATION : router.resolve(initialLocation); } let nextReturn = void 0; function setNextGuardReturn(returnValue) { nextReturn = returnValue; } function consumeNextReturn(to, options$1 = {}) { if (nextReturn != null || runInComponentGuards || useRealNavigation) { const removeGuard = router.beforeEach(() => { const value = nextReturn; removeGuard(); nextReturn = void 0; return value; }); const record = router.currentRoute.value.matched[depth.value]; if (record && !runInComponentGuards) { record.leaveGuards.clear(); record.updateGuards.clear(); Object.values(record.components || {}).forEach((component) => { delete component.beforeRouteUpdate; delete component.beforeRouteLeave; }); } pendingNavigation = (options$1.replace ? replace : push)(to); pendingNavigation.catch(() => {}).finally(() => { pendingNavigation = void 0; }); return pendingNavigation; } try { router.currentRoute.value = router.resolve(to); } catch (error) { if (noUndeclaredRoutes) throw error; } return Promise.resolve(); } let pendingNavigation; function getPendingNavigation() { return pendingNavigation || Promise.resolve(); } function setParams(params) { router.currentRoute.value = router.resolve({ params }); return nextTick(); } function setQuery(query) { router.currentRoute.value = router.resolve({ query }); return nextTick(); } function setHash(hash) { router.currentRoute.value = router.resolve({ hash }); return nextTick(); } const depth = ref(0); reset(); return { ...router, push: pushMock, replace: replaceMock, addRoute: addRouteMock, depth, setNextGuardReturn, getPendingNavigation, setParams, setQuery, setHash, reset }; } //#endregion //#region src/injections.ts /** * Inject global variables, overriding any previously inject router mock * * @param router - router mock to inject */ function injectRouterMock(router) { router = router || createRouterMock(); const provides = createProvide(router); const route = provides[routeLocationKey]; Object.assign(config.global.provide, provides); config.global.mocks.$router = router; config.global.mocks.$route = route; config.global.components.RouterView = RouterView; config.global.components.RouterLink = RouterLink; config.global.stubs.RouterLink = true; config.global.stubs.RouterView = true; return { router, route }; } /** * Creates an object of properties to be provided at your application level to * mock what is injected by vue-router * * @param router - router mock instance */ function createProvide(router) { const route = createReactiveRouteLocation(router.currentRoute); const matchedRouteRef = computed(() => router.currentRoute.value.matched[router.depth.value]); return { [routerKey]: router, [routeLocationKey]: route, [routerViewLocationKey]: router.currentRoute, [matchedRouteKey]: matchedRouteRef }; } //#endregion //#region src/plugin.ts function plugin(wrapper) { const router = getRouter(); router.currentRoute.value.matched.forEach((record) => { for (const name in record.components) record.instances[name] = wrapper.vm; }); wrapper.router = router; return wrapper; } function getRouter() { return config.global.provide[routerKey]; } //#endregion export { EmptyView, plugin as VueRouterMock, createProvide, createRouterMock, getRouter, injectRouterMock }; //# sourceMappingURL=index.js.map