[READ-ONLY] Mirror of https://github.com/danielroe/nuxt-vitest. An vitest environment with support for testing code that needs a Nuxt runtime environment
nuxt nuxt-module testing unit-testing vitest
0

Configure Feed

Select the types of activity you want to include in your feed.

feat: mock `<RouterLink>`

+59 -51
+1 -1
playground/app.vue
··· 1 1 <template> 2 2 <LazySomeComponent /> 3 3 <!-- TODO: <NuxtPage /> --> 4 - <!-- TODO: <NuxtLink to="/test">Test link</NuxtLink> --> 4 + <NuxtLink to="/test">Test link</NuxtLink> 5 5 </template> 6 6 7 7 <script setup>
+6
playground/pages/index.vue
··· 1 1 <template> 2 2 <div>Index page</div> 3 3 </template> 4 + 5 + <script setup> 6 + definePageMeta({ 7 + value: 'set in index', 8 + }) 9 + </script>
+8 -6
playground/tests/index.spec.ts
··· 25 25 expect(app.$router).toBeDefined() 26 26 }) 27 27 28 - it('works with route composables', async () => { 29 - expect(useRoute().matched[0].meta).toMatchInlineSnapshot('{}') 28 + it('defaults to index page', async () => { 29 + expect(useRoute().matched[0].meta).toMatchInlineSnapshot(` 30 + { 31 + "value": "set in index", 32 + } 33 + `) 30 34 expect(useRoute().fullPath).toMatchInlineSnapshot('"/"') 31 - // TODO: 32 - // await useRouter().push('/test') 33 - // expect(useRoute().fullPath).toMatchInlineSnapshot('"/test"') 35 + // TODO: should it be possible to push to other routes? 34 36 }) 35 37 }) 36 38 ··· 40 42 expect(component.html()).toMatchInlineSnapshot(` 41 43 "<div>This is an auto-imported component</div> 42 44 <!-- TODO: <NuxtPage /> --> 43 - <!-- TODO: <NuxtLink to=\\"/test\\">Test link</NuxtLink> -->" 45 + <a href=\\"/test\\">Test link</a>" 44 46 `) 45 47 }) 46 48
+6 -1
src/index.ts
··· 9 9 const nuxt = await loadNuxt({ 10 10 cwd: rootDir, 11 11 dev: false, 12 - overrides: { ssr: false }, 12 + overrides: { 13 + ssr: false, 14 + app: { 15 + rootId: 'nuxt-test' 16 + }, 17 + }, 13 18 }) 14 19 return new Promise<InlineConfig>((resolve, reject) => { 15 20 nuxt.hook('vite:extendConfig', config => {
+21
src/runtime/components/RouterLink.ts
··· 1 + export const RouterLink = defineComponent({ 2 + functional: true, 3 + props: { 4 + to: String, 5 + custom: Boolean, 6 + replace: Boolean, 7 + // Not implemented 8 + activeClass: String, 9 + exactActiveClass: String, 10 + ariaCurrentValue: String 11 + }, 12 + setup: (props, { slots }) => { 13 + const navigate = () => {} 14 + return () => { 15 + const route = props.to ? useRouter().resolve(props.to) : {} 16 + return props.custom 17 + ? slots.default?.({ href: props.to, navigate, route }) 18 + : h('a', { href: props.to, onClick: (e: MouseEvent) => { e.preventDefault(); return navigate() } }, slots) 19 + } 20 + } 21 + })
+8 -33
src/runtime/mount.ts
··· 1 1 import { mount, VueWrapper } from '@vue/test-utils' 2 2 import { h, DefineComponent, Suspense } from 'vue' 3 3 4 + import { RouterLink } from './components/RouterLink' 5 + 4 6 // @ts-expect-error virtual file 5 7 import NuxtRoot from '#build/root-component.mjs' 6 8 ··· 9 11 const vm = mount({ 10 12 setup: NuxtRoot.setup, 11 13 render: () => h(Suspense, { onResolve: () => resolve(vm as any) }, { default: () => h(component) }) 14 + }, { 15 + global: { 16 + components: { 17 + RouterLink 18 + } 19 + } 12 20 }) 13 21 }) 14 22 } 15 - 16 - 17 - export async function mountSuspendedWithRoute<T extends DefineComponent<any, any, any, any>> (component: T) { 18 - return new Promise<VueWrapper<InstanceType<T>>>(async resolve => { 19 - // const vueApp = createApp({ 20 - // setup: NuxtRoot.setup, 21 - // render: () => h(Suspense, { onResolve: () => resolve(vueApp as any) }, { default: () => h(component) }) 22 - // }) 23 - 24 - // const nuxt = createNuxtApp({ vueApp }) 25 - 26 - // try { 27 - // await applyPlugins(nuxt, plugins) 28 - // } catch (err) { 29 - // await nuxt.callHook('app:error', err) 30 - // nuxt.payload.error = (nuxt.payload.error || err) as any 31 - // } 32 - 33 - // try { 34 - // await nuxt.hooks.callHook('app:created', vueApp) 35 - // await nuxt.hooks.callHook('app:beforeMount', vueApp) 36 - // const app = document.createElement('div') 37 - // document.body.appendChild(app) 38 - // app.id = 'something' 39 - // vueApp.mount('#something') 40 - // await nuxt.hooks.callHook('app:mounted', vueApp) 41 - // await nextTick() 42 - // } catch (err) { 43 - // await nuxt.callHook('app:error', err) 44 - // nuxt.payload.error = (nuxt.payload.error || err) as any 45 - // } 46 - }) 47 - }
+9 -10
src/vitest-environment-nuxt/index.ts
··· 1 1 import type { Environment } from 'vitest' 2 - import { builtinEnvironments, populateGlobal } from 'vitest/environments' 3 2 import { Window, GlobalWindow } from 'happy-dom' 4 3 import { createFetch } from 'ohmyfetch' 5 - import { App, createApp, defineEventHandler, toNodeListener } from 'h3' 4 + import { App, createApp, toNodeListener } from 'h3' 6 5 import { 7 6 createCall, 8 7 createFetch as createLocalFetch, 9 8 } from 'unenv/runtime/fetch/index' 9 + // @ts-expect-error TODO: add subpath types 10 + import * as viteEnvironments from 'vitest/environments' 11 + const { populateGlobal } = viteEnvironments as typeof import('vitest/dist/environments') 10 12 11 13 export default <Environment> { 12 14 name: 'nuxt', ··· 14 16 const win = new (GlobalWindow || Window)() as any as (Window & { 15 17 __app: App 16 18 __registry: Set<string> 19 + __NUXT__: any 17 20 $fetch: any 18 21 fetch: any 19 22 }) 20 23 21 - // @ts-expect-error undeclared property on window 22 24 win.__NUXT__ = { 23 25 serverRendered: false, 24 26 config: { ··· 30 32 } 31 33 32 34 const app = win.document.createElement('div') 35 + app.id = 'nuxt-test' 33 36 win.document.body.appendChild(app) 34 37 35 - // Workaround for happy-dom bug 36 - const { querySelector } = win.document 37 - app.id = 'nuxt' 38 - function qsWrapper (selectors) { 39 - if (selectors === '#__nuxt') selectors = '#nuxt' 40 - return querySelector(selectors) 38 + // @ts-expect-error 39 + win.IntersectionObserver = win.IntersectionObserver || class IntersectionObserver { 40 + observe () {} 41 41 } 42 - win.document.querySelector = qsWrapper 43 42 44 43 const h3App = createApp() 45 44