]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
test: migrate type tests from uvr
authorEduardo San Martin Morote <posva13@gmail.com>
Mon, 17 Jun 2024 13:25:49 +0000 (15:25 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Mon, 17 Jun 2024 13:25:49 +0000 (15:25 +0200)
12 files changed:
.github/workflows/test.yml
packages/router/__tests__/createRouter.test-d.ts [new file with mode: 0644]
packages/router/__tests__/routeLocation.test-d.ts [new file with mode: 0644]
packages/router/package.json
packages/router/test-dts/components.test-d.tsx
packages/router/test-dts/createRouter.test-d.ts [deleted file]
packages/router/test-dts/index.d.ts
packages/router/test-dts/legacy.test-d.ts
packages/router/test-dts/meta.test-d.ts
packages/router/test-dts/navigationGuards.test-d.ts
packages/router/test-dts/tsconfig.json
packages/router/test-dts/typed-routes.test-d.ts [new file with mode: 0644]

index 73af699d7b4d1d075a19566f830727c86663b2d7..eb8631883e4be1b69e2f0f94e9e5c5d1794eb851 100644 (file)
@@ -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 (file)
index 0000000..64e48ca
--- /dev/null
@@ -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 (file)
index 0000000..f228ba8
--- /dev/null
@@ -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<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
+      }
+    })
+  })
+})
index 9b39fac9ff9b10e2a881c7b25401746796481fd4..b97046514731f874452d57aed6cd68527dd96676 100644 (file)
@@ -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",
index e34707fdd1dd51201c45264ab3def7a09f70fcee..532f66bac4248f4365e5932630e48fea013e9f16 100644 (file)
@@ -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(<RouterLink />)
 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} />)
diff --git a/packages/router/test-dts/createRouter.test-d.ts b/packages/router/test-dts/createRouter.test-d.ts
deleted file mode 100644 (file)
index 75ff3b8..0000000
+++ /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)
index 1008e4d73c066ba903de6c42fb99f9e9327aa92e..e7c8be7b6b9ba3aa5d7c3e03c93bad88d0b195dd 100644 (file)
@@ -1,7 +1,2 @@
 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
index cd28179b49697e58d4e084c52c0164c1f71f5598..8f129a7e612b5c835ea1b00993678fec2079ff31 100644 (file)
@@ -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<Router>(this.$router)
-      expectType<RouteLocationNormalizedLoaded>(this.$route)
+      expectTypeOf<Router>(this.$router)
+      expectTypeOf<RouteLocationNormalizedLoaded>(this.$route)
     },
   },
 })
index 5e3d6e9b2fdb916fc5c28f136a0c03659eae2e14..32e2010faf936578e5cde8ac3432808e689f356f 100644 (file)
@@ -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<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)
index 71a244eb8cad594a55abfdd20c555fc68921c81d..6bf24ed8a7822e122c55cadc2b3636c9336452f7 100644 (file)
@@ -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<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)
   }
 })
index 1a6da3927969644b3fbdda297f9e6a277ffd8a1b..1d95ccdf2821f00e3715c3b99bd3ad3fa118df20 100644 (file)
@@ -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 (file)
index 0000000..e12bdab
--- /dev/null
@@ -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<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
+    })
+  })
+})