From: Eduardo San Martin Morote Date: Mon, 17 Jun 2024 13:25:49 +0000 (+0200) Subject: test: migrate type tests from uvr X-Git-Tag: v4.4.0-alpha.3~2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f7f78f0d28ca1c14d5c8991968f221b63d76bb93;p=thirdparty%2Fvuejs%2Frouter.git test: migrate type tests from uvr --- diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73af699d..eb863188 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -36,10 +36,9 @@ jobs: - 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 diff --git a/packages/router/__tests__/createRouter.test-d.ts b/packages/router/__tests__/createRouter.test-d.ts new file mode 100644 index 00000000..64e48ca3 --- /dev/null +++ b/packages/router/__tests__/createRouter.test-d.ts @@ -0,0 +1,47 @@ +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) {}, + }) + }) +}) diff --git a/packages/router/__tests__/routeLocation.test-d.ts b/packages/router/__tests__/routeLocation.test-d.ts new file mode 100644 index 00000000..f228ba82 --- /dev/null +++ b/packages/router/__tests__/routeLocation.test-d.ts @@ -0,0 +1,82 @@ +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, Record> + '/[other]': RouteRecordInfo< + '/[other]', + '/:other', + { other: ParamValue }, + { other: ParamValue } + > + '/[name]': RouteRecordInfo< + '/[name]', + '/:name', + { name: ParamValue }, + { name: ParamValue } + > + '/[...path]': RouteRecordInfo< + '/[...path]', + '/:path(.*)', + { path: ParamValue }, + { path: ParamValue } + > + '/deep/nesting/works/[[files]]+': RouteRecordInfo< + '/deep/nesting/works/[[files]]+', + '/deep/nesting/works/:files*', + { files?: ParamValueZeroOrMore }, + { files?: ParamValueZeroOrMore } + > +} + +describe('Route Location types', () => { + it('RouteLocationNormalized', () => { + function withRoute( + fn: ( + to: RouteLocationNormalizedTypedList[keyof RouteNamedMap] + ) => void + ): void + function withRoute( + name: Name, + fn: (to: RouteLocationNormalizedTypedList[Name]) => void + ): void + function withRoute(...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 + } + }) + }) +}) diff --git a/packages/router/package.json b/packages/router/package.json index 9b39fac9..b9704651 100644 --- a/packages/router/package.json +++ b/packages/router/package.json @@ -97,9 +97,8 @@ "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", diff --git a/packages/router/test-dts/components.test-d.tsx b/packages/router/test-dts/components.test-d.tsx index e34707fd..532f66ba 100644 --- a/packages/router/test-dts/components.test-d.tsx +++ b/packages/router/test-dts/components.test-d.tsx @@ -4,9 +4,8 @@ import { RouterView, createRouter, createMemoryHistory, - expectError, - expectType, } from './index' +import { expectTypeOf } from 'vitest' let router = createRouter({ history: createMemoryHistory(), @@ -20,13 +19,13 @@ expectError() expectError() // @ts-expect-error: invalid prop expectError() -expectType() -expectType() -expectType() -expectType() -expectType() +expectTypeOf() +expectTypeOf() +expectTypeOf() +expectTypeOf() +expectTypeOf() // RouterView -expectType() -expectType() -expectType() +expectTypeOf() +expectTypeOf() +expectTypeOf() diff --git a/packages/router/test-dts/createRouter.test-d.ts b/packages/router/test-dts/createRouter.test-d.ts deleted file mode 100644 index 75ff3b87..00000000 --- a/packages/router/test-dts/createRouter.test-d.ts +++ /dev/null @@ -1,61 +0,0 @@ -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) diff --git a/packages/router/test-dts/index.d.ts b/packages/router/test-dts/index.d.ts index 1008e4d7..e7c8be7b 100644 --- a/packages/router/test-dts/index.d.ts +++ b/packages/router/test-dts/index.d.ts @@ -1,7 +1,2 @@ export * from '../dist/vue-router' // export * from '../src' - -export function describe(_name: string, _fn: () => void): void -export function expectType(value: T): void -export function expectError(value: T): void -export function expectAssignable(value: T2): void diff --git a/packages/router/test-dts/legacy.test-d.ts b/packages/router/test-dts/legacy.test-d.ts index cd28179b..8f129a7e 100644 --- a/packages/router/test-dts/legacy.test-d.ts +++ b/packages/router/test-dts/legacy.test-d.ts @@ -1,11 +1,12 @@ -import { Router, RouteLocationNormalizedLoaded, expectType } from './index' +import { expectTypeOf } from 'vitest' +import { Router, RouteLocationNormalizedLoaded } from './index' import { defineComponent } from 'vue' defineComponent({ methods: { doStuff() { - expectType(this.$router) - expectType(this.$route) + expectTypeOf(this.$router) + expectTypeOf(this.$route) }, }, }) diff --git a/packages/router/test-dts/meta.test-d.ts b/packages/router/test-dts/meta.test-d.ts index 5e3d6e9b..32e2010f 100644 --- a/packages/router/test-dts/meta.test-d.ts +++ b/packages/router/test-dts/meta.test-d.ts @@ -1,5 +1,6 @@ -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({}) @@ -10,34 +11,41 @@ declare module './index' { } } -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(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(to.meta.lol) + if (to.meta.nested.foo == 'foo' || to.meta.lol) return false + }) + }) }) - -const app = createApp({}) -app.use(router) diff --git a/packages/router/test-dts/navigationGuards.test-d.ts b/packages/router/test-dts/navigationGuards.test-d.ts index 71a244eb..6bf24ed8 100644 --- a/packages/router/test-dts/navigationGuards.test-d.ts +++ b/packages/router/test-dts/navigationGuards.test-d.ts @@ -1,7 +1,7 @@ +import { expectTypeOf } from 'vitest' import { createRouter, createWebHistory, - expectType, isNavigationFailure, NavigationFailure, NavigationFailureType, @@ -45,13 +45,13 @@ router.beforeEach((to, from, next) => { }) router.afterEach((to, from, failure) => { - expectType(failure) + expectTypeOf(failure) if (isNavigationFailure(failure)) { - expectType(failure.from) - expectType(failure.to) + expectTypeOf(failure.from) + expectTypeOf(failure.to) } if (isNavigationFailure(failure, NavigationFailureType.cancelled)) { - expectType(failure.from) - expectType(failure.to) + expectTypeOf(failure.from) + expectTypeOf(failure.to) } }) diff --git a/packages/router/test-dts/tsconfig.json b/packages/router/test-dts/tsconfig.json index 1a6da392..1d95ccdf 100644 --- a/packages/router/test-dts/tsconfig.json +++ b/packages/router/test-dts/tsconfig.json @@ -4,10 +4,19 @@ "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" + ] } diff --git a/packages/router/test-dts/typed-routes.test-d.ts b/packages/router/test-dts/typed-routes.test-d.ts new file mode 100644 index 00000000..e12bdab2 --- /dev/null +++ b/packages/router/test-dts/typed-routes.test-d.ts @@ -0,0 +1,139 @@ +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 }, + { path: ParamValue } + > + '/[a]': RouteRecordInfo< + '/[a]', + '/:a', + { a: ParamValue }, + { a: ParamValue } + > + '/a': RouteRecordInfo<'/a', '/a', Record, Record> + '/[id]+': RouteRecordInfo< + '/[id]+', + '/:id+', + { id: ParamValueOneOrMore }, + { id: ParamValueOneOrMore } + > +} + +declare module './index' { + interface TypesConfig { + RouteNamedMap: RouteMap + } +} + +describe('RouterTyped', () => { + const router = createRouter({ + history: createWebHistory(), + routes: [], + }) + + it('resolve', () => { + expectTypeOf>(router.resolve({ name: '/a' }).params) + expectTypeOf<{ a: ParamValue }>( + router.resolve({ name: '/[a]' }).params + ) + + expectTypeOf>( + router.resolve({ name: '/a' }) + ) + expectTypeOf<'/a'>( + // @ts-expect-error: cannot infer based on path + router.resolve({ path: '/a' }).name + ) + expectTypeOf(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 }>(to.params) + } + // @ts-expect-error: no route named this way + if (from.name === '/[id]') { + } else if (to.name === '/[a]') { + expectTypeOf<{ a: ParamValue }>(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 }>(to.params) + } + // @ts-expect-error: no route named this way + if (from.name === '/[id]') { + } else if (to.name === '/[a]') { + expectTypeOf<{ a: ParamValue }>(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 }>(to.params) + } + // @ts-expect-error: no route named this way + if (from.name === '/[id]') { + } else if (to.name === '/[a]') { + expectTypeOf<{ a: ParamValue }>(to.params) + } + if (Math.random()) { + return { name: '/[a]', params: { a: 2 } } + } else if (Math.random()) { + return '/any route does' + } + return true + }) + }) +})