@nuxt/test-utils 3.19.2 → 3.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  import { defineEventHandler } from 'h3';
2
2
  import { mount } from '@vue/test-utils';
3
- import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance, unref, isReadonly, defineComponent as defineComponent$1, effectScope } from 'vue';
3
+ import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance, effectScope, defineComponent as defineComponent$1 } from 'vue';
4
4
  import { defu } from 'defu';
5
5
  import { defineComponent, useRouter, h, tryUseNuxtApp } from '#imports';
6
6
  import NuxtRoot from '#build/root-component.mjs';
@@ -11,16 +11,25 @@ function registerEndpoint(url, options) {
11
11
  if (!app) {
12
12
  throw new Error("registerEndpoint() can only be used in a `@nuxt/test-utils` runtime environment");
13
13
  }
14
- const config = typeof options === "function" ? { handler: options, method: void 0 } : options;
14
+ const config = typeof options === "function" ? { handler: options, method: void 0, once: false } : options;
15
15
  config.handler = defineEventHandler(config.handler);
16
16
  const hasBeenRegistered = window.__registry.has(url);
17
17
  endpointRegistry[url] ||= [];
18
18
  endpointRegistry[url].push(config);
19
19
  if (!hasBeenRegistered) {
20
20
  window.__registry.add(url);
21
- app.use("/_" + url, defineEventHandler((event) => {
21
+ app.use("/_" + url, defineEventHandler(async (event) => {
22
22
  const latestHandler = [...endpointRegistry[url] || []].reverse().find((config2) => config2.method ? event.method === config2.method : true);
23
- return latestHandler?.handler(event);
23
+ if (!latestHandler) return;
24
+ const result = await latestHandler.handler(event);
25
+ if (!latestHandler.once) return result;
26
+ const index = endpointRegistry[url]?.indexOf(latestHandler);
27
+ if (index === void 0 || index === -1) return result;
28
+ endpointRegistry[url]?.splice(index, 1);
29
+ if (endpointRegistry[url]?.length === 0) {
30
+ window.__registry.delete(url);
31
+ }
32
+ return result;
24
33
  }), {
25
34
  match(_, event) {
26
35
  return endpointRegistry[url]?.some((config2) => config2.method ? event?.method === config2.method : true) ?? false;
@@ -34,7 +43,7 @@ function registerEndpoint(url, options) {
34
43
  }
35
44
  };
36
45
  }
37
- function mockNuxtImport(_name, _factory) {
46
+ function mockNuxtImport(_target, _factory) {
38
47
  throw new Error(
39
48
  "mockNuxtImport() is a macro and it did not get transpiled. This may be an internal bug of @nuxt/test-utils."
40
49
  );
@@ -87,16 +96,48 @@ async function mountSuspended(component, options) {
87
96
  route = "/",
88
97
  ..._options
89
98
  } = options || {};
99
+ for (const cleanupFunction of globalThis.__cleanup || []) {
100
+ cleanupFunction();
101
+ }
90
102
  const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
91
- const { render, setup, data, computed, methods } = component;
103
+ const { render, setup, ...componentRest } = component;
104
+ let wrappedInstance = null;
92
105
  let setupContext;
93
106
  let setupState;
94
107
  const setProps = reactive({});
95
- let passedProps;
96
- const wrappedSetup = async (props2, setupContext2) => {
97
- passedProps = props2;
108
+ function patchInstanceAppContext() {
109
+ const app = getCurrentInstance()?.appContext.app;
110
+ if (!app) return;
111
+ for (const [key, value] of Object.entries(vueApp)) {
112
+ if (key in app) continue;
113
+ app[key] = value;
114
+ }
115
+ }
116
+ let componentScope = null;
117
+ const wrappedSetup = async (props2, setupContext2, instanceContext) => {
118
+ const currentInstance = getCurrentInstance();
119
+ if (currentInstance) {
120
+ currentInstance.emit = (event, ...args) => {
121
+ setupContext2.emit(event, ...args);
122
+ };
123
+ }
98
124
  if (setup) {
99
- const result = await setup(props2, setupContext2);
125
+ let result;
126
+ if (options?.scoped) {
127
+ componentScope = effectScope();
128
+ globalThis.__cleanup ||= [];
129
+ globalThis.__cleanup.push(() => {
130
+ componentScope?.stop();
131
+ });
132
+ result = await componentScope?.run(async () => {
133
+ return await setup(props2, setupContext2);
134
+ });
135
+ } else {
136
+ result = await setup(props2, setupContext2);
137
+ }
138
+ if (wrappedInstance?.exposed) {
139
+ instanceContext.expose(wrappedInstance.exposed);
140
+ }
100
141
  setupState = result && typeof result === "object" ? result : {};
101
142
  return result;
102
143
  }
@@ -105,15 +146,32 @@ async function mountSuspended(component, options) {
105
146
  (resolve) => {
106
147
  const vm = mount(
107
148
  {
149
+ __cssModules: componentRest.__cssModules,
150
+ inheritAttrs: false,
108
151
  setup: (props2, ctx) => {
152
+ patchInstanceAppContext();
153
+ wrappedInstance = getCurrentInstance();
109
154
  setupContext = ctx;
110
- return NuxtRoot.setup(props2, {
111
- ...ctx,
112
- expose: () => {
113
- }
114
- });
155
+ if (options?.scoped) {
156
+ const scope = effectScope();
157
+ globalThis.__cleanup ||= [];
158
+ globalThis.__cleanup.push(() => {
159
+ scope.stop();
160
+ });
161
+ return scope.run(() => NuxtRoot.setup(props2, {
162
+ ...ctx,
163
+ expose: () => {
164
+ }
165
+ }));
166
+ } else {
167
+ return NuxtRoot.setup(props2, {
168
+ ...ctx,
169
+ expose: () => {
170
+ }
171
+ });
172
+ }
115
173
  },
116
- render: (renderContext) => h$1(
174
+ render: () => h$1(
117
175
  Suspense,
118
176
  {
119
177
  onResolve: () => nextTick().then(() => {
@@ -130,66 +188,13 @@ async function mountSuspended(component, options) {
130
188
  async setup() {
131
189
  const router = useRouter();
132
190
  await router.replace(route);
133
- let interceptedEmit = null;
134
191
  const clonedComponent = {
135
- name: "MountSuspendedComponent",
192
+ components: {},
136
193
  ...component,
137
- render: render ? function(_ctx, ...args) {
138
- const currentInstance = getCurrentInstance();
139
- if (currentInstance && currentInstance.emit !== interceptedEmit) {
140
- const oldEmit = currentInstance.emit;
141
- interceptedEmit = (event, ...args2) => {
142
- oldEmit(event, ...args2);
143
- setupContext.emit(event, ...args2);
144
- };
145
- currentInstance.emit = interceptedEmit;
146
- }
147
- if (data && typeof data === "function") {
148
- const dataObject = data();
149
- for (const key in dataObject) {
150
- renderContext[key] = dataObject[key];
151
- }
152
- }
153
- for (const key in setupState || {}) {
154
- const warn = console.warn;
155
- console.warn = () => {
156
- };
157
- try {
158
- renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
159
- } catch {
160
- } finally {
161
- console.warn = warn;
162
- }
163
- if (key === "props") {
164
- renderContext[key] = cloneProps$1(renderContext[key]);
165
- }
166
- }
167
- const propsContext = "props" in renderContext ? renderContext.props : renderContext;
168
- for (const key in props || {}) {
169
- propsContext[key] = _ctx[key];
170
- }
171
- for (const key in passedProps || {}) {
172
- propsContext[key] = passedProps[key];
173
- }
174
- if (methods && typeof methods === "object") {
175
- for (const [key, value] of Object.entries(methods)) {
176
- renderContext[key] = value.bind(renderContext);
177
- }
178
- }
179
- if (computed && typeof computed === "object") {
180
- for (const [key, value] of Object.entries(computed)) {
181
- if ("get" in value) {
182
- renderContext[key] = value.get.call(renderContext);
183
- } else {
184
- renderContext[key] = value.call(renderContext);
185
- }
186
- }
187
- }
188
- return render.call(this, renderContext, ...args);
189
- } : void 0,
190
- setup: setup ? (props2) => wrappedSetup(props2, setupContext) : void 0
194
+ name: "MountSuspendedComponent",
195
+ setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
191
196
  };
192
- return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, slots);
197
+ return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, setupContext.slots);
193
198
  }
194
199
  })
195
200
  }
@@ -198,11 +203,18 @@ async function mountSuspended(component, options) {
198
203
  defu(
199
204
  _options,
200
205
  {
206
+ props,
201
207
  slots,
202
208
  attrs,
203
209
  global: {
204
210
  config: {
205
- globalProperties: vueApp.config.globalProperties
211
+ globalProperties: {
212
+ ...vueApp.config.globalProperties,
213
+ // make all properties/keys enumerable.
214
+ ...Object.fromEntries(
215
+ Object.getOwnPropertyNames(vueApp.config.globalProperties).map((key) => [key, vueApp.config.globalProperties[key]])
216
+ )
217
+ }
206
218
  },
207
219
  directives: vueApp._context.directives,
208
220
  provide: vueApp._context.provides,
@@ -219,33 +231,46 @@ async function mountSuspended(component, options) {
219
231
  }
220
232
  );
221
233
  }
222
- function cloneProps$1(props) {
223
- const newProps = reactive({});
224
- for (const key in props) {
225
- newProps[key] = props[key];
226
- }
227
- return newProps;
228
- }
229
234
  function wrappedMountedWrapper(wrapper) {
230
- const proxy = new Proxy(wrapper, {
231
- get: (target, prop, receiver) => {
232
- if (prop === "element") {
233
- const component = target.findComponent({ name: "MountSuspendedComponent" });
234
- return component[prop];
235
- } else {
236
- return Reflect.get(target, prop, receiver);
237
- }
235
+ const component = wrapper.findComponent({ name: "MountSuspendedComponent" });
236
+ const wrapperProps = [
237
+ "setProps",
238
+ "emitted",
239
+ "setupState",
240
+ "unmount"
241
+ ];
242
+ return new Proxy(wrapper, {
243
+ get: (_, prop, receiver) => {
244
+ if (prop === "getCurrentComponent") return getCurrentComponentPatchedProxy;
245
+ const target = wrapperProps.includes(prop) ? wrapper : Reflect.has(component, prop) ? component : wrapper;
246
+ const value = Reflect.get(target, prop, receiver);
247
+ return typeof value === "function" ? value.bind(target) : value;
238
248
  }
239
249
  });
240
- for (const key of ["props"]) {
241
- proxy[key] = new Proxy(wrapper[key], {
242
- apply: (target, thisArg, args) => {
243
- const component = thisArg.findComponent({ name: "MountSuspendedComponent" });
244
- return component[key](...args);
250
+ function getCurrentComponentPatchedProxy() {
251
+ const currentComponent = component.getCurrentComponent();
252
+ return new Proxy(currentComponent, {
253
+ get: (target, prop, receiver) => {
254
+ const value = Reflect.get(target, prop, receiver);
255
+ if (prop === "proxy" && value) {
256
+ return new Proxy(value, {
257
+ get(o, p, r) {
258
+ if (!Reflect.has(currentComponent.props, p)) {
259
+ const setupState = wrapper.setupState;
260
+ if (setupState && typeof setupState === "object") {
261
+ if (Reflect.has(setupState, p)) {
262
+ return Reflect.get(setupState, p, r);
263
+ }
264
+ }
265
+ }
266
+ return Reflect.get(o, p, r);
267
+ }
268
+ });
269
+ }
270
+ return value;
245
271
  }
246
272
  });
247
273
  }
248
- return proxy;
249
274
  }
250
275
 
251
276
  const WRAPPER_EL_ID = "test-wrapper";
@@ -259,19 +284,36 @@ async function renderSuspended(component, options) {
259
284
  } = options || {};
260
285
  const { render: renderFromTestingLibrary } = await import('@testing-library/vue');
261
286
  const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
262
- const { render, setup, data, computed, methods } = component;
287
+ const { render, setup, ...componentRest } = component;
288
+ let wrappedInstance = null;
263
289
  let setupContext;
264
290
  let setupState;
291
+ const setProps = reactive({});
292
+ function patchInstanceAppContext() {
293
+ const app = getCurrentInstance()?.appContext.app;
294
+ if (!app) return;
295
+ for (const [key, value] of Object.entries(vueApp)) {
296
+ if (key in app) continue;
297
+ app[key] = value;
298
+ }
299
+ }
265
300
  for (const fn of window.__cleanup || []) {
266
301
  fn();
267
302
  }
268
303
  document.querySelector(`#${WRAPPER_EL_ID}`)?.remove();
269
- let passedProps;
270
- const wrappedSetup = async (props2, setupContext2) => {
271
- passedProps = props2;
304
+ const wrappedSetup = async (props2, setupContext2, instanceContext) => {
305
+ const currentInstance = getCurrentInstance();
306
+ if (currentInstance) {
307
+ currentInstance.emit = (event, ...args) => {
308
+ setupContext2.emit(event, ...args);
309
+ };
310
+ }
272
311
  if (setup) {
273
312
  const result = await setup(props2, setupContext2);
274
313
  setupState = result && typeof result === "object" ? result : {};
314
+ if (wrappedInstance?.exposed) {
315
+ instanceContext.expose(wrappedInstance.exposed);
316
+ }
275
317
  return result;
276
318
  }
277
319
  };
@@ -284,7 +326,11 @@ async function renderSuspended(component, options) {
284
326
  return new Promise((resolve) => {
285
327
  const utils = renderFromTestingLibrary(
286
328
  {
329
+ __cssModules: componentRest.__cssModules,
330
+ inheritAttrs: false,
287
331
  setup: (props2, ctx) => {
332
+ patchInstanceAppContext();
333
+ wrappedInstance = getCurrentInstance();
288
334
  setupContext = ctx;
289
335
  const scope = effectScope();
290
336
  window.__cleanup ||= [];
@@ -296,7 +342,7 @@ async function renderSuspended(component, options) {
296
342
  expose: () => ({})
297
343
  }));
298
344
  },
299
- render: (renderContext) => (
345
+ render: () => (
300
346
  // See discussions in https://github.com/testing-library/vue-testing-library/issues/230
301
347
  // we add this additional root element because otherwise testing-library breaks
302
348
  // because there's no root element while Suspense is resolving
@@ -309,6 +355,10 @@ async function renderSuspended(component, options) {
309
355
  {
310
356
  onResolve: () => nextTick().then(() => {
311
357
  utils.setupState = setupState;
358
+ utils.rerender = async (props2) => {
359
+ Object.assign(setProps, props2);
360
+ await nextTick();
361
+ };
312
362
  resolve(utils);
313
363
  })
314
364
  },
@@ -319,55 +369,13 @@ async function renderSuspended(component, options) {
319
369
  const router = useRouter();
320
370
  await router.replace(route);
321
371
  const clonedComponent = {
322
- name: "RenderSuspendedComponent",
372
+ components: {},
323
373
  ...component,
324
- render: render ? function(_ctx, ...args) {
325
- if (data && typeof data === "function") {
326
- const dataObject = data();
327
- for (const key in dataObject) {
328
- renderContext[key] = dataObject[key];
329
- }
330
- }
331
- for (const key in setupState || {}) {
332
- const warn = console.warn;
333
- console.warn = () => {
334
- };
335
- try {
336
- renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
337
- } catch {
338
- } finally {
339
- console.warn = warn;
340
- }
341
- if (key === "props") {
342
- renderContext[key] = cloneProps(renderContext[key]);
343
- }
344
- }
345
- const propsContext = "props" in renderContext ? renderContext.props : renderContext;
346
- for (const key in props || {}) {
347
- propsContext[key] = _ctx[key];
348
- }
349
- for (const key in passedProps || {}) {
350
- propsContext[key] = passedProps[key];
351
- }
352
- if (methods && typeof methods === "object") {
353
- for (const [key, value] of Object.entries(methods)) {
354
- renderContext[key] = value.bind(renderContext);
355
- }
356
- }
357
- if (computed && typeof computed === "object") {
358
- for (const [key, value] of Object.entries(computed)) {
359
- if ("get" in value) {
360
- renderContext[key] = value.get.call(renderContext);
361
- } else {
362
- renderContext[key] = value.call(renderContext);
363
- }
364
- }
365
- }
366
- return render.call(this, renderContext, ...args);
367
- } : void 0,
368
- setup: setup ? (props2) => wrappedSetup(props2, setupContext) : void 0
374
+ name: "RenderSuspendedComponent",
375
+ render,
376
+ setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
369
377
  };
370
- return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...attrs }, slots);
378
+ return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...setProps, ...attrs }, setupContext.slots);
371
379
  }
372
380
  })
373
381
  }
@@ -377,11 +385,18 @@ async function renderSuspended(component, options) {
377
385
  )
378
386
  },
379
387
  defu(_options, {
388
+ props,
380
389
  slots,
381
390
  attrs,
382
391
  global: {
383
392
  config: {
384
- globalProperties: vueApp.config.globalProperties
393
+ globalProperties: {
394
+ ...vueApp.config.globalProperties,
395
+ // make all properties/keys enumerable.
396
+ ...Object.fromEntries(
397
+ Object.getOwnPropertyNames(vueApp.config.globalProperties).map((key) => [key, vueApp.config.globalProperties[key]])
398
+ )
399
+ }
385
400
  },
386
401
  directives: vueApp._context.directives,
387
402
  provide: vueApp._context.provides,
@@ -391,12 +406,5 @@ async function renderSuspended(component, options) {
391
406
  );
392
407
  });
393
408
  }
394
- function cloneProps(props) {
395
- const newProps = reactive({});
396
- for (const key in props) {
397
- newProps[key] = props[key];
398
- }
399
- return newProps;
400
- }
401
409
 
402
410
  export { mockComponent, mockNuxtImport, mountSuspended, registerEndpoint, renderSuspended };
@@ -29,41 +29,41 @@ interface TestOptions {
29
29
  fixture: string;
30
30
  /**
31
31
  * Name of the configuration file.
32
- * @default `'nuxt.config`
32
+ * @default 'nuxt.config'
33
33
  */
34
34
  configFile: string;
35
35
  /**
36
36
  * Path to a directory with a Nuxt app to be put under test.
37
- * @default `'.'`
37
+ * @default '.'
38
38
  */
39
39
  rootDir: string;
40
40
  buildDir: string;
41
41
  nuxtConfig: NuxtConfig;
42
42
  /**
43
43
  * Whether to run a separate build step.
44
- * @default `true` (`false` if `browser` or `server` is disabled, or if a `host` is provided)
44
+ * @default true // (`false` if `browser` or `server` is disabled, or if a `host` is provided)
45
45
  */
46
46
  build: boolean;
47
47
  dev: boolean;
48
48
  /**
49
49
  * The amount of time (in milliseconds) to allow for `setupTest` to complete its work (which could include building or generating files for a Nuxt application, depending on the options that are passed).
50
- * @default `120000` or `240000` on windows
50
+ * @default 120000 // or `240000` on windows
51
51
  */
52
52
  setupTimeout: number;
53
53
  /**
54
54
  * The amount of time (in milliseconds) to allow tearing down the test environment, such as closing the browser.
55
- * @default `30000`
55
+ * @default 30000
56
56
  */
57
57
  teardownTimeout: number;
58
58
  waitFor: number;
59
59
  /**
60
60
  * Under the hood, Nuxt test utils uses [`playwright`](https://playwright.dev) to carry out browser testing. If this option is set, a browser will be launched and can be controlled in the subsequent test suite.
61
- * @default `false`
61
+ * @default false
62
62
  */
63
63
  browser: boolean;
64
64
  /**
65
65
  * Specify the runner for the test suite. One of `'vitest' | 'jest' | 'cucumber' | 'bun'`.
66
- * @default `vitest`
66
+ * @default 'vitest'
67
67
  */
68
68
  runner: TestRunner;
69
69
  logLevel: number;
@@ -75,17 +75,17 @@ interface TestOptions {
75
75
  };
76
76
  /**
77
77
  * Whether to launch a server to respond to requests in the test suite.
78
- * @default `true` (`false` if a `host` is provided)
78
+ * @default true // (`false` if a `host` is provided)
79
79
  */
80
80
  server: boolean;
81
81
  /**
82
82
  * If provided, a URL to use as the test target instead of building and running a new server. Useful for running "real" end-to-end tests against a deployed version of your application, or against an already running local server.
83
- * @default `undefined`
83
+ * @default undefined
84
84
  */
85
85
  host?: string;
86
86
  /**
87
87
  * If provided, set the launched test server port to the value.
88
- * @default `undefined`
88
+ * @default undefined
89
89
  */
90
90
  port?: number;
91
91
  env?: StartServerOptions['env'];
@@ -109,7 +109,7 @@ interface TestHooks {
109
109
  afterAll: () => Promise<void>;
110
110
  beforeAll: () => Promise<void>;
111
111
  /**
112
- * @deprecated use `beforeAll` intead
112
+ * @deprecated use `beforeAll` instead
113
113
  */
114
114
  setup: () => Promise<void>;
115
115
  ctx: TestContext;
@@ -1,8 +1,8 @@
1
- import { u as useTestContext, d as url, c as createTestContext, a as startServer, b as stopServer, s as setTestContext } from './test-utils.B8qEdk9k.mjs';
1
+ import { u as useTestContext, d as url, c as createTestContext, a as startServer, b as stopServer, s as setTestContext } from './test-utils.CtwoJP76.mjs';
2
2
  import { existsSync, promises } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { defu } from 'defu';
5
- import * as _kit from '@nuxt/kit';
5
+ import { l as loadKit } from './test-utils.G1ew4kEe.mjs';
6
6
 
7
7
  async function createBrowser() {
8
8
  const ctx = useTestContext();
@@ -57,9 +57,8 @@ async function waitForHydration(page, url2, waitUntil) {
57
57
  }
58
58
  }
59
59
 
60
- const kit = _kit.default || _kit;
61
60
  const isNuxtApp = (dir) => {
62
- return existsSync(dir) && (existsSync(resolve(dir, "pages")) || existsSync(resolve(dir, "nuxt.config.js")) || existsSync(resolve(dir, "nuxt.config.mjs")) || existsSync(resolve(dir, "nuxt.config.cjs")) || existsSync(resolve(dir, "nuxt.config.ts")));
61
+ return existsSync(dir) && (existsSync(resolve(dir, "pages")) || existsSync(resolve(dir, "nuxt.config.js")) || existsSync(resolve(dir, "nuxt.config.mjs")) || existsSync(resolve(dir, "nuxt.config.cjs")) || existsSync(resolve(dir, "nuxt.config.ts")) || existsSync(resolve(dir, ".config", "nuxt.js")) || existsSync(resolve(dir, ".config", "nuxt.mjs")) || existsSync(resolve(dir, ".config", "nuxt.cjs")) || existsSync(resolve(dir, ".config", "nuxt.ts")));
63
62
  };
64
63
  const resolveRootDir = () => {
65
64
  const { options } = useTestContext();
@@ -91,7 +90,8 @@ async function loadFixture() {
91
90
  });
92
91
  }
93
92
  if (ctx.options.build) {
94
- ctx.nuxt = await kit.loadNuxt({
93
+ const { loadNuxt } = await loadKit(ctx.options.rootDir);
94
+ ctx.nuxt = await loadNuxt({
95
95
  cwd: ctx.options.rootDir,
96
96
  dev: ctx.options.dev,
97
97
  overrides: ctx.options.nuxtConfig,
@@ -107,10 +107,11 @@ async function loadFixture() {
107
107
  }
108
108
  async function buildFixture() {
109
109
  const ctx = useTestContext();
110
- const prevLevel = kit.logger.level;
111
- kit.logger.level = ctx.options.logLevel;
112
- await kit.buildNuxt(ctx.nuxt);
113
- kit.logger.level = prevLevel;
110
+ const { buildNuxt, logger } = await loadKit(ctx.options.rootDir);
111
+ const prevLevel = logger.level;
112
+ logger.level = ctx.options.logLevel;
113
+ await buildNuxt(ctx.nuxt);
114
+ logger.level = prevLevel;
114
115
  }
115
116
 
116
117
  async function setupBun(hooks) {
@@ -14,7 +14,7 @@ function createTestContext(options) {
14
14
  fixture: "fixture",
15
15
  configFile: "nuxt.config",
16
16
  setupTimeout: isWindows ? 24e4 : 12e4,
17
- teardownTimeout: 3e4,
17
+ teardownTimeout: isWindows ? 6e4 : 3e4,
18
18
  dev: !!JSON.parse(process.env.NUXT_TEST_DEV || "false"),
19
19
  logLevel: 1,
20
20
  server: true,
@@ -0,0 +1,67 @@
1
+ import destr from 'destr';
2
+ import { snakeCase } from 'scule';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { resolveModulePath } from 'exsolve';
5
+
6
+ function getEnv(key, opts) {
7
+ const env = opts.env ?? process.env;
8
+ const envKey = snakeCase(key).toUpperCase();
9
+ return destr(
10
+ env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey]
11
+ );
12
+ }
13
+ function _isObject(input) {
14
+ return typeof input === "object" && !Array.isArray(input);
15
+ }
16
+ function applyEnv(obj, opts, parentKey = "") {
17
+ for (const key in obj) {
18
+ const subKey = parentKey ? `${parentKey}_${key}` : key;
19
+ const envValue = getEnv(subKey, opts);
20
+ if (_isObject(obj[key])) {
21
+ if (_isObject(envValue)) {
22
+ obj[key] = { ...obj[key], ...envValue };
23
+ applyEnv(obj[key], opts, subKey);
24
+ } else if (envValue === void 0) {
25
+ applyEnv(obj[key], opts, subKey);
26
+ } else {
27
+ obj[key] = envValue ?? obj[key];
28
+ }
29
+ } else {
30
+ obj[key] = envValue ?? obj[key];
31
+ }
32
+ }
33
+ return obj;
34
+ }
35
+ async function loadKit(rootDir) {
36
+ try {
37
+ const kitPath = resolveModulePath("@nuxt/kit", { from: tryResolveNuxt(rootDir) || rootDir });
38
+ let kit = await import(pathToFileURL(kitPath).href);
39
+ if (!kit.writeTypes) {
40
+ kit = {
41
+ ...kit,
42
+ writeTypes: () => {
43
+ throw new Error("`writeTypes` is not available in this version of `@nuxt/kit`. Please upgrade to v3.7 or newer.");
44
+ }
45
+ };
46
+ }
47
+ return kit;
48
+ } catch (e) {
49
+ if (e.toString().includes("Cannot find module '@nuxt/kit'")) {
50
+ throw new Error(
51
+ "nuxi requires `@nuxt/kit` to be installed in your project. Try installing `nuxt` v3+ or `@nuxt/bridge` first."
52
+ );
53
+ }
54
+ throw e;
55
+ }
56
+ }
57
+ function tryResolveNuxt(rootDir) {
58
+ for (const pkg of ["nuxt-nightly", "nuxt", "nuxt3", "nuxt-edge"]) {
59
+ const path = resolveModulePath(pkg, { from: rootDir, try: true });
60
+ if (path) {
61
+ return path;
62
+ }
63
+ }
64
+ return null;
65
+ }
66
+
67
+ export { applyEnv as a, loadKit as l };