- run: pnpm install
- run: pnpm run lint
- run: pnpm run -r test:types
- - run: pnpm run -r test:unit
- run: pnpm run -r build
- run: pnpm run -r build:dts
- - run: pnpm run -r test:dts
+ - run: pnpm run -r test:unit
# e2e tests that that run locally
- run: pnpm run -r test:e2e:ci
--- /dev/null
+import { describe, it } from 'vitest'
+import { createRouter, createWebHistory } from '../src'
+import { defineComponent, h } from 'vue'
+
+describe('createRouter', () => {
+ const component = defineComponent({})
+
+ const WithProps = defineComponent({
+ props: {
+ id: {
+ type: String,
+ required: true,
+ },
+ },
+ })
+
+ const Foo = defineComponent({
+ props: {
+ test: String,
+ },
+ setup() {
+ return {
+ title: 'homepage',
+ }
+ },
+ render() {
+ return h('div', `${this.title}: ${this.test}`)
+ },
+ })
+
+ it('works', () => {
+ createRouter({
+ history: createWebHistory(),
+ routes: [
+ { path: '/', component },
+ { path: '/foo', component: Foo },
+ { path: '/', component: WithProps },
+ ],
+ parseQuery: search => ({}),
+ stringifyQuery: query => '',
+ strict: true,
+ end: true,
+ sensitive: true,
+ scrollBehavior(to, from, savedPosition) {},
+ })
+ })
+})
--- /dev/null
+import { describe, it, expectTypeOf } from 'vitest'
+import type {
+ RouteRecordName,
+ ParamValue,
+ ParamValueZeroOrMore,
+ RouteRecordInfo,
+ RouteLocationNormalizedTypedList,
+} from '../src'
+
+// TODO: could we move this to an .d.ts file that is only loaded for tests?
+// https://github.com/microsoft/TypeScript/issues/15300
+type RouteNamedMap = {
+ home: RouteRecordInfo<'/', '/', Record<never, never>, Record<never, never>>
+ '/[other]': RouteRecordInfo<
+ '/[other]',
+ '/:other',
+ { other: ParamValue<true> },
+ { other: ParamValue<false> }
+ >
+ '/[name]': RouteRecordInfo<
+ '/[name]',
+ '/:name',
+ { name: ParamValue<true> },
+ { name: ParamValue<false> }
+ >
+ '/[...path]': RouteRecordInfo<
+ '/[...path]',
+ '/:path(.*)',
+ { path: ParamValue<true> },
+ { path: ParamValue<false> }
+ >
+ '/deep/nesting/works/[[files]]+': RouteRecordInfo<
+ '/deep/nesting/works/[[files]]+',
+ '/deep/nesting/works/:files*',
+ { files?: ParamValueZeroOrMore<true> },
+ { files?: ParamValueZeroOrMore<false> }
+ >
+}
+
+describe('Route Location types', () => {
+ it('RouteLocationNormalized', () => {
+ function withRoute(
+ fn: (
+ to: RouteLocationNormalizedTypedList<RouteNamedMap>[keyof RouteNamedMap]
+ ) => void
+ ): void
+ function withRoute<Name extends keyof RouteNamedMap>(
+ name: Name,
+ fn: (to: RouteLocationNormalizedTypedList<RouteNamedMap>[Name]) => void
+ ): void
+ function withRoute<Name extends RouteRecordName>(...args: unknown[]) {}
+
+ withRoute('/[name]', to => {
+ expectTypeOf(to.params).toEqualTypeOf<{ name: string }>()
+ expectTypeOf(to.params).not.toEqualTypeOf<{ notExisting: string }>()
+ expectTypeOf(to.params).not.toEqualTypeOf<{ other: string }>()
+ })
+
+ withRoute('/[name]' as keyof RouteNamedMap, to => {
+ // @ts-expect-error: no all params have this
+ to.params.name
+ if (to.name === '/[name]') {
+ to.params.name
+ // @ts-expect-error: no param other
+ to.params.other
+ }
+ })
+
+ withRoute(to => {
+ // @ts-expect-error: not all params object have a name
+ to.params.name
+ // @ts-expect-error: no route named like that
+ if (to.name === '') {
+ }
+ if (to.name === '/[name]') {
+ expectTypeOf(to.params).toEqualTypeOf<{ name: string }>()
+ // @ts-expect-error: no param other
+ to.params.other
+ }
+ })
+ })
+})
"build:size": "pnpm run build && rollup -c size-checks/rollup.config.mjs",
"dev:e2e": "vite --config e2e/vite.config.mjs",
"test:types": "tsc --build tsconfig.json",
- "test:dts": "tsc -p ./test-dts/tsconfig.json",
- "test:unit": "vitest --coverage",
- "test": "pnpm run test:types && pnpm run test:unit && pnpm run build && pnpm run build:dts && pnpm run test:e2e",
+ "test:unit": "vitest --coverage run",
+ "test": "pnpm run test:types && pnpm run build && pnpm run build:dts && pnpm run test:unit && pnpm run test:e2e",
"test:e2e": "pnpm run test:e2e:headless",
"test:e2e:headless": "node e2e/runner.mjs --env chrome-headless",
"test:e2e:native": "node e2e/runner.mjs --env chrome",
RouterView,
createRouter,
createMemoryHistory,
- expectError,
- expectType,
} from './index'
+import { expectTypeOf } from 'vitest'
let router = createRouter({
history: createMemoryHistory(),
expectError(<RouterLink to="/" custom="text" />)
// @ts-expect-error: invalid prop
expectError(<RouterLink to="/" replace="text" />)
-expectType<JSX.Element>(<RouterLink to="/foo" replace />)
-expectType<JSX.Element>(<RouterLink to="/foo" />)
-expectType<JSX.Element>(<RouterLink class="link" to="/foo" />)
-expectType<JSX.Element>(<RouterLink to={{ path: '/foo' }} />)
-expectType<JSX.Element>(<RouterLink to={{ path: '/foo' }} custom />)
+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
-expectType<JSX.Element>(<RouterView class="view" />)
-expectType<JSX.Element>(<RouterView name="foo" />)
-expectType<JSX.Element>(<RouterView route={router.currentRoute.value} />)
+expectTypeOf<JSX.Element>(<RouterView class="view" />)
+expectTypeOf<JSX.Element>(<RouterView name="foo" />)
+expectTypeOf<JSX.Element>(<RouterView route={router.currentRoute.value} />)
+++ /dev/null
-import {
- createRouter,
- createWebHistory,
- NavigationGuard,
- NavigationGuardNext,
- RouteLocationNormalized,
-} from './index'
-import { createApp, defineComponent, h } from 'vue'
-
-const component = defineComponent({})
-
-const WithProps = defineComponent({
- props: {
- id: {
- type: String,
- required: true,
- },
- },
-})
-
-const Foo = defineComponent({
- props: {
- test: String,
- },
- setup() {
- return {
- title: 'homepage',
- }
- },
- render() {
- return h('div', `${this.title}: ${this.test}`)
- },
-})
-
-const router = createRouter({
- history: createWebHistory(),
- routes: [
- { path: '/', component },
- { path: '/foo', component: Foo },
- { path: '/', component: WithProps },
- ],
- parseQuery: search => ({}),
- stringifyQuery: query => '',
- strict: true,
- end: true,
- sensitive: true,
- scrollBehavior(to, from, savedPosition) {},
-})
-
-export const loggedInGuard: NavigationGuard = (to, from, next) => next('/')
-function beforeGuardFn(
- to: RouteLocationNormalized,
- from: RouteLocationNormalized,
- next: NavigationGuardNext
-) {}
-
-router.beforeEach(loggedInGuard)
-router.beforeEach(beforeGuardFn)
-
-const app = createApp({})
-app.use(router)
export * from '../dist/vue-router'
// export * from '../src'
-
-export function describe(_name: string, _fn: () => void): void
-export function expectType<T>(value: T): void
-export function expectError<T>(value: T): void
-export function expectAssignable<T, T2 extends T = T>(value: T2): void
-import { Router, RouteLocationNormalizedLoaded, expectType } from './index'
+import { expectTypeOf } from 'vitest'
+import { Router, RouteLocationNormalizedLoaded } from './index'
import { defineComponent } from 'vue'
defineComponent({
methods: {
doStuff() {
- expectType<Router>(this.$router)
- expectType<RouteLocationNormalizedLoaded>(this.$route)
+ expectTypeOf<Router>(this.$router)
+ expectTypeOf<RouteLocationNormalizedLoaded>(this.$route)
},
},
})
-import { createRouter, createWebHistory, expectType } from './index'
-import { createApp, defineComponent } from 'vue'
+import { createRouter, createWebHistory } from './index'
+import { defineComponent } from 'vue'
+import { describe, it, expectTypeOf } from 'vitest'
const component = defineComponent({})
}
}
-const router = createRouter({
- history: createWebHistory(),
- routes: [
- {
- path: '/',
- component,
- meta: {
- requiresAuth: true,
- lol: true,
- nested: {
- foo: 'bar',
+describe('RouteMeta', () => {
+ it('route creation', () => {
+ const router = createRouter({
+ history: createWebHistory(),
+ routes: [
+ {
+ path: '/',
+ component,
+ meta: {
+ requiresAuth: true,
+ lol: true,
+ nested: {
+ foo: 'bar',
+ },
+ },
},
- },
- },
- {
- path: '/foo',
- component,
- // @ts-expect-error
- meta: {},
- },
- ],
-})
+ {
+ path: '/foo',
+ component,
+ // @ts-expect-error
+ meta: {},
+ },
+ ],
+ })
+ })
-router.beforeEach(to => {
- expectType<{ requiresAuth?: Boolean; nested: { foo: string } }>(to.meta)
- expectType<unknown>(to.meta.lol)
- if (to.meta.nested.foo == 'foo' || to.meta.lol) return false
+ it('route location in guards', () => {
+ const router = createRouter({
+ history: createWebHistory(),
+ routes: [],
+ })
+ router.beforeEach(to => {
+ expectTypeOf<{ requiresAuth?: Boolean; nested: { foo: string } }>(to.meta)
+ expectTypeOf<unknown>(to.meta.lol)
+ if (to.meta.nested.foo == 'foo' || to.meta.lol) return false
+ })
+ })
})
-
-const app = createApp({})
-app.use(router)
+import { expectTypeOf } from 'vitest'
import {
createRouter,
createWebHistory,
- expectType,
isNavigationFailure,
NavigationFailure,
NavigationFailureType,
})
router.afterEach((to, from, failure) => {
- expectType<NavigationFailure | undefined | void>(failure)
+ expectTypeOf<NavigationFailure | undefined | void>(failure)
if (isNavigationFailure(failure)) {
- expectType<RouteLocationNormalized>(failure.from)
- expectType<RouteLocationRaw>(failure.to)
+ expectTypeOf<RouteLocationNormalized>(failure.from)
+ expectTypeOf<RouteLocationRaw>(failure.to)
}
if (isNavigationFailure(failure, NavigationFailureType.cancelled)) {
- expectType<RouteLocationNormalized>(failure.from)
- expectType<RouteLocationRaw>(failure.to)
+ expectTypeOf<RouteLocationNormalized>(failure.from)
+ expectTypeOf<RouteLocationRaw>(failure.to)
}
})
"noEmit": true,
"declaration": true,
"paths": {
- "vue-router": ["../dist"]
+ "vue-router": [
+ "../dist"
+ ]
},
"noImplicitReturns": false
},
- "include": ["./"],
- "exclude": ["../__tests__", "../src"]
+ "include": [
+ "./",
+ "components.test-d.tsx",
+ "../__tests__/createRouter.test-d.ts"
+ ],
+ "exclude": [
+ "../__tests__",
+ "../src"
+ ]
}
--- /dev/null
+import { describe, it, expectTypeOf } from 'vitest'
+import {
+ type RouteRecordInfo,
+ type ParamValue,
+ type ParamValueOneOrMore,
+ type RouteLocationTyped,
+ createRouter,
+ createWebHistory,
+} from './index'
+
+// type is needed instead of an interface
+// https://github.com/microsoft/TypeScript/issues/15300
+type RouteMap = {
+ '/[...path]': RouteRecordInfo<
+ '/[...path]',
+ '/:path(.*)',
+ { path: ParamValue<true> },
+ { path: ParamValue<false> }
+ >
+ '/[a]': RouteRecordInfo<
+ '/[a]',
+ '/:a',
+ { a: ParamValue<true> },
+ { a: ParamValue<false> }
+ >
+ '/a': RouteRecordInfo<'/a', '/a', Record<never, never>, Record<never, never>>
+ '/[id]+': RouteRecordInfo<
+ '/[id]+',
+ '/:id+',
+ { id: ParamValueOneOrMore<true> },
+ { id: ParamValueOneOrMore<false> }
+ >
+}
+
+declare module './index' {
+ interface TypesConfig {
+ RouteNamedMap: RouteMap
+ }
+}
+
+describe('RouterTyped', () => {
+ const router = createRouter({
+ history: createWebHistory(),
+ routes: [],
+ })
+
+ it('resolve', () => {
+ expectTypeOf<Record<never, never>>(router.resolve({ name: '/a' }).params)
+ expectTypeOf<{ a: ParamValue<true> }>(
+ router.resolve({ name: '/[a]' }).params
+ )
+
+ expectTypeOf<RouteLocationTyped<RouteMap, '/a'>>(
+ router.resolve({ name: '/a' })
+ )
+ expectTypeOf<'/a'>(
+ // @ts-expect-error: cannot infer based on path
+ router.resolve({ path: '/a' }).name
+ )
+ expectTypeOf<keyof RouteMap>(router.resolve({ path: '/a' }).name)
+ })
+
+ it('resolve', () => {
+ router.push({ name: '/a', params: { a: 2 } })
+ // @ts-expect-error
+ router.push({ name: '/[a]', params: {} })
+ // still allow relative params
+ router.push({ name: '/[a]' })
+ // @ts-expect-error
+ router.push({ name: '/[a]', params: { a: [2] } })
+ router.push({ name: '/[id]+', params: { id: [2] } })
+ router.push({ name: '/[id]+', params: { id: [2, '3'] } })
+ // @ts-expect-error
+ router.push({ name: '/[id]+', params: { id: 2 } })
+ })
+
+ it('beforeEach', () => {
+ router.beforeEach((to, from) => {
+ // @ts-expect-error: no route named this way
+ if (to.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ // @ts-expect-error: no route named this way
+ if (from.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ if (Math.random()) {
+ return { name: '/[a]', params: { a: 2 } }
+ } else if (Math.random()) {
+ return '/any route does'
+ }
+ return true
+ })
+ })
+
+ it('beforeResolve', () => {
+ router.beforeResolve((to, from) => {
+ // @ts-expect-error: no route named this way
+ if (to.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ // @ts-expect-error: no route named this way
+ if (from.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ if (Math.random()) {
+ return { name: '/[a]', params: { a: 2 } }
+ } else if (Math.random()) {
+ return '/any route does'
+ }
+ return true
+ })
+ })
+
+ it('afterEach', () => {
+ router.afterEach((to, from) => {
+ // @ts-expect-error: no route named this way
+ if (to.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ // @ts-expect-error: no route named this way
+ if (from.name === '/[id]') {
+ } else if (to.name === '/[a]') {
+ expectTypeOf<{ a: ParamValue<true> }>(to.params)
+ }
+ if (Math.random()) {
+ return { name: '/[a]', params: { a: 2 } }
+ } else if (Math.random()) {
+ return '/any route does'
+ }
+ return true
+ })
+ })
+})