RouteRecordMultipleViewsWithChildren,
RouteRecordRedirect,
RouteMeta,
+ _RouteMetaBase,
RouteComponent,
// RawRouteComponent,
RouteParamsGeneric,
/**
* Internal type for common properties among all kind of {@link RouteRecordRaw}.
*/
-export interface _RouteRecordBase extends PathParserOptions {
+export interface _RouteRecordBase
+ extends PathParserOptions,
+ _RouteRecordBaseMeta {
/**
* Path of the record. Should start with `/` unless the record is the child of
* another record.
/**
* Arbitrary data attached to the record.
*/
- meta?: RouteMeta
+ // meta?: RouteMeta
/**
* Array of nested routes.
props?: _RouteRecordProps | Record<string, _RouteRecordProps>
}
+/**
+ * Default type for RouteMeta when not augmented.
+ * @internal
+ */
+export type _RouteMetaBase = Record<string | number | symbol, unknown>
+
/**
* Interface to type `meta` fields in route records.
*
* }
* ```
*/
-export interface RouteMeta extends Record<string | number | symbol, unknown> {}
+export interface RouteMeta extends _RouteMetaBase {}
+
+/**
+ * Returns `true` if the passed `RouteMeta` type hasn't been augmented. Return `false` otherwise.
+ * @internal
+ */
+export type IsRouteMetaBase<RM> = _RouteMetaBase extends RM ? true : false
+/**
+ * Returns `true` if the passed `RouteMeta` type has been augmented with required fields. Return `false` otherwise.
+ * @internal
+ */
+export type IsRouteMetaRequired<RM> = Partial<RM> extends RM ? false : true
+
+export type _RouteRecordBaseMeta = IsRouteMetaRequired<RouteMeta> extends true
+ ? {
+ /**
+ * Arbitrary data attached to the record. Required because the `RouteMeta` type has been augmented with required
+ * fields.
+ */
+ meta: RouteMeta
+ }
+ : {
+ /**
+ * Arbitrary data attached to the record.
+ */
+ meta?: RouteMeta
+ }
/**
* Route Record defining one single component with the `component` option.
createRouter,
createMemoryHistory,
} from './index'
-import { expectTypeOf } from 'vitest'
+import { it, describe, expectTypeOf } from 'vitest'
-let router = createRouter({
- history: createMemoryHistory(),
- routes: [],
-})
+describe('Components', () => {
+ let router = createRouter({
+ history: createMemoryHistory(),
+ routes: [],
+ })
-// RouterLink
-// @ts-expect-error missing to
-expectError(<RouterLink />)
-// @ts-expect-error: invalid prop
-expectError(<RouterLink to="/" custom="text" />)
-// @ts-expect-error: invalid prop
-expectError(<RouterLink to="/" replace="text" />)
-expectTypeOf<JSX.Element>(<RouterLink to="/foo" replace />)
-expectTypeOf<JSX.Element>(<RouterLink to="/foo" />)
-expectTypeOf<JSX.Element>(<RouterLink class="link" to="/foo" />)
-expectTypeOf<JSX.Element>(<RouterLink to={{ path: '/foo' }} />)
-expectTypeOf<JSX.Element>(<RouterLink to={{ path: '/foo' }} custom />)
+ // TODO: split into multiple tests
+ it('works', () => {
+ // RouterLink
+ // @ts-expect-error missing to
+ expectError(<RouterLink />)
+ // @ts-expect-error: invalid prop
+ expectError(<RouterLink to="/" custom="text" />)
+ // @ts-expect-error: invalid prop
+ expectError(<RouterLink to="/" replace="text" />)
+ expectTypeOf<JSX.Element>(<RouterLink to="/foo" replace />)
+ expectTypeOf<JSX.Element>(<RouterLink to="/foo" />)
+ expectTypeOf<JSX.Element>(<RouterLink class="link" to="/foo" />)
+ expectTypeOf<JSX.Element>(<RouterLink to={{ path: '/foo' }} />)
+ expectTypeOf<JSX.Element>(<RouterLink to={{ path: '/foo' }} custom />)
-// RouterView
-expectTypeOf<JSX.Element>(<RouterView class="view" />)
-expectTypeOf<JSX.Element>(<RouterView name="foo" />)
-expectTypeOf<JSX.Element>(<RouterView route={router.currentRoute.value} />)
+ // RouterView
+ expectTypeOf<JSX.Element>(<RouterView class="view" />)
+ expectTypeOf<JSX.Element>(<RouterView name="foo" />)
+ expectTypeOf<JSX.Element>(<RouterView route={router.currentRoute.value} />)
+ })
+})
-import { expectTypeOf } from 'vitest'
-import { Router, RouteLocationNormalizedLoaded } from './index'
+import { describe, expectTypeOf, it } from 'vitest'
+import {
+ useRouter,
+ useRoute,
+ // rename types for better error messages, otherwise they have the same name
+ // RouteLocationNormalizedLoadedTyped as I_RLNLT
+} from './index'
import { defineComponent } from 'vue'
-defineComponent({
- methods: {
- doStuff() {
- expectTypeOf<Router>(this.$router)
- expectTypeOf<RouteLocationNormalizedLoaded>(this.$route)
- },
- },
+describe('Instance types', () => {
+ it('creates a $route instance property', () => {
+ defineComponent({
+ methods: {
+ doStuff() {
+ // TODO: can't do a proper check because of typed routes
+ expectTypeOf(this.$route.params).toMatchTypeOf(useRoute().params)
+ },
+ },
+ })
+ })
+
+ it('creates $router instance properties', () => {
+ defineComponent({
+ methods: {
+ doStuff() {
+ // TODO: can't do a proper check because of typed routes
+ expectTypeOf(this.$router.back).toEqualTypeOf(useRouter().back)
+ },
+ },
+ })
+ })
})
const component = defineComponent({})
-declare module './index' {
+declare module '.' {
interface RouteMeta {
requiresAuth?: boolean
- nested: { foo: string }
+ // TODO: it would be nice to be able to test required meta without polluting all tests
+ nested?: { foo: string }
}
}
},
},
},
- {
- path: '/foo',
- component,
- // @ts-expect-error
- meta: {},
- },
],
})
+
+ router.addRoute({
+ path: '/foo',
+ component,
+ meta: {
+ nested: {
+ foo: 'foo',
+ },
+ },
+ })
})
it('route location in guards', () => {
routes: [],
})
router.beforeEach(to => {
- expectTypeOf<{ requiresAuth?: Boolean; nested: { foo: string } }>(to.meta)
+ expectTypeOf<{ requiresAuth?: Boolean; nested?: { foo: string } }>(
+ to.meta
+ )
expectTypeOf<unknown>(to.meta.lol)
- if (to.meta.nested.foo == 'foo' || to.meta.lol) return false
+ if (to.meta.nested?.foo == 'foo' || to.meta.lol) return false
+ return
})
})
})
-import { expectTypeOf } from 'vitest'
+import { expectTypeOf, describe, it } from 'vitest'
import {
createRouter,
createWebHistory,
routes: [],
})
-router.beforeEach((to, from) => {
- return { path: '/' }
-})
-
-router.beforeEach((to, from) => {
- return '/'
-})
-
-router.beforeEach((to, from) => {
- return false
-})
-
-router.beforeEach((to, from, next) => {
- next(undefined)
-})
-
-// @ts-expect-error
-router.beforeEach((to, from, next) => {
- return Symbol('not supported')
-})
-// @ts-expect-error
-router.beforeEach(() => {
- return Symbol('not supported')
-})
-
-router.beforeEach((to, from, next) => {
- // @ts-expect-error
- next(Symbol('not supported'))
-})
-
-router.afterEach((to, from, failure) => {
- expectTypeOf<NavigationFailure | undefined | void>(failure)
- if (isNavigationFailure(failure)) {
- expectTypeOf<RouteLocationNormalized>(failure.from)
- expectTypeOf<RouteLocationRaw>(failure.to)
- }
- if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
- expectTypeOf<RouteLocationNormalized>(failure.from)
- expectTypeOf<RouteLocationRaw>(failure.to)
- }
+describe('Navigation guards', () => {
+ // TODO: split into multiple tests
+ it('works', () => {
+ router.beforeEach((to, from) => {
+ return { path: '/' }
+ })
+
+ router.beforeEach((to, from) => {
+ return '/'
+ })
+
+ router.beforeEach((to, from) => {
+ return false
+ })
+
+ router.beforeEach((to, from, next) => {
+ next(undefined)
+ })
+
+ // @ts-expect-error
+ router.beforeEach((to, from, next) => {
+ return Symbol('not supported')
+ })
+ // @ts-expect-error
+ router.beforeEach(() => {
+ return Symbol('not supported')
+ })
+
+ router.beforeEach((to, from, next) => {
+ // @ts-expect-error
+ next(Symbol('not supported'))
+ })
+
+ router.afterEach((to, from, failure) => {
+ expectTypeOf<NavigationFailure | undefined | void>(failure)
+ if (isNavigationFailure(failure)) {
+ expectTypeOf<RouteLocationNormalized>(failure.from)
+ expectTypeOf<RouteLocationRaw>(failure.to)
+ }
+ if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
+ expectTypeOf<RouteLocationNormalized>(failure.from)
+ expectTypeOf<RouteLocationRaw>(failure.to)
+ }
+ })
+ })
})
-import { RouteRecordRaw } from './index'
+import { describe, it } from 'vitest'
+import type { RouteLocationNormalized, RouteRecordRaw } from './index'
import { defineComponent } from 'vue'
const component = defineComponent({})
const routes: RouteRecordRaw[] = []
-routes.push({ path: '/', redirect: '/foo' })
+describe('RouteRecords', () => {
+ // TODO: split into multiple tests
+ it('works', () => {
+ routes.push({ path: '/', redirect: '/foo' })
-// @ts-expect-error cannot have components and component at the same time
-routes.push({ path: '/', components, component })
+ // @ts-expect-error cannot have components and component at the same time
+ routes.push({ path: '/', components, component })
-// a redirect record with children to point to a child
-routes.push({
- path: '/',
- redirect: '/foo',
- children: [
- {
- path: 'foo',
+ // a redirect record with children to point to a child
+ routes.push({
+ path: '/',
+ redirect: '/foo',
+ children: [
+ {
+ path: 'foo',
+ component,
+ },
+ ],
+ })
+
+ // same but with a nested route
+ routes.push({
+ path: '/',
component,
- },
- ],
-})
+ redirect: '/foo',
+ children: [
+ {
+ path: 'foo',
+ component,
+ },
+ ],
+ })
-// same but with a nested route
-routes.push({
- path: '/',
- component,
- redirect: '/foo',
- children: [
- {
- path: 'foo',
+ routes.push({ path: '/a/b', component, props: true })
+ routes.push({
+ path: '/a/b',
component,
- },
- ],
-})
+ props: (to: RouteLocationNormalized<'/[id]+'>) => to.params.id,
+ })
+ // @ts-expect-error: props should be an object
+ routes.push({ path: '/a/b', components, props: to => to.params.id })
+ routes.push({
+ path: '/a/b',
+ components,
+ props: {
+ default: (to: RouteLocationNormalized<'/[id]+'>) => to.params.id,
+ },
+ })
+ routes.push({ path: '/', components, props: true })
-routes.push({ path: '/', component, props: true })
-routes.push({ path: '/', component, props: to => to.params.id })
-// @ts-expect-error: props should be an object
-routes.push({ path: '/', components, props: to => to.params.id })
-routes.push({ path: '/', components, props: { default: to => to.params.id } })
-routes.push({ path: '/', components, props: true })
-
-// let r: RouteRecordRaw = {
-// path: '/',
-// component,
-// components,
-// }
-
-export function filterNestedChildren(children: RouteRecordRaw[]) {
- return children.filter(r => {
- if (r.redirect) {
- r.children?.map(() => {})
- }
- if (r.children) {
- r.children = filterNestedChildren(r.children)
+ // let r: RouteRecordRaw = {
+ // path: '/',
+ // component,
+ // components,
+ // }
+
+ function filterNestedChildren(children: RouteRecordRaw[]) {
+ return children.filter(r => {
+ if (r.redirect) {
+ r.children?.map(() => {})
+ }
+ if (r.children) {
+ r.children = filterNestedChildren(r.children)
+ }
+ })
}
+ filterNestedChildren(routes)
})
-}
+})
// type is needed instead of an interface
// https://github.com/microsoft/TypeScript/issues/15300
-type RouteMap = {
+export type RouteMap = {
'/[...path]': RouteRecordInfo<
'/[...path]',
'/:path(.*)',
"include": [
"src/global.d.ts",
"src/**/*.ts",
- "__tests__/**/*.ts"
+ "__tests__/**/*.ts",
+ "test-dts/**/*.ts"
],
"compilerOptions": {
"baseUrl": ".",
"noEmit": true,
"target": "esnext",
"module": "esnext",
- "moduleResolution": "node",
+ "moduleResolution": "Bundler",
"allowJs": false,
"noUnusedLocals": true,
"strictNullChecks": true,
checker: 'vue-tsc',
// only: true,
// by default it includes all specs too
- include: ['**/*.test-d.ts'],
+ // include: ['**/*.test-d.ts'],
// tsconfig: './tsconfig.typecheck.json',
},