@nuxt/test-utils-nightly 3.20.2-20251204-114711-db7a66f → 3.20.2-20251204-151132-89cb523

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.
package/dist/config.mjs CHANGED
@@ -1,42 +1,16 @@
1
- import process$1 from 'node:process';
1
+ import process from 'node:process';
2
2
  import { defineConfig } from 'vite';
3
3
  import { setupDotenv } from 'c12';
4
4
  import { defu } from 'defu';
5
- import { createResolver, findPath, loadNuxt, buildNuxt } from '@nuxt/kit';
6
- import destr from 'destr';
7
- import { snakeCase } from 'scule';
5
+ import { createResolver, findPath } from '@nuxt/kit';
6
+ import { a as applyEnv, l as loadKit } from './shared/test-utils-nightly.G1ew4kEe.mjs';
7
+ import 'destr';
8
+ import 'scule';
9
+ import 'node:url';
10
+ import 'exsolve';
8
11
 
9
- function getEnv(key, opts) {
10
- const env = opts.env ?? process.env;
11
- const envKey = snakeCase(key).toUpperCase();
12
- return destr(
13
- env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey]
14
- );
15
- }
16
- function _isObject(input) {
17
- return typeof input === "object" && !Array.isArray(input);
18
- }
19
- function applyEnv(obj, opts, parentKey = "") {
20
- for (const key in obj) {
21
- const subKey = parentKey ? `${parentKey}_${key}` : key;
22
- const envValue = getEnv(subKey, opts);
23
- if (_isObject(obj[key])) {
24
- if (_isObject(envValue)) {
25
- obj[key] = { ...obj[key], ...envValue };
26
- applyEnv(obj[key], opts, subKey);
27
- } else if (envValue === void 0) {
28
- applyEnv(obj[key], opts, subKey);
29
- } else {
30
- obj[key] = envValue ?? obj[key];
31
- }
32
- } else {
33
- obj[key] = envValue ?? obj[key];
34
- }
35
- }
36
- return obj;
37
- }
38
-
39
- async function startNuxtAndGetViteConfig(rootDir = process$1.cwd(), options = {}) {
12
+ async function startNuxtAndGetViteConfig(rootDir = process.cwd(), options = {}) {
13
+ const { buildNuxt, loadNuxt } = await loadKit(rootDir);
40
14
  const nuxt = await loadNuxt({
41
15
  cwd: rootDir,
42
16
  dev: false,
@@ -89,7 +63,7 @@ const excludedPlugins = [
89
63
  "vite-plugin-vue-tracer"
90
64
  ];
91
65
  async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
92
- const { rootDir = process$1.cwd(), ..._overrides } = loadNuxtOptions.overrides || {};
66
+ const { rootDir = process.cwd(), ..._overrides } = loadNuxtOptions.overrides || {};
93
67
  if (!options) {
94
68
  options = await startNuxtAndGetViteConfig(rootDir, {
95
69
  dotenv: loadNuxtOptions.dotenv,
@@ -117,7 +91,7 @@ async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
117
91
  noDiscovery: true
118
92
  },
119
93
  test: {
120
- dir: process$1.cwd(),
94
+ dir: process.cwd(),
121
95
  environmentOptions: {
122
96
  nuxtRuntimeConfig: applyEnv(structuredClone(options.nuxt.options.runtimeConfig), {
123
97
  prefix: "NUXT_",
@@ -214,14 +188,14 @@ async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
214
188
  return resolvedConfig;
215
189
  }
216
190
  async function defineVitestProject(config) {
217
- if (process$1.env.__NUXT_VITEST_RESOLVED__) return config;
191
+ if (process.env.__NUXT_VITEST_RESOLVED__) return config;
218
192
  const resolvedConfig = await resolveConfig(config);
219
193
  resolvedConfig.test.environment = "nuxt";
220
194
  return resolvedConfig;
221
195
  }
222
196
  function defineVitestConfig(config = {}) {
223
197
  return defineConfig(async () => {
224
- if (process$1.env.__NUXT_VITEST_RESOLVED__) return config;
198
+ if (process.env.__NUXT_VITEST_RESOLVED__) return config;
225
199
  const resolvedConfig = await resolveConfig(config);
226
200
  if (resolvedConfig.test.browser?.enabled) {
227
201
  return resolvedConfig;
package/dist/e2e.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { b as buildFixture, c as createBrowser, a as createPage, d as createTest, g as getBrowser, l as loadFixture, e as setup, s as setupMaps, w as waitForHydration } from './shared/test-utils-nightly.C0HvikGn.mjs';
1
+ export { b as buildFixture, c as createBrowser, a as createPage, d as createTest, g as getBrowser, l as loadFixture, e as setup, s as setupMaps, w as waitForHydration } from './shared/test-utils-nightly.DqnRFuuO.mjs';
2
2
  import { u as useTestContext } from './shared/test-utils-nightly.CtwoJP76.mjs';
3
3
  export { $ as $fetch, c as createTestContext, e as exposeContextToEnv, f as fetch, i as isDev, r as recoverContextFromEnv, s as setTestContext, a as startServer, b as stopServer, d as url } from './shared/test-utils-nightly.CtwoJP76.mjs';
4
4
  import { consola } from 'consola';
@@ -7,7 +7,11 @@ import { distDir } from '#dirs';
7
7
  import 'node:fs';
8
8
  import 'node:path';
9
9
  import 'defu';
10
- import '@nuxt/kit';
10
+ import './shared/test-utils-nightly.G1ew4kEe.mjs';
11
+ import 'destr';
12
+ import 'scule';
13
+ import 'node:url';
14
+ import 'exsolve';
11
15
  import 'tinyexec';
12
16
  import 'get-port-please';
13
17
  import 'ofetch';
package/dist/module.mjs CHANGED
@@ -1,21 +1,23 @@
1
1
  import { pathToFileURL } from 'node:url';
2
- import { useNuxt, resolveIgnorePatterns, addVitePlugin, defineNuxtModule, createResolver, resolvePath, importModule, logger } from '@nuxt/kit';
2
+ import { resolveIgnorePatterns, defineNuxtModule, createResolver, resolvePath, importModule, logger } from '@nuxt/kit';
3
3
  import { getPort } from 'get-port-please';
4
4
  import { h } from 'vue';
5
5
  import { debounce } from 'perfect-debounce';
6
6
  import { isCI } from 'std-env';
7
7
  import { defu } from 'defu';
8
+ import { extname, join, dirname, relative } from 'pathe';
8
9
  import { getVitestConfigFromNuxt } from './config.mjs';
9
10
  import { walk } from 'estree-walker';
10
11
  import MagicString from 'magic-string';
11
12
  import { createUnplugin } from 'unplugin';
13
+ import { l as loadKit } from './shared/test-utils-nightly.G1ew4kEe.mjs';
12
14
  import { readFileSync } from 'node:fs';
13
- import { extname, join, dirname, relative } from 'pathe';
14
15
  import 'node:process';
15
16
  import 'vite';
16
17
  import 'c12';
17
18
  import 'destr';
18
19
  import 'scule';
20
+ import 'exsolve';
19
21
 
20
22
  const PLUGIN_NAME$1 = "nuxt:vitest:mock-transform";
21
23
  const HELPER_MOCK_IMPORT = "mockNuxtImport";
@@ -67,16 +69,16 @@ const createMockPlugin = (ctx) => createUnplugin(() => {
67
69
  startOf(node)
68
70
  );
69
71
  }
70
- const importName = node.arguments[0];
71
- if (!isLiteral(importName) || typeof importName.value !== "string") {
72
+ const importTarget = node.arguments[0];
73
+ const name = isLiteral(importTarget) ? importTarget.value : isIdentifier(importTarget) ? importTarget.name : void 0;
74
+ if (typeof name !== "string") {
72
75
  return this.error(
73
76
  new Error(
74
- `The first argument of ${HELPER_MOCK_IMPORT}() must be a string literal`
77
+ `The first argument of ${HELPER_MOCK_IMPORT}() must be a string literal or mocked target`
75
78
  ),
76
- startOf(importName)
79
+ startOf(importTarget)
77
80
  );
78
81
  }
79
- const name = importName.value;
80
82
  const importItem = ctx.imports.find((_) => name === (_.as || _.name));
81
83
  if (!importItem) {
82
84
  console.log({ imports: ctx.imports });
@@ -262,8 +264,8 @@ function endOf(node) {
262
264
  return "range" in node && node.range ? node.range[1] : "end" in node ? node.end : void 0;
263
265
  }
264
266
 
265
- function setupImportMocking() {
266
- const nuxt = useNuxt();
267
+ async function setupImportMocking(nuxt) {
268
+ const { addVitePlugin } = await loadKit(nuxt.options.rootDir);
267
269
  const ctx = {
268
270
  components: [],
269
271
  imports: []
@@ -328,8 +330,9 @@ const module$1 = defineNuxtModule({
328
330
  },
329
331
  async setup(options, nuxt) {
330
332
  if (nuxt.options.test || nuxt.options.dev) {
331
- setupImportMocking();
333
+ await setupImportMocking(nuxt);
332
334
  }
335
+ const { addVitePlugin } = await loadKit(nuxt.options.rootDir);
333
336
  const resolver = createResolver(import.meta.url);
334
337
  addVitePlugin(NuxtRootStubPlugin.vite({
335
338
  entry: await resolvePath("#app/entry", { alias: nuxt.options.alias }),
@@ -1,16 +1,20 @@
1
1
  import defu from 'defu';
2
2
  import { test as test$1 } from '@playwright/test';
3
3
  export { expect } from '@playwright/test';
4
- import { w as waitForHydration, d as createTest } from './shared/test-utils-nightly.C0HvikGn.mjs';
4
+ import { w as waitForHydration, d as createTest } from './shared/test-utils-nightly.DqnRFuuO.mjs';
5
5
  import 'node:path';
6
6
  import 'ufo';
7
7
  import 'std-env';
8
8
  import 'consola';
9
9
  import 'node:fs';
10
- import '@nuxt/kit';
10
+ import 'destr';
11
+ import 'scule';
12
+ import 'node:url';
13
+ import 'exsolve';
11
14
  import { d as url } from './shared/test-utils-nightly.CtwoJP76.mjs';
12
15
  import 'pathe';
13
16
  import '#dirs';
17
+ import './shared/test-utils-nightly.G1ew4kEe.mjs';
14
18
  import 'tinyexec';
15
19
  import 'get-port-please';
16
20
  import 'ofetch';
@@ -1,13 +1,12 @@
1
- import * as _kit from "@nuxt/kit";
1
+ import { consola } from "consola";
2
2
  import { createTest, exposeContextToEnv } from "@nuxt/test-utils/e2e";
3
- const kit = _kit.default || _kit;
4
3
  const options = JSON.parse(process.env.NUXT_TEST_OPTIONS || "{}");
5
4
  const hooks = createTest(options);
6
5
  export const setup = async () => {
7
- kit.logger.info("Building Nuxt app...");
6
+ consola.info("Building Nuxt app...");
8
7
  await hooks.beforeAll();
9
8
  exposeContextToEnv();
10
- kit.logger.info("Running tests...");
9
+ consola.info("Running tests...");
11
10
  };
12
11
  export const teardown = async () => {
13
12
  await hooks.afterAll();
@@ -2,8 +2,7 @@ import { EventHandler, HTTPMethod } from 'h3';
2
2
  import { SetupContext, RenderFunction, ComputedOptions, MethodOptions, ComponentOptionsMixin, EmitsOptions, ComponentInjectOptions, ComponentOptionsWithoutProps, ComponentOptionsWithArrayProps, ComponentPropsOptions, ComponentOptionsWithObjectProps } from 'vue';
3
3
  import { ComponentMountingOptions, mount } from '@vue/test-utils';
4
4
  import { RouteLocationRaw } from 'vue-router';
5
- import * as _testing_library_vue from '@testing-library/vue';
6
- import { RenderOptions as RenderOptions$1 } from '@testing-library/vue';
5
+ import { RenderOptions, RenderResult } from '@testing-library/vue';
7
6
 
8
7
  type Awaitable<T> = T | Promise<T>;
9
8
  type OptionalFunction<T> = T | (() => Awaitable<T>);
@@ -37,7 +36,7 @@ declare function registerEndpoint(url: string, options: EventHandler | {
37
36
  }): () => void;
38
37
  /**
39
38
  * `mockNuxtImport` allows you to mock Nuxt's auto import functionality.
40
- * @param _name - name of an import to mock.
39
+ * @param _target - name of an import to mock or mocked target.
41
40
  * @param _factory - factory function that returns mocked import.
42
41
  * @example
43
42
  * ```ts
@@ -48,10 +47,17 @@ declare function registerEndpoint(url: string, options: EventHandler | {
48
47
  * return { value: 'mocked storage' }
49
48
  * }
50
49
  * })
50
+ *
51
+ * // With mocked target
52
+ * mockNuxtImport(useStorage, () => {
53
+ * return () => {
54
+ * return { value: 'mocked storage' }
55
+ * }
56
+ * })
51
57
  * ```
52
58
  * @see https://nuxt.com/docs/getting-started/testing#mocknuxtimport
53
59
  */
54
- declare function mockNuxtImport<T = unknown>(_name: string, _factory: () => T | Promise<T>): void;
60
+ declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: () => T | Promise<T>): void;
55
61
  /**
56
62
  * `mockComponent` allows you to mock Nuxt's component.
57
63
  * @param path - component name in PascalCase, or the relative path of the component.
@@ -93,6 +99,9 @@ type MountSuspendedOptions<T> = ComponentMountingOptions<T> & {
93
99
  route?: RouteLocationRaw;
94
100
  scoped?: boolean;
95
101
  };
102
+ type MountSuspendedResult<T> = ReturnType<typeof mount<T>> & {
103
+ setupState: SetupState$1;
104
+ };
96
105
  type SetupState$1 = Record<string, any>;
97
106
  /**
98
107
  * `mountSuspended` allows you to mount any vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins. For example:
@@ -120,16 +129,17 @@ type SetupState$1 = Record<string, any>;
120
129
  * @param component the component to be tested
121
130
  * @param options optional options to set up your component
122
131
  */
123
- declare function mountSuspended<T>(component: T, options?: MountSuspendedOptions<T>): Promise<ReturnType<typeof mount<T>> & {
124
- setupState: SetupState$1;
125
- }>;
132
+ declare function mountSuspended<T>(component: T, options?: MountSuspendedOptions<T>): Promise<MountSuspendedResult<T>>;
126
133
  declare global {
127
134
  var __cleanup: Array<() => void> | undefined;
128
135
  }
129
136
 
130
- type RenderOptions<C = unknown> = RenderOptions$1<C> & {
137
+ type RenderSuspendeOptions<T> = RenderOptions<T> & {
131
138
  route?: RouteLocationRaw;
132
139
  };
140
+ type RenderSuspendeResult = RenderResult & {
141
+ setupState: SetupState;
142
+ };
133
143
  type SetupState = Record<string, any>;
134
144
  /**
135
145
  * `renderSuspended` allows you to mount any vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins.
@@ -161,9 +171,7 @@ type SetupState = Record<string, any>;
161
171
  * @param component the component to be tested
162
172
  * @param options optional options to set up your component
163
173
  */
164
- declare function renderSuspended<T>(component: T, options?: RenderOptions<T>): Promise<_testing_library_vue.RenderResult & {
165
- setupState: SetupState;
166
- }>;
174
+ declare function renderSuspended<T>(component: T, options?: RenderSuspendeOptions<T>): Promise<RenderSuspendeResult>;
167
175
  declare global {
168
176
  interface Window {
169
177
  __cleanup?: Array<() => void>;
@@ -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, effectScope, unref, isReadonly, getCurrentInstance, isRef, defineComponent as defineComponent$1 } 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';
@@ -43,7 +43,7 @@ function registerEndpoint(url, options) {
43
43
  }
44
44
  };
45
45
  }
46
- function mockNuxtImport(_name, _factory) {
46
+ function mockNuxtImport(_target, _factory) {
47
47
  throw new Error(
48
48
  "mockNuxtImport() is a macro and it did not get transpiled. This may be an internal bug of @nuxt/test-utils."
49
49
  );
@@ -100,27 +100,11 @@ async function mountSuspended(component, options) {
100
100
  cleanupFunction();
101
101
  }
102
102
  const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
103
- const { render, setup, data, computed, methods, ...componentRest } = component;
103
+ const { render, setup, ...componentRest } = component;
104
+ let wrappedInstance = null;
104
105
  let setupContext;
105
106
  let setupState;
106
107
  const setProps = reactive({});
107
- let interceptedEmit = null;
108
- function getInterceptedEmitFunction(emit) {
109
- if (emit !== interceptedEmit) {
110
- interceptedEmit = interceptedEmit ?? ((event, ...args) => {
111
- emit(event, ...args);
112
- setupContext.emit(event, ...args);
113
- });
114
- }
115
- return interceptedEmit;
116
- }
117
- function interceptEmitOnCurrentInstance() {
118
- const currentInstance = getCurrentInstance();
119
- if (!currentInstance) {
120
- return;
121
- }
122
- currentInstance.emit = getInterceptedEmitFunction(currentInstance.emit);
123
- }
124
108
  function patchInstanceAppContext() {
125
109
  const app = getCurrentInstance()?.appContext.app;
126
110
  if (!app) return;
@@ -129,11 +113,14 @@ async function mountSuspended(component, options) {
129
113
  app[key] = value;
130
114
  }
131
115
  }
132
- let passedProps;
133
116
  let componentScope = null;
134
- const wrappedSetup = async (props2, setupContext2) => {
135
- interceptEmitOnCurrentInstance();
136
- passedProps = props2;
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
+ }
137
124
  if (setup) {
138
125
  let result;
139
126
  if (options?.scoped) {
@@ -148,6 +135,9 @@ async function mountSuspended(component, options) {
148
135
  } else {
149
136
  result = await setup(props2, setupContext2);
150
137
  }
138
+ if (wrappedInstance?.exposed) {
139
+ instanceContext.expose(wrappedInstance.exposed);
140
+ }
151
141
  setupState = result && typeof result === "object" ? result : {};
152
142
  return result;
153
143
  }
@@ -157,8 +147,10 @@ async function mountSuspended(component, options) {
157
147
  const vm = mount(
158
148
  {
159
149
  __cssModules: componentRest.__cssModules,
150
+ inheritAttrs: false,
160
151
  setup: (props2, ctx) => {
161
152
  patchInstanceAppContext();
153
+ wrappedInstance = getCurrentInstance();
162
154
  setupContext = ctx;
163
155
  if (options?.scoped) {
164
156
  const scope = effectScope();
@@ -179,7 +171,7 @@ async function mountSuspended(component, options) {
179
171
  });
180
172
  }
181
173
  },
182
- render: (renderContext) => h$1(
174
+ render: () => h$1(
183
175
  Suspense,
184
176
  {
185
177
  onResolve: () => nextTick().then(() => {
@@ -197,56 +189,12 @@ async function mountSuspended(component, options) {
197
189
  const router = useRouter();
198
190
  await router.replace(route);
199
191
  const clonedComponent = {
200
- name: "MountSuspendedComponent",
192
+ components: {},
201
193
  ...component,
202
- render: render ? function(_ctx, ...args) {
203
- interceptEmitOnCurrentInstance();
204
- if (data && typeof data === "function") {
205
- const dataObject = data();
206
- for (const key in dataObject) {
207
- renderContext[key] = dataObject[key];
208
- }
209
- }
210
- for (const key in setupState || {}) {
211
- const warn = console.warn;
212
- console.warn = () => {
213
- };
214
- try {
215
- renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
216
- } catch {
217
- } finally {
218
- console.warn = warn;
219
- }
220
- if (key === "props") {
221
- renderContext[key] = cloneProps$1(renderContext[key]);
222
- }
223
- }
224
- const propsContext = "props" in renderContext ? renderContext.props : renderContext;
225
- for (const key in props || {}) {
226
- propsContext[key] = _ctx[key];
227
- }
228
- for (const key in passedProps || {}) {
229
- propsContext[key] = passedProps[key];
230
- }
231
- if (methods && typeof methods === "object") {
232
- for (const [key, value] of Object.entries(methods)) {
233
- renderContext[key] = value.bind(renderContext);
234
- }
235
- }
236
- if (computed && typeof computed === "object") {
237
- for (const [key, value] of Object.entries(computed)) {
238
- if ("get" in value) {
239
- renderContext[key] = value.get.call(renderContext);
240
- } else {
241
- renderContext[key] = value.call(renderContext);
242
- }
243
- }
244
- }
245
- return render.call(this, renderContext, ...args);
246
- } : void 0,
247
- setup: (props2) => wrappedSetup(props2, setupContext)
194
+ name: "MountSuspendedComponent",
195
+ setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
248
196
  };
249
- return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, slots);
197
+ return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, setupContext.slots);
250
198
  }
251
199
  })
252
200
  }
@@ -255,6 +203,7 @@ async function mountSuspended(component, options) {
255
203
  defu(
256
204
  _options,
257
205
  {
206
+ props,
258
207
  slots,
259
208
  attrs,
260
209
  global: {
@@ -282,58 +231,46 @@ async function mountSuspended(component, options) {
282
231
  }
283
232
  );
284
233
  }
285
- function cloneProps$1(props) {
286
- const newProps = reactive({});
287
- for (const key in props) {
288
- newProps[key] = props[key];
289
- }
290
- return newProps;
291
- }
292
234
  function wrappedMountedWrapper(wrapper) {
293
- const proxy = new Proxy(wrapper, {
294
- get: (target, prop, receiver) => {
295
- if (prop === "element") {
296
- const component = target.findComponent({ name: "MountSuspendedComponent" });
297
- return component[prop];
298
- } else if (prop === "vm") {
299
- const vm = Reflect.get(target, prop, receiver);
300
- return createVMProxy(vm, wrapper.setupState);
301
- } else {
302
- return Reflect.get(target, prop, receiver);
303
- }
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;
304
248
  }
305
249
  });
306
- for (const key of ["props"]) {
307
- proxy[key] = new Proxy(wrapper[key], {
308
- apply: (target, thisArg, args) => {
309
- const component = thisArg.findComponent({ name: "MountSuspendedComponent" });
310
- 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;
311
271
  }
312
272
  });
313
273
  }
314
- return proxy;
315
- }
316
- function createVMProxy(vm, setupState) {
317
- return new Proxy(vm, {
318
- get(target, key, receiver) {
319
- const value = Reflect.get(target, key, receiver);
320
- if (setupState && typeof setupState === "object" && key in setupState) {
321
- return unref(setupState[key]);
322
- }
323
- return value;
324
- },
325
- set(target, key, value, receiver) {
326
- if (setupState && typeof setupState === "object" && key in setupState) {
327
- const setupValue = setupState[key];
328
- if (setupValue && isRef(setupValue)) {
329
- setupValue.value = value;
330
- return true;
331
- }
332
- return Reflect.set(setupState, key, value, receiver);
333
- }
334
- return Reflect.set(target, key, value, receiver);
335
- }
336
- });
337
274
  }
338
275
 
339
276
  const WRAPPER_EL_ID = "test-wrapper";
@@ -347,27 +284,11 @@ async function renderSuspended(component, options) {
347
284
  } = options || {};
348
285
  const { render: renderFromTestingLibrary } = await import('@testing-library/vue');
349
286
  const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
350
- const { render, setup, data, computed, methods, ...componentRest } = component;
287
+ const { render, setup, ...componentRest } = component;
288
+ let wrappedInstance = null;
351
289
  let setupContext;
352
290
  let setupState;
353
291
  const setProps = reactive({});
354
- let interceptedEmit = null;
355
- function getInterceptedEmitFunction(emit) {
356
- if (emit !== interceptedEmit) {
357
- interceptedEmit = interceptedEmit ?? ((event, ...args) => {
358
- emit(event, ...args);
359
- setupContext.emit(event, ...args);
360
- });
361
- }
362
- return interceptedEmit;
363
- }
364
- function interceptEmitOnCurrentInstance() {
365
- const currentInstance = getCurrentInstance();
366
- if (!currentInstance) {
367
- return;
368
- }
369
- currentInstance.emit = getInterceptedEmitFunction(currentInstance.emit);
370
- }
371
292
  function patchInstanceAppContext() {
372
293
  const app = getCurrentInstance()?.appContext.app;
373
294
  if (!app) return;
@@ -380,13 +301,19 @@ async function renderSuspended(component, options) {
380
301
  fn();
381
302
  }
382
303
  document.querySelector(`#${WRAPPER_EL_ID}`)?.remove();
383
- let passedProps;
384
- const wrappedSetup = async (props2, setupContext2) => {
385
- interceptEmitOnCurrentInstance();
386
- 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
+ }
387
311
  if (setup) {
388
312
  const result = await setup(props2, setupContext2);
389
313
  setupState = result && typeof result === "object" ? result : {};
314
+ if (wrappedInstance?.exposed) {
315
+ instanceContext.expose(wrappedInstance.exposed);
316
+ }
390
317
  return result;
391
318
  }
392
319
  };
@@ -400,8 +327,10 @@ async function renderSuspended(component, options) {
400
327
  const utils = renderFromTestingLibrary(
401
328
  {
402
329
  __cssModules: componentRest.__cssModules,
330
+ inheritAttrs: false,
403
331
  setup: (props2, ctx) => {
404
332
  patchInstanceAppContext();
333
+ wrappedInstance = getCurrentInstance();
405
334
  setupContext = ctx;
406
335
  const scope = effectScope();
407
336
  window.__cleanup ||= [];
@@ -413,7 +342,7 @@ async function renderSuspended(component, options) {
413
342
  expose: () => ({})
414
343
  }));
415
344
  },
416
- render: (renderContext) => (
345
+ render: () => (
417
346
  // See discussions in https://github.com/testing-library/vue-testing-library/issues/230
418
347
  // we add this additional root element because otherwise testing-library breaks
419
348
  // because there's no root element while Suspense is resolving
@@ -440,56 +369,13 @@ async function renderSuspended(component, options) {
440
369
  const router = useRouter();
441
370
  await router.replace(route);
442
371
  const clonedComponent = {
443
- name: "RenderSuspendedComponent",
372
+ components: {},
444
373
  ...component,
445
- render: render ? function(_ctx, ...args) {
446
- interceptEmitOnCurrentInstance();
447
- if (data && typeof data === "function") {
448
- const dataObject = data();
449
- for (const key in dataObject) {
450
- renderContext[key] = dataObject[key];
451
- }
452
- }
453
- for (const key in setupState || {}) {
454
- const warn = console.warn;
455
- console.warn = () => {
456
- };
457
- try {
458
- renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
459
- } catch {
460
- } finally {
461
- console.warn = warn;
462
- }
463
- if (key === "props") {
464
- renderContext[key] = cloneProps(renderContext[key]);
465
- }
466
- }
467
- const propsContext = "props" in renderContext ? renderContext.props : renderContext;
468
- for (const key in props || {}) {
469
- propsContext[key] = _ctx[key];
470
- }
471
- for (const key in passedProps || {}) {
472
- propsContext[key] = passedProps[key];
473
- }
474
- if (methods && typeof methods === "object") {
475
- for (const [key, value] of Object.entries(methods)) {
476
- renderContext[key] = value.bind(renderContext);
477
- }
478
- }
479
- if (computed && typeof computed === "object") {
480
- for (const [key, value] of Object.entries(computed)) {
481
- if ("get" in value) {
482
- renderContext[key] = value.get.call(renderContext);
483
- } else {
484
- renderContext[key] = value.call(renderContext);
485
- }
486
- }
487
- }
488
- return render.call(this, renderContext, ...args);
489
- } : void 0,
490
- setup: (props2) => wrappedSetup(props2, setupContext)
374
+ name: "RenderSuspendedComponent",
375
+ render,
376
+ setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
491
377
  };
492
- return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...setProps, ...attrs }, slots);
378
+ return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...setProps, ...attrs }, setupContext.slots);
493
379
  }
494
380
  })
495
381
  }
@@ -499,6 +385,7 @@ async function renderSuspended(component, options) {
499
385
  )
500
386
  },
501
387
  defu(_options, {
388
+ props,
502
389
  slots,
503
390
  attrs,
504
391
  global: {
@@ -519,12 +406,5 @@ async function renderSuspended(component, options) {
519
406
  );
520
407
  });
521
408
  }
522
- function cloneProps(props) {
523
- const newProps = reactive({});
524
- for (const key in props) {
525
- newProps[key] = props[key];
526
- }
527
- return newProps;
528
- }
529
409
 
530
410
  export { mockComponent, mockNuxtImport, mountSuspended, registerEndpoint, renderSuspended };
@@ -2,7 +2,7 @@ import { u as useTestContext, d as url, c as createTestContext, a as startServer
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-nightly.G1ew4kEe.mjs';
6
6
 
7
7
  async function createBrowser() {
8
8
  const ctx = useTestContext();
@@ -57,7 +57,6 @@ async function waitForHydration(page, url2, waitUntil) {
57
57
  }
58
58
  }
59
59
 
60
- const kit = _kit.default || _kit;
61
60
  const isNuxtApp = (dir) => {
62
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
  };
@@ -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) {
@@ -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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/test-utils-nightly",
3
- "version": "3.20.2-20251204-114711-db7a66f",
3
+ "version": "3.20.2-20251204-151132-89cb523",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/nuxt/test-utils.git"
@@ -67,12 +67,13 @@
67
67
  "dev:prepare": "nuxt prepare && unbuild --stub && pnpm -r dev:prepare"
68
68
  },
69
69
  "dependencies": {
70
- "@nuxt/kit": "^4.1.3",
71
- "c12": "^3.3.1",
70
+ "@nuxt/kit": "^3.20.1",
71
+ "c12": "^3.3.2",
72
72
  "consola": "^3.4.2",
73
73
  "defu": "^6.1.4",
74
74
  "destr": "^2.0.5",
75
75
  "estree-walker": "^3.0.3",
76
+ "exsolve": "^1.0.8",
76
77
  "fake-indexeddb": "^6.2.5",
77
78
  "get-port-please": "^3.2.0",
78
79
  "h3": "^1.15.4",
@@ -88,19 +89,19 @@
88
89
  "std-env": "^3.10.0",
89
90
  "tinyexec": "^1.0.2",
90
91
  "ufo": "^1.6.1",
91
- "unplugin": "^2.3.10",
92
+ "unplugin": "^2.3.11",
92
93
  "vitest-environment-nuxt": "^1.0.1",
93
- "vue": "^3.5.24"
94
+ "vue": "^3.5.25"
94
95
  },
95
96
  "devDependencies": {
96
- "@cucumber/cucumber": "12.2.0",
97
+ "@cucumber/cucumber": "12.3.0",
97
98
  "@jest/globals": "30.2.0",
98
99
  "@nuxt/devtools-kit": "2.7.0",
99
- "@nuxt/eslint-config": "1.10.0",
100
- "@nuxt/schema": "4.1.3",
101
- "@playwright/test": "1.56.1",
100
+ "@nuxt/eslint-config": "1.11.0",
101
+ "@nuxt/schema": "4.2.1",
102
+ "@playwright/test": "1.57.0",
102
103
  "@testing-library/vue": "8.1.0",
103
- "@types/bun": "1.3.2",
104
+ "@types/bun": "1.3.3",
104
105
  "@types/estree": "1.0.8",
105
106
  "@types/jsdom": "27.0.0",
106
107
  "@types/node": "latest",
@@ -110,24 +111,25 @@
110
111
  "compatx": "0.2.0",
111
112
  "eslint": "9.39.1",
112
113
  "installed-check": "9.3.0",
113
- "knip": "5.68.0",
114
+ "knip": "5.71.0",
114
115
  "nitropack": "2.12.9",
115
- "nuxt": "4.1.3",
116
- "pkg-pr-new": "0.0.60",
117
- "playwright-core": "1.56.1",
118
- "rollup": "4.53.2",
116
+ "nuxt": "4.2.1",
117
+ "pkg-pr-new": "0.0.62",
118
+ "playwright-core": "1.57.0",
119
+ "rollup": "4.53.3",
119
120
  "semver": "7.7.3",
120
121
  "typescript": "5.9.3",
121
122
  "unbuild": "latest",
122
123
  "unimport": "5.5.0",
123
- "vite": "7.2.2",
124
+ "vite": "7.2.6",
124
125
  "vitest": "3.2.4",
125
126
  "vue-router": "4.6.3",
126
- "vue-tsc": "3.1.3"
127
+ "vue-tsc": "3.1.5"
127
128
  },
128
129
  "peerDependencies": {
129
130
  "@cucumber/cucumber": "^10.3.1 || >=11.0.0",
130
131
  "@jest/globals": "^29.5.0 || >=30.0.0",
132
+ "@nuxt/kit": ">=3.20.1",
131
133
  "@playwright/test": "^1.43.1",
132
134
  "@testing-library/vue": "^7.0.0 || ^8.0.1",
133
135
  "@vue/test-utils": "^2.4.2",
@@ -143,6 +145,9 @@
143
145
  "@jest/globals": {
144
146
  "optional": true
145
147
  },
148
+ "@nuxt/kit": {
149
+ "optional": true
150
+ },
146
151
  "@playwright/test": {
147
152
  "optional": true
148
153
  },
@@ -169,19 +174,18 @@
169
174
  }
170
175
  },
171
176
  "resolutions": {
172
- "@cucumber/cucumber": "12.2.0",
173
- "@nuxt/kit": "^4.1.3",
174
- "@nuxt/schema": "4.1.3",
177
+ "@cucumber/cucumber": "12.3.0",
178
+ "@nuxt/schema": "4.2.1",
175
179
  "@nuxt/test-utils": "workspace:*",
176
- "@types/node": "22.18.8",
177
- "rollup": "4.53.2",
178
- "vite": "7.2.2",
180
+ "@types/node": "24.10.1",
181
+ "rollup": "4.53.3",
182
+ "vite": "7.2.6",
179
183
  "vite-node": "3.2.4",
180
184
  "vitest": "3.2.4",
181
- "vue": "^3.5.24"
185
+ "vue": "^3.5.25"
182
186
  },
183
187
  "engines": {
184
188
  "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
185
189
  },
186
- "packageManager": "pnpm@10.21.0"
190
+ "packageManager": "pnpm@10.24.0"
187
191
  }