Skip to content

Commit f4ae58a

Browse files
committed
feat: add nuxt-vitest and vitest-environment-nuxt code and tests
2 parents 1598543 + 8b428b4 commit f4ae58a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2445
-0
lines changed

src/nuxt-vitest/config.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import type { Nuxt, NuxtConfig, ViteConfig } from '@nuxt/schema'
2+
import type { InlineConfig as VitestConfig } from 'vitest'
3+
import { defineConfig, mergeConfig } from 'vite'
4+
import type { InlineConfig } from 'vite'
5+
import vuePlugin from '@vitejs/plugin-vue'
6+
import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
7+
import { defu } from 'defu'
8+
9+
interface GetVitestConfigOptions {
10+
nuxt: Nuxt
11+
viteConfig: InlineConfig
12+
}
13+
14+
// https://github.com/nuxt/framework/issues/6496
15+
async function startNuxtAndGetViteConfig(
16+
rootDir = process.cwd(),
17+
overrides?: Partial<NuxtConfig>
18+
) {
19+
const { loadNuxt, buildNuxt } = await import('@nuxt/kit')
20+
const nuxt = await loadNuxt({
21+
cwd: rootDir,
22+
dev: false,
23+
overrides: defu(
24+
{
25+
ssr: false,
26+
test: true,
27+
modules: ['nuxt-vitest'],
28+
},
29+
overrides
30+
),
31+
})
32+
33+
if (
34+
!nuxt.options._installedModules.find(i => i?.meta?.name === 'nuxt-vitest')
35+
) {
36+
throw new Error(
37+
'Failed to load nuxt-vitest module. You may need to add it to your nuxt.config.'
38+
)
39+
}
40+
41+
const promise = new Promise<GetVitestConfigOptions>((resolve, reject) => {
42+
nuxt.hook('vite:extendConfig', (viteConfig, { isClient }) => {
43+
if (isClient) {
44+
resolve({ nuxt, viteConfig })
45+
throw new Error('_stop_')
46+
}
47+
})
48+
buildNuxt(nuxt).catch(err => {
49+
if (!err.toString().includes('_stop_')) {
50+
reject(err)
51+
}
52+
})
53+
}).finally(() => nuxt.close())
54+
55+
return promise
56+
}
57+
58+
const vuePlugins = {
59+
'vite:vue': [vuePlugin, 'vue'],
60+
'vite:vue-jsx': [viteJsxPlugin, 'vueJsx'],
61+
} as const
62+
63+
export async function getVitestConfigFromNuxt(
64+
options?: GetVitestConfigOptions,
65+
overrides?: NuxtConfig
66+
): Promise<InlineConfig & { test: VitestConfig }> {
67+
const { rootDir = process.cwd(), ..._overrides } = overrides || {}
68+
if (!options) options = await startNuxtAndGetViteConfig(rootDir, _overrides)
69+
options.viteConfig.plugins = options.viteConfig.plugins || []
70+
options.viteConfig.plugins = options.viteConfig.plugins.filter(
71+
p => (p as any)?.name !== 'nuxt:import-protection'
72+
)
73+
74+
for (const name in vuePlugins) {
75+
if (!options.viteConfig.plugins?.some(p => (p as any)?.name === name)) {
76+
const [plugin, key] = vuePlugins[name as keyof typeof vuePlugins]
77+
options.viteConfig.plugins.unshift(
78+
// @ts-expect-error mismatching component options
79+
plugin((options.viteConfig as ViteConfig)[key])
80+
)
81+
}
82+
}
83+
84+
return {
85+
...options.viteConfig,
86+
define: {
87+
...options.viteConfig.define,
88+
['process.env.NODE_ENV']: 'process.env.NODE_ENV',
89+
},
90+
server: {
91+
...options.viteConfig.server,
92+
middlewareMode: false,
93+
},
94+
plugins: [
95+
...options.viteConfig.plugins,
96+
{
97+
name: 'disable-auto-execute',
98+
enforce: 'pre',
99+
transform(code, id) {
100+
if (id.match(/nuxt3?\/.*\/entry\./)) {
101+
return code.replace(
102+
/(?<!vueAppPromise = )entry\(\)\.catch/,
103+
'Promise.resolve().catch'
104+
)
105+
}
106+
},
107+
},
108+
],
109+
test: {
110+
...options.viteConfig.test,
111+
dir: process.cwd(),
112+
environmentOptions: {
113+
...options.viteConfig.test?.environmentOptions,
114+
nuxt: {
115+
rootId: options.nuxt.options.app.rootId || undefined,
116+
...options.viteConfig.test?.environmentOptions?.nuxt,
117+
mock: {
118+
intersectionObserver: true,
119+
indexedDb: false,
120+
...options.viteConfig.test?.environmentOptions?.nuxt?.mock,
121+
},
122+
},
123+
nuxtRuntimeConfig: options.nuxt.options.runtimeConfig,
124+
nuxtRouteRules: defu(
125+
{},
126+
options.nuxt.options.routeRules,
127+
options.nuxt.options.nitro?.routeRules
128+
),
129+
},
130+
environmentMatchGlobs: [
131+
['**/*.nuxt.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}', 'nuxt'],
132+
['{test,tests}/nuxt/**.*', 'nuxt'],
133+
...(options.viteConfig.test?.environmentMatchGlobs || []),
134+
],
135+
deps: {
136+
...options.viteConfig.test?.deps,
137+
inline: [
138+
// vite-node defaults
139+
/\/node_modules\/(.*\/)?(nuxt|nuxt3)\//,
140+
/^#/,
141+
// additional deps
142+
'vitest-environment-nuxt',
143+
...(options.nuxt.options.build.transpile.filter(
144+
r => typeof r === 'string' || r instanceof RegExp
145+
) as Array<string | RegExp>),
146+
...(typeof options.viteConfig.test?.deps?.inline !== 'boolean'
147+
? typeof options.viteConfig.test?.deps?.inline
148+
: []),
149+
],
150+
},
151+
},
152+
}
153+
}
154+
155+
export function defineVitestConfig(config: InlineConfig = {}) {
156+
return defineConfig(async () => {
157+
// When Nuxt module calls `startVitest`, we don't need to call `getVitestConfigFromNuxt` again
158+
if (process.env.__NUXT_VITEST_RESOLVED__) return config
159+
160+
const overrides = config.test?.environmentOptions?.nuxt?.overrides || {}
161+
overrides.rootDir = config.test?.environmentOptions?.nuxt?.rootDir
162+
163+
return mergeConfig(
164+
await getVitestConfigFromNuxt(undefined, overrides),
165+
config
166+
)
167+
})
168+
}
169+
170+
declare module 'vitest' {
171+
interface EnvironmentOptions {
172+
nuxt?: {
173+
rootDir?: string
174+
/**
175+
* The starting URL for your Nuxt window environment
176+
* @default {http://localhost:3000}
177+
*/
178+
url?: string
179+
overrides?: NuxtConfig
180+
/**
181+
* The id of the root div to which the app should be mounted. You should also set `app.rootId` to the same value.
182+
* @default {nuxt-test}
183+
*/
184+
rootId?: string
185+
/**
186+
* The name of the DOM environment to use.
187+
*
188+
* It also needs to be installed as a dev dependency in your project.
189+
* @default {happy-dom}
190+
*/
191+
domEnvironment?: 'happy-dom' | 'jsdom'
192+
193+
mock?: {
194+
intersectionObserver?: boolean
195+
indexedDb?: boolean
196+
}
197+
}
198+
}
199+
}

src/nuxt-vitest/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { defineVitestConfig } from './config'
2+
export { default } from './module'

0 commit comments

Comments
 (0)