Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions examples/app-vitest-full/components/TestTeleport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<template>
<div>
<Teleport to="#teleports">
<h1 id="teleport-title">
Teleport Title
</h1>
</Teleport>
</div>
</template>
12 changes: 12 additions & 0 deletions examples/app-vitest-full/pages/other/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<template>
<div>
<ul>
<li>
<NuxtLink to="./options-api">Options Api</NuxtLink>
</li>
<li>
<NuxtLink to="./teleports">Teleports</NuxtLink>
</li>
</ul>
</div>
</template>
8 changes: 8 additions & 0 deletions examples/app-vitest-full/pages/other/teleports.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<template>
<div>
<h2>
Teleport
</h2>
<TestTeleport />
</div>
</template>
12 changes: 11 additions & 1 deletion examples/app-vitest-full/tests/nuxt/mount-suspended.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import ComponentWithCssVar from '~/components/ComponentWithCssVar.vue'
import ComponentWithPluginProvidedValue from '~/components/ComponentWithPluginProvidedValue.vue'
import GenericStateComponent from '~/components/GenericStateComponent.vue'

import { BoundAttrs } from '#components'
import { BoundAttrs, TestTeleport } from '#components'
import DirectiveComponent from '~/components/DirectiveComponent.vue'
import CustomComponent from '~/components/CustomComponent.vue'
import WrapperElement from '~/components/WrapperElement.vue'
Expand Down Expand Up @@ -592,6 +592,16 @@ it('element should be changed', async () => {
expect(component.element.tagName).toBe('SPAN')
})

it('teleport should work', async () => {
expect(document.getElementById('teleport-title')).toBeFalsy()

const wrapper = await mountSuspended(TestTeleport)
expect(document.getElementById('teleport-title')).toBeTruthy()

wrapper.unmount()
expect(document.getElementById('teleport-title')).toBeFalsy()
})

