]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: add replace and refactor types to work
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 4 May 2022 16:55:11 +0000 (18:55 +0200)
committerEduardo San Martin Morote <posva@users.noreply.github.com>
Thu, 30 Jun 2022 07:59:00 +0000 (09:59 +0200)
14 files changed:
__tests__/errors.spec.ts
__tests__/matcher/resolve.spec.ts
__tests__/mount.ts
__tests__/utils.ts
src/index.ts
src/location.ts
src/matcher/pathParserRanker.ts
src/router.ts
src/types/index.ts
src/types/named.ts
src/types/paths.ts
src/utils/index.ts
test-dts/index.d.ts
test-dts/namedRoutes.test-d.ts

index 3f652795f2237430d895a1d1f9710022de5380f9..8fd0e046ba9e77444fb11b8460a7de35fc6dc972 100644 (file)
@@ -16,7 +16,7 @@ import {
   RouteLocationNormalized,
 } from '../src/types'
 
-const routes: RouteRecordRaw[] = [
+const routes: Readonly<RouteRecordRaw>[] = [
   { path: '/', component: components.Home },
   { path: '/redirect', redirect: '/' },
   { path: '/foo', component: components.Foo, name: 'Foo' },
index 8bff76ae07c2c89efab36881800743e29f2ba3fb..45362fc8064c13adf91703f37bc5e09ae49c5c8f 100644 (file)
@@ -42,9 +42,11 @@ describe('RouterMatcher.resolve', () => {
       throw new Error('not handled')
     } else {
       // use one single record
+      // @ts-expect-error: One way or the other it failse
       if (!resolved.matched) resolved.matched = record.map(normalizeRouteRecord)
       // allow passing an expect.any(Array)
       else if (Array.isArray(resolved.matched))
+        // @ts-expect-error: same as above
         resolved.matched = resolved.matched.map(m => ({
           ...normalizeRouteRecord(m as any),
           aliasOf: m.aliasOf,
@@ -58,6 +60,7 @@ describe('RouterMatcher.resolve', () => {
     const startCopy: MatcherLocation = {
       ...start,
       matched: start.matched.map(m => ({
+        // @ts-expect-error: okay...
         ...normalizeRouteRecord(m),
         aliasOf: m.aliasOf,
       })) as MatcherLocation['matched'],
index d5a4574488e875c45b1eebf147ff87f5cedacc39..0fbbce14db96fd7d55839942f0f52c51fe59b75a 100644 (file)
@@ -4,17 +4,24 @@ import {
   routeLocationKey,
   routerViewLocationKey,
 } from '../src/injectionSymbols'
+import { RouteLocationNormalized } from 'src'
 
-export function createMockedRoute(initialValue: RouteLocationNormalizedLoose) {
+export function createMockedRoute(
+  initialValue: RouteLocationNormalizedLoose | RouteLocationNormalized
+) {
   const route = {} as {
     [k in keyof RouteLocationNormalizedLoose]: ComputedRef<
       RouteLocationNormalizedLoose[k]
     >
   }
 
-  const routeRef = shallowRef(initialValue)
+  const routeRef = shallowRef<
+    RouteLocationNormalized | RouteLocationNormalizedLoose
+  >(initialValue)
 
-  function set(newRoute: RouteLocationNormalizedLoose) {
+  function set(
+    newRoute: RouteLocationNormalizedLoose | RouteLocationNormalized
+  ) {
     routeRef.value = newRoute
     return nextTick()
   }
index 7878291a51fb597076259341480a74e377eed43e..ee9dd582810e4ad840fce30ff290480bde60feba 100644 (file)
@@ -17,6 +17,7 @@ import {
   createRouter,
   Router,
   RouterView,
+  RouteRecordNormalized,
 } from '../src'
 
 export const tick = (time?: number) =>
@@ -57,7 +58,7 @@ export interface RouteRecordViewLoose
   instances: Record<string, any>
   enterCallbacks: Record<string, Function[]>
   props: Record<string, _RouteRecordProps>
-  aliasOf: RouteRecordViewLoose | undefined
+  aliasOf: RouteRecordNormalized | RouteRecordViewLoose | undefined
   children?: RouteRecordRaw[]
   components: Record<string, RouteComponent> | null | undefined
 }
index d1eb2d1333b98d719f627ff51c0706aefb5a6155..a972d3268bacef5cea7d1d7ba1b52a62972d8fdb 100644 (file)
@@ -69,6 +69,7 @@ export type {
   _RemoveUntilClosingPar,
 } from './types/paths'
 export type { RouteNamedMap } from './types/named'
+export type { Config, RouterTyped } from './typedRouter'
 
 export { createRouter } from './router'
 export type { Router, RouterOptions, RouterScrollBehavior } from './router'
index 70cfbdc84e0d33eba6b52ee6b746274b071edc83..78efa330211e0f294ad2b911bcbabdc5d72a0e76 100644 (file)
@@ -165,8 +165,8 @@ export function isSameRouteLocationParams(
 }
 
 function isSameRouteLocationParamsValue(
-  a: RouteParamValue | RouteParamValue[],
-  b: RouteParamValue | RouteParamValue[]
+  a: RouteParamValue | readonly RouteParamValue[],
+  b: RouteParamValue | readonly RouteParamValue[]
 ): boolean {
   return Array.isArray(a)
     ? isEquivalentArray(a, b)
index f225fa7a1be513236e7b62d6198163d0e5824b83..57dd088473e23ab6c9a81f8613fb3683afba9f99 100644 (file)
@@ -1,7 +1,7 @@
 import { Token, TokenType } from './pathTokenizer'
 import { assign } from '../utils'
 
-export type PathParams = Record<string, string | string[]>
+export type PathParams = Record<string, string | readonly string[]>
 
 /**
  * A param in a url like `/users/:id`
@@ -241,13 +241,16 @@ export function tokensToParser(
           path += token.value
         } else if (token.type === TokenType.Param) {
           const { value, repeatable, optional } = token
-          const param: string | string[] = value in params ? params[value] : ''
+          const param: string | readonly string[] =
+            value in params ? params[value] : ''
 
           if (Array.isArray(param) && !repeatable)
             throw new Error(
               `Provided param "${value}" is an array but it is not repeatable (* or + modifiers)`
             )
-          const text: string = Array.isArray(param) ? param.join('/') : param
+          const text: string = Array.isArray(param)
+            ? (param as string[]).join('/')
+            : (param as string)
           if (!text) {
             if (optional) {
               // if we have more than one optional param like /:a?-static and there are more segments, we don't need to
index d05f3c0715a474ae594c8b7f29c9a5cae00d1f85..faed396c1dc93b51c124aef556c500c44e93eb26 100644 (file)
@@ -13,6 +13,7 @@ import {
   RouteLocationOptions,
   MatcherLocationRaw,
   RouteParams,
+  RouteLocationNamedRaw,
 } from './types'
 import { RouterHistory, HistoryState, NavigationType } from './history/common'
 import {
@@ -68,6 +69,7 @@ import {
   routerViewLocationKey,
 } from './injectionSymbols'
 import { addDevtools } from './devtools'
+import { RouteNamedMap, RouteNamedMapGeneric } from './types/named'
 
 /**
  * Internal type to define an ErrorHandler
@@ -236,7 +238,7 @@ export interface Router<Options extends RouterOptions = RouterOptions> {
    * Returns the {@link RouteLocation normalized version} of a
    * {@link RouteLocationRaw route location}. Also includes an `href` property
    * that includes any existing `base`. By default the `currentLocation` used is
-   * `route.currentRoute` and should only be overriden in advanced use cases.
+   * `route.currentRoute` and should only be overridden in advanced use cases.
    *
    * @param to - Raw route location to resolve
    * @param currentLocation - Optional current location to resolve against
@@ -252,7 +254,14 @@ export interface Router<Options extends RouterOptions = RouterOptions> {
    *
    * @param to - Route location to navigate to
    */
-  push(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
+  push<
+    RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
+    Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
+  >(
+    to: RouteNamedMapGeneric extends RouteMap
+      ? RouteLocationRaw
+      : RouteLocationNamedRaw<RouteMap, Name>
+  ): Promise<NavigationFailure | void | undefined>
 
   /**
    * Programmatically navigate to a new URL by replacing the current entry in
@@ -260,7 +269,15 @@ export interface Router<Options extends RouterOptions = RouterOptions> {
    *
    * @param to - Route location to navigate to
    */
-  replace(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>
+  replace<
+    RouteMap extends RouteNamedMapGeneric = RouteNamedMap<Options['routes']>,
+    Name extends keyof RouteMap = keyof RouteNamedMap<Options['routes']>
+  >(
+    to: RouteNamedMapGeneric extends RouteMap
+      ? RouteLocationRaw
+      : RouteLocationNamedRaw<RouteMap, Name>
+  ): Promise<NavigationFailure | void | undefined>
+
   /**
    * Go back in history if possible by calling `history.back()`. Equivalent to
    * `router.go(-1)`.
@@ -585,11 +602,11 @@ export function createRouter<Options extends RouterOptions>(
     }
   }
 
-  function push(to: RouteLocationRaw | RouteLocation) {
+  function push(to: RouteLocationRaw) {
     return pushWithRedirect(to)
   }
 
-  function replace(to: RouteLocationRaw | RouteLocationNormalized) {
+  function replace(to: RouteLocationRaw) {
     return push(assign(locationAsObject(to), { replace: true }))
   }
 
@@ -1168,7 +1185,9 @@ export function createRouter<Options extends RouterOptions>(
     resolve,
     options,
 
+    // @ts-expect-error: FIXME: can't type this one correctly without too much hussle
     push,
+    // @ts-expect-error: same
     replace,
     go,
     back: () => go(-1),
index 3cfc9698567fd6d43dc68164d392c4ea4b2d7a4c..fc5d7b13590b476d6904fbe7000994e9ec9cee55 100644 (file)
@@ -4,7 +4,7 @@ import { Ref, ComponentPublicInstance, Component, DefineComponent } from 'vue'
 import { RouteRecord, RouteRecordNormalized } from '../matcher/types'
 import { HistoryState } from '../history/common'
 import { NavigationFailure } from '../errors'
-import { NamedLocationMap } from './named'
+import { RouteNamedMapGeneric } from './named'
 
 export { NamedLocationMap, ExtractNamedRoutes, ExtractRoutes } from './named'
 
@@ -35,10 +35,13 @@ export type RouteParamValue = string
  * @internal
  */
 export type RouteParamValueRaw = RouteParamValue | number | null | undefined
-export type RouteParams = Record<string, RouteParamValue | RouteParamValue[]>
+export type RouteParams = Record<
+  string,
+  RouteParamValue | readonly RouteParamValue[]
+>
 export type RouteParamsRaw = Record<
   string,
-  RouteParamValueRaw | Exclude<RouteParamValueRaw, null | undefined>[]
+  RouteParamValueRaw | readonly Exclude<RouteParamValueRaw, null | undefined>[]
 >
 
 /**
@@ -68,14 +71,22 @@ export interface LocationAsName {
  * @internal
  */
 export interface LocationAsRelativeRaw<
-  T extends keyof NamedLocationMap = keyof NamedLocationMap
+  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+  Name extends keyof RouteMap = keyof RouteMap
 > {
-  name?: {} extends NamedLocationMap ? RouteRecordName : T
-  params?: {} extends NamedLocationMap ? RouteParamsRaw : NamedLocationMap[T]
+  name?: RouteNamedMapGeneric extends RouteMap ? RouteRecordName : Name
+  params?: RouteNamedMapGeneric extends RouteMap
+    ? RouteParamsRaw
+    : RouteMap[Name]['paramsRaw']
 }
 
-export interface LocationAsRelative {
-  params?: RouteParams
+export interface LocationAsRelative<
+  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+  Name extends keyof RouteMap = keyof RouteMap
+> {
+  params?: RouteNamedMapGeneric extends RouteMap
+    ? RouteParams
+    : RouteMap[Name]['params']
 }
 
 export interface RouteLocationOptions {
@@ -100,8 +111,18 @@ export interface RouteLocationOptions {
  */
 export type RouteLocationRaw =
   | string
-  | (RouteQueryAndHash & LocationAsPath & RouteLocationOptions)
-  | (RouteQueryAndHash & LocationAsRelativeRaw & RouteLocationOptions)
+  | RouteLocationPathRaw
+  | RouteLocationNamedRaw
+
+export type RouteLocationNamedRaw<
+  RouteMap extends RouteNamedMapGeneric = RouteNamedMapGeneric,
+  Name extends keyof RouteMap = keyof RouteMap
+> = RouteQueryAndHash &
+  LocationAsRelativeRaw<RouteMap, Name> &
+  RouteLocationOptions
+
+export type RouteLocationPathRaw =
+  | RouteQueryAndHash & LocationAsPath & RouteLocationOptions
 
 export interface RouteLocationMatched extends RouteRecordNormalized {
   // components cannot be Lazy<RouteComponent>
@@ -199,15 +220,14 @@ export type _RouteRecordProps =
  * Common properties among all kind of {@link RouteRecordRaw}
  * @internal
  */
-export interface _RouteRecordBase<Path extends string = string>
-  extends PathParserOptions {
+export interface _RouteRecordBase extends PathParserOptions {
   /**
    * Path of the record. Should start with `/` unless the record is the child of
    * another record.
    *
    * @example `/users/:id` matches `/users/1` as well as `/users/posva`.
    */
-  path: Path
+  path: string
 
   /**
    * Where to redirect if the route is directly matched. The redirection happens
@@ -270,8 +290,7 @@ export type RouteRecordRedirectOption =
 /**
  * Route Record defining one single component with the `component` option.
  */
-export interface RouteRecordSingleView<Path extends string = string>
-  extends _RouteRecordBase<Path> {
+export interface RouteRecordSingleView extends _RouteRecordBase {
   /**
    * Component to display when the URL matches this route.
    */
@@ -286,8 +305,7 @@ export interface RouteRecordSingleView<Path extends string = string>
 /**
  * Route Record defining one single component with a nested view.
  */
-export interface RouteRecordSingleViewWithChildren<Path extends string = string>
-  extends _RouteRecordBase<Path> {
+export interface RouteRecordSingleViewWithChildren extends _RouteRecordBase {
   /**
    * Component to display when the URL matches this route.
    */
@@ -308,8 +326,7 @@ export interface RouteRecordSingleViewWithChildren<Path extends string = string>
 /**
  * Route Record defining multiple named components with the `components` option.
  */
-export interface RouteRecordMultipleViews<Path extends string = string>
-  extends _RouteRecordBase<Path> {
+export interface RouteRecordMultipleViews extends _RouteRecordBase {
   /**
    * Components to display when the URL matches this route. Allow using named views.
    */
@@ -327,16 +344,14 @@ export interface RouteRecordMultipleViews<Path extends string = string>
 /**
  * Route Record defining multiple named components with the `components` option and children.
  */
-export interface RouteRecordMultipleViewsWithChildren<
-  Path extends string = string
-> extends _RouteRecordBase<Path> {
+export interface RouteRecordMultipleViewsWithChildren extends _RouteRecordBase {
   /**
    * Components to display when the URL matches this route. Allow using named views.
    */
   components?: Record<string, RawRouteComponent> | null | undefined
   component?: never
 
-  children: RouteRecordRaw[]
+  children: Readonly<RouteRecordRaw>[]
 
   /**
    * Allow passing down params as props to the component rendered by
@@ -350,19 +365,18 @@ export interface RouteRecordMultipleViewsWithChildren<
  * Route Record that defines a redirect. Cannot have `component` or `components`
  * as it is never rendered.
  */
-export interface RouteRecordRedirect<Path extends string = string>
-  extends _RouteRecordBase<Path> {
+export interface RouteRecordRedirect extends _RouteRecordBase {
   redirect: RouteRecordRedirectOption
   component?: never
   components?: never
 }
 
-export type RouteRecordRaw<Path extends string = string> =
-  | RouteRecordSingleView<Path>
-  | RouteRecordSingleViewWithChildren<Path>
-  | RouteRecordMultipleViews<Path>
-  | RouteRecordMultipleViewsWithChildren<Path>
-  | RouteRecordRedirect<Path>
+export type RouteRecordRaw =
+  | RouteRecordSingleView
+  | RouteRecordSingleViewWithChildren
+  | RouteRecordMultipleViews
+  | RouteRecordMultipleViewsWithChildren
+  | RouteRecordRedirect
 
 /**
  * Initial route location where the router is. Can be used in navigation guards
index ce3cf33c1bf2a2a0385e423d9e7c1b432c61681c..9b981a31d5df679e249c5dcc61452fdb1311f261 100644 (file)
@@ -1,6 +1,6 @@
-import type { RouteRecordRaw } from '.'
+import type { RouteParams, RouteParamsRaw, RouteRecordRaw } from '.'
 import type { Router } from '../router'
-import { JoinPath, ParamsFromPath } from './paths'
+import type { JoinPath, ParamsFromPath, ParamsRawFromPath } from './paths'
 
 /**
  * This will flat the routes into an object with `key` === `router.name`
@@ -57,13 +57,20 @@ export type RouteNamedMap<
         children?: infer Children
       }
         ? Path extends string
-          ? (Name extends string
+          ? (Name extends string | symbol
               ? {
-                  [N in Name]: ParamsFromPath<JoinPath<Prefix, Path>>
+                  [N in Name]: {
+                    // name: N
+                    params: ParamsFromPath<JoinPath<Prefix, Path>>
+                    // TODO: ParamsRawFromPath
+                    paramsRaw: ParamsRawFromPath<JoinPath<Prefix, Path>>
+                    path: JoinPath<Prefix, Path>
+                  }
                 }
               : {
                   // NO_NAME: 1
                 }) &
+              // Recurse children
               (Children extends Readonly<RouteRecordRaw[]>
                 ? RouteNamedMap<Children, JoinPath<Prefix, Path>>
                 : {
@@ -73,8 +80,18 @@ export type RouteNamedMap<
         : {
             // EMPTY: 1
           }) &
-        RouteNamedMap<Rest>
+        RouteNamedMap<Rest, Prefix>
     : never // R must be a valid route record
   : {
       // END: 1
     }
+
+export type RouteNamedMapGeneric = Record<
+  string | symbol | number,
+  // TODO: use RouteParams, RouteParamRaw
+  {
+    params: RouteParams
+    paramsRaw: RouteParamsRaw
+    path: string
+  }
+>
index 4d32cfd414f4a6f31b20960e21ccd9fb61b3b778..5da7e49d3606d9638bf36e090a305eb467efe12b 100644 (file)
@@ -1,3 +1,5 @@
+import { RouteParams, RouteParamsRaw, RouteParamValueRaw } from '.'
+
 /**
  * Extract an object of params given a path like `/users/:id`.
  *
@@ -7,16 +9,12 @@
  * ```
  */
 export type ParamsFromPath<P extends string = string> = string extends P
-  ? PathParams // Generic version
-  : _ExtractParamsPath<_RemoveRegexpFromParam<P>>
+  ? RouteParams // Generic version
+  : _ExtractParamsPath<_RemoveRegexpFromParam<P>, false>
 
-/**
- * Generic possible params from a path (after parsing).
- */
-export type PathParams = Record<
-  string,
-  string | readonly string[] | undefined | null
->
+export type ParamsRawFromPath<P extends string = string> = string extends P
+  ? RouteParamsRaw // Generic version
+  : _ExtractParamsPath<_RemoveRegexpFromParam<P>, true>
 
 /**
  * Possible param modifiers.
@@ -56,17 +54,19 @@ type _ParamDelimiter =
  *
  * @internal
  */
-export type _ExtractParamsPath<P extends string> =
-  P extends `${string}{${infer PP}}${infer Rest}`
-    ? (PP extends `${infer N}${_ParamModifier}`
-        ? PP extends `${N}${infer M}`
-          ? M extends _ParamModifier
-            ? _ParamToObject<N, M>
-            : never
+export type _ExtractParamsPath<
+  P extends string,
+  isRaw extends boolean
+> = P extends `${string}{${infer PP}}${infer Rest}`
+  ? (PP extends `${infer N}${_ParamModifier}`
+      ? PP extends `${N}${infer M}`
+        ? M extends _ParamModifier
+          ? _ParamToObject<N, M, isRaw>
           : never
-        : _ParamToObject<PP, ''>) &
-        _ExtractParamsPath<Rest>
-    : {}
+        : never
+      : _ParamToObject<PP, '', isRaw>) &
+      _ExtractParamsPath<Rest, isRaw>
+  : {}
 
 /**
  * Gets the possible type of a param based on its modifier M.
@@ -74,17 +74,34 @@ export type _ExtractParamsPath<P extends string> =
  * @internal
  */
 export type _ModifierParamValue<
-  M extends _ParamModifier | '' = _ParamModifier | ''
+  M extends _ParamModifier | '' = _ParamModifier | '',
+  isRaw extends boolean = false
 > = '' extends M
-  ? string
+  ? _ParamValue<isRaw>
   : '+' extends M
-  ? readonly [string, ...string[]]
+  ? _ParamValueOneOrMore<isRaw>
   : '*' extends M
-  ? readonly string[] | undefined | null
+  ? _ParamValueZeroOrMore<isRaw>
   : '?' extends M
-  ? string | undefined | null
+  ? _ParamValueZeroOrOne<isRaw>
   : never
 
+export type _ParamValueOneOrMore<isRaw extends boolean> = true extends isRaw
+  ? readonly [string | number, ...(string | number)[]]
+  : readonly [string, ...string[]]
+
+export type _ParamValueZeroOrMore<isRaw extends boolean> = true extends isRaw
+  ? readonly (string | number)[] | undefined | null
+  : readonly string[] | undefined | null
+
+export type _ParamValueZeroOrOne<isRaw extends boolean> = true extends isRaw
+  ? RouteParamValueRaw
+  : string
+
+export type _ParamValue<isRaw extends boolean> = true extends isRaw
+  ? string | number
+  : string
+
 /**
  * Given a param name N and its modifier M, creates a param object for the pair.
  *
@@ -92,13 +109,14 @@ export type _ModifierParamValue<
  */
 export type _ParamToObject<
   N extends string,
-  M extends _ParamModifier | ''
+  M extends _ParamModifier | '',
+  isRaw extends boolean
 > = M extends '?' | '*'
   ? {
-      [K in N]?: _ModifierParamValue<M>
+      [K in N]?: _ModifierParamValue<M, isRaw>
     }
   : {
-      [K in N]: _ModifierParamValue<M>
+      [K in N]: _ModifierParamValue<M, isRaw>
     }
 
 /**
index a961bc17a3818d6b03e137589836fe0824b4bdb8..1e5576af72804cb05ddb82b96a6807615f6682f9 100644 (file)
@@ -1,4 +1,9 @@
-import { RouteParams, RouteComponent, RouteParamsRaw } from '../types'
+import {
+  RouteParams,
+  RouteComponent,
+  RouteParamsRaw,
+  RouteParamValueRaw,
+} from '../types'
 
 export * from './env'
 
@@ -16,7 +21,9 @@ export function applyToParams(
 
   for (const key in params) {
     const value = params[key]
-    newParams[key] = Array.isArray(value) ? value.map(fn) : fn(value)
+    newParams[key] = Array.isArray(value)
+      ? value.map(fn)
+      : fn(value as Exclude<RouteParamValueRaw, any[]>)
   }
 
   return newParams
index 6c37c4863900f08565a4d26a957c786af172003f..573a9fdb572c45f6f37f5f5445cc508b624178ea 100644 (file)
@@ -1,4 +1,5 @@
-export * from '../dist/vue-router'
+// export * from '../dist/vue-router'
+export * from '../src'
 
 export function describe(_name: string, _fn: () => void): void
 export function expectType<T>(value: T): void
index ce187e7ea2acc03819b878a86a93e33f1d2c19b1..9b0d5953df39ac12c28634a2cf7f0f15861715d8 100644 (file)
@@ -1,70 +1,26 @@
 import {
-  ExtractNamedRoutes,
-  Router,
-  ExtractRoutes,
   createRouter,
   createWebHistory,
   RouteRecordRaw,
   expectType,
   RouteNamedMap,
+  RouterTyped,
+  Router,
+  RouteLocationRaw,
 } from './index'
 import { DefineComponent } from 'vue'
 import { JoinPath } from 'src/types/paths'
 
-declare const Comp: DefineComponent
 declare const component: DefineComponent
 declare const components: { default: DefineComponent }
 
-const routes = [
-  {
-    path: 'my-path',
-    name: 'test',
-    component: Comp,
-  },
-  {
-    path: 'my-path',
-    name: 'my-other-path',
-    component: Comp,
-  },
-  {
-    path: 'random',
-    name: 'tt',
-    children: [
-      {
-        path: 'random-child',
-        name: 'random-child',
-        component: Comp,
-      },
-    ],
-  },
-  {
-    name: '1',
-    children: [
-      {
-        name: '2',
-        children: [
-          {
-            name: '3',
-            children: [{ name: '4' }, { path: '', children: [{ name: '5' }] }],
-          },
-        ],
-      },
-    ],
-  },
-] as const
-
-export function defineRoutes<
-  Path extends string,
-  Routes extends Readonly<RouteRecordRaw<Path>[]>
->(routes: Routes): Routes {
-  return routes
-}
+const routeName = Symbol()
 
 const r2 = createRouter({
   history: createWebHistory(),
   routes: [
     { path: '/users/:id', name: 'UserDetails', component },
-    { path: '/no-name', /* no name */ component },
+    { path: '/no-name', /* no name */ components },
     {
       path: '/nested',
       name: 'nested',
@@ -79,11 +35,59 @@ const r2 = createRouter({
             },
           ],
         },
+        { path: ':opt?', name: 'optional', component },
+        // still skipped
+        { path: 'other', name: routeName, component },
       ],
     },
   ] as const,
 })
 
+const methods = ['push', 'replace'] as const
+for (const method of methods) {
+  r2.push({ name: 'UserDetails' })
+  r2.replace({ name: 'UserDetails' })
+
+  // accepts missing params because of relative locations is valid
+  r2[method]({ name: 'UserDetails' })
+  // @ts-expect-error: but expects a correct id
+  r2[method]({ name: 'UserDetails', params: {} })
+  // @ts-expect-error: no invalid params
+  r2[method]({ name: 'UserDetails', params: { id: '2', nope: 'oops' } })
+  // other options are valid
+  r2[method]({ name: 'UserDetails', query: { valid: 'true' }, replace: true })
+  r2[method]({ name: 'UserDetails', params: { id: '2' } })
+  // accepts numbers
+  r2[method]({ name: 'UserDetails', params: { id: 2 } })
+  // @ts-expect-error: fails an null
+  r2[method]({ name: 'UserDetails', params: { id: null } })
+  // @ts-expect-error: and undefined
+  r2[method]({ name: 'UserDetails', params: { id: undefined } })
+  // nested params work too
+  r2[method]({ name: 'nested-c', params: { a: '2', c: '3' } })
+  r2[method]({ name: 'optional' })
+  // optional params are more flexible
+  r2[method]({ name: 'optional', params: {} })
+  r2[method]({ name: 'optional', params: { opt: 'hey' } })
+  r2[method]({ name: 'optional', params: { opt: null } })
+  r2[method]({ name: 'optional', params: { opt: undefined } })
+  // works with symbols too
+  r2[method]({ name: routeName })
+  // @ts-expect-error: but not other symbols
+  r2[method]({ name: Symbol() })
+  // any path is still valid
+  r2[method]('/path')
+  // relative push can have any of the params
+  r2[method]({ params: { a: 2 } })
+  r2[method]({ params: {} })
+  r2[method]({ params: { opt: 'hey' } })
+}
+
+r2.push({} as unknown as RouteLocationRaw)
+r2.replace({} as unknown as RouteLocationRaw)
+
+// createMap(r2.options.routes, true).
+
 function joinPath<A extends string, B extends string>(
   prefix: A,
   path: B
@@ -102,70 +106,48 @@ expectType<'/nested/:a'>(joinPath('/nested/', ':a'))
 expectType<'/:a'>(joinPath('/nested', '/:a'))
 
 expectType<{
-  UserDetails: { id: string }
-  nested: {}
-  'nested-a': { a: string }
-  'nested-c': { a: string; c: string }
+  UserDetails: { params: { id: string }; path: '/users/:id' }
+  nested: { params: {}; path: '/nested' }
+  'nested-a': { params: { a: string }; path: '/nested/:a' }
+  'nested-c': { params: { a: string; c: string }; path: '/nested/:a/b/:c' }
 }>(createMap(r2.options.routes))
 
 expectType<{
-  UserDetails: { nope: string }
+  UserDetails: { params: { nope: string } }
   // @ts-expect-error
 }>(createMap(r2.options.routes))
 expectType<{
-  'nested-c': { a: string; d: string }
+  UserDetails: { path: '/users' }
   // @ts-expect-error
 }>(createMap(r2.options.routes))
 expectType<{
-  nope: {}
+  'nested-c': { path: '/' }
+  // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+  'nested-c': { params: { a: string; d: string } }
+  // @ts-expect-error
+}>(createMap(r2.options.routes))
+expectType<{
+  nope: { params: {} }
   // @ts-expect-error
 }>(createMap(r2.options.routes))
 
-declare const typed: ExtractNamedRoutes<typeof routes>
-
-typed['my-other-path']
-typed['random-child']
-typed.test
-typed.tt
-typed[1]
-typed[2]
-typed[3]
-typed[4]
-typed[5]
-//@ts-expect-error
-typed['non-existing']
-
-declare module './index' {
-  interface NamedLocationMap {
-    'my-other-path': {
-      id: string
-    }
+declare module '../src' {
+  // declare module '../dist/vue-router' {
+  export interface Config {
+    Router: typeof r2
   }
 }
 
-declare const router: Router
-
-router.push({
-  name: 'my-other-path',
-  params: {
-    id: '222',
-    // @ts-expect-error does not exist
-    nonExistent: '22',
-  },
-})
-
-router.push({
-  // @ts-expect-error location name does not exist
-  name: 'random-location',
-})
-
-const otherRouter = createRouter({
-  history: {} as any,
-  routes: [{ path: 'e', name: 'test', component: Comp }] as const,
-})
-
-declare const otherRoutes: ExtractRoutes<typeof otherRouter>
+function getTypedRouter(): RouterTyped {
+  return {} as any
+}
 
-otherRoutes.test
+const typedRouter = getTypedRouter()
+// this one is true if we comment out the line with Router: typeof r2
+// expectType<Router>(typedRouter)
+expectType<typeof r2>(typedRouter)
+typedRouter.push({ name: 'UserDetails' })
 // @ts-expect-error
-otherRoutes.test2
+typedRouter.push({ name: 'nope' })