@nuxt/test-utils 3.23.0 → 4.0.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.
package/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  # Nuxt Test Utils
2
2
 
3
+ [![nuxt.care health](https://img.shields.io/endpoint?url=https://nuxt.care/api/badge/test-utils)](https://nuxt.care/?search=test-utils)
4
+
5
+
3
6
  🧪 [Nuxt](https://nuxt.com/) Test Utils
4
7
 
5
8
  ## Overview
package/dist/config.d.mts CHANGED
@@ -57,11 +57,6 @@ declare module 'vitest/node' {
57
57
  nuxt?: NuxtEnvironmentOptions;
58
58
  }
59
59
  }
60
- declare module 'vitest' {
61
- interface EnvironmentOptions {
62
- nuxt?: NuxtEnvironmentOptions;
63
- }
64
- }
65
60
 
66
61
  export { defineVitestConfig, defineVitestProject, getVitestConfigFromNuxt };
67
62
  export type { NuxtEnvironmentOptions };
package/dist/config.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import process from 'node:process';
2
2
  import { defineConfig } from 'vite';
3
3
  import { setupDotenv } from 'c12';
4
- import { defu } from 'defu';
4
+ import { createDefu, defu } from 'defu';
5
5
  import { createResolver, findPath } from '@nuxt/kit';
6
6
  import { resolveModulePath } from 'exsolve';
7
7
  import { getPackageInfoSync } from 'local-pkg';
@@ -241,25 +241,29 @@ function defineVitestConfig(config = {}) {
241
241
  }
242
242
  const defaultEnvironment = resolvedConfig.test.environment || "node";
243
243
  if (defaultEnvironment !== "nuxt") {
244
- const key = "projects" in resolvedConfig.test ? "projects" : "workspace" in resolvedConfig.test ? "workspace" : await import('vitest/package.json', { with: { type: 'json' } }).then((r) => {
245
- const [major, minor] = (r.default || r).version.split(".");
246
- return Number.parseInt(major, 10) > 3 || Number.parseInt(major, 10) === 3 && Number.parseInt(minor, 10) >= 2;
247
- }) ? "projects" : "workspace";
248
- resolvedConfig.test[key] = [];
249
- resolvedConfig.test[key].push({
250
- extends: true,
244
+ const merge = createDefu((obj, key, value) => {
245
+ if (Array.isArray(value) && Array.isArray(obj[key])) {
246
+ obj[key] = [.../* @__PURE__ */ new Set([...value, ...obj[key]])];
247
+ return true;
248
+ }
249
+ });
250
+ const nuxtProject = merge({
251
+ ...resolvedConfig,
251
252
  test: {
253
+ ...resolvedConfig.test,
252
254
  name: "nuxt",
253
255
  environment: "nuxt",
254
- include: [
255
- "**/*.nuxt.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
256
- "{test,tests}/nuxt/**.*"
257
- ]
256
+ include: []
258
257
  }
259
- });
260
- resolvedConfig.test[key].push({
261
- extends: true,
258
+ }, resolvedConfig);
259
+ nuxtProject.test.include = [
260
+ "**/*.nuxt.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}",
261
+ "{test,tests}/nuxt/**.*"
262
+ ];
263
+ const defaultProject = merge({
264
+ ...resolvedConfig,
262
265
  test: {
266
+ ...resolvedConfig.test,
263
267
  name: defaultEnvironment,
264
268
  environment: defaultEnvironment,
265
269
  exclude: [
@@ -272,7 +276,10 @@ function defineVitestConfig(config = {}) {
272
276
  "./{test,tests}/nuxt/**.*"
273
277
  ]
274
278
  }
275
- });
279
+ }, resolvedConfig);
280
+ resolvedConfig.test = {
281
+ projects: [nuxtProject, defaultProject]
282
+ };
276
283
  }
277
284
  return resolvedConfig;
278
285
  });
package/dist/e2e.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { T as TestOptions, b as TestContext, a as TestHooks } from './shared/test-utils.C9GKP_T5.mjs';
2
- export { $ as $fetch, G as GotoOptions, N as NuxtPage, S as StartServerOptions, h as TestRunner, c as createBrowser, d as createPage, f as fetch, g as getBrowser, s as startServer, e as stopServer, u as url, w as waitForHydration } from './shared/test-utils.C9GKP_T5.mjs';
1
+ import { T as TestOptions, b as TestContext, a as TestHooks } from './shared/test-utils.BLyxqr96.mjs';
2
+ export { $ as $fetch, G as GotoOptions, N as NuxtPage, S as StartServerOptions, c as TestRunner, d as createBrowser, e as createPage, f as fetch, g as getBrowser, s as startServer, h as stopServer, u as url, w as waitForHydration } from './shared/test-utils.BLyxqr96.mjs';
3
3
  import { LogType } from 'consola';
4
4
  import 'playwright-core';
5
5
  import '@nuxt/schema';
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.5cnw0YZR.mjs';
1
+ export { b as buildFixture, c as createBrowser, a as createPage, d as createTest, g as getBrowser, l as loadFixture, s as setup, e as setupMaps, w as waitForHydration } from './shared/test-utils.E_cAGA8l.mjs';
2
2
  import { u as useTestContext } from './shared/test-utils.BsmyE2FA.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.BsmyE2FA.mjs';
4
4
  import { consola } from 'consola';
package/dist/module.d.mts CHANGED
@@ -1,10 +1,10 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
- import { UserConfig } from 'vitest/node';
2
+ import { TestUserConfig } from 'vitest/config';
3
3
 
4
4
  interface NuxtVitestOptions {
5
5
  startOnBoot?: boolean;
6
6
  logToConsole?: boolean;
7
- vitestConfig?: UserConfig;
7
+ vitestConfig?: TestUserConfig;
8
8
  }
9
9
  declare const _default: _nuxt_schema.NuxtModule<NuxtVitestOptions, NuxtVitestOptions, false>;
10
10
 
package/dist/module.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { resolveIgnorePatterns, logger, useNuxt, addDevServerHandler, defineNuxtModule, createResolver, resolvePath } from '@nuxt/kit';
1
+ import { resolveIgnorePatterns, logger, addDevServerHandler, useNuxt, defineNuxtModule, createResolver, resolvePath } from '@nuxt/kit';
2
+ import { refreshCustomTabs, addCustomTab, onDevToolsInitialized } from '@nuxt/devtools-kit';
2
3
  import { extname, join, dirname, relative, resolve } from 'pathe';
3
4
  import { isCI, hasTTY, provider } from 'std-env';
4
5
  import { walk } from 'estree-walker';
@@ -13,7 +14,7 @@ import { detectPackageManager, addDependency } from 'nypm';
13
14
  import { h } from 'vue';
14
15
  import { debounce } from 'perfect-debounce';
15
16
  import { fork } from 'node:child_process';
16
- import { c as createVitestTestSummary, l as listenCliMessages, s as sendMessageToCli } from './shared/test-utils.DDUpsMYL.mjs';
17
+ import { l as listenCliMessages, s as sendMessageToCli, c as createVitestTestSummary } from './shared/test-utils.DDUpsMYL.mjs';
17
18
  import { distDir } from '#dirs';
18
19
  import 'destr';
19
20
  import 'scule';
@@ -24,6 +25,7 @@ const PLUGIN_NAME$1 = "nuxt:vitest:mock-transform";
24
25
  const HELPER_MOCK_IMPORT = "mockNuxtImport";
25
26
  const HELPER_MOCK_COMPONENT = "mockComponent";
26
27
  const HELPER_MOCK_HOIST = "__NUXT_VITEST_MOCKS";
28
+ const HELPER_MOCK_HOIST_ORIGINAL = "__NUXT_VITEST_MOCKS_ORIGINAL";
27
29
  const HELPERS_NAME = [HELPER_MOCK_IMPORT, HELPER_MOCK_COMPONENT];
28
30
  const createMockPlugin = (ctx) => createUnplugin(() => {
29
31
  function transform(code, id) {
@@ -150,25 +152,23 @@ const createMockPlugin = (ctx) => createUnplugin(() => {
150
152
  ...Array.from(mockImportMap.entries()).flatMap(
151
153
  ([from, mocks]) => {
152
154
  importPathsList.add(from);
155
+ const quotedFrom = JSON.stringify(from);
156
+ const mockModuleEntry = `globalThis.${HELPER_MOCK_HOIST}[${quotedFrom}]`;
153
157
  const lines = [
154
- `vi.mock(${JSON.stringify(from)}, async (importOriginal) => {`,
155
- ` const mocks = globalThis.${HELPER_MOCK_HOIST}`,
156
- ` if (!mocks[${JSON.stringify(from)}]) {`,
157
- ` mocks[${JSON.stringify(from)}] = { ...await importOriginal(${JSON.stringify(from)}) }`,
158
+ `vi.mock(${quotedFrom}, async (importOriginal) => {`,
159
+ ` if (!${mockModuleEntry}) {`,
160
+ ` const original = await importOriginal(${quotedFrom})`,
161
+ ` ${mockModuleEntry} = { ...original }`,
162
+ ` ${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL} = { ...original }`,
158
163
  ` }`
159
164
  ];
160
165
  for (const mock of mocks) {
161
- if (mock.import.name === "default") {
162
- lines.push(
163
- ` mocks[${JSON.stringify(from)}]["default"] = await (${mock.factory})();`
164
- );
165
- } else {
166
- lines.push(
167
- ` mocks[${JSON.stringify(from)}][${JSON.stringify(mock.name)}] = await (${mock.factory})();`
168
- );
169
- }
166
+ const quotedName = JSON.stringify(mock.import.name === "default" ? "default" : mock.name);
167
+ lines.push(
168
+ ` ${mockModuleEntry}[${quotedName}] = await (${mock.factory})(${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL}[${quotedName}]);`
169
+ );
170
170
  }
171
- lines.push(` return mocks[${JSON.stringify(from)}] `);
171
+ lines.push(` return ${mockModuleEntry} `);
172
172
  lines.push(`});`);
173
173
  return lines;
174
174
  }
@@ -768,17 +768,9 @@ async function updateGitignore(nuxt, answers) {
768
768
  async function setupDevTools(vitestWrapper, nuxt = useNuxt()) {
769
769
  const iframeSrc = "/__test_utils_vitest__/";
770
770
  const updateTabs = debounce(() => {
771
- nuxt.callHook("devtools:customTabs:refresh");
771
+ refreshCustomTabs(nuxt);
772
772
  }, 100);
773
- nuxt.hook("devtools:customTabs", (tabs) => {
774
- const tab = createVitestCustomTab(vitestWrapper, { iframeSrc });
775
- const index = tabs.findIndex(({ name }) => tab.name === name);
776
- if (index === -1) {
777
- tabs.push(tab);
778
- } else {
779
- tabs.splice(index, 1, tab);
780
- }
781
- });
773
+ addCustomTab(() => createVitestCustomTab(vitestWrapper, { iframeSrc }), nuxt);
782
774
  addDevServerHandler({
783
775
  route: iframeSrc,
784
776
  handler: Object.assign(() => iframeContentHtml(vitestWrapper.uiUrl), { __is_handler__: true })
@@ -961,7 +953,7 @@ function vitestWrapper(options) {
961
953
  };
962
954
  }
963
955
 
964
- const version = "3.23.0";
956
+ const version = "4.0.0";
965
957
  const pkg = {
966
958
  version: version};
967
959
 
@@ -1007,9 +999,9 @@ const module$1 = defineNuxtModule({
1007
999
  if (!nuxt.options.dev) return;
1008
1000
  if (process.env.TEST || process.env.VITE_TEST) return;
1009
1001
  const vitestWrapper2 = createVitestWrapper(options, nuxt);
1010
- nuxt.hook("devtools:before", async () => {
1002
+ onDevToolsInitialized(async () => {
1011
1003
  await setupDevTools(vitestWrapper2, nuxt);
1012
- });
1004
+ }, nuxt);
1013
1005
  if (options.startOnBoot) {
1014
1006
  vitestWrapper2.start();
1015
1007
  }
@@ -1,7 +1,7 @@
1
1
  import * as _playwright_test from '@playwright/test';
2
2
  export { expect } from '@playwright/test';
3
3
  import { Response } from 'playwright-core';
4
- import { T as TestOptions$1, G as GotoOptions, a as TestHooks } from './shared/test-utils.C9GKP_T5.mjs';
4
+ import { T as TestOptions$1, G as GotoOptions, a as TestHooks } from './shared/test-utils.BLyxqr96.mjs';
5
5
  import '@nuxt/schema';
6
6
  import 'tinyexec';
7
7
  import 'ofetch';
@@ -1,10 +1,10 @@
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.5cnw0YZR.mjs';
4
+ import { isWindows } from 'std-env';
5
+ import { w as waitForHydration, d as createTest } from './shared/test-utils.E_cAGA8l.mjs';
5
6
  import 'node:path';
6
7
  import 'ufo';
7
- import 'std-env';
8
8
  import 'consola';
9
9
  import 'node:fs';
10
10
  import 'destr';
@@ -19,6 +19,7 @@ import 'tinyexec';
19
19
  import 'get-port-please';
20
20
  import 'ofetch';
21
21
 
22
+ const FIXTURE_TIMEOUT = isWindows ? 12e4 : 6e4;
22
23
  const test = test$1.extend({
23
24
  nuxt: [void 0, { option: true, scope: "worker" }],
24
25
  defaults: [{ nuxt: void 0 }, { option: true, scope: "worker" }],
@@ -29,7 +30,7 @@ const test = test$1.extend({
29
30
  await use(hooks);
30
31
  await hooks.afterAll();
31
32
  },
32
- { scope: "worker" }
33
+ { scope: "worker", timeout: FIXTURE_TIMEOUT }
33
34
  ],
34
35
  baseURL: async ({ _nuxtHooks }, use) => {
35
36
  _nuxtHooks.beforeEach();
@@ -1,8 +1,5 @@
1
1
  import environmentOptions from "nuxt-vitest-environment-options";
2
2
  import { setupNuxt } from "./shared/nuxt.mjs";
3
3
  import { setupWindow } from "./shared/environment.mjs";
4
- const el = document.querySelector(environmentOptions.nuxt.rootId || "nuxt-test");
5
- if (!el) {
6
- await setupWindow(window, environmentOptions);
7
- await setupNuxt();
8
- }
4
+ await setupWindow(window, environmentOptions);
5
+ await setupNuxt();
@@ -1,7 +1,8 @@
1
- import { vi } from "vitest";
1
+ import { beforeAll } from "vitest";
2
2
  import { setupNuxt } from "./shared/nuxt.mjs";
3
3
  if (typeof window !== "undefined" && window.__NUXT_VITEST_ENVIRONMENT__) {
4
- await setupNuxt();
5
- vi.resetModules();
4
+ beforeAll(async () => {
5
+ await setupNuxt();
6
+ });
6
7
  }
7
8
  export {};
@@ -16,16 +16,6 @@ export async function setupWindow(win, environmentOptions) {
16
16
  data: {},
17
17
  state: {}
18
18
  };
19
- const rootId = environmentOptions.nuxt.rootId || "nuxt-test";
20
- let el;
21
- try {
22
- el = win.document.querySelector(rootId);
23
- } catch {
24
- }
25
- if (el) {
26
- return () => {
27
- };
28
- }
29
19
  const consoleInfo = console.info;
30
20
  console.info = (...args) => {
31
21
  if (args[0] === "<Suspense> is an experimental feature and its API will likely change.") {
@@ -34,7 +24,7 @@ export async function setupWindow(win, environmentOptions) {
34
24
  return consoleInfo(...args);
35
25
  };
36
26
  const app = win.document.createElement("div");
37
- app.id = rootId;
27
+ app.id = environmentOptions.nuxt.rootId || "nuxt-test";
38
28
  win.document.body.appendChild(app);
39
29
  if (!win.fetch || !("Request" in win)) {
40
30
  await import("node-fetch-native/polyfill");
@@ -1,7 +1,7 @@
1
1
  import { config } from "@vue/test-utils";
2
2
  const PLUGIN_NAME = "nuxt-test-utils";
3
3
  export function getVueWrapperPlugin() {
4
- const installed = config.plugins.VueWrapper.installedPlugins.find(({ options: options2 }) => options2._name === PLUGIN_NAME);
4
+ const installed = config.plugins.VueWrapper.installedPlugins.find(({ options: options2 }) => options2?._name === PLUGIN_NAME);
5
5
  if (installed) return installed.options;
6
6
  const options = createPluginOptions();
7
7
  config.plugins.VueWrapper.install((instance, options2) => {
@@ -58,7 +58,7 @@ declare function registerEndpoint(url: string, options: EventHandler | {
58
58
  * ```
59
59
  * @see https://nuxt.com/docs/getting-started/testing#mocknuxtimport
60
60
  */
61
- declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: () => T | Promise<T>): void;
61
+ declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: (original: T) => T | Promise<T>): void;
62
62
  /**
63
63
  * `mockComponent` allows you to mock Nuxt's component.
64
64
  * @param path - component name in PascalCase, or the relative path of the component.
@@ -1,17 +1,32 @@
1
1
  import { mount } from '@vue/test-utils';
2
- import { reactive, h as h$1, Suspense, nextTick as nextTick$1, getCurrentInstance, onErrorCaptured, effectScope } from 'vue';
2
+ import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance, onErrorCaptured, effectScope } from 'vue';
3
3
  import { defu } from 'defu';
4
4
  import { defineComponent, useRouter, h, tryUseNuxtApp } from '#imports';
5
5
  import NuxtRoot from '#build/root-component.mjs';
6
6
 
7
- const endpointRegistry = {};
7
+ function getEndpointRegistry() {
8
+ const app = window.__app ?? {};
9
+ return app._registeredEndpointRegistry ||= {};
10
+ }
11
+ function findEndpointRegistryHandlers(url) {
12
+ const endpointRegistry = getEndpointRegistry();
13
+ const pathname = url.replace(/[?#].*$/, "");
14
+ for (const [key, handlers] of Object.entries(endpointRegistry)) {
15
+ if (key === url || key === pathname) {
16
+ if (handlers?.length) {
17
+ return handlers;
18
+ }
19
+ }
20
+ }
21
+ }
8
22
  function registerEndpoint(url, options) {
9
23
  const app = window.__app;
10
24
  if (!app) {
11
25
  throw new Error("registerEndpoint() can only be used in a `@nuxt/test-utils` runtime environment");
12
26
  }
13
- const config = typeof options === "function" ? { handler: options, method: void 0, once: false } : options;
27
+ const config = typeof options === "function" ? { url, handler: options, method: void 0, once: false } : { ...options, url };
14
28
  config.handler = Object.assign(config.handler, { __is_handler__: true });
29
+ const endpointRegistry = getEndpointRegistry();
15
30
  endpointRegistry[url] ||= [];
16
31
  endpointRegistry[url].push(config);
17
32
  window.__registry.add(url);
@@ -34,16 +49,17 @@ function mockComponent(_path, _component) {
34
49
  );
35
50
  }
36
51
  const handler = Object.assign(async (event) => {
37
- const url = "url" in event && event.url ? event.url.pathname.replace(/^\/_/, "") : event.path.replace(/[?#].*$/, "").replace(/^\/_/, "");
38
- const latestHandler = [...endpointRegistry[url] || []].reverse().find((config) => config.method ? event.method === config.method : true);
52
+ const url = "url" in event && event.url ? (event.url.pathname + event.url.search).replace(/^\/_/, "") : event.path.replace(/^\/_/, "");
53
+ const registeredHandlers = findEndpointRegistryHandlers(url);
54
+ const latestHandler = [...registeredHandlers || []].reverse().find((config) => config.method ? event.method === config.method : true);
39
55
  if (!latestHandler) return;
40
56
  const result = await latestHandler.handler(event);
41
57
  if (!latestHandler.once) return result;
42
- const index = endpointRegistry[url]?.indexOf(latestHandler);
58
+ const index = registeredHandlers?.indexOf(latestHandler);
43
59
  if (index === void 0 || index === -1) return result;
44
- endpointRegistry[url]?.splice(index, 1);
45
- if (endpointRegistry[url]?.length === 0) {
46
- window.__registry.delete(url);
60
+ registeredHandlers?.splice(index, 1);
61
+ if (registeredHandlers?.length === 0) {
62
+ window.__registry.delete(latestHandler.url);
47
63
  }
48
64
  return result;
49
65
  }, { __is_handler__: true });
@@ -51,9 +67,10 @@ function registerGlobalHandler(app) {
51
67
  app.use(handler, {
52
68
  match: (...args) => {
53
69
  const [eventOrPath, _event = eventOrPath] = args;
54
- const url = typeof eventOrPath === "string" ? eventOrPath.replace(/^\/_/, "").replace(/[?#].*$/, "") : eventOrPath.url.pathname.replace(/^\/_/, "");
70
+ const url = typeof eventOrPath === "string" ? eventOrPath.replace(/^\/_/, "") : (eventOrPath.url.pathname + eventOrPath.url.search).replace(/^\/_/, "");
55
71
  const event = _event;
56
- return endpointRegistry[url]?.some((config) => config.method ? event?.method === config.method : true) ?? false;
72
+ const registeredHandlers = findEndpointRegistryHandlers(url);
73
+ return registeredHandlers?.some((config) => config.method ? event?.method === config.method : true) ?? false;
57
74
  }
58
75
  });
59
76
  return true;
@@ -197,7 +214,7 @@ function wrapperSuspended(component, options, {
197
214
  render: wrappedRender(() => h$1(
198
215
  Suspense,
199
216
  {
200
- onResolve: () => nextTick$1().then(() => {
217
+ onResolve: () => nextTick().then(() => {
201
218
  if (isMountSettled) return;
202
219
  isMountSettled = true;
203
220
  wrapper.setupState = setupState;
@@ -1,4 +1,4 @@
1
- import { Browser, Page, Response as Response$1, BrowserContextOptions, LaunchOptions } from 'playwright-core';
1
+ import { Page, Response as Response$1, BrowserContextOptions, Browser, LaunchOptions } from 'playwright-core';
2
2
  import { NuxtConfig, Nuxt } from '@nuxt/schema';
3
3
  import { exec } from 'tinyexec';
4
4
  import { $Fetch } from 'ofetch';
@@ -116,5 +116,5 @@ interface TestHooks {
116
116
  ctx: TestContext;
117
117
  }
118
118
 
119
- export { $fetch as $, createBrowser as c, createPage as d, stopServer as e, fetch as f, getBrowser as g, startServer as s, url as u, waitForHydration as w };
120
- export type { GotoOptions as G, NuxtPage as N, StartServerOptions as S, TestOptions as T, TestHooks as a, TestContext as b, TestRunner as h };
119
+ export { $fetch as $, createBrowser as d, createPage as e, fetch as f, getBrowser as g, stopServer as h, startServer as s, url as u, waitForHydration as w };
120
+ export type { GotoOptions as G, NuxtPage as N, StartServerOptions as S, TestOptions as T, TestHooks as a, TestContext as b, TestRunner as c };
@@ -117,10 +117,10 @@ async function buildFixture() {
117
117
  async function setupBun(hooks) {
118
118
  const bunTest = await import('bun:test');
119
119
  hooks.ctx.mockFn = bunTest.mock;
120
- bunTest.beforeAll(hooks.beforeAll);
120
+ bunTest.beforeAll(hooks.beforeAll, hooks.ctx.options.setupTimeout);
121
121
  bunTest.beforeEach(hooks.beforeEach);
122
122
  bunTest.afterEach(hooks.afterEach);
123
- bunTest.afterAll(hooks.afterAll);
123
+ bunTest.afterAll(hooks.afterAll, hooks.ctx.options.teardownTimeout);
124
124
  }
125
125
 
126
126
  async function setupCucumber(hooks) {
@@ -209,4 +209,4 @@ async function setup(options = {}) {
209
209
  await setupFn(hooks);
210
210
  }
211
211
 
212
- export { createPage as a, buildFixture as b, createBrowser as c, createTest as d, setup as e, getBrowser as g, loadFixture as l, setupMaps as s, waitForHydration as w };
212
+ export { createPage as a, buildFixture as b, createBrowser as c, createTest as d, setupMaps as e, getBrowser as g, loadFixture as l, setup as s, waitForHydration as w };
@@ -2,7 +2,7 @@ import { Environment } from 'vitest/environments';
2
2
  import { H3Event as H3Event$1 } from 'h3';
3
3
  import { H3Event } from 'h3-next';
4
4
  import { $Fetch } from 'nitropack';
5
- import { JSDOMOptions, HappyDOMOptions } from 'vitest/node';
5
+ import { EnvironmentOptions } from 'vitest/node';
6
6
 
7
7
  declare const _default: Environment;
8
8
 
@@ -29,8 +29,8 @@ interface NuxtWindow extends Window {
29
29
  Headers: typeof Headers;
30
30
  }
31
31
  interface EnvironmentNuxtOptions {
32
- jsdom?: JSDOMOptions;
33
- happyDom?: HappyDOMOptions;
32
+ jsdom?: EnvironmentOptions['jsdom'];
33
+ happyDom?: EnvironmentOptions['happyDOM'];
34
34
  }
35
35
  type EnvironmentNuxt = (global: typeof globalThis, options: EnvironmentNuxtOptions) => Promise<{
36
36
  window: NuxtWindow;
@@ -128,16 +128,6 @@ async function setupWindow(win, environmentOptions) {
128
128
  data: {},
129
129
  state: {}
130
130
  };
131
- const rootId = environmentOptions.nuxt.rootId || "nuxt-test";
132
- let el;
133
- try {
134
- el = win.document.querySelector(rootId);
135
- } catch {
136
- }
137
- if (el) {
138
- return () => {
139
- };
140
- }
141
131
  const consoleInfo = console.info;
142
132
  console.info = (...args) => {
143
133
  if (args[0] === "<Suspense> is an experimental feature and its API will likely change.") {
@@ -146,7 +136,7 @@ async function setupWindow(win, environmentOptions) {
146
136
  return consoleInfo(...args);
147
137
  };
148
138
  const app = win.document.createElement("div");
149
- app.id = rootId;
139
+ app.id = environmentOptions.nuxt.rootId || "nuxt-test";
150
140
  win.document.body.appendChild(app);
151
141
  if (!win.fetch || !("Request" in win)) {
152
142
  await import('node-fetch-native/polyfill');
@@ -247,22 +237,22 @@ const environmentMap = {
247
237
  };
248
238
  const index = {
249
239
  name: "nuxt",
250
- transformMode: "web",
240
+ viteEnvironment: "client",
251
241
  async setup(global, environmentOptions) {
252
242
  const url = joinURL(
253
- environmentOptions?.nuxt.url ?? "http://localhost:3000",
254
- environmentOptions?.nuxtRuntimeConfig.app?.baseURL || "/"
243
+ environmentOptions.nuxt?.url ?? "http://localhost:3000",
244
+ environmentOptions.nuxtRuntimeConfig?.app?.baseURL || "/"
255
245
  );
256
- const environmentName = environmentOptions.nuxt.domEnvironment;
246
+ const environmentName = environmentOptions.nuxt?.domEnvironment;
257
247
  const environment = environmentMap[environmentName] || environmentMap["happy-dom"];
258
248
  const { window: win, teardown } = await environment(global, defu(environmentOptions, {
259
249
  happyDom: { url },
260
250
  jsdom: { url }
261
251
  }));
262
- if (environmentOptions?.nuxt?.mock?.intersectionObserver) {
252
+ if (environmentOptions.nuxt?.mock?.intersectionObserver) {
263
253
  win.IntersectionObserver ||= IntersectionObserver;
264
254
  }
265
- if (environmentOptions?.nuxt?.mock?.indexedDb) {
255
+ if (environmentOptions.nuxt?.mock?.indexedDb) {
266
256
  win.indexedDB = indexedDB;
267
257
  }
268
258
  const teardownWindow = await setupWindow(win, environmentOptions);
@@ -32,10 +32,16 @@ function createCustomReporter(onVitestInit) {
32
32
  onTestRunStart() {
33
33
  sendMessageToHost("updated", toUpdatedResult());
34
34
  },
35
- onTaskUpdate() {
35
+ onTestModuleCollected() {
36
36
  sendMessageToHost("updated", toUpdatedResult());
37
37
  },
38
- onFinished() {
38
+ onTestCaseResult() {
39
+ sendMessageToHost("updated", toUpdatedResult());
40
+ },
41
+ onTestModuleEnd() {
42
+ sendMessageToHost("updated", toUpdatedResult());
43
+ },
44
+ onTestRunEnd() {
39
45
  sendMessageToHost("finished", toFinishedResult());
40
46
  }
41
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/test-utils",
3
- "version": "3.23.0",
3
+ "version": "4.0.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/nuxt/test-utils.git"
@@ -57,7 +57,7 @@
57
57
  "lint": "eslint .",
58
58
  "lint:fix": "eslint . --fix",
59
59
  "test": "pnpm test:types && pnpm test:unit && pnpm test:examples",
60
- "test:examples": "pnpm --filter '!example-app-cucumber' --filter '!example-app-jest' --filter '!example-app-bun' -r test && pnpm --filter example-app-cucumber -r test",
60
+ "test:examples": "pnpm --filter '!@nuxt/test-utils' --filter '!example-app-cucumber' --filter '!example-app-jest' --filter '!example-app-bun' -r test && pnpm --filter example-app-cucumber -r test",
61
61
  "test:knip": "knip",
62
62
  "test:engines": "pnpm installed-check --no-workspaces --ignore-dev",
63
63
  "test:types": "vue-tsc --noEmit",
@@ -67,8 +67,9 @@
67
67
  "dev:prepare": "nuxt prepare && unbuild --stub && pnpm -r dev:prepare"
68
68
  },
69
69
  "dependencies": {
70
- "@clack/prompts": "1.0.0-alpha.9",
71
- "@nuxt/kit": "^3.20.2",
70
+ "@clack/prompts": "1.0.0",
71
+ "@nuxt/devtools-kit": "^2.7.0",
72
+ "@nuxt/kit": "^3.21.0",
72
73
  "c12": "^3.3.3",
73
74
  "consola": "^3.4.2",
74
75
  "defu": "^6.1.4",
@@ -77,70 +78,69 @@
77
78
  "exsolve": "^1.0.8",
78
79
  "fake-indexeddb": "^6.2.5",
79
80
  "get-port-please": "^3.2.0",
80
- "h3": "^1.15.4",
81
- "h3-next": "npm:h3@^2.0.1-rc.7",
81
+ "h3": "^1.15.5",
82
+ "h3-next": "npm:h3@2.0.1-rc.11",
82
83
  "local-pkg": "^1.1.2",
83
84
  "magic-string": "^0.30.21",
84
85
  "node-fetch-native": "^1.6.7",
85
86
  "node-mock-http": "^1.0.4",
86
- "nypm": "^0.6.2",
87
+ "nypm": "^0.6.4",
87
88
  "ofetch": "^1.5.1",
88
89
  "pathe": "^2.0.3",
89
- "perfect-debounce": "^2.0.0",
90
+ "perfect-debounce": "^2.1.0",
90
91
  "radix3": "^1.1.2",
91
92
  "scule": "^1.3.0",
92
93
  "std-env": "^3.10.0",
93
94
  "tinyexec": "^1.0.2",
94
- "ufo": "^1.6.1",
95
- "unplugin": "^2.3.11",
95
+ "ufo": "^1.6.3",
96
+ "unplugin": "^3.0.0",
96
97
  "vitest-environment-nuxt": "^1.0.1",
97
- "vue": "^3.5.26"
98
+ "vue": "^3.5.27"
98
99
  },
99
100
  "devDependencies": {
100
- "@cucumber/cucumber": "12.5.0",
101
+ "@cucumber/cucumber": "12.6.0",
101
102
  "@jest/globals": "30.2.0",
102
- "@nuxt/devtools-kit": "2.7.0",
103
- "@nuxt/eslint-config": "1.12.1",
104
- "@nuxt/schema": "4.2.2",
105
- "@playwright/test": "1.57.0",
103
+ "@nuxt/eslint-config": "1.13.0",
104
+ "@nuxt/schema": "4.3.0",
105
+ "@playwright/test": "1.58.1",
106
106
  "@testing-library/vue": "8.1.0",
107
- "@types/bun": "1.3.5",
107
+ "@types/bun": "1.3.8",
108
108
  "@types/estree": "1.0.8",
109
109
  "@types/jsdom": "27.0.0",
110
110
  "@types/node": "latest",
111
111
  "@types/semver": "7.7.1",
112
- "@vitest/browser-playwright": "4.0.16",
112
+ "@vitest/browser-playwright": "4.0.18",
113
113
  "@vue/test-utils": "2.4.6",
114
114
  "changelogen": "0.6.2",
115
115
  "compatx": "0.2.0",
116
116
  "eslint": "9.39.2",
117
117
  "installed-check": "9.3.0",
118
- "knip": "5.80.0",
119
- "nitropack": "2.12.9",
120
- "nuxt": "4.2.2",
121
- "oxc-parser": "^0.107.0",
122
- "pkg-pr-new": "0.0.62",
123
- "playwright-core": "1.57.0",
124
- "rollup": "4.55.1",
118
+ "knip": "5.83.0",
119
+ "nitropack": "2.13.1",
120
+ "nuxt": "4.3.0",
121
+ "oxc-parser": "0.112.0",
122
+ "pkg-pr-new": "0.0.63",
123
+ "playwright-core": "1.58.1",
124
+ "rollup": "4.57.1",
125
125
  "semver": "7.7.3",
126
126
  "typescript": "5.9.3",
127
127
  "unbuild": "latest",
128
128
  "unimport": "5.6.0",
129
- "vite": "7.3.0",
130
- "vitest": "3.2.4",
131
- "vue-router": "4.6.4",
132
- "vue-tsc": "3.2.2"
129
+ "vite": "7.3.1",
130
+ "vitest": "4.0.18",
131
+ "vue-router": "5.0.2",
132
+ "vue-tsc": "3.2.4"
133
133
  },
134
134
  "peerDependencies": {
135
- "@cucumber/cucumber": "^10.3.1 || >=11.0.0",
136
- "@jest/globals": "^29.5.0 || >=30.0.0",
135
+ "@cucumber/cucumber": ">=11.0.0",
136
+ "@jest/globals": ">=30.0.0",
137
137
  "@playwright/test": "^1.43.1",
138
- "@testing-library/vue": "^7.0.0 || ^8.0.1",
138
+ "@testing-library/vue": "^8.0.1",
139
139
  "@vue/test-utils": "^2.4.2",
140
- "happy-dom": "*",
141
- "jsdom": "*",
140
+ "happy-dom": ">=20.0.11",
141
+ "jsdom": ">=27.4.0",
142
142
  "playwright-core": "^1.43.1",
143
- "vitest": "^3.2.0"
143
+ "vitest": "^4.0.2"
144
144
  },
145
145
  "peerDependenciesMeta": {
146
146
  "@cucumber/cucumber": {
@@ -175,19 +175,19 @@
175
175
  }
176
176
  },
177
177
  "resolutions": {
178
- "@cucumber/cucumber": "12.5.0",
179
- "@nuxt/schema": "4.2.2",
178
+ "@cucumber/cucumber": "12.6.0",
179
+ "@nuxt/schema": "4.3.0",
180
180
  "@nuxt/test-utils": "workspace:*",
181
- "@types/node": "24.10.4",
181
+ "@types/node": "24.10.9",
182
182
  "nitro": "https://pkg.pr.new/nitrojs/nitro@00598a8",
183
- "rollup": "4.55.1",
184
- "vite": "7.3.0",
185
- "vite-node": "5.2.0",
186
- "vitest": "3.2.4",
187
- "vue": "^3.5.26"
183
+ "rollup": "4.57.1",
184
+ "vite": "7.3.1",
185
+ "vite-node": "5.3.0",
186
+ "vitest": "4.0.18",
187
+ "vue": "^3.5.27"
188
188
  },
189
189
  "engines": {
190
- "node": "^20.11.1 || ^22.0.0 || >=24.0.0"
190
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
191
191
  },
192
- "packageManager": "pnpm@10.27.0"
192
+ "packageManager": "pnpm@10.28.2"
193
193
  }