const { useCounterMock } = vi.hoisted(() => {
return {
useCounterMock: vi.fn(() => {
Expand Down
12 changes: 11 additions & 1 deletion examples/app-vitest-full/tests/nuxt/render-suspended.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import CompostionApi from '~/components/TestComponentWithCompostionApi.vue'
import OptionsApiWithData from '~/components/TestComponentWithOptionsApiWithData.vue'
import OptionsApiWithSetup from '~/components/TestComponentWithOptionsApiWithSetup.vue'

import { BoundAttrs, OptionsApiComputed, OptionsApiEmits, OptionsApiWatch, ScriptSetupEmits, ScriptSetupWatch } from '#components'
import { BoundAttrs, OptionsApiComputed, OptionsApiEmits, OptionsApiWatch, ScriptSetupEmits, ScriptSetupWatch, TestTeleport } from '#components'

const formats = {
ExportDefaultComponent,
Expand Down Expand Up @@ -398,3 +398,13 @@ it('renders links correctly', async () => {
</div>"
`)
})

it('teleport should work', async () => {
expect(document.getElementById('teleport-title')).toBeFalsy()

const wrapper = await renderSuspended(TestTeleport)
expect(document.getElementById('teleport-title')).toBeTruthy()

wrapper.unmount()
expect(document.getElementById('teleport-title')).toBeFalsy()
})
15 changes: 15 additions & 0 deletions examples/app-vitest-workspace/app1/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
devtools: { enabled: true },
app: {
rootAttrs: {
class: 'nuxt-root-class',
style: '',
tabindex: 0,
spellcheck: true,
draggable: 'true', // as `<div draggable="true" />`
},
teleportAttrs: {
class: 'teleport-class',
tabindex: -1,
spellcheck: false,
draggable: true, // as `<div draggable />`
},
},
compatibilityDate: '2024-04-03',
})
22 changes: 22 additions & 0 deletions examples/app-vitest-workspace/app1/test/app.nuxt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,26 @@ describe('app', () => {
// @ts-expect-error injected global, not typed
expect(__NUXT_VITEST_RESOLVED__).toBe(true)
})

it('should exist the app root element', () => {
const element = document.getElementById('__nuxt')
expect(element).toBeTruthy()
expect(element?.tagName).toBe('DIV')
expect(element?.getAttribute('class')).toBe('nuxt-root-class')
expect(element?.getAttribute('style')).toBe('')
expect(element?.getAttribute('tabindex')).toBe('0')
expect(element?.getAttribute('spellcheck')).toBe('')
expect(element?.getAttribute('draggable')).toBe('true')
})

it('should exist the app teleport element', () => {
const element = document.getElementById('teleports')
expect(element).toBeTruthy()
expect(element?.tagName).toBe('DIV')
expect(element?.getAttribute('class')).toBe('teleport-class')
expect(element?.getAttribute('style')).toBe(null)
expect(element?.getAttribute('tabindex')).toBe('-1')
expect(element?.getAttribute('spellcheck')).toBe(null)
expect(element?.getAttribute('draggable')).toBe('')
})
})
5 changes: 5 additions & 0 deletions examples/app-vitest-workspace/app2/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@ export default defineNuxtConfig({
modules: ['@nuxtjs/color-mode', '@nuxt/ui'],
pages: false,
devtools: { enabled: true },
app: {
rootAttrs: {
id: undefined,
},
},
compatibilityDate: '2024-04-03',
})
6 changes: 6 additions & 0 deletions examples/app-vitest-workspace/app2/test/nuxt/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,10 @@ describe('app', () => {
it('useRuntimeConfig', () => {
expect(useRuntimeConfig().icon).toBeDefined()
})

it('should exist the app root element', () => {
const element = document.getElementById('nuxt-test')
expect(element).toBeTruthy()
expect(element?.tagName).toBe('DIV')
})
})
15 changes: 15 additions & 0 deletions examples/app-vitest-workspace/app3/test/nuxt/app.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from 'vitest'

describe('app', () => {
it('should exist the app root element', () => {
const element = document.getElementById('__test_root')
expect(element).toBeTruthy()
expect(element?.tagName).toBe('MAIN')
})

it('should exist the app teleport element', () => {
const element = document.getElementById('__test_teleport')
expect(element).toBeTruthy()
expect(element?.tagName).toBe('P')
})
})
12 changes: 12 additions & 0 deletions examples/app-vitest-workspace/app3/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ export default defineVitestProject({
environmentOptions: {
nuxt: {
rootDir: fileURLToPath(new URL('.', import.meta.url)),
overrides: {
app: {
rootAttrs: {
id: '__test_root',
},
rootTag: 'main',
teleportAttrs: {
id: '__test_teleport',
},
teleportTag: 'p',
},
},
},
},
},
Expand Down
54 changes: 39 additions & 15 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,27 @@ export async function getVitestConfigFromNuxt(
},
test: {
environmentOptions: {
nuxtRuntimeConfig: applyEnv(structuredClone(options.nuxt.options.runtimeConfig), {
prefix: 'NUXT_',
env: await setupDotenv(defu(loadNuxtOptions.dotenv, {
cwd: rootDir,
fileName: '.env.test',
})),
}),
nuxtRouteRules: defu(
{},
options.nuxt.options.routeRules,
options.nuxt.options.nitro?.routeRules,
),
},
nuxtConfig: {
runtimeConfig: applyEnv(structuredClone(options.nuxt.options.runtimeConfig), {
prefix: 'NUXT_',
env: await setupDotenv(defu(loadNuxtOptions.dotenv, {
cwd: rootDir,
fileName: '.env.test',
})),
}),
routeRules: defu(
{},
options.nuxt.options.routeRules,
options.nuxt.options.nitro?.routeRules,
),
app: {
rootAttrs: options.nuxt.options.app.rootAttrs,
rootTag: options.nuxt.options.app.rootTag,
teleportAttrs: options.nuxt.options.app.teleportAttrs,
teleportTag: options.nuxt.options.app.teleportTag,
},
},
} satisfies Omit<NuxtEnvironmentResolvedOptions, 'nuxt'>,
server: {
deps: {
inline: [
Expand Down Expand Up @@ -225,14 +233,14 @@ export async function getVitestConfigFromNuxt(
test: {
environmentOptions: {
nuxt: {
rootId: options.nuxt.options.app.rootId || undefined,
rootId: options.nuxt.options.app.rootAttrs?.id || undefined,
h3Version: h3Info?.version?.startsWith('2.') ? 2 : 1,
mock: {
intersectionObserver: true,
indexedDb: false,
},
},
},
} satisfies NuxtEnvironmentResolvedOptions,
} satisfies VitestConfig,
},
) as ViteUserConfig & { test: VitestConfig }
Expand Down Expand Up @@ -405,6 +413,7 @@ export interface NuxtEnvironmentOptions {
/**
* The id of the root div to which the app should be mounted. You should also set `app.rootId` to the same value.
* @default 'nuxt-test'
* @deprecated Prefer `overrides.app.rootAttrs.id` instead
*/
rootId?: string
/**
Expand All @@ -423,6 +432,21 @@ export interface NuxtEnvironmentOptions {
}
}

/**
* @internal
*/
export interface NuxtEnvironmentResolvedOptions {
nuxt: NuxtEnvironmentOptions
nuxtConfig?: {
app: Pick<
NonNullable<NuxtConfig['app']>,
'rootAttrs' | 'rootTag' | 'teleportTag' | 'teleportAttrs'
>
runtimeConfig: NuxtConfig['runtimeConfig']
routeRules: NuxtConfig['routeRules']
}
}

declare module 'vitest/node' {
interface EnvironmentOptions {
nuxt?: NuxtEnvironmentOptions
Expand Down
45 changes: 34 additions & 11 deletions src/runtime/shared/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ import { joinURL } from 'ufo'
import { defineEventHandler } from './h3.ts'
import { createRouter as createRadixRouter, exportMatcher, toRouteMatcher } from 'radix3'
import type { NuxtWindow } from '../../vitest-environment.ts'
import type { NuxtEnvironmentOptions } from '../../config.ts'
import type { NuxtEnvironmentResolvedOptions } from '../../config.ts'
import { createFetchForH3V1 } from './h3-v1.ts'
import { createFetchForH3V2 } from './h3-v2.ts'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export async function setupWindow(win: NuxtWindow, environmentOptions: { nuxt: NuxtEnvironmentOptions, nuxtRuntimeConfig?: Record<string, any>, nuxtRouteRules?: Record<string, any> }) {
export async function setupWindow(win: NuxtWindow, environmentOptions: NuxtEnvironmentResolvedOptions) {
const nuxtConfig = environmentOptions.nuxtConfig

win.__NUXT_VITEST_ENVIRONMENT__ = true
win.__NUXT__ = {
serverRendered: false,
config: {
public: {},
app: { baseURL: '/' },
...environmentOptions?.nuxtRuntimeConfig,
...nuxtConfig?.runtimeConfig,
},
data: {},
state: {},
Expand All @@ -29,10 +30,11 @@ export async function setupWindow(win: NuxtWindow, environmentOptions: { nuxt: N
return consoleInfo(...args)
}

const app = win.document.createElement('div')
// this is a workaround for a happy-dom bug with ids beginning with _
app.id = environmentOptions.nuxt.rootId || 'nuxt-test'
win.document.body.appendChild(app)
createElementAndAppend(win, nuxtConfig?.app.rootTag || 'div', {
...nuxtConfig?.app.rootAttrs,
id: environmentOptions.nuxt.rootId || 'nuxt-test',
})
createElementAndAppend(win, nuxtConfig?.app.teleportTag || 'div', nuxtConfig?.app.teleportAttrs)

if (!win.fetch || !('Request' in win)) {
await import('node-fetch-native/polyfill')
Expand Down Expand Up @@ -66,12 +68,12 @@ export async function setupWindow(win: NuxtWindow, environmentOptions: { nuxt: N
// App manifest support
const timestamp = Date.now()
const routeRulesMatcher = toRouteMatcher(
createRadixRouter({ routes: environmentOptions.nuxtRouteRules || {} }),
createRadixRouter({ routes: nuxtConfig?.routeRules || {} }),
)
const matcher = exportMatcher(routeRulesMatcher)
const manifestOutputPath = joinURL(
environmentOptions?.nuxtRuntimeConfig?.app?.baseURL || '/',
environmentOptions?.nuxtRuntimeConfig?.app?.buildAssetsDir || '_nuxt',
nuxtConfig?.runtimeConfig?.app?.baseURL || '/',
nuxtConfig?.runtimeConfig?.app?.buildAssetsDir || '_nuxt',
'builds',
)
const manifestBaseRoutePath = joinURL('/_', manifestOutputPath)
Expand Down Expand Up @@ -103,3 +105,24 @@ export async function setupWindow(win: NuxtWindow, environmentOptions: { nuxt: N
console.info = consoleInfo
}
}

function createElementAndAppend(
win: NuxtWindow,
tag: string,
attrs: NonNullable<NuxtEnvironmentResolvedOptions['nuxtConfig']>['app']['rootAttrs']
| NonNullable<NuxtEnvironmentResolvedOptions['nuxtConfig']>['app']['teleportAttrs']
| undefined,
) {
if (attrs?.id && win.document.getElementById(attrs.id)) {
return
}

const element = win.document.createElement(tag)
for (const [key, value] of Object.entries(attrs ?? {})) {
if (value !== false && value != null) {
element.setAttribute(key, value === true ? '' : String(value))
}
}

win.document.body.appendChild(element)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"moduleResolution": "Bundler",
"allowImportingTsExtensions": true
"allowImportingTsExtensions": true,
"stripInternal": true,
},
"exclude": [
"config.d.ts",
Expand Down
Loading