From 62796dce1b49de1fd9fc5b104c659d3046d0dde5 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Wed, 25 Dec 2024 17:05:56 -0600 Subject: [PATCH 1/7] Add a stop method. start and stop history listener in start and stop --- src/services/createRouter.ts | 9 +++++++-- src/types/router.ts | 4 ++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/services/createRouter.ts b/src/services/createRouter.ts index 68aacad9..26145183 100644 --- a/src/services/createRouter.ts +++ b/src/services/createRouter.ts @@ -263,8 +263,6 @@ export function createRouter(notFoundRoute, push) - history.startListening() - const initialUrl = getInitialUrl(options?.initialUrl) const initialState = history.location.state const { host } = parseUrl(initialUrl) @@ -282,9 +280,15 @@ export function createRouter Promise, + /** + * Stops the router and teardown any listeners. + */ + stop: () => void, } /** From 1b431381f30f73a390ce46d485d71792d06aeb2d Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 28 Dec 2024 21:00:06 -0500 Subject: [PATCH 2/7] Types only implementation for plugins --- src/services/createRouter.ts | 41 ++++++++++++++++++++++++++---------- src/types/index.ts | 1 + src/types/router.ts | 15 +++++++------ src/types/routerPlugin.ts | 23 ++++++++++++++++++++ src/types/utilities.ts | 13 +++++++++++- 5 files changed, 75 insertions(+), 18 deletions(-) create mode 100644 src/types/routerPlugin.ts diff --git a/src/services/createRouter.ts b/src/services/createRouter.ts index 26145183..e194a0df 100644 --- a/src/services/createRouter.ts +++ b/src/services/createRouter.ts @@ -32,6 +32,7 @@ import { ResolvedRoute } from '@/types/resolved' import { createResolvedRouteForUrl } from '@/services/createResolvedRouteForUrl' import { combineUrl } from '@/services/urlCombine' import { RouterReject } from '@/types/routerReject' +import { EmptyRouterPlugin, RouterPlugin } from '@/types/routerPlugin' type RouterUpdateOptions = { replace?: boolean, @@ -61,9 +62,23 @@ type RouterUpdateOptions = { * const router = createRouter(routes) * ``` */ -export function createRouter(routes: TRoutes, options?: TOptions): Router -export function createRouter(arrayOfRoutes: TRoutes[], options?: TOptions): Router -export function createRouter(routesOrArrayOfRoutes: TRoutes | TRoutes[], options?: TOptions): Router { +export function createRouter< + const TRoutes extends Routes, + const TOptions extends RouterOptions, + const TPlugin extends RouterPlugin = EmptyRouterPlugin + >(routes: TRoutes, options?: TOptions, plugins?: TPlugin[]): Router + + export function createRouter< + const TRoutes extends Routes, + const TOptions extends RouterOptions, + const TPlugin extends RouterPlugin = EmptyRouterPlugin +>(arrayOfRoutes: TRoutes[], options?: TOptions, plugins?: TPlugin[]): Router + +export function createRouter< + const TRoutes extends Routes, + const TOptions extends RouterOptions, + const TPlugin extends RouterPlugin = EmptyRouterPlugin +>(routesOrArrayOfRoutes: TRoutes | TRoutes[], options?: TOptions, plugins?: TPlugin[]): Router { const flattenedRoutes = isNestedArray(routesOrArrayOfRoutes) ? routesOrArrayOfRoutes.flat() : routesOrArrayOfRoutes const routes = insertBaseRoute(flattenedRoutes, options?.base) @@ -187,8 +202,8 @@ export function createRouter = ( - source: RoutesName, + const resolve: RouterResolve = ( + source: RoutesName, params: Record = {}, options: RouterResolveOptions = {}, ) => { @@ -201,8 +216,8 @@ export function createRouter = ( - source: Url | RoutesName | ResolvedRoute, + const push: RouterPush = ( + source: Url | RoutesName | ResolvedRoute, paramsOrOptions?: Record | RouterPushOptions, maybeOptions?: RouterPushOptions, ) => { @@ -236,7 +251,11 @@ export function createRouter = (source: Url | RoutesName | ResolvedRoute, paramsOrOptions?: Record | RouterReplaceOptions, maybeOptions?: RouterReplaceOptions) => { + const replace: RouterReplace = ( + source: Url | RoutesName | ResolvedRoute, + paramsOrOptions?: Record | RouterReplaceOptions, + maybeOptions?: RouterReplaceOptions + ) => { if (isUrl(source)) { const options: RouterPushOptions = { ...paramsOrOptions, replace: true } @@ -255,13 +274,13 @@ export function createRouter = (type) => { + const reject: RouterReject = (type) => { setRejection(type) } const { setRejection, rejection, getRejectionRoute } = createRouterReject(options ?? {}) const notFoundRoute = getRejectionRoute('NotFound') - const { currentRoute, routerRoute, updateRoute } = createCurrentRoute(notFoundRoute, push) + const { currentRoute, routerRoute, updateRoute } = createCurrentRoute(notFoundRoute, push) const initialUrl = getInitialUrl(options?.initialUrl) const initialState = history.location.state @@ -304,7 +323,7 @@ export function createRouter = { + const router: Router = { route: routerRoute, resolve, find, diff --git a/src/types/index.ts b/src/types/index.ts index cf657035..a4937ae4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,3 +7,4 @@ export * from './url' export type { ResolvedRoute } from './resolved' export type { QuerySource } from './query' export type { PrefetchConfig, PrefetchStrategy } from './prefetch' +export type { RouterPlugin } from './routerPlugin' \ No newline at end of file diff --git a/src/types/router.ts b/src/types/router.ts index 32c4504f..c03a5194 100644 --- a/src/types/router.ts +++ b/src/types/router.ts @@ -9,6 +9,8 @@ import { RouterPush } from '@/types/routerPush' import { RouterReplace } from '@/types/routerReplace' import { RouterResolve, RouterResolveOptions } from '@/types/RouterResolve' import { RouterReject } from './routerReject' +import { RouterPlugin } from './routerPlugin' +import { KeysOfUnion } from './utilities' /** * Options to initialize a {@link Router} instance. @@ -43,7 +45,8 @@ export type RouterOptions = { export type Router< TRoutes extends Routes = any, - TOptions extends RouterOptions = any + TOptions extends RouterOptions = any, + TPlugin extends RouterPlugin = any > = { /** * Installs the router into a Vue application instance. @@ -53,11 +56,11 @@ export type Router< /** * Manages the current route state. */ - route: RouterRoutes, + route: RouterRoutes | RouterRoutes, /** * Creates a ResolvedRoute record for a given route name and params. */ - resolve: RouterResolve, + resolve: RouterResolve, /** * Creates a ResolvedRoute record for a given URL. */ @@ -65,15 +68,15 @@ export type Router< /** * Navigates to a specified path or route object in the history stack, adding a new entry. */ - push: RouterPush, + push: RouterPush, /** * Replaces the current entry in the history stack with a new one. */ - replace: RouterReplace, + replace: RouterReplace, /** * Handles route rejection based on a specified rejection type. */ - reject: RouterReject, + reject: RouterReject>, /** * Forces the router to re-evaluate the current route. */ diff --git a/src/types/routerPlugin.ts b/src/types/routerPlugin.ts new file mode 100644 index 00000000..b74aa4b3 --- /dev/null +++ b/src/types/routerPlugin.ts @@ -0,0 +1,23 @@ +import { Component } from "vue"; +import { BeforeRouteHook, AfterRouteHook } from "./hooks"; +import { Routes } from "./route"; +import { MaybeArray } from "./utilities"; + +export type EmptyRouterPlugin = { + routes: [], + rejections: {}, +} + +export type RouterPlugin< + TRoutes extends Routes = Routes, + TRejections extends Record = Record +> = { + routes: TRoutes, + rejections: TRejections, + onBeforeRouteEnter?: MaybeArray, + onAfterRouteEnter?: MaybeArray, + onBeforeRouteUpdate?: MaybeArray, + onAfterRouteUpdate?: MaybeArray, + onBeforeRouteLeave?: MaybeArray, + onAfterRouteLeave?: MaybeArray, +} diff --git a/src/types/utilities.ts b/src/types/utilities.ts index a54e8589..2170c633 100644 --- a/src/types/utilities.ts +++ b/src/types/utilities.ts @@ -32,4 +32,15 @@ export type AllPropertiesAreOptional = Record extends T : IsEmptyObject> -export type AsString = T extends string ? T : never \ No newline at end of file +/** + * Converts a type to a string if it is a string, otherwise returns never. + * Specifically useful when using keyof T to produce a union of strings + * rather than string | number | symbol. + */ +export type AsString = T extends string ? T : never + + +/** + * Extracts the keys of a union type. + */ +export type KeysOfUnion = T extends T ? keyof T: never \ No newline at end of file From 2b3fb0298bf0f3008244721d0893a57aa9a76870 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 28 Dec 2024 21:47:42 -0500 Subject: [PATCH 3/7] Implement plugins in createRouter --- src/services/createRouter.ts | 15 +++- .../createRouterPlugin.browser.spec.ts | 84 +++++++++++++++++++ src/services/createRouterPlugin.ts | 42 ++++++++++ src/services/createRouterReject.ts | 17 +--- src/services/index.ts | 1 + 5 files changed, 141 insertions(+), 18 deletions(-) create mode 100644 src/services/createRouterPlugin.browser.spec.ts create mode 100644 src/services/createRouterPlugin.ts diff --git a/src/services/createRouter.ts b/src/services/createRouter.ts index e194a0df..2b0542e0 100644 --- a/src/services/createRouter.ts +++ b/src/services/createRouter.ts @@ -33,6 +33,7 @@ import { createResolvedRouteForUrl } from '@/services/createResolvedRouteForUrl' import { combineUrl } from '@/services/urlCombine' import { RouterReject } from '@/types/routerReject' import { EmptyRouterPlugin, RouterPlugin } from '@/types/routerPlugin' +import { addRouterPluginHooks } from './createRouterPlugin' type RouterUpdateOptions = { replace?: boolean, @@ -78,9 +79,9 @@ export function createRouter< const TRoutes extends Routes, const TOptions extends RouterOptions, const TPlugin extends RouterPlugin = EmptyRouterPlugin ->(routesOrArrayOfRoutes: TRoutes | TRoutes[], options?: TOptions, plugins?: TPlugin[]): Router { - const flattenedRoutes = isNestedArray(routesOrArrayOfRoutes) ? routesOrArrayOfRoutes.flat() : routesOrArrayOfRoutes - const routes = insertBaseRoute(flattenedRoutes, options?.base) +>(routesOrArrayOfRoutes: TRoutes | TRoutes[], options?: TOptions, plugins: TPlugin[] = []): Router { + const pluginRoutes = plugins.map((plugin) => plugin.routes) + const routes = insertBaseRoute([...routesOrArrayOfRoutes, ...pluginRoutes].flat(), options?.base) checkDuplicateNames(routes) @@ -278,7 +279,11 @@ export function createRouter< setRejection(type) } - const { setRejection, rejection, getRejectionRoute } = createRouterReject(options ?? {}) + const { setRejection, rejection, getRejectionRoute } = createRouterReject({ + ...plugins.reduce((rejections, plugin) => ({ ...rejections, ...plugin.rejections }), {}), + ...options?.rejections, + }) + const notFoundRoute = getRejectionRoute('NotFound') const { currentRoute, routerRoute, updateRoute } = createCurrentRoute(notFoundRoute, push) @@ -347,5 +352,7 @@ export function createRouter< stop, } + plugins.forEach(plugin => addRouterPluginHooks(router, plugin)) + return router } diff --git a/src/services/createRouterPlugin.browser.spec.ts b/src/services/createRouterPlugin.browser.spec.ts new file mode 100644 index 00000000..0302a707 --- /dev/null +++ b/src/services/createRouterPlugin.browser.spec.ts @@ -0,0 +1,84 @@ +import { test, expect, vi } from "vitest" +import { createRoute } from "./createRoute" +import { createRouter } from "./createRouter" +import { createRouterPlugin } from "./createRouterPlugin" +import { component, routes } from "@/utilities/testHelpers" +import { flushPromises } from "@vue/test-utils" +import { mount } from "@vue/test-utils" + +test('given a plugin, adds the routes to the router', async () => { + const plugin = createRouterPlugin({ + routes: [createRoute({ name: 'plugin', path: '/plugin', component })], + rejections: { + 'plugin': component, + }, + }) + + const router = createRouter([], { initialUrl: '/plugin' }, [plugin]) + + await router.start() + + expect(router.route.name).toBe('plugin') +}) + +test('given a plugin, adds the rejections to the router', async () => { + const plugin = createRouterPlugin({ + rejections: { + 'plugin': { template: '
This is a plugin rejection
' }, + }, + }) + + const router = createRouter([], { initialUrl: '/' }, [plugin]) + + await router.start() + + const root = { + template: '', + } + + const wrapper = mount(root, { + global: { + plugins: [router], + }, + }) + + await router.reject('plugin') + + await flushPromises() + + expect(wrapper.html()).toBe('
This is a plugin rejection
') +}) + +test('given a plugin, adds the hooks to the router', async () => { + const plugin = createRouterPlugin({ + onBeforeRouteEnter: vi.fn(), + onBeforeRouteUpdate: vi.fn(), + onBeforeRouteLeave: vi.fn(), + onAfterRouteEnter: vi.fn(), + onAfterRouteUpdate: vi.fn(), + onAfterRouteLeave: vi.fn(), + }) + + const router = createRouter(routes, { initialUrl: '/parentA/valueA' }, [plugin]) + + await router.start() + + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) + + await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) + + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(2) + expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(2) + + await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) + + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(3) + expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(2) + expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(2) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(3) +}) diff --git a/src/services/createRouterPlugin.ts b/src/services/createRouterPlugin.ts new file mode 100644 index 00000000..14608895 --- /dev/null +++ b/src/services/createRouterPlugin.ts @@ -0,0 +1,42 @@ +import { Routes } from "@/types/route"; +import { Router } from "@/types/router"; +import { RouterPlugin } from "@/types/routerPlugin"; +import { asArray } from "@/utilities/array"; +import { Component } from "vue"; + +export function createRouterPlugin< + TRoutes extends Routes = [], + TRejections extends Record = {} +>(plugin: Partial>): RouterPlugin { + return { + routes: plugin.routes ?? [] as unknown as TRoutes, + rejections: plugin.rejections ?? {} as TRejections, + ...plugin, + } +} + +export function addRouterPluginHooks(router: Router, plugin: RouterPlugin) { + if (plugin.onBeforeRouteEnter) { + asArray(plugin.onBeforeRouteEnter).forEach(hook => router.onBeforeRouteEnter(hook)) + } + + if (plugin.onAfterRouteEnter) { + asArray(plugin.onAfterRouteEnter).forEach(hook => router.onAfterRouteEnter(hook)) + } + + if (plugin.onBeforeRouteUpdate) { + asArray(plugin.onBeforeRouteUpdate).forEach(hook => router.onBeforeRouteUpdate(hook)) + } + + if (plugin.onAfterRouteUpdate) { + asArray(plugin.onAfterRouteUpdate).forEach(hook => router.onAfterRouteUpdate(hook)) + } + + if (plugin.onBeforeRouteLeave) { + asArray(plugin.onBeforeRouteLeave).forEach(hook => router.onBeforeRouteLeave(hook)) + } + + if (plugin.onAfterRouteLeave) { + asArray(plugin.onAfterRouteLeave).forEach(hook => router.onAfterRouteLeave(hook)) + } +} diff --git a/src/services/createRouterReject.ts b/src/services/createRouterReject.ts index 44fac5e5..47b22f27 100644 --- a/src/services/createRouterReject.ts +++ b/src/services/createRouterReject.ts @@ -5,8 +5,7 @@ import { createRouteId } from '@/services/createRouteId' import { isRejectionRouteSymbol } from '@/services/isRejectionRoute' import { ResolvedRoute } from '@/types/resolved' -export const builtInRejections: ['NotFound'] = ['NotFound'] -export type BuiltInRejectionType = typeof builtInRejections[number] +export type BuiltInRejectionType = 'NotFound' export type RouterSetReject = (type: string | null) => void @@ -14,25 +13,15 @@ type GetRejectionRoute = (type: string) => ResolvedRoute export type RouterRejection = Ref -type CreateRouterRejectContext = { - rejections?: Partial>, -} - export type CreateRouterReject = { setRejection: RouterSetReject, rejection: RouterRejection, getRejectionRoute: GetRejectionRoute, } -export function createRouterReject({ - rejections: customRejectionComponents, -}: CreateRouterRejectContext): CreateRouterReject { +export function createRouterReject(rejections: Partial>): CreateRouterReject { const getRejectionComponent = (type: string): Component => { - const components = { - ...customRejectionComponents, - } - - return markRaw(components[type] ?? genericRejection(type)) + return markRaw(rejections[type] ?? genericRejection(type)) } const getRejectionRoute: GetRejectionRoute = (type) => { diff --git a/src/services/index.ts b/src/services/index.ts index 002a62f1..3242e61f 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,3 +4,4 @@ export * from './path' export * from './query' export type { RouterRoute } from './createRouterRoute' export { withDefault } from './withDefault' +export { createRouterPlugin } from './createRouterPlugin' From fd5434127218a8d7e6d24bc4b85333c7b265889a Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 28 Dec 2024 21:47:51 -0500 Subject: [PATCH 4/7] Add a failing test around global hooks --- src/services/createRouter.spec.ts | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/services/createRouter.spec.ts b/src/services/createRouter.spec.ts index 1f829ad9..e002ecce 100644 --- a/src/services/createRouter.spec.ts +++ b/src/services/createRouter.spec.ts @@ -671,3 +671,42 @@ describe('router.push', () => { expect(router.route.state).toMatchObject({ zoo: 123 }) }) }) + +test('global hooks are called correctly', async () => { + const router = createRouter(routes, { initialUrl: '/parentA/valueA' }) + + const onBeforeRouteEnter = vi.fn() + const onBeforeRouteUpdate = vi.fn() + const onBeforeRouteLeave = vi.fn() + const onAfterRouteEnter = vi.fn() + const onAfterRouteUpdate = vi.fn() + const onAfterRouteLeave = vi.fn() + + router.onBeforeRouteEnter(onBeforeRouteEnter) + router.onAfterRouteEnter(onAfterRouteEnter) + router.onBeforeRouteUpdate(onBeforeRouteUpdate) + router.onAfterRouteUpdate(onAfterRouteUpdate) + router.onBeforeRouteLeave(onBeforeRouteLeave) + router.onAfterRouteLeave(onAfterRouteLeave) + + await router.start() + + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) + + await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) + + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(2) + expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(1) + expect(onAfterRouteUpdate).toHaveBeenCalledTimes(1) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(2) + + await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) + + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(3) + expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(2) + expect(onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) + expect(onAfterRouteUpdate).toHaveBeenCalledTimes(2) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(3) +}) From 7d871f2ffeda60266491d7be36a0159e08db3cbc Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 28 Dec 2024 21:57:43 -0500 Subject: [PATCH 5/7] Remove `isNestedArray` utility. We can just call `.flat` all the time. --- src/services/createRouter.ts | 1 - src/utilities/guards.ts | 4 ---- 2 files changed, 5 deletions(-) diff --git a/src/services/createRouter.ts b/src/services/createRouter.ts index 2b0542e0..ea9a0c6b 100644 --- a/src/services/createRouter.ts +++ b/src/services/createRouter.ts @@ -21,7 +21,6 @@ import { RouterReplace, RouterReplaceOptions } from '@/types/routerReplace' import { RoutesName } from '@/types/routesMap' import { Url, isUrl } from '@/types/url' import { checkDuplicateNames } from '@/utilities/checkDuplicateNames' -import { isNestedArray } from '@/utilities/guards' import { createUniqueIdSequence } from '@/services/createUniqueIdSequence' import { createVisibilityObserver } from './createVisibilityObserver' import { visibilityObserverKey } from '@/compositions/useVisibilityObserver' diff --git a/src/utilities/guards.ts b/src/utilities/guards.ts index 28416fed..9c08c40e 100644 --- a/src/utilities/guards.ts +++ b/src/utilities/guards.ts @@ -20,10 +20,6 @@ export function hasProperty< return true } -export function isNestedArray(value: T | T[]): value is T[] { - return value.every((item) => Array.isArray(item)) -} - export function stringHasValue(value: string | undefined): value is string { return typeof value === 'string' && value.length > 0 } From 2bed4556f9ba0b2be33cebca27f9c8851044a8cc Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 30 Dec 2024 21:44:19 -0500 Subject: [PATCH 6/7] Update hook tests to match implementation --- src/services/createRouter.spec.ts | 25 ++++++++++++--- .../createRouterPlugin.browser.spec.ts | 32 ++++++++++++++++--- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/services/createRouter.spec.ts b/src/services/createRouter.spec.ts index e002ecce..7624e1df 100644 --- a/src/services/createRouter.spec.ts +++ b/src/services/createRouter.spec.ts @@ -692,21 +692,36 @@ test('global hooks are called correctly', async () => { await router.start() expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) + expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(0) + expect(onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) + expect(onAfterRouteUpdate).toHaveBeenCalledTimes(0) expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) - - expect(onBeforeRouteEnter).toHaveBeenCalledTimes(2) + + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(1) + expect(onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteUpdate).toHaveBeenCalledTimes(1) - expect(onAfterRouteEnter).toHaveBeenCalledTimes(2) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) - expect(onBeforeRouteEnter).toHaveBeenCalledTimes(3) + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(2) expect(onBeforeRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteUpdate).toHaveBeenCalledTimes(2) - expect(onAfterRouteEnter).toHaveBeenCalledTimes(3) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) + + await router.push('parentB') + + expect(onBeforeRouteEnter).toHaveBeenCalledTimes(2) + expect(onBeforeRouteUpdate).toHaveBeenCalledTimes(2) + expect(onBeforeRouteLeave).toHaveBeenCalledTimes(2) + expect(onAfterRouteLeave).toHaveBeenCalledTimes(2) + expect(onAfterRouteUpdate).toHaveBeenCalledTimes(2) + expect(onAfterRouteEnter).toHaveBeenCalledTimes(2) }) diff --git a/src/services/createRouterPlugin.browser.spec.ts b/src/services/createRouterPlugin.browser.spec.ts index 0302a707..c0be8015 100644 --- a/src/services/createRouterPlugin.browser.spec.ts +++ b/src/services/createRouterPlugin.browser.spec.ts @@ -61,24 +61,46 @@ test('given a plugin, adds the hooks to the router', async () => { const router = createRouter(routes, { initialUrl: '/parentA/valueA' }, [plugin]) + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(0) + expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(0) + expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(0) + expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(0) + expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(0) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(0) + await router.start() expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) + expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(0) + expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(0) expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) - - expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(2) + + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(1) + expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(1) + expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(1) - expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(2) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) - expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(3) + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(2) expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(2) - expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(3) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) + + await router.push('parentB') + + expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(2) + expect(plugin.onBeforeRouteUpdate).toHaveBeenCalledTimes(2) + expect(plugin.onBeforeRouteLeave).toHaveBeenCalledTimes(2) + expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(2) + expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(2) + expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(2) }) From 7c8e6e12cd0d7c7e13700ef60e42f5035d57617a Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 30 Dec 2024 21:57:40 -0500 Subject: [PATCH 7/7] lint fixes --- src/services/createRouter.spec.ts | 6 ++--- src/services/createRouter.ts | 16 +++++++----- .../createRouterPlugin.browser.spec.ts | 24 ++++++++--------- src/services/createRouterPlugin.ts | 26 +++++++++---------- src/types/index.ts | 2 +- src/types/routerPlugin.ts | 8 +++--- src/types/utilities.ts | 4 +-- 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/src/services/createRouter.spec.ts b/src/services/createRouter.spec.ts index 7624e1df..3a538d97 100644 --- a/src/services/createRouter.spec.ts +++ b/src/services/createRouter.spec.ts @@ -697,7 +697,7 @@ test('global hooks are called correctly', async () => { expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteUpdate).toHaveBeenCalledTimes(0) expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) @@ -706,7 +706,7 @@ test('global hooks are called correctly', async () => { expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteUpdate).toHaveBeenCalledTimes(1) expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) expect(onBeforeRouteEnter).toHaveBeenCalledTimes(1) @@ -715,7 +715,7 @@ test('global hooks are called correctly', async () => { expect(onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(onAfterRouteUpdate).toHaveBeenCalledTimes(2) expect(onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentB') expect(onBeforeRouteEnter).toHaveBeenCalledTimes(2) diff --git a/src/services/createRouter.ts b/src/services/createRouter.ts index ea9a0c6b..6eda3c85 100644 --- a/src/services/createRouter.ts +++ b/src/services/createRouter.ts @@ -63,12 +63,12 @@ type RouterUpdateOptions = { * ``` */ export function createRouter< - const TRoutes extends Routes, - const TOptions extends RouterOptions, + const TRoutes extends Routes, + const TOptions extends RouterOptions, const TPlugin extends RouterPlugin = EmptyRouterPlugin - >(routes: TRoutes, options?: TOptions, plugins?: TPlugin[]): Router - - export function createRouter< +>(routes: TRoutes, options?: TOptions, plugins?: TPlugin[]): Router + +export function createRouter< const TRoutes extends Routes, const TOptions extends RouterOptions, const TPlugin extends RouterPlugin = EmptyRouterPlugin @@ -254,7 +254,7 @@ export function createRouter< const replace: RouterReplace = ( source: Url | RoutesName | ResolvedRoute, paramsOrOptions?: Record | RouterReplaceOptions, - maybeOptions?: RouterReplaceOptions + maybeOptions?: RouterReplaceOptions, ) => { if (isUrl(source)) { const options: RouterPushOptions = { ...paramsOrOptions, replace: true } @@ -351,7 +351,9 @@ export function createRouter< stop, } - plugins.forEach(plugin => addRouterPluginHooks(router, plugin)) + plugins.forEach((plugin) => { + addRouterPluginHooks(router, plugin) + }) return router } diff --git a/src/services/createRouterPlugin.browser.spec.ts b/src/services/createRouterPlugin.browser.spec.ts index c0be8015..595157c7 100644 --- a/src/services/createRouterPlugin.browser.spec.ts +++ b/src/services/createRouterPlugin.browser.spec.ts @@ -1,16 +1,16 @@ -import { test, expect, vi } from "vitest" -import { createRoute } from "./createRoute" -import { createRouter } from "./createRouter" -import { createRouterPlugin } from "./createRouterPlugin" -import { component, routes } from "@/utilities/testHelpers" -import { flushPromises } from "@vue/test-utils" -import { mount } from "@vue/test-utils" +import { test, expect, vi } from 'vitest' +import { createRoute } from './createRoute' +import { createRouter } from './createRouter' +import { createRouterPlugin } from './createRouterPlugin' +import { component, routes } from '@/utilities/testHelpers' +import { flushPromises } from '@vue/test-utils' +import { mount } from '@vue/test-utils' test('given a plugin, adds the routes to the router', async () => { const plugin = createRouterPlugin({ routes: [createRoute({ name: 'plugin', path: '/plugin', component })], rejections: { - 'plugin': component, + plugin: component, }, }) @@ -24,7 +24,7 @@ test('given a plugin, adds the routes to the router', async () => { test('given a plugin, adds the rejections to the router', async () => { const plugin = createRouterPlugin({ rejections: { - 'plugin': { template: '
This is a plugin rejection
' }, + plugin: { template: '
This is a plugin rejection
' }, }, }) @@ -76,7 +76,7 @@ test('given a plugin, adds the hooks to the router', async () => { expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(0) expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentA.childA', { paramA: 'valueA', paramB: 'valueB' }) expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) @@ -85,7 +85,7 @@ test('given a plugin, adds the hooks to the router', async () => { expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentA.childB', { paramA: 'valueB', paramD: 'valueD' }) expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(1) @@ -94,7 +94,7 @@ test('given a plugin, adds the hooks to the router', async () => { expect(plugin.onAfterRouteLeave).toHaveBeenCalledTimes(1) expect(plugin.onAfterRouteUpdate).toHaveBeenCalledTimes(2) expect(plugin.onAfterRouteEnter).toHaveBeenCalledTimes(1) - + await router.push('parentB') expect(plugin.onBeforeRouteEnter).toHaveBeenCalledTimes(2) diff --git a/src/services/createRouterPlugin.ts b/src/services/createRouterPlugin.ts index 14608895..7ae5e234 100644 --- a/src/services/createRouterPlugin.ts +++ b/src/services/createRouterPlugin.ts @@ -1,11 +1,11 @@ -import { Routes } from "@/types/route"; -import { Router } from "@/types/router"; -import { RouterPlugin } from "@/types/routerPlugin"; -import { asArray } from "@/utilities/array"; -import { Component } from "vue"; +import { Routes } from '@/types/route' +import { Router } from '@/types/router' +import { RouterPlugin } from '@/types/routerPlugin' +import { asArray } from '@/utilities/array' +import { Component } from 'vue' export function createRouterPlugin< - TRoutes extends Routes = [], + TRoutes extends Routes = [], TRejections extends Record = {} >(plugin: Partial>): RouterPlugin { return { @@ -15,28 +15,28 @@ export function createRouterPlugin< } } -export function addRouterPluginHooks(router: Router, plugin: RouterPlugin) { +export function addRouterPluginHooks(router: Router, plugin: RouterPlugin): void { if (plugin.onBeforeRouteEnter) { - asArray(plugin.onBeforeRouteEnter).forEach(hook => router.onBeforeRouteEnter(hook)) + asArray(plugin.onBeforeRouteEnter).forEach((hook) => router.onBeforeRouteEnter(hook)) } if (plugin.onAfterRouteEnter) { - asArray(plugin.onAfterRouteEnter).forEach(hook => router.onAfterRouteEnter(hook)) + asArray(plugin.onAfterRouteEnter).forEach((hook) => router.onAfterRouteEnter(hook)) } if (plugin.onBeforeRouteUpdate) { - asArray(plugin.onBeforeRouteUpdate).forEach(hook => router.onBeforeRouteUpdate(hook)) + asArray(plugin.onBeforeRouteUpdate).forEach((hook) => router.onBeforeRouteUpdate(hook)) } if (plugin.onAfterRouteUpdate) { - asArray(plugin.onAfterRouteUpdate).forEach(hook => router.onAfterRouteUpdate(hook)) + asArray(plugin.onAfterRouteUpdate).forEach((hook) => router.onAfterRouteUpdate(hook)) } if (plugin.onBeforeRouteLeave) { - asArray(plugin.onBeforeRouteLeave).forEach(hook => router.onBeforeRouteLeave(hook)) + asArray(plugin.onBeforeRouteLeave).forEach((hook) => router.onBeforeRouteLeave(hook)) } if (plugin.onAfterRouteLeave) { - asArray(plugin.onAfterRouteLeave).forEach(hook => router.onAfterRouteLeave(hook)) + asArray(plugin.onAfterRouteLeave).forEach((hook) => router.onAfterRouteLeave(hook)) } } diff --git a/src/types/index.ts b/src/types/index.ts index a4937ae4..6d3a2c68 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,4 +7,4 @@ export * from './url' export type { ResolvedRoute } from './resolved' export type { QuerySource } from './query' export type { PrefetchConfig, PrefetchStrategy } from './prefetch' -export type { RouterPlugin } from './routerPlugin' \ No newline at end of file +export type { RouterPlugin } from './routerPlugin' diff --git a/src/types/routerPlugin.ts b/src/types/routerPlugin.ts index b74aa4b3..a1fdf1e6 100644 --- a/src/types/routerPlugin.ts +++ b/src/types/routerPlugin.ts @@ -1,7 +1,7 @@ -import { Component } from "vue"; -import { BeforeRouteHook, AfterRouteHook } from "./hooks"; -import { Routes } from "./route"; -import { MaybeArray } from "./utilities"; +import { Component } from 'vue' +import { BeforeRouteHook, AfterRouteHook } from './hooks' +import { Routes } from './route' +import { MaybeArray } from './utilities' export type EmptyRouterPlugin = { routes: [], diff --git a/src/types/utilities.ts b/src/types/utilities.ts index 2170c633..f003d5f0 100644 --- a/src/types/utilities.ts +++ b/src/types/utilities.ts @@ -31,7 +31,6 @@ export type AllPropertiesAreOptional = Record extends T ? true : IsEmptyObject> - /** * Converts a type to a string if it is a string, otherwise returns never. * Specifically useful when using keyof T to produce a union of strings @@ -39,8 +38,7 @@ export type AllPropertiesAreOptional = Record extends T */ export type AsString = T extends string ? T : never - /** * Extracts the keys of a union type. */ -export type KeysOfUnion = T extends T ? keyof T: never \ No newline at end of file +export type KeysOfUnion = T extends T ? keyof T : never