vue-router-mock
Version:
Easily test your components by mocking the router
253 lines (244 loc) • 7.38 kB
JavaScript
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