From 3359c35fbc6c72063b16e77a3741f3a5dbfa64e4 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 11 Jan 2025 23:20:52 -0600 Subject: [PATCH 01/23] Routes with a single component working --- src/services/component.ts | 2 +- src/services/createRoute.ts | 168 +++++++++++++++----------------- src/types/createRouteOptions.ts | 12 +++ src/types/route.ts | 22 +++-- src/types/utilities.ts | 2 + 5 files changed, 107 insertions(+), 99 deletions(-) diff --git a/src/services/component.ts b/src/services/component.ts index e523f16d..00a783db 100644 --- a/src/services/component.ts +++ b/src/services/component.ts @@ -11,7 +11,7 @@ export type ComponentProps = TComponent extends Co ? ComponentProps : TComponent extends FunctionalComponent ? T - : never + : {} type ComponentPropsGetter = () => MaybePromise> diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 39b31980..1a0f5177 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -6,108 +6,94 @@ import { CombineQuery } from '@/services/combineQuery' import { CombineState } from '@/services/combineState' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' -import { CreateRouteOptions, WithComponent, WithComponents, WithParent, WithState, WithoutComponents, WithoutParent, WithoutState, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' -import { Hash, toHash, ToHash } from '@/types/hash' +import { CreateRouteOptions, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' +import { toHash, ToHash } from '@/types/hash' import { Host } from '@/types/host' import { toName, ToName } from '@/types/name' -import { Param } from '@/types/paramTypes' -import { Path, ToPath, toPath } from '@/types/path' -import { Query, ToQuery, toQuery } from '@/types/query' -import { RouteMeta } from '@/types/register' +import { ToPath, toPath } from '@/types/path' +import { ToQuery, toQuery } from '@/types/query' import { Route } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' -import { WithHooks } from '@/types/hooks' +import { ComponentProps } from './component' +import { ResolvedRoute } from '@/types/resolved' +import { PropsCallbackContext } from '@/types/props' +import { MaybePromise } from '@/types/utilities' +import RouterView from '@/components/routerView.vue' +import { RouteMeta } from '@/types/register' +import { Param } from '@/types' -export function createRoute< - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithoutComponents - & WithoutParent - & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +export type ToMeta = TMeta extends undefined ? {} : unknown extends TMeta ? {} : TMeta +export type ToState | undefined> = TState extends undefined ? Record : unknown extends TState ? {} : TState -export function createRoute< - const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithoutComponents - & WithParent - & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> +type ToMatch< + TOptions extends CreateRouteOptions, + TProps extends AnyFunction | undefined +> = Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState +> & { props: TProps } -export function createRoute< - TComponent extends Component, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithComponent, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName>> - & WithoutParent - & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +type Matches< + TOptions extends CreateRouteOptions, + TProps extends AnyFunction | undefined +> = TOptions extends { parent: infer TParent extends Route } + ? [...TParent['matches'], ToMatch] + : [ToMatch] -export function createRoute< - TComponent extends Component, - const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithComponent, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']>> - & WithParent - & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> +type AnyFunction = (...args: any[]) => any -export function createRoute< - TComponents extends Record, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithComponents, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName>> - & WithoutParent - & (WithState | WithoutState)): -Route, Host<'', {}>, ToPath, ToQuery, ToHash, TMeta, TState, TName> +type ToRoute< + TOptions extends CreateRouteOptions, + TProps extends AnyFunction | undefined +> = TOptions extends { parent: infer TParent extends Route } + ? Route< + ToName, + Host<'', {}>, + CombinePath, ToPath>, + CombineQuery, ToQuery>, + CombineHash, ToHash>, + CombineMeta, ToMeta>, + CombineState, ToState>, + Matches + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + Matches + > + +type PropsGetter< + TOptions extends CreateRouteOptions, + TComopnent extends Component +> = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> + +type ToComponent< + TComponent extends Component | undefined +> = TComponent extends Component + ? TComponent + : typeof RouterView + +type WithProps< + TOptions extends CreateRouteOptions, + TPropsGetter extends PropsGetter> +> = Partial> extends ReturnType + ? [ props?: TPropsGetter ] + : [ props: TPropsGetter ] export function createRoute< - TComponents extends Record, - const TParent extends Route, - const TName extends string | undefined = undefined, - const TPath extends string | Path | undefined = undefined, - const TQuery extends string | Query | undefined = undefined, - const THash extends string | Hash | undefined = undefined, - const TMeta extends RouteMeta = RouteMeta, - const TState extends Record = Record ->(options: CreateRouteOptions - & WithHooks - & WithComponents, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']>> - & WithParent - & (WithState | WithoutState)): -Route, Host<'', {}>, CombinePath>, CombineQuery>, CombineHash>, CombineMeta, CombineState, TName | TParent['matches'][number]['name']> + const TOptions extends CreateRouteOptions, + const TPropsGetter extends PropsGetter> +>(options: TOptions, ...args: WithProps): ToRoute export function createRoute(options: CreateRouteOptions): Route { const id = createRouteId() diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index f656a636..7ed63e4c 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -128,6 +128,18 @@ export type CreateRouteOptions< * Determines what assets are prefetched when router-link is rendered for this route. Overrides router level prefetch. */ prefetch?: PrefetchConfig, + /** + * Type params for additional data intended to be stored in history state, all keys will be optional unless a default is provided. + */ + state?: Record, + /** + * An optional parent route. + */ + parent?: Route, + /** + * An optional component to render when this route is matched. + */ + component?: Component, } export function combineRoutes(parent: Route, child: Route): Route { diff --git a/src/types/route.ts b/src/types/route.ts index 30e7a251..21dcfb52 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -1,4 +1,3 @@ -import { CreateRouteOptions, WithComponent, WithComponents, WithHost, WithParent, WithState, WithoutComponents, WithoutHost, WithoutParent, WithoutState } from '@/types/createRouteOptions' import { Hash } from '@/types/hash' import { Host } from '@/types/host' import { Param } from '@/types/paramTypes' @@ -6,7 +5,7 @@ import { Path } from '@/types/path' import { PrefetchConfig } from '@/types/prefetch' import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' -import { WithHooks } from '@/types/hooks' +import { LastInArray } from './utilities' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. @@ -16,8 +15,17 @@ export type Routes = readonly Route[] /** * The Route properties originally provided to `createRoute`. The only change is normalizing meta to always default to an empty object. */ -type CreateRouteOptionsMatched = CreateRouteOptions & WithHooks & (WithHost | WithoutHost) & (WithComponent | WithComponents | WithoutComponents) & (WithParent | WithoutParent) & (WithState | WithoutState) & { id: string, meta: RouteMeta } - +export type CreatedRouteOptions = { + id: string, + name?: string, + host?: Host | string | undefined, + path?: Path | string | undefined, + query?: Query | string | undefined, + hash?: Hash | string | undefined, + meta?: RouteMeta, + state?: Record, + prefetch?: PrefetchConfig, +} /** * Represents the structure of a route within the application. Return value of `createRoute` * @template TName - Represents the unique name identifying the route, typically a string. @@ -32,7 +40,7 @@ export type Route< THash extends Hash = Hash, TMeta extends RouteMeta = RouteMeta, TState extends Record = Record, - TMatchNames extends string | undefined = string | undefined + TMatches extends CreatedRouteOptions[] = CreatedRouteOptions[] > = { /** * Unique identifier for the route, generated by router. @@ -41,12 +49,12 @@ export type Route< /** * The specific route properties that were matched in the current route. */ - matched: CreateRouteOptionsMatched, + matched: LastInArray, /** * The specific route properties that were matched in the current route, including any ancestors. * Order of routes will be from greatest ancestor to narrowest matched. */ - matches: CreateRouteOptionsMatched[], + matches: TMatches, /** * Identifier for the route as defined by user. Name must be unique among named routes. Name is used for routing and for matching. */ diff --git a/src/types/utilities.ts b/src/types/utilities.ts index c8329b5b..a7b14473 100644 --- a/src/types/utilities.ts +++ b/src/types/utilities.ts @@ -14,6 +14,8 @@ export type MaybeArray = T | T[] export type AsArray = T extends MaybeArray ? U[] : T +export type LastInArray = T extends [...any[], infer Last] ? Last : TFallback + export type MaybePromise = T | Promise // Copied and modified from [type-fest](https://github.com/sindresorhus/type-fest/blob/main/source/replace.d.ts) From d7bec30269aee2ace3caf4a2b21a77c3d1ed716c Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sat, 11 Jan 2025 23:22:26 -0600 Subject: [PATCH 02/23] Commit a working test --- src/components/routerView.browser.spec.ts | 27 ++++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/routerView.browser.spec.ts b/src/components/routerView.browser.spec.ts index ad05f563..96d8967c 100644 --- a/src/components/routerView.browser.spec.ts +++ b/src/components/routerView.browser.spec.ts @@ -1,5 +1,3 @@ -/* eslint-disable @typescript-eslint/no-confusing-void-expression */ -/* eslint-disable @typescript-eslint/only-throw-error */ import { mount, flushPromises } from '@vue/test-utils' import { expect, test } from 'vitest' import { defineAsyncComponent, h } from 'vue' @@ -256,16 +254,20 @@ test('Renders the route component when the router.push does match after a reject }) test('Renders the multiple components when using named route views', async () => { - const route = createRoute({ - name: 'foo', - path: '/', - components: { - default: { template: '_default_' }, - one: { template: '_one_' }, - two: { template: '_two_' }, - }, + const parent = createRoute({ + name: 'parent', + path: '/parent/[param]', }) + const route = createRoute({ + parent, + name: 'child', + path: '/child', + component: echo, + }, ({ params, name }) => ({ value: params.param, extra: name })) + + type T = ReturnType + const router = createRouter([route], { initialUrl: '/', }) @@ -432,9 +434,8 @@ test('Props from route can trigger reject', async () => { name: 'routeA', path: '/routeA', component: echo, - props: (__, context) => { - throw context.reject('NotFound') - }, + }, (__, context) => { + throw context.reject('NotFound') }) const router = createRouter([routeA], { From 574eadc1d99d7f4f39e3de4ff70869f3847dadcb Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:06:06 -0600 Subject: [PATCH 03/23] Add support for multiple named components --- src/services/createRoute.ts | 50 ++++++++++++++++++++++----------- src/types/createRouteOptions.ts | 8 +++++- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 1a0f5177..7f9f597f 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -27,7 +27,7 @@ export type ToState | undefined> = TState e type ToMatch< TOptions extends CreateRouteOptions, - TProps extends AnyFunction | undefined + TProps extends RouteProps | undefined > = Route< ToName, Host<'', {}>, @@ -40,16 +40,14 @@ type ToMatch< type Matches< TOptions extends CreateRouteOptions, - TProps extends AnyFunction | undefined + TProps extends RouteProps | undefined > = TOptions extends { parent: infer TParent extends Route } ? [...TParent['matches'], ToMatch] : [ToMatch] -type AnyFunction = (...args: any[]) => any - type ToRoute< TOptions extends CreateRouteOptions, - TProps extends AnyFunction | undefined + TProps extends RouteProps | undefined > = TOptions extends { parent: infer TParent extends Route } ? Route< ToName, @@ -77,25 +75,43 @@ type PropsGetter< TComopnent extends Component > = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> -type ToComponent< - TComponent extends Component | undefined -> = TComponent extends Component - ? TComponent - : typeof RouterView +type ComponentPropsAreOptional< + TComponent extends Component +> = Partial> extends ComponentProps + ? true + : false + +type RoutePropsRecord< + TOptions extends CreateRouteOptions, + TComponents extends Record +> = { [K in keyof TComponents as ComponentPropsAreOptional extends true ? K : never]?: PropsGetter } +& { [K in keyof TComponents as ComponentPropsAreOptional extends false ? K : never]: PropsGetter } + +type RouteProps< + TOptions extends CreateRouteOptions +> = TOptions['component'] extends Component + ? PropsGetter + : TOptions['components'] extends Record + ? RoutePropsRecord + : PropsGetter type WithProps< TOptions extends CreateRouteOptions, - TPropsGetter extends PropsGetter> -> = Partial> extends ReturnType - ? [ props?: TPropsGetter ] - : [ props: TPropsGetter ] + TProps extends RouteProps +> = TProps extends PropsGetter + ? Partial> extends ReturnType + ? [ props?: TProps ] + : [ props: TProps ] + : Partial extends TProps + ? [ props?: TProps ] + : [ props: TProps ] export function createRoute< const TOptions extends CreateRouteOptions, - const TPropsGetter extends PropsGetter> ->(options: TOptions, ...args: WithProps): ToRoute + const TProps extends RouteProps +>(options: TOptions, ...args: WithProps): ToRoute -export function createRoute(options: CreateRouteOptions): Route { +export function createRoute(options: CreateRouteOptions, props?: unknown): Route { const id = createRouteId() const name = toName(options.name) const path = toPath(options.path) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 7ed63e4c..68dc8c5e 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -133,13 +133,19 @@ export type CreateRouteOptions< */ state?: Record, /** - * An optional parent route. + * An optional parent route to nest this route under. */ parent?: Route, /** * An optional component to render when this route is matched. + * + * @default RouterView */ component?: Component, + /** + * An object of named components to render using named views + */ + components?: Record, } export function combineRoutes(parent: Route, child: Route): Route { From 5a426802cd354e26d4ed6c30be71c3356293275b Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:12:02 -0600 Subject: [PATCH 04/23] Simplify created type (though its not correct yet) --- src/types/route.ts | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/types/route.ts b/src/types/route.ts index 21dcfb52..19a027ae 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -6,6 +6,7 @@ import { PrefetchConfig } from '@/types/prefetch' import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' import { LastInArray } from './utilities' +import { CreateRouteOptions } from './createRouteOptions' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. @@ -15,16 +16,8 @@ export type Routes = readonly Route[] /** * The Route properties originally provided to `createRoute`. The only change is normalizing meta to always default to an empty object. */ -export type CreatedRouteOptions = { +export type CreatedRouteOptions = CreateRouteOptions & { id: string, - name?: string, - host?: Host | string | undefined, - path?: Path | string | undefined, - query?: Query | string | undefined, - hash?: Hash | string | undefined, - meta?: RouteMeta, - state?: Record, - prefetch?: PrefetchConfig, } /** * Represents the structure of a route within the application. Return value of `createRoute` From 12c62a62f41ca3ef29ce1361a51f9f7e062cb9c0 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:29:28 -0600 Subject: [PATCH 05/23] Start organizing new types some --- src/services/createRoute.ts | 100 ++-------------------- src/types/createRouteOptions.ts | 147 ++++++++++++++++++++------------ src/types/meta.ts | 3 + src/types/route.ts | 2 +- src/types/state.ts | 2 + 5 files changed, 106 insertions(+), 148 deletions(-) create mode 100644 src/types/meta.ts diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 7f9f597f..2ded2933 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -1,101 +1,15 @@ import { Component, markRaw } from 'vue' -import { CombineHash } from '@/services/combineHash' -import { CombineMeta } from '@/services/combineMeta' -import { CombinePath } from '@/services/combinePath' -import { CombineQuery } from '@/services/combineQuery' -import { CombineState } from '@/services/combineState' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' -import { CreateRouteOptions, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' -import { toHash, ToHash } from '@/types/hash' -import { Host } from '@/types/host' -import { toName, ToName } from '@/types/name' -import { ToPath, toPath } from '@/types/path' -import { ToQuery, toQuery } from '@/types/query' +import { CreateRouteOptions, PropsGetter, RouteProps, ToRoute, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' +import { toHash } from '@/types/hash' +import { toName } from '@/types/name' +import { toPath } from '@/types/path' +import { toQuery } from '@/types/query' import { Route } from '@/types/route' import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' -import { ComponentProps } from './component' -import { ResolvedRoute } from '@/types/resolved' -import { PropsCallbackContext } from '@/types/props' -import { MaybePromise } from '@/types/utilities' -import RouterView from '@/components/routerView.vue' -import { RouteMeta } from '@/types/register' -import { Param } from '@/types' -export type ToMeta = TMeta extends undefined ? {} : unknown extends TMeta ? {} : TMeta -export type ToState | undefined> = TState extends undefined ? Record : unknown extends TState ? {} : TState - -type ToMatch< - TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined -> = Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState -> & { props: TProps } - -type Matches< - TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined -> = TOptions extends { parent: infer TParent extends Route } - ? [...TParent['matches'], ToMatch] - : [ToMatch] - -type ToRoute< - TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined -> = TOptions extends { parent: infer TParent extends Route } - ? Route< - ToName, - Host<'', {}>, - CombinePath, ToPath>, - CombineQuery, ToQuery>, - CombineHash, ToHash>, - CombineMeta, ToMeta>, - CombineState, ToState>, - Matches - > - : Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - Matches - > - -type PropsGetter< - TOptions extends CreateRouteOptions, - TComopnent extends Component -> = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> - -type ComponentPropsAreOptional< - TComponent extends Component -> = Partial> extends ComponentProps - ? true - : false - -type RoutePropsRecord< - TOptions extends CreateRouteOptions, - TComponents extends Record -> = { [K in keyof TComponents as ComponentPropsAreOptional extends true ? K : never]?: PropsGetter } -& { [K in keyof TComponents as ComponentPropsAreOptional extends false ? K : never]: PropsGetter } - -type RouteProps< - TOptions extends CreateRouteOptions -> = TOptions['component'] extends Component - ? PropsGetter - : TOptions['components'] extends Record - ? RoutePropsRecord - : PropsGetter - -type WithProps< +type CreateRouteWithProps< TOptions extends CreateRouteOptions, TProps extends RouteProps > = TProps extends PropsGetter @@ -109,7 +23,7 @@ type WithProps< export function createRoute< const TOptions extends CreateRouteOptions, const TProps extends RouteProps ->(options: TOptions, ...args: WithProps): ToRoute +>(options: TOptions, ...args: CreateRouteWithProps): ToRoute export function createRoute(options: CreateRouteOptions, props?: unknown): Route { const id = createRouteId() diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 68dc8c5e..87db05da 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -1,21 +1,25 @@ import { Component } from 'vue' -import { combineHash } from '@/services/combineHash' -import { combineMeta } from '@/services/combineMeta' -import { combinePath } from '@/services/combinePath' -import { combineQuery } from '@/services/combineQuery' -import { combineState } from '@/services/combineState' -import { ComponentProps } from '@/services/component' -import { Hash } from '@/types/hash' +import { CombineHash, combineHash } from '@/services/combineHash' +import { CombineMeta, combineMeta } from '@/services/combineMeta' +import { CombinePath, combinePath } from '@/services/combinePath' +import { CombineQuery, combineQuery } from '@/services/combineQuery' +import { CombineState, combineState } from '@/services/combineState' +import { Hash, ToHash } from '@/types/hash' import { Host } from '@/types/host' import { Param } from '@/types/paramTypes' -import { Path } from '@/types/path' +import { Path, ToPath } from '@/types/path' import { PrefetchConfig } from '@/types/prefetch' -import { Query } from '@/types/query' +import { Query, ToQuery } from '@/types/query' import { RouteMeta } from '@/types/register' import { Route } from '@/types/route' -import { MaybePromise } from '@/types/utilities' import { ResolvedRoute } from './resolved' +import { ComponentProps } from '@/services/component' import { PropsCallbackContext } from './props' +import { MaybePromise } from './utilities' +import { RouterView } from '@/components' +import { ToMeta } from './meta' +import { ToState } from './state' +import { ToName } from './name' export type WithHost = { /** @@ -44,59 +48,18 @@ export type WithoutParent = { parent?: never, } -export type WithComponent< - TComponent extends Component = Component, - TRoute extends Route = Route -> = { - /** - * A Vue component, which can be either synchronous or asynchronous components. - */ - component: TComponent, - props?: (route: ResolvedRoute, context: PropsCallbackContext) => TComponent extends Component ? MaybePromise> : {}, -} - -export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & WithComponent { +export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & { component: Component } { return 'component' in options && Boolean(options.component) } -export type WithComponents< - TComponents extends Record = Record, - TRoute extends Route = Route -> = { - /** - * Multiple components for named views, which can be either synchronous or asynchronous components. - */ - components: TComponents, - props?: { - [TKey in keyof TComponents]?: (route: ResolvedRoute, context: PropsCallbackContext) => TComponents[TKey] extends Component ? MaybePromise> : {} - }, -} - -export type WithoutComponents = { - component?: never, - components?: never, - props?: never, -} - -export function isWithComponents(options: CreateRouteOptions): options is CreateRouteOptions & WithComponents { +export function isWithComponents(options: CreateRouteOptions): options is CreateRouteOptions & { components: Record } { return 'components' in options && Boolean(options.components) } -export type WithState = Record> = { - /** - * Type params for additional data intended to be stored in history state, all keys will be optional unless a default is provided. - */ - state: TState, -} - -export function isWithState(options: CreateRouteOptions): options is CreateRouteOptions & WithState { +export function isWithState(options: CreateRouteOptions): options is CreateRouteOptions & { state: Record } { return 'state' in options && Boolean(options.state) } -export type WithoutState = { - state?: never, -} - export type CreateRouteOptions< TName extends string | undefined = string | undefined, TPath extends string | Path | undefined = string | Path | undefined, @@ -146,8 +109,84 @@ export type CreateRouteOptions< * An object of named components to render using named views */ components?: Record, + /** + * Props have been moved to the second argument of `createRoute`. This property can no longer be used. + * + * @deprecated + */ + props?: never, } +export type PropsGetter< + TOptions extends CreateRouteOptions, + TComopnent extends Component +> = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> + +type ComponentPropsAreOptional< + TComponent extends Component +> = Partial> extends ComponentProps + ? true + : false + +type RoutePropsRecord< + TOptions extends CreateRouteOptions, + TComponents extends Record +> = { [K in keyof TComponents as ComponentPropsAreOptional extends true ? K : never]?: PropsGetter } +& { [K in keyof TComponents as ComponentPropsAreOptional extends false ? K : never]: PropsGetter } + +export type RouteProps< + TOptions extends CreateRouteOptions +> = TOptions['component'] extends Component + ? PropsGetter + : TOptions['components'] extends Record + ? RoutePropsRecord + : PropsGetter + +type ToMatch< + TOptions extends CreateRouteOptions, + TProps extends RouteProps | undefined +> = Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState +> & { props: TProps } + +type ToMatches< + TOptions extends CreateRouteOptions, + TProps extends RouteProps | undefined +> = TOptions extends { parent: infer TParent extends Route } + ? [...TParent['matches'], ToMatch] + : [ToMatch] + +export type ToRoute< + TOptions extends CreateRouteOptions, + TProps extends RouteProps | undefined +> = TOptions extends { parent: infer TParent extends Route } + ? Route< + ToName, + Host<'', {}>, + CombinePath, ToPath>, + CombineQuery, ToQuery>, + CombineHash, ToHash>, + CombineMeta, ToMeta>, + CombineState, ToState>, + ToMatches + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + ToMatches + > + export function combineRoutes(parent: Route, child: Route): Route { return { ...child, diff --git a/src/types/meta.ts b/src/types/meta.ts new file mode 100644 index 00000000..4e7432c0 --- /dev/null +++ b/src/types/meta.ts @@ -0,0 +1,3 @@ +import { RouteMeta } from './register' + +export type ToMeta = TMeta extends undefined ? {} : unknown extends TMeta ? {} : TMeta diff --git a/src/types/route.ts b/src/types/route.ts index 19a027ae..237f8185 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -16,7 +16,7 @@ export type Routes = readonly Route[] /** * The Route properties originally provided to `createRoute`. The only change is normalizing meta to always default to an empty object. */ -export type CreatedRouteOptions = CreateRouteOptions & { +export type CreatedRouteOptions = Omit & { id: string, } /** diff --git a/src/types/state.ts b/src/types/state.ts index ec33bf82..ec6901c3 100644 --- a/src/types/state.ts +++ b/src/types/state.ts @@ -3,6 +3,8 @@ import { Param } from '@/types/paramTypes' import { Routes } from '@/types/route' import { RouteGetByKey } from '@/types/routeWithParams' +export type ToState | undefined> = TState extends undefined ? Record : unknown extends TState ? {} : TState + export type ExtractRouteStateParamsAsOptional> = ExtractParamTypes<{ [K in keyof T as K extends string ? `?${K}` : never]: T[K] }> From 8b205332d9bf2fd9cf709c65366e45fe485d0cfa Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:32:05 -0600 Subject: [PATCH 06/23] Add props to the match when creating a route --- src/services/createRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 2ded2933..461731fd 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -33,7 +33,7 @@ export function createRoute(options: CreateRouteOptions, props?: unknown): Route const hash = toHash(options.hash) const meta = options.meta ?? {} const state = isWithState(options) ? options.state : {} - const rawRoute = markRaw({ id, meta: {}, state: {}, ...options }) + const rawRoute = markRaw({ id, meta: {}, state: {}, ...options, props }) const route = { id, From f6ac1b6d1bbdc5e41c3b1e4477fe232ee962f8e5 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:44:07 -0600 Subject: [PATCH 07/23] Fix some types and type guards --- src/types/createRouteOptions.ts | 9 +++++---- src/types/route.ts | 4 +++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 87db05da..61a64d3b 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -20,6 +20,7 @@ import { RouterView } from '@/components' import { ToMeta } from './meta' import { ToState } from './state' import { ToName } from './name' +import { WithHooks } from './hooks' export type WithHost = { /** @@ -48,15 +49,15 @@ export type WithoutParent = { parent?: never, } -export function isWithComponent(options: CreateRouteOptions): options is CreateRouteOptions & { component: Component } { +export function isWithComponent>(options: T): options is T & { component: Component } { return 'component' in options && Boolean(options.component) } -export function isWithComponents(options: CreateRouteOptions): options is CreateRouteOptions & { components: Record } { +export function isWithComponents>(options: T): options is T & { components: Record } { return 'components' in options && Boolean(options.components) } -export function isWithState(options: CreateRouteOptions): options is CreateRouteOptions & { state: Record } { +export function isWithState>(options: T): options is T & { state: Record } { return 'state' in options && Boolean(options.state) } @@ -66,7 +67,7 @@ export type CreateRouteOptions< TQuery extends string | Query | undefined = string | Query | undefined, THash extends string | Hash | undefined = string | Hash | undefined, TMeta extends RouteMeta = RouteMeta -> = { +> = WithHooks & { /** * Name for route, used to create route keys and in navigation. */ diff --git a/src/types/route.ts b/src/types/route.ts index 237f8185..aa886934 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -7,6 +7,7 @@ import { Query } from '@/types/query' import { RouteMeta } from '@/types/register' import { LastInArray } from './utilities' import { CreateRouteOptions } from './createRouteOptions' +import { WithHooks } from './hooks' /** * Represents an immutable array of Route instances. Return value of `createRoute`, expected param for `createRouter`. @@ -16,8 +17,9 @@ export type Routes = readonly Route[] /** * The Route properties originally provided to `createRoute`. The only change is normalizing meta to always default to an empty object. */ -export type CreatedRouteOptions = Omit & { +export type CreatedRouteOptions = Omit & WithHooks & { id: string, + props: unknown, } /** * Represents the structure of a route within the application. Return value of `createRoute` From d7e439184710bbbc51a4d31ff5b252d618de4e43 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 14:45:00 -0600 Subject: [PATCH 08/23] Make props optional in match type --- src/types/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/route.ts b/src/types/route.ts index aa886934..cf90aa6b 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -19,7 +19,7 @@ export type Routes = readonly Route[] */ export type CreatedRouteOptions = Omit & WithHooks & { id: string, - props: unknown, + props?: unknown, } /** * Represents the structure of a route within the application. Return value of `createRoute` From 24733398739e00f254f0f4e55a24348d25820c45 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 19:36:50 -0600 Subject: [PATCH 09/23] Update some types to have defaults for generics. Start fixing the types in the prop store --- src/services/createPropStore.ts | 15 +++++++-------- src/services/createRoute.ts | 8 ++++---- src/types/createRouteOptions.ts | 26 +++++++++++++++++--------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/services/createPropStore.ts b/src/services/createPropStore.ts index c5cb5d76..9d8fce6b 100644 --- a/src/services/createPropStore.ts +++ b/src/services/createPropStore.ts @@ -1,5 +1,5 @@ import { InjectionKey, reactive } from 'vue' -import { isWithComponent, isWithComponents } from '@/types/createRouteOptions' +import { isWithComopnentProps, isWithComponentPropsRecord, PropsGetter } from '@/types/createRouteOptions' import { getPrefetchOption, PrefetchConfigs, PrefetchStrategy } from '@/types/prefetch' import { ResolvedRoute } from '@/types/resolved' import { Route } from '@/types/route' @@ -7,11 +7,10 @@ import { CallbackPushResponse, CallbackRejectResponse, CallbackSuccessResponse, import { CallbackContextPushError } from '@/errors/callbackContextPushError' import { CallbackContextRejectionError } from '@/errors/callbackContextRejectionError' import { getPropsValue } from '@/utilities/props' -import { PropsCallbackContext } from '@/types/props' export const propStoreKey: InjectionKey = Symbol() -type ComponentProps = { id: string, name: string, props?: (params: ResolvedRoute, context: PropsCallbackContext) => unknown } +type ComponentProps = { id: string, name: string, props?: PropsGetter } type SetPropsResponse = CallbackSuccessResponse | CallbackPushResponse | CallbackRejectResponse @@ -117,11 +116,7 @@ export function createPropStore(): PropStore { } function getComponentProps(options: Route['matched']): ComponentProps[] { - if (isWithComponents(options)) { - return Object.entries(options.props ?? {}).map(([name, props]) => ({ id: options.id, name, props })) - } - - if (isWithComponent(options)) { + if (isWithComopnentProps(options)) { return [ { id: options.id, @@ -131,6 +126,10 @@ export function createPropStore(): PropStore { ] } + if (isWithComponentPropsRecord(options)) { + return Object.entries(options.props).map(([name, props]) => ({ id: options.id, name, props })) + } + return [] } diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 461731fd..21dfdcce 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -1,7 +1,7 @@ import { Component, markRaw } from 'vue' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' -import { CreateRouteOptions, PropsGetter, RouteProps, ToRoute, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' +import { CreateRouteOptions, PropsGetter, CreateRouteProps, ToRoute, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' import { toHash } from '@/types/hash' import { toName } from '@/types/name' import { toPath } from '@/types/path' @@ -11,7 +11,7 @@ import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' type CreateRouteWithProps< TOptions extends CreateRouteOptions, - TProps extends RouteProps + TProps extends CreateRouteProps > = TProps extends PropsGetter ? Partial> extends ReturnType ? [ props?: TProps ] @@ -22,10 +22,10 @@ type CreateRouteWithProps< export function createRoute< const TOptions extends CreateRouteOptions, - const TProps extends RouteProps + const TProps extends CreateRouteProps >(options: TOptions, ...args: CreateRouteWithProps): ToRoute -export function createRoute(options: CreateRouteOptions, props?: unknown): Route { +export function createRoute(options: CreateRouteOptions, props?: CreateRouteProps): Route { const id = createRouteId() const name = toName(options.name) const path = toPath(options.path) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 61a64d3b..6bf97361 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -53,10 +53,18 @@ export function isWithComponent>(options: T): return 'component' in options && Boolean(options.component) } +export function isWithComopnentProps>(options: T): options is T & { props: PropsGetter } { + return 'props' in options && typeof options.props === 'function' +} + export function isWithComponents>(options: T): options is T & { components: Record } { return 'components' in options && Boolean(options.components) } +export function isWithComponentPropsRecord>(options: T): options is T & { props: RoutePropsRecord } { + return 'props' in options && typeof options.props === 'object' +} + export function isWithState>(options: T): options is T & { state: Record } { return 'state' in options && Boolean(options.state) } @@ -119,8 +127,8 @@ export type CreateRouteOptions< } export type PropsGetter< - TOptions extends CreateRouteOptions, - TComopnent extends Component + TOptions extends CreateRouteOptions = CreateRouteOptions, + TComopnent extends Component = Component > = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> type ComponentPropsAreOptional< @@ -130,13 +138,13 @@ type ComponentPropsAreOptional< : false type RoutePropsRecord< - TOptions extends CreateRouteOptions, - TComponents extends Record + TOptions extends CreateRouteOptions = CreateRouteOptions, + TComponents extends Record = Record > = { [K in keyof TComponents as ComponentPropsAreOptional extends true ? K : never]?: PropsGetter } & { [K in keyof TComponents as ComponentPropsAreOptional extends false ? K : never]: PropsGetter } -export type RouteProps< - TOptions extends CreateRouteOptions +export type CreateRouteProps< + TOptions extends CreateRouteOptions = CreateRouteOptions > = TOptions['component'] extends Component ? PropsGetter : TOptions['components'] extends Record @@ -145,7 +153,7 @@ export type RouteProps< type ToMatch< TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined + TProps extends CreateRouteProps | undefined > = Route< ToName, Host<'', {}>, @@ -158,14 +166,14 @@ type ToMatch< type ToMatches< TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined + TProps extends CreateRouteProps | undefined > = TOptions extends { parent: infer TParent extends Route } ? [...TParent['matches'], ToMatch] : [ToMatch] export type ToRoute< TOptions extends CreateRouteOptions, - TProps extends RouteProps | undefined + TProps extends CreateRouteProps | undefined > = TOptions extends { parent: infer TParent extends Route } ? Route< ToName, From 53e60dca50762704fdd763f7c9f59da54b81a4e9 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 19:40:13 -0600 Subject: [PATCH 10/23] Fix parent and child arguments being backwards --- src/types/createRouteOptions.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 6bf97361..83a3ae38 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -178,11 +178,11 @@ export type ToRoute< ? Route< ToName, Host<'', {}>, - CombinePath, ToPath>, - CombineQuery, ToQuery>, - CombineHash, ToHash>, - CombineMeta, ToMeta>, - CombineState, ToState>, + CombinePath, ToPath>, + CombineQuery, ToQuery>, + CombineHash, ToHash>, + CombineMeta, ToMeta>, + CombineState, ToState>, ToMatches > : Route< From f1365772389d1e42e7031785b8a5653a58d766d0 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Sun, 12 Jan 2025 19:48:41 -0600 Subject: [PATCH 11/23] Fix tests --- src/components/routerLink.browser.spec.ts | 24 +++----- src/components/routerView.browser.spec.ts | 74 +++++++++-------------- 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/src/components/routerLink.browser.spec.ts b/src/components/routerLink.browser.spec.ts index 8322630f..cc95d956 100644 --- a/src/components/routerLink.browser.spec.ts +++ b/src/components/routerLink.browser.spec.ts @@ -560,8 +560,7 @@ describe('prefetch props', () => { name: 'route', path: '/route', component: echo, - props: callback, - }) + }, callback) const router = createRouter([route], { initialUrl: '/', @@ -604,8 +603,7 @@ describe('prefetch props', () => { path: '/route', component: echo, prefetch, - props: callback, - }) + }, callback) const router = createRouter([route], { initialUrl: '/', @@ -646,8 +644,7 @@ describe('prefetch props', () => { name: 'route', path: '/route', component: echo, - props: callback, - }) + }, callback) const router = createRouter([route], { initialUrl: '/', @@ -690,8 +687,7 @@ describe('prefetch props', () => { path: '/echo', component: echo, prefetch: { props: 'eager' }, - props, - }) + }, props) const router = createRouter([home, route], { initialUrl: '/', @@ -735,8 +731,7 @@ describe('prefetch props', () => { path: '/parent', component: echo, prefetch: { props: false }, - props: parentProps, - }) + }, parentProps) const child = createRoute({ parent, @@ -744,8 +739,7 @@ describe('prefetch props', () => { path: '/child', component: echo, prefetch: { props: 'eager' }, - props: childProps, - }) + }, childProps) const router = createRouter([home, child], { initialUrl: '/', @@ -781,8 +775,7 @@ describe('prefetch props', () => { path: '/routeB', component: echo, prefetch: { props: 'lazy' }, - props: callback, - }) + }, callback) const router = createRouter([routeA, routeB], { initialUrl: '/routeA', @@ -889,8 +882,7 @@ describe('prefetch props', () => { path: '/routeB', component: echo, prefetch: { props: 'intent' }, - props: callback, - }) + }, callback) const router = createRouter([routeA, routeB], { initialUrl: '/routeA', diff --git a/src/components/routerView.browser.spec.ts b/src/components/routerView.browser.spec.ts index 96d8967c..157c6c52 100644 --- a/src/components/routerView.browser.spec.ts +++ b/src/components/routerView.browser.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-confusing-void-expression */ +/* eslint-disable @typescript-eslint/only-throw-error */ import { mount, flushPromises } from '@vue/test-utils' import { expect, test } from 'vitest' import { defineAsyncComponent, h } from 'vue' @@ -254,19 +256,15 @@ test('Renders the route component when the router.push does match after a reject }) test('Renders the multiple components when using named route views', async () => { - const parent = createRoute({ - name: 'parent', - path: '/parent/[param]', - }) - const route = createRoute({ - parent, - name: 'child', - path: '/child', - component: echo, - }, ({ params, name }) => ({ value: params.param, extra: name })) - - type T = ReturnType + name: 'foo', + path: '/', + components: { + default: { template: '_default_' }, + one: { template: '_one_' }, + two: { template: '_two_' }, + }, + }) const router = createRouter([route], { initialUrl: '/', @@ -296,17 +294,13 @@ test('Binds props and attrs from route', async () => { name: 'routeA', path: '/routeA/[param]', component: echo, - props: (route) => ({ value: route.params.param }), - }) + }, (route) => ({ value: route.params.param })) const routeB = createRoute({ name: 'routeB', path: '/routeB/[param]', component: echo, - props: (route) => { - return { value: route.params.param } - }, - }) + }, (route) => ({ value: route.params.param })) const router = createRouter([routeA, routeB], { initialUrl: '/', @@ -338,17 +332,13 @@ test('Updates props and attrs when route params change', async () => { name: 'sync', path: '/sync/[param]', component: echo, - props: (route) => ({ value: route.params.param }), - }) + }, (route) => ({ value: route.params.param })) const asyncProps = createRoute({ name: 'async', path: '/async/[param]', component: echo, - props: async (route) => { - return { value: route.params.param } - }, - }) + }, async (route) => ({ value: route.params.param })) const router = createRouter([syncProps, asyncProps], { initialUrl: '/', @@ -392,19 +382,17 @@ test('Props from route can trigger push', async () => { name: 'routeA', path: '/routeA', component: echo, - props: (__, context) => { - throw context.push('routeB') - }, + }, (__, context) => { + throw context.push('routeB') }) const routeB = createRoute({ name: 'routeB', path: '/routeB', component: echo, - props: () => ({ - value: 'routeB', - }), - }) + }, () => ({ + value: 'routeB', + })) const router = createRouter([routeA, routeB], { initialUrl: '/', @@ -473,19 +461,17 @@ test('prefetched props trigger push when navigation is initiated', async () => { path: '/routeB', component: echo, prefetch: { props: true }, - props: (__, { push }) => { - throw push('routeC') - }, + }, (__, { push }) => { + throw push('routeC') }) const routeC = createRoute({ name: 'routeC', path: '/routeC', component: echo, - props: () => ({ - value: 'routeC', - }), - }) + }, () => ({ + value: 'routeC', + })) const router = createRouter([routeA, routeB, routeC], { initialUrl: '/routeA', @@ -524,19 +510,17 @@ test('prefetched async props trigger push when navigation is initiated', async ( path: '/routeB', component, prefetch: { props: true }, - props: async (__, { push }) => { - throw push('routeC') - }, + }, (__, { push }) => { + throw push('routeC') }) const routeC = createRoute({ name: 'routeC', path: '/routeC', component: echo, - props: () => ({ - value: 'routeC', - }), - }) + }, () => ({ + value: 'routeC', + })) const router = createRouter([routeA, routeB, routeC], { initialUrl: '/routeA', From 23b2fd8b415d6fb600631198f29d631ef82a6a7b Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 11:52:48 -0600 Subject: [PATCH 12/23] Make sure match aligns with CreatedRouteOptions --- src/types/createRouteOptions.ts | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 83a3ae38..40943549 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -154,15 +154,7 @@ export type CreateRouteProps< type ToMatch< TOptions extends CreateRouteOptions, TProps extends CreateRouteProps | undefined -> = Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState -> & { props: TProps } +> = Omit & { id: string, props?: TProps } type ToMatches< TOptions extends CreateRouteOptions, From 9d36255248410271a88184d13c67837fb17794bc Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 16:52:31 -0600 Subject: [PATCH 13/23] Fix ToRoute returning an overly specific route when given CreateRouteOptions --- src/services/createRoute.ts | 4 +-- src/types/createRouteOptions.ts | 46 +++++++++++++++++---------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 21dfdcce..6e02653f 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -1,4 +1,4 @@ -import { Component, markRaw } from 'vue' +import { markRaw } from 'vue' import { createRouteId } from '@/services/createRouteId' import { host } from '@/services/host' import { CreateRouteOptions, PropsGetter, CreateRouteProps, ToRoute, combineRoutes, isWithParent, isWithState } from '@/types/createRouteOptions' @@ -12,7 +12,7 @@ import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' type CreateRouteWithProps< TOptions extends CreateRouteOptions, TProps extends CreateRouteProps -> = TProps extends PropsGetter +> = TProps extends PropsGetter ? Partial> extends ReturnType ? [ props?: TProps ] : [ props: TProps ] diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 40943549..37250834 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -154,7 +154,7 @@ export type CreateRouteProps< type ToMatch< TOptions extends CreateRouteOptions, TProps extends CreateRouteProps | undefined -> = Omit & { id: string, props?: TProps } +> = Omit & { id: string, props: TProps } type ToMatches< TOptions extends CreateRouteOptions, @@ -166,27 +166,29 @@ type ToMatches< export type ToRoute< TOptions extends CreateRouteOptions, TProps extends CreateRouteProps | undefined -> = TOptions extends { parent: infer TParent extends Route } - ? Route< - ToName, - Host<'', {}>, - CombinePath, ToPath>, - CombineQuery, ToQuery>, - CombineHash, ToHash>, - CombineMeta, ToMeta>, - CombineState, ToState>, - ToMatches - > - : Route< - ToName, - Host<'', {}>, - ToPath, - ToQuery, - ToHash, - ToMeta, - ToState, - ToMatches - > +> = CreateRouteOptions extends TOptions + ? Route + : TOptions extends { parent: infer TParent extends Route } + ? Route< + ToName, + Host<'', {}>, + CombinePath, ToPath>, + CombineQuery, ToQuery>, + CombineHash, ToHash>, + CombineMeta, ToMeta>, + CombineState, ToState>, + ToMatches + > + : Route< + ToName, + Host<'', {}>, + ToPath, + ToQuery, + ToHash, + ToMeta, + ToState, + ToMatches + > export function combineRoutes(parent: Route, child: Route): Route { return { From 374a2d8bdc586da9edab89d7c5b941acf5d6f881 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 16:55:26 -0600 Subject: [PATCH 14/23] Add todo --- src/types/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/types/route.ts b/src/types/route.ts index cf90aa6b..2abc3f2a 100644 --- a/src/types/route.ts +++ b/src/types/route.ts @@ -19,6 +19,7 @@ export type Routes = readonly Route[] */ export type CreatedRouteOptions = Omit & WithHooks & { id: string, + // todo: this should not be optional props?: unknown, } /** From fcb32050f5c80b01243d90b912d4516aa9059b34 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 17:13:46 -0600 Subject: [PATCH 15/23] Fix broken type test --- src/types/routeWithParams.spec-d.ts | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/types/routeWithParams.spec-d.ts b/src/types/routeWithParams.spec-d.ts index 17e7dc9d..2dae10e0 100644 --- a/src/types/routeWithParams.spec-d.ts +++ b/src/types/routeWithParams.spec-d.ts @@ -1,17 +1,23 @@ import { expectTypeOf, test } from 'vitest' -import { Hash } from '@/types/hash' -import { Host } from '@/types/host' -import { Path } from '@/types/path' -import { Query } from '@/types/query' -import { Route } from '@/types/route' import { RouteGetByKey } from '@/types/routeWithParams' -import { routes } from '@/utilities/testHelpers' -import { RouteMeta } from './register' -import { Param } from './paramTypes' +import { createRoute } from '@/services/createRoute' test('RouteGetByName works as expected', () => { - type Source = RouteGetByKey - type Expect = Route<'parentA', Host<'', {}>, Path<'/parentA/[paramA]', {}>, Query<'', {}>, Hash<''>, RouteMeta, Record, 'parentA'> + const parentA = createRoute({ + name: 'parentA', + path: '/parentA/[paramA]', + }) + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const childA = createRoute({ + parent: parentA, + name: 'parentA.childA', + path: '/childA/[?paramB]', + }) + + type Routes = [typeof parentA, typeof childA] + type Source = RouteGetByKey + type Expect = typeof childA expectTypeOf().toEqualTypeOf() }) From f697f098dea634e599742c8b80e633514b627cd2 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 19:38:04 -0600 Subject: [PATCH 16/23] Add type tests for createRoute --- src/services/createRoute.spec-d.ts | 324 +++++++++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/services/createRoute.spec-d.ts diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts new file mode 100644 index 00000000..2ed735ba --- /dev/null +++ b/src/services/createRoute.spec-d.ts @@ -0,0 +1,324 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { expectTypeOf, test } from 'vitest' +import { createRoute } from './createRoute' +import { Route } from '@/types/route' +import { path } from './path' +import { query } from './query' +import { PrefetchConfig } from '@/types/prefetch' +import { Identity } from '@/types/utilities' +import echo from '@/components/echo' +import { component } from '@/utilities/testHelpers' + +test('empty options returns an empty route', () => { + const route = createRoute({}) + + expectTypeOf().toEqualTypeOf() +}) + +test('options with name', () => { + const route = createRoute({ name: 'foo' }) + type Source = typeof route['name'] + type Expect = 'foo' + + expectTypeOf().toEqualTypeOf() +}) + +test('options with name and parent', () => { + const parent = createRoute({ name: 'parent' }) + const route = createRoute({ + name: 'child', + parent, + }) + + type Source = typeof route['name'] + type Expect = 'child' + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path', () => { + const route = createRoute({ path: '/foo' }) + + type Source = typeof route['path'] + type Expect = { value: '/foo', params: {} } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path and parent', () => { + const parent = createRoute({ path: '/foo' }) + const route = createRoute({ + path: '/bar', + parent, + }) + + type Source = typeof route['path'] + type Expect = { value: '/foo/bar', params: {} } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path with params', () => { + const route = createRoute({ path: '/foo/[bar]' }) + type Source = typeof route['path'] + type Expect = { value: '/foo/[bar]', params: { bar: StringConstructor } } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path with params and parent', () => { + const parent = createRoute({ + path: '/parent/[parentParam]', + }) + + const route = createRoute({ + path: '/child/[childParam]', + parent, + }) + + type Source = typeof route['path'] + type Expect = { + value: '/parent/[parentParam]/child/[childParam]', + params: { + parentParam: StringConstructor, + childParam: StringConstructor, + }, + } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path with params with custom param types', () => { + const route = createRoute({ + path: path('/foo/[bar]', { bar: Number }), + }) + + type Source = typeof route['path'] + type Expect = { + value: '/foo/[bar]', + params: { bar: NumberConstructor }, + } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with path with params with custom param types and parent', () => { + const parent = createRoute({ + path: path('/parent/[parentParam]', { parentParam: Number }), + }) + + const route = createRoute({ + path: path('/child/[childParam]', { childParam: Boolean }), + parent, + }) + + type Source = typeof route['path'] + type Expect = { + value: '/parent/[parentParam]/child/[childParam]', + params: { + parentParam: NumberConstructor, + childParam: BooleanConstructor, + }, + } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query', () => { + const route = createRoute({ + query: 'foo=bar', + }) + + type Source = typeof route['query'] + type Expect = { value: 'foo=bar', params: {} } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query and parent', () => { + const parent = createRoute({ + query: 'parent=parent', + }) + + const route = createRoute({ + query: 'child=child', + parent, + }) + + type Source = typeof route['query'] + type Expect = { value: 'parent=parent&child=child', params: {} } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query with params', () => { + const route = createRoute({ query: 'foo=[bar]' }) + type Source = typeof route['query'] + type Expect = { value: 'foo=[bar]', params: { bar: StringConstructor } } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query with params and parent', () => { + const parent = createRoute({ + query: 'parent=[parentParam]', + }) + + const route = createRoute({ + query: 'child=[childParam]', + parent, + }) + + type Source = typeof route['query'] + type Expect = { + value: 'parent=[parentParam]&child=[childParam]', + params: { + parentParam: StringConstructor, + childParam: StringConstructor, + }, + } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query with params with custom param types', () => { + const route = createRoute({ query: query('foo=[bar]', { bar: Number }) }) + type Source = typeof route['query'] + type Expect = { value: 'foo=[bar]', params: { bar: NumberConstructor } } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with query with params with custom param types and parent', () => { + const parent = createRoute({ + query: query('parent=[parentParam]', { parentParam: Number }), + }) + + const route = createRoute({ + query: query('child=[childParam]', { childParam: Boolean }), + parent, + }) + + type Source = typeof route['query'] + type Expect = { + value: 'parent=[parentParam]&child=[childParam]', + params: { + parentParam: NumberConstructor, + childParam: BooleanConstructor, + }, + } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with hash', () => { + const route = createRoute({ hash: 'foo' }) + + type Source = typeof route['hash'] + type Expect = { value: 'foo' } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with hash and parent', () => { + const parent = createRoute({ hash: 'parent' }) + const route = createRoute({ hash: 'child', parent }) + + type Source = typeof route['hash'] + type Expect = { value: 'parentchild' } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with meta', () => { + const route = createRoute({ meta: { foo: 'bar' } }) + type Source = typeof route['meta'] + type Expect = Readonly<{ foo: 'bar' }> + + expectTypeOf().toEqualTypeOf() +}) + +test('options with meta and parent', () => { + const parent = createRoute({ meta: { parent: 'parent' } }) + const route = createRoute({ parent, meta: { child: 'child' } }) + + type Source = Identity + type Expect = Readonly<{ parent: 'parent', child: 'child' }> + + expectTypeOf().toEqualTypeOf() +}) + +test('options with state', () => { + const route = createRoute({ state: { foo: String } }) + type Source = typeof route['state'] + type Expect = Readonly<{ foo: StringConstructor }> + + expectTypeOf().toEqualTypeOf() +}) + +test('options with state and parent', () => { + const parent = createRoute({ state: { parent: String } }) + const route = createRoute({ parent, state: { child: String } }) + + type Source = Identity + type Expect = Readonly<{ parent: StringConstructor, child: StringConstructor }> + + expectTypeOf().toEqualTypeOf() +}) + +test('options with component and optional props without second argument', () => { + const route = createRoute({ + component, + }) + + type Source = typeof route['matched']['props'] + type Expect = undefined + + expectTypeOf().toEqualTypeOf() +}) + +test('options with component and optional props with second argument', () => { + const route = createRoute({ + component, + }, () => ({ foo: 'bar' })) + + type Source = typeof route['matched']['props'] + type Expect = () => { foo: string } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with component and required props missing second argument', () => { + // @ts-expect-error should require second argument + const route = createRoute({ + component: echo, + }) + + type Source = typeof route['matched']['props'] + type Expect = undefined + + expectTypeOf().toEqualTypeOf() +}) + +test('options with component and required props with second argument ', () => { + const route = createRoute({ + component: echo, + }, () => ({ value: 'bar', extra: true })) + + type Source = typeof route['matched']['props'] + type Expect = () => { value: string, extra: boolean } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with component and required props with second argument with incorrect type', () => { + const route = createRoute({ + component: echo, + // @ts-expect-error should not accept incorrect type + }, () => ({ value: true, foo: 'bar' })) + + type Source = typeof route['matched']['props'] + type Expect = () => { value: boolean, foo: string } + + expectTypeOf().toEqualTypeOf() +}) From aa045cbe403921419de71b6095081407171876ae Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 19:41:09 -0600 Subject: [PATCH 17/23] Remove unused type --- src/services/createRoute.spec-d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts index 2ed735ba..a4df8610 100644 --- a/src/services/createRoute.spec-d.ts +++ b/src/services/createRoute.spec-d.ts @@ -4,7 +4,6 @@ import { createRoute } from './createRoute' import { Route } from '@/types/route' import { path } from './path' import { query } from './query' -import { PrefetchConfig } from '@/types/prefetch' import { Identity } from '@/types/utilities' import echo from '@/components/echo' import { component } from '@/utilities/testHelpers' From 8002742a3fcb49af0dc7eeba0c05c32d400a4d95 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 20:20:15 -0600 Subject: [PATCH 18/23] Add components tests --- src/services/createRoute.spec-d.ts | 75 ++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts index a4df8610..334767c6 100644 --- a/src/services/createRoute.spec-d.ts +++ b/src/services/createRoute.spec-d.ts @@ -321,3 +321,78 @@ test('options with component and required props with second argument with incorr expectTypeOf().toEqualTypeOf() }) + +test('options with components and optional props without second argument', () => { + const route = createRoute({ + components: { + component, + }, + }) + + type Source = typeof route['matched']['props'] + type Expect = undefined + + expectTypeOf().toEqualTypeOf() +}) + +test('options with components and optional props with second argument', () => { + const route = createRoute({ + components: { + default: component, + }, + }, { + default: () => ({ foo: 'bar' }), + }) + + type Source = typeof route['matched']['props'] + type Expect = { default: () => { foo: string } } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with components and required props missing second argument', () => { + // @ts-expect-error should require second argument + const route = createRoute({ + components: { + default: echo, + }, + }) + + type Source = typeof route['matched']['props'] + type Expect = undefined + + expectTypeOf().toEqualTypeOf() +}) + +test('options with components and required props with second argument ', () => { + const route = createRoute({ + components: { + default: echo, + }, + }, { + default: () => ({ value: 'bar', extra: true }), + }) + + type Source = typeof route['matched']['props'] + type Expect = { default: () => { value: string, extra: boolean } } + + expectTypeOf().toEqualTypeOf() +}) + +test('options with components and required props with second argument with incorrect type', () => { + const route = createRoute({ + components: { + default: echo, + }, + }, { + // @ts-expect-error should not accept incorrect type + default: () => ({ value: true, foo: 'bar' }), + }) + + type Source = typeof route['matched']['props'] + type Expect = { + default: () => { value: boolean, foo: string }, + } + + expectTypeOf().toEqualTypeOf() +}) From 927867a68d1d927aa556709a7a027989012599aa Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Mon, 13 Jan 2025 20:22:04 -0600 Subject: [PATCH 19/23] Fix missing undefined types for props --- src/services/createRoute.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 6e02653f..71da936e 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -11,18 +11,18 @@ import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' type CreateRouteWithProps< TOptions extends CreateRouteOptions, - TProps extends CreateRouteProps -> = TProps extends PropsGetter - ? Partial> extends ReturnType + TProps extends CreateRouteProps | undefined +> = CreateRouteProps extends PropsGetter + ? Partial>> extends ReturnType> ? [ props?: TProps ] : [ props: TProps ] - : Partial extends TProps + : Partial> extends CreateRouteProps ? [ props?: TProps ] : [ props: TProps ] export function createRoute< const TOptions extends CreateRouteOptions, - const TProps extends CreateRouteProps + const TProps extends CreateRouteProps | undefined = undefined >(options: TOptions, ...args: CreateRouteWithProps): ToRoute export function createRoute(options: CreateRouteOptions, props?: CreateRouteProps): Route { From 15f32a5640491c0551bf897ead1dba7c2332f0ff Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 14 Jan 2025 19:24:22 -0600 Subject: [PATCH 20/23] shout out to spell checker plugin --- src/services/createPropStore.ts | 4 ++-- src/types/createRouteOptions.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/services/createPropStore.ts b/src/services/createPropStore.ts index 9d8fce6b..015c34c2 100644 --- a/src/services/createPropStore.ts +++ b/src/services/createPropStore.ts @@ -1,5 +1,5 @@ import { InjectionKey, reactive } from 'vue' -import { isWithComopnentProps, isWithComponentPropsRecord, PropsGetter } from '@/types/createRouteOptions' +import { isWithComponentProps, isWithComponentPropsRecord, PropsGetter } from '@/types/createRouteOptions' import { getPrefetchOption, PrefetchConfigs, PrefetchStrategy } from '@/types/prefetch' import { ResolvedRoute } from '@/types/resolved' import { Route } from '@/types/route' @@ -116,7 +116,7 @@ export function createPropStore(): PropStore { } function getComponentProps(options: Route['matched']): ComponentProps[] { - if (isWithComopnentProps(options)) { + if (isWithComponentProps(options)) { return [ { id: options.id, diff --git a/src/types/createRouteOptions.ts b/src/types/createRouteOptions.ts index 37250834..4bcf85b5 100644 --- a/src/types/createRouteOptions.ts +++ b/src/types/createRouteOptions.ts @@ -53,7 +53,7 @@ export function isWithComponent>(options: T): return 'component' in options && Boolean(options.component) } -export function isWithComopnentProps>(options: T): options is T & { props: PropsGetter } { +export function isWithComponentProps>(options: T): options is T & { props: PropsGetter } { return 'props' in options && typeof options.props === 'function' } @@ -128,8 +128,8 @@ export type CreateRouteOptions< export type PropsGetter< TOptions extends CreateRouteOptions = CreateRouteOptions, - TComopnent extends Component = Component -> = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> + TComponent extends Component = Component +> = (route: ResolvedRoute>, context: PropsCallbackContext) => MaybePromise> type ComponentPropsAreOptional< TComponent extends Component From 5f6a74cb7cdbff70d54ccd6bb20053e3eafdb070 Mon Sep 17 00:00:00 2001 From: Evan Sutherland Date: Tue, 14 Jan 2025 19:30:16 -0600 Subject: [PATCH 21/23] made undefined not a valid 2nd argument --- src/services/createRoute.spec-d.ts | 52 ++++++++++++++++++------------ src/services/createRoute.ts | 4 +-- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts index 334767c6..562217e2 100644 --- a/src/services/createRoute.spec-d.ts +++ b/src/services/createRoute.spec-d.ts @@ -310,17 +310,17 @@ test('options with component and required props with second argument ', () => { expectTypeOf().toEqualTypeOf() }) -test('options with component and required props with second argument with incorrect type', () => { - const route = createRoute({ - component: echo, - // @ts-expect-error should not accept incorrect type - }, () => ({ value: true, foo: 'bar' })) +// test('options with component and required props with second argument with incorrect type', () => { +// const route = createRoute({ +// component: echo, +// // @ts-expect-error should not accept incorrect type +// }, () => ({ value: true, foo: 'bar' })) - type Source = typeof route['matched']['props'] - type Expect = () => { value: boolean, foo: string } +// type Source = typeof route['matched']['props'] +// type Expect = () => { value: boolean, foo: string } - expectTypeOf().toEqualTypeOf() -}) +// expectTypeOf().toEqualTypeOf() +// }) test('options with components and optional props without second argument', () => { const route = createRoute({ @@ -379,20 +379,32 @@ test('options with components and required props with second argument ', () => { expectTypeOf().toEqualTypeOf() }) -test('options with components and required props with second argument with incorrect type', () => { +// test('options with components and required props with second argument with incorrect type', () => { +// const route = createRoute({ +// components: { +// default: echo, +// }, +// }, { +// // @ts-expect-error should not accept incorrect type +// default: () => ({ value: true, foo: 'bar' }), +// }) + +// type Source = typeof route['matched']['props'] +// type Expect = { +// default: () => { value: boolean, foo: string }, +// } + +// expectTypeOf().toEqualTypeOf() +// }) + +test('undefined is not a valid value for 2nd argument of createRoute', () => { const route = createRoute({ - components: { - default: echo, - }, - }, { - // @ts-expect-error should not accept incorrect type - default: () => ({ value: true, foo: 'bar' }), - }) + component: echo, + // @ts-expect-error should accept undefined + }, undefined) type Source = typeof route['matched']['props'] - type Expect = { - default: () => { value: boolean, foo: string }, - } + type Expect = undefined expectTypeOf().toEqualTypeOf() }) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 71da936e..87406f04 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -22,8 +22,8 @@ type CreateRouteWithProps< export function createRoute< const TOptions extends CreateRouteOptions, - const TProps extends CreateRouteProps | undefined = undefined ->(options: TOptions, ...args: CreateRouteWithProps): ToRoute + const TProps extends CreateRouteProps +>(options: TOptions, ...args: CreateRouteWithProps): ToRoute extends TProps ? undefined : TProps> export function createRoute(options: CreateRouteOptions, props?: CreateRouteProps): Route { const id = createRouteId() From 0f41ce95c3e943fc69c7728d17466a5210bbe916 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Tue, 14 Jan 2025 19:47:40 -0600 Subject: [PATCH 22/23] Restore commented out tests and fix assertions --- src/services/createRoute.spec-d.ts | 48 ++++++++++++++---------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/services/createRoute.spec-d.ts b/src/services/createRoute.spec-d.ts index 562217e2..6276e57c 100644 --- a/src/services/createRoute.spec-d.ts +++ b/src/services/createRoute.spec-d.ts @@ -310,17 +310,17 @@ test('options with component and required props with second argument ', () => { expectTypeOf().toEqualTypeOf() }) -// test('options with component and required props with second argument with incorrect type', () => { -// const route = createRoute({ -// component: echo, -// // @ts-expect-error should not accept incorrect type -// }, () => ({ value: true, foo: 'bar' })) +test('options with component and required props with second argument with incorrect type', () => { + const route = createRoute({ + component: echo, + // @ts-expect-error should not accept incorrect type + }, () => ({ value: true, foo: 'bar' })) -// type Source = typeof route['matched']['props'] -// type Expect = () => { value: boolean, foo: string } + type Source = typeof route['matched']['props'] + type Expect = undefined -// expectTypeOf().toEqualTypeOf() -// }) + expectTypeOf().toEqualTypeOf() +}) test('options with components and optional props without second argument', () => { const route = createRoute({ @@ -379,28 +379,26 @@ test('options with components and required props with second argument ', () => { expectTypeOf().toEqualTypeOf() }) -// test('options with components and required props with second argument with incorrect type', () => { -// const route = createRoute({ -// components: { -// default: echo, -// }, -// }, { -// // @ts-expect-error should not accept incorrect type -// default: () => ({ value: true, foo: 'bar' }), -// }) +test('options with components and required props with second argument with incorrect type', () => { + const route = createRoute({ + components: { + default: echo, + }, + }, { + // @ts-expect-error should not accept incorrect type + default: () => ({ value: true, foo: 'bar' }), + }) -// type Source = typeof route['matched']['props'] -// type Expect = { -// default: () => { value: boolean, foo: string }, -// } + type Source = typeof route['matched']['props'] + type Expect = undefined -// expectTypeOf().toEqualTypeOf() -// }) + expectTypeOf().toEqualTypeOf() +}) test('undefined is not a valid value for 2nd argument of createRoute', () => { const route = createRoute({ component: echo, - // @ts-expect-error should accept undefined + // @ts-expect-error should not accept undefined }, undefined) type Source = typeof route['matched']['props'] From 2b3a711f64661cb3d227889ea8c5c027555c6137 Mon Sep 17 00:00:00 2001 From: Craig Harshbarger Date: Tue, 14 Jan 2025 19:47:59 -0600 Subject: [PATCH 23/23] Remove now unnecessary `| undefined` from `CreateRouteWithProps` --- src/services/createRoute.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/createRoute.ts b/src/services/createRoute.ts index 87406f04..76a7321b 100644 --- a/src/services/createRoute.ts +++ b/src/services/createRoute.ts @@ -11,7 +11,7 @@ import { checkDuplicateParams } from '@/utilities/checkDuplicateKeys' type CreateRouteWithProps< TOptions extends CreateRouteOptions, - TProps extends CreateRouteProps | undefined + TProps extends CreateRouteProps > = CreateRouteProps extends PropsGetter ? Partial>> extends ReturnType> ? [ props?: TProps ]