diff --git a/examples/app-vitest-full/.env.test b/examples/app-vitest-full/.env.test new file mode 100644 index 000000000..5a94c642f --- /dev/null +++ b/examples/app-vitest-full/.env.test @@ -0,0 +1 @@ +NUXT_PUBLIC_TEST_VALUE=123 diff --git a/examples/app-vitest-full/nuxt.config.ts b/examples/app-vitest-full/nuxt.config.ts index 3999404d0..be929c215 100644 --- a/examples/app-vitest-full/nuxt.config.ts +++ b/examples/app-vitest-full/nuxt.config.ts @@ -29,6 +29,7 @@ export default defineNuxtConfig({ runtimeConfig: { public: { hello: 'world', + testValue: 'default' }, }, }) diff --git a/examples/app-vitest-full/tests/nuxt/config.spec.ts b/examples/app-vitest-full/tests/nuxt/config.spec.ts index 808ca4c30..acebbf24f 100644 --- a/examples/app-vitest-full/tests/nuxt/config.spec.ts +++ b/examples/app-vitest-full/tests/nuxt/config.spec.ts @@ -5,5 +5,6 @@ it('should return the runtimeConfig from nuxt.config', () => { expect(config).toBeTypeOf('object') expect(config?.public).toEqual({ hello: 'world', + testValue: 123, }) }) diff --git a/examples/app-vitest-full/vitest.config.ts b/examples/app-vitest-full/vitest.config.ts index 239060876..ec2d604e9 100644 --- a/examples/app-vitest-full/vitest.config.ts +++ b/examples/app-vitest-full/vitest.config.ts @@ -10,9 +10,7 @@ export default defineVitestConfig({ environmentOptions: { nuxt: { rootDir: fileURLToPath(new URL('./', import.meta.url)), - domEnvironment: - (process.env.VITEST_DOM_ENV as 'happy-dom' | 'jsdom') ?? 'happy-dom', - + domEnvironment: (process.env.VITEST_DOM_ENV as 'happy-dom' | 'jsdom') ?? 'happy-dom', mock: { indexedDb: true, }, diff --git a/package.json b/package.json index 53ab682b4..ff36ea6f2 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,10 @@ "dependencies": { "@nuxt/kit": "^3.8.2", "@nuxt/schema": "^3.8.2", + "c12": "^1.5.1", "consola": "^3.2.3", "defu": "^6.1.3", + "destr": "^2.0.2", "estree-walker": "^3.0.3", "execa": "^8.0.1", "fake-indexeddb": "^5.0.1", @@ -53,6 +55,7 @@ "pathe": "^1.1.1", "perfect-debounce": "^1.0.0", "radix3": "^1.1.0", + "scule": "^1.1.1", "std-env": "^3.6.0", "ufo": "^1.3.2", "unenv": "^1.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ccfa0c513..d125fa10c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,12 +19,18 @@ importers: '@nuxt/schema': specifier: ^3.8.2 version: 3.8.2(rollup@4.9.1) + c12: + specifier: ^1.5.1 + version: 1.5.1 consola: specifier: ^3.2.3 version: 3.2.3 defu: specifier: ^6.1.3 version: 6.1.3 + destr: + specifier: ^2.0.2 + version: 2.0.2 estree-walker: specifier: ^3.0.3 version: 3.0.3 @@ -64,6 +70,9 @@ importers: radix3: specifier: ^1.1.0 version: 1.1.0 + scule: + specifier: ^1.1.1 + version: 1.1.1 std-env: specifier: ^3.6.0 version: 3.6.0 @@ -371,6 +380,7 @@ packages: /@antfu/utils@0.7.7: resolution: {integrity: sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==} + dev: true /@babel/code-frame@7.23.4: resolution: {integrity: sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==} @@ -1858,6 +1868,7 @@ packages: transitivePeerDependencies: - rollup - supports-color + dev: true /@nuxt/devtools-wizard@1.0.2: resolution: {integrity: sha512-fY9Y0eCJG7eSuUgnjImWVfLZPZymoHxjrVsdhfKs3yRJvB2siaabluxvdy2OTYc+WpIxuey0hjqpv+dVtnYw1A==} @@ -1904,6 +1915,7 @@ packages: prompts: 2.4.2 rc9: 2.1.1 semver: 7.5.4 + dev: true /@nuxt/devtools@1.0.2(idb-keyval@6.2.1)(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): resolution: {integrity: sha512-nfqvRc36Sh20gDVs3pViHvcyyz3NmaNqgJnWUXgfjSCsT7G/p2wHsoNUXYYTF7kpk3kbNusftaWrIzAiTiXC9A==} @@ -1973,7 +1985,7 @@ packages: - xml2js dev: true - /@nuxt/devtools@1.0.5(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): + /@nuxt/devtools@1.0.5(idb-keyval@6.2.1)(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): resolution: {integrity: sha512-kGgxDFD3/Zw0HqCRl+S+ZIZ0NxGJoiseTzKreD2sW8q7otnqSSjte3z4qhGWI2HpvwN0Gwu/C4FtfkAVGUxPTQ==} hasBin: true peerDependencies: @@ -2000,7 +2012,7 @@ packages: local-pkg: 0.5.0 magicast: 0.3.2 nitropack: 2.8.1(idb-keyval@6.2.1) - nuxt: 3.8.2(eslint@8.56.0)(rollup@4.9.1)(typescript@5.2.2)(vite@5.0.10)(vue-tsc@1.8.25) + nuxt: 3.8.2(eslint@8.56.0)(idb-keyval@6.2.1)(rollup@4.9.1)(typescript@5.2.2)(vite@5.0.10)(vue-tsc@1.8.19) nypm: 0.3.3 ofetch: 1.3.3 ohash: 1.1.3 @@ -2039,17 +2051,18 @@ packages: - supports-color - utf-8-validate - xml2js + dev: true - /@nuxt/devtools@1.0.6(idb-keyval@6.2.1)(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): - resolution: {integrity: sha512-3P914IHBvKl2aYSrwaCAU9E1ndVNnGJR0Jn0XKUFktsbjU5kGlwLGrtRKXAw4Yz1VNiSZPrapVrFOQWbXRGRvg==} + /@nuxt/devtools@1.0.5(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): + resolution: {integrity: sha512-kGgxDFD3/Zw0HqCRl+S+ZIZ0NxGJoiseTzKreD2sW8q7otnqSSjte3z4qhGWI2HpvwN0Gwu/C4FtfkAVGUxPTQ==} hasBin: true peerDependencies: - nuxt: ^3.8.2 + nuxt: ^3.8.1 vite: 5.0.10 dependencies: - '@antfu/utils': 0.7.7 - '@nuxt/devtools-kit': 1.0.6(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) - '@nuxt/devtools-wizard': 1.0.6 + '@antfu/utils': 0.7.6 + '@nuxt/devtools-kit': 1.0.5(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) + '@nuxt/devtools-wizard': 1.0.5 '@nuxt/kit': 3.8.2(rollup@4.9.1) birpc: 0.2.14 consola: 3.2.3 @@ -2067,7 +2080,7 @@ packages: local-pkg: 0.5.0 magicast: 0.3.2 nitropack: 2.8.1(idb-keyval@6.2.1) - nuxt: 3.8.2(eslint@8.56.0)(idb-keyval@6.2.1)(rollup@4.9.1)(typescript@5.2.2)(vite@5.0.10)(vue-tsc@1.8.19) + nuxt: 3.8.2(eslint@8.56.0)(rollup@4.9.1)(typescript@5.2.2)(vite@5.0.10)(vue-tsc@1.8.25) nypm: 0.3.3 ofetch: 1.3.3 ohash: 1.1.3 @@ -2085,7 +2098,7 @@ packages: vite-plugin-inspect: 0.8.1(@nuxt/kit@3.8.2)(rollup@4.9.1)(vite@5.0.10) vite-plugin-vue-inspector: 4.0.2(vite@5.0.10) which: 3.0.1 - ws: 8.15.1 + ws: 8.14.2 transitivePeerDependencies: - '@azure/app-configuration' - '@azure/cosmos' @@ -2106,7 +2119,6 @@ packages: - supports-color - utf-8-validate - xml2js - dev: true /@nuxt/devtools@1.0.6(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10): resolution: {integrity: sha512-3P914IHBvKl2aYSrwaCAU9E1ndVNnGJR0Jn0XKUFktsbjU5kGlwLGrtRKXAw4Yz1VNiSZPrapVrFOQWbXRGRvg==} @@ -2174,6 +2186,7 @@ packages: - supports-color - utf-8-validate - xml2js + dev: true /@nuxt/eslint-config@0.2.0(eslint@8.56.0): resolution: {integrity: sha512-NeJX8TLcnNAjQFiDs3XhP+9CHKK8jaKsP7eUyCSrQdgY7nqWe7VJx64lwzx5FTT4cW3RHMEyH+Y0qzLGYYoa/A==} @@ -8788,7 +8801,7 @@ packages: optional: true dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.0.6(idb-keyval@6.2.1)(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) + '@nuxt/devtools': 1.0.5(idb-keyval@6.2.1)(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) '@nuxt/kit': 3.8.2(rollup@4.9.1) '@nuxt/schema': 3.8.2(rollup@4.9.1) '@nuxt/telemetry': 2.5.3(rollup@4.9.1) @@ -8893,7 +8906,7 @@ packages: optional: true dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/devtools': 1.0.6(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) + '@nuxt/devtools': 1.0.5(nuxt@3.8.2)(rollup@4.9.1)(vite@5.0.10) '@nuxt/kit': 3.8.2(rollup@4.9.1) '@nuxt/schema': 3.8.2(rollup@4.9.1) '@nuxt/telemetry': 2.5.3(rollup@4.9.1) @@ -12114,6 +12127,7 @@ packages: optional: true utf-8-validate: optional: true + dev: true /xml-name-validator@4.0.0: resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} diff --git a/src/config.ts b/src/config.ts index 2751538d3..2d4b95593 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,31 +1,44 @@ import type { Nuxt, NuxtConfig } from '@nuxt/schema' import type { InlineConfig as VitestConfig } from 'vitest' import { defineConfig } from 'vite' +import { setupDotenv } from 'c12' +import type { DotenvOptions } from 'c12' import type { InlineConfig } from 'vite' import { defu } from 'defu' import { createResolver } from '@nuxt/kit' +import { applyEnv } from './utils' + interface GetVitestConfigOptions { nuxt: Nuxt viteConfig: InlineConfig } +interface LoadNuxtOptions { + dotenv?: Partial + overrides?: Partial +} + // https://github.com/nuxt/framework/issues/6496 async function startNuxtAndGetViteConfig( rootDir = process.cwd(), - overrides?: Partial + options: LoadNuxtOptions = {} ) { const { loadNuxt, buildNuxt } = await import('@nuxt/kit') const nuxt = await loadNuxt({ cwd: rootDir, dev: false, + dotenv: defu(options.dotenv, { + cwd: rootDir, + fileName: '.env.test' + }), overrides: defu( { ssr: false, test: true, modules: ['@nuxt/test-utils/module'], }, - overrides + options.overrides ), }) @@ -61,14 +74,17 @@ const excludedPlugins = [ export async function getVitestConfigFromNuxt( options?: GetVitestConfigOptions, - overrides?: NuxtConfig + loadNuxtOptions: LoadNuxtOptions = {} ): Promise { - const { rootDir = process.cwd(), ..._overrides } = overrides || {} + const { rootDir = process.cwd(), ..._overrides } = loadNuxtOptions.overrides || {} if (!options) { options = await startNuxtAndGetViteConfig(rootDir, { - test: true, - ..._overrides + dotenv: loadNuxtOptions.dotenv, + overrides: { + test: true, + ..._overrides + } }) } @@ -83,7 +99,13 @@ export async function getVitestConfigFromNuxt( test: { dir: process.cwd(), environmentOptions: { - nuxtRuntimeConfig: options.nuxt.options.runtimeConfig, + 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, @@ -184,7 +206,10 @@ export function defineVitestConfig(config: InlineConfig & { test?: VitestConfig return defu( config, - await getVitestConfigFromNuxt(undefined, structuredClone(overrides)), + await getVitestConfigFromNuxt(undefined, { + dotenv: config.test?.environmentOptions?.nuxt?.dotenv, + overrides: structuredClone(overrides) + }), ) }) } @@ -198,6 +223,13 @@ declare module 'vitest' { * @default {http://localhost:3000} */ url?: string + /** + * You can define how environment options are read when loading the Nuxt configuration. + */ + dotenv?: Partial + /** + * Configuration that will override the values in your `nuxt.config` file. + */ overrides?: NuxtConfig /** * The id of the root div to which the app should be mounted. You should also set `app.rootId` to the same value. diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 000000000..861ec09ee --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,50 @@ +// TODO: export these +// https://github.com/unjs/nitro/tree/main/src/runtime/utils.env.ts + +import destr from 'destr' +import { snakeCase } from 'scule' + +export type EnvOptions = { + env?: Record + prefix?: string + altPrefix?: string +} + +export function getEnv (key: string, opts: EnvOptions) { + const env = opts.env ?? process.env + const envKey = snakeCase(key).toUpperCase() + return destr( + env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey] + ) +} + +function _isObject (input: unknown) { + return typeof input === 'object' && !Array.isArray(input) +} + +export function applyEnv (obj: Record, opts: EnvOptions, parentKey = '') { + for (const key in obj) { + const subKey = parentKey ? `${parentKey}_${key}` : key + const envValue = getEnv(subKey, opts) + if (_isObject(obj[key])) { + // Same as before + if (_isObject(envValue)) { + obj[key] = { ...obj[key], ...(envValue as object) } + applyEnv(obj[key], opts, subKey) + } + // If envValue is undefined + // Then proceed to nested properties + else if (envValue === undefined) { + applyEnv(obj[key], opts, subKey) + } + // If envValue is a primitive other than undefined + // Then set objValue and ignore the nested properties + else { + obj[key] = envValue ?? obj[key] + } + } else { + obj[key] = envValue ?? obj[key] + } + } + return obj +}