@nuxt/test-utils 3.22.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.
@@ -3,11 +3,119 @@ import { joinURL } from 'ufo';
3
3
  import defu from 'defu';
4
4
  import { populateGlobal } from 'vitest/environments';
5
5
  import { createFetch } from 'ofetch';
6
- import { createApp, toNodeListener, defineEventHandler } from 'h3';
7
6
  import { toRouteMatcher, createRouter, exportMatcher } from 'radix3';
8
- import { fetchNodeRequestHandler } from 'node-mock-http';
9
7
  import { importModule } from 'local-pkg';
10
8
 
9
+ function defineEventHandler(handler) {
10
+ return Object.assign(handler, { __is_handler__: true });
11
+ }
12
+
13
+ async function createFetchForH3V1() {
14
+ const [{ createApp, toNodeListener }, { fetchNodeRequestHandler }] = await Promise.all([
15
+ import('h3'),
16
+ import('node-mock-http')
17
+ ]);
18
+ const h3App = createApp();
19
+ const nodeHandler = toNodeListener(h3App);
20
+ const registry = /* @__PURE__ */ new Set();
21
+ const _fetch = fetch;
22
+ const h3Fetch = (async (input, _init) => {
23
+ let url;
24
+ let init = _init;
25
+ if (typeof input === "string") {
26
+ url = input;
27
+ } else if (input instanceof URL) {
28
+ url = input.toString();
29
+ } else {
30
+ url = input.url;
31
+ init = {
32
+ method: init?.method ?? input.method,
33
+ body: init?.body ?? input.body,
34
+ headers: init?.headers ?? input.headers
35
+ };
36
+ }
37
+ const base = url.split("?")[0];
38
+ if (registry.has(base) || registry.has(url)) {
39
+ url = "/_" + url;
40
+ }
41
+ if (url.startsWith("/")) {
42
+ const response = await fetchNodeRequestHandler(nodeHandler, url, init);
43
+ return normalizeFetchResponse(response);
44
+ }
45
+ return _fetch(input, _init);
46
+ });
47
+ return {
48
+ h3App,
49
+ registry,
50
+ fetch: h3Fetch
51
+ };
52
+ }
53
+ function normalizeFetchResponse(response) {
54
+ if (!response.headers.has("set-cookie")) {
55
+ return response;
56
+ }
57
+ return new Response(response.body, {
58
+ status: response.status,
59
+ statusText: response.statusText,
60
+ headers: normalizeCookieHeaders(response.headers)
61
+ });
62
+ }
63
+ function normalizeCookieHeader(header = "") {
64
+ return splitCookiesString(joinHeaders(header));
65
+ }
66
+ function normalizeCookieHeaders(headers) {
67
+ const outgoingHeaders = new Headers();
68
+ for (const [name, header] of headers) {
69
+ if (name === "set-cookie") {
70
+ for (const cookie of normalizeCookieHeader(header)) {
71
+ outgoingHeaders.append("set-cookie", cookie);
72
+ }
73
+ } else {
74
+ outgoingHeaders.set(name, joinHeaders(header));
75
+ }
76
+ }
77
+ return outgoingHeaders;
78
+ }
79
+ function joinHeaders(value) {
80
+ return Array.isArray(value) ? value.join(", ") : String(value);
81
+ }
82
+
83
+ async function createFetchForH3V2() {
84
+ const { H3 } = await import('h3-next/generic');
85
+ const h3App = new H3();
86
+ const registry = /* @__PURE__ */ new Set();
87
+ const _fetch = fetch;
88
+ const h3Fetch = (async (input, _init) => {
89
+ let url;
90
+ let init = _init;
91
+ if (typeof input === "string") {
92
+ url = input;
93
+ } else if (input instanceof URL) {
94
+ url = input.toString();
95
+ } else {
96
+ url = input.url;
97
+ init = {
98
+ method: init?.method ?? input.method,
99
+ body: init?.body ?? input.body,
100
+ headers: init?.headers ?? input.headers
101
+ };
102
+ }
103
+ const base = url.split("?")[0];
104
+ if (registry.has(base) || registry.has(url)) {
105
+ return h3App.fetch(new Request("/_" + url, init));
106
+ }
107
+ if (url.startsWith("/")) {
108
+ return new Response("Not Found", { status: 404, statusText: "Not Found" });
109
+ }
110
+ return _fetch(input, _init);
111
+ });
112
+ return {
113
+ h3App,
114
+ registry,
115
+ fetch: h3Fetch
116
+ };
117
+ }
118
+
11
119
  async function setupWindow(win, environmentOptions) {
12
120
  win.__NUXT_VITEST_ENVIRONMENT__ = true;
13
121
  win.__NUXT__ = {
@@ -20,16 +128,6 @@ async function setupWindow(win, environmentOptions) {
20
128
  data: {},
21
129
  state: {}
22
130
  };
23
- const rootId = environmentOptions.nuxt.rootId || "nuxt-test";
24
- let el;
25
- try {
26
- el = win.document.querySelector(rootId);
27
- } catch {
28
- }
29
- if (el) {
30
- return () => {
31
- };
32
- }
33
131
  const consoleInfo = console.info;
34
132
  console.info = (...args) => {
35
133
  if (args[0] === "<Suspense> is an experimental feature and its API will likely change.") {
@@ -38,9 +136,8 @@ async function setupWindow(win, environmentOptions) {
38
136
  return consoleInfo(...args);
39
137
  };
40
138
  const app = win.document.createElement("div");
41
- app.id = rootId;
139
+ app.id = environmentOptions.nuxt.rootId || "nuxt-test";
42
140
  win.document.body.appendChild(app);
43
- const h3App = createApp();
44
141
  if (!win.fetch || !("Request" in win)) {
45
142
  await import('node-fetch-native/polyfill');
46
143
  win.URLSearchParams = globalThis.URLSearchParams;
@@ -54,37 +151,11 @@ async function setupWindow(win, environmentOptions) {
54
151
  }
55
152
  };
56
153
  }
57
- const nodeHandler = toNodeListener(h3App);
58
- const registry = /* @__PURE__ */ new Set();
59
- const _fetch = fetch;
60
- win.fetch = async (input, _init) => {
61
- let url;
62
- let init = _init;
63
- if (typeof input === "string") {
64
- url = input;
65
- } else if (input instanceof URL) {
66
- url = input.toString();
67
- } else {
68
- url = input.url;
69
- init = {
70
- method: init?.method ?? input.method,
71
- body: init?.body ?? input.body,
72
- headers: init?.headers ?? input.headers
73
- };
74
- }
75
- const base = url.split("?")[0];
76
- if (registry.has(base) || registry.has(url)) {
77
- url = "/_" + url;
78
- }
79
- if (url.startsWith("/")) {
80
- const response = await fetchNodeRequestHandler(nodeHandler, url, init);
81
- return normalizeFetchResponse(response);
82
- }
83
- return _fetch(input, _init);
84
- };
154
+ const res = environmentOptions.nuxt.h3Version === 2 ? await createFetchForH3V2() : await createFetchForH3V1();
155
+ win.fetch = res.fetch;
85
156
  win.$fetch = createFetch({ fetch: win.fetch, Headers: win.Headers });
86
- win.__registry = registry;
87
- win.__app = h3App;
157
+ win.__registry = res.registry;
158
+ win.__app = res.h3App;
88
159
  const timestamp = Date.now();
89
160
  const routeRulesMatcher = toRouteMatcher(
90
161
  createRouter({ routes: environmentOptions.nuxtRouteRules || {} })
@@ -97,14 +168,14 @@ async function setupWindow(win, environmentOptions) {
97
168
  );
98
169
  const manifestBaseRoutePath = joinURL("/_", manifestOutputPath);
99
170
  const buildId = win.__NUXT__.config?.app.buildId || "test";
100
- h3App.use(
171
+ res.h3App.use(
101
172
  `${manifestBaseRoutePath}/latest.json`,
102
173
  defineEventHandler(() => ({
103
174
  id: buildId,
104
175
  timestamp
105
176
  }))
106
177
  );
107
- h3App.use(
178
+ res.h3App.use(
108
179
  `${manifestBaseRoutePath}/meta/${buildId}.json`,
109
180
  defineEventHandler(() => ({
110
181
  id: buildId,
@@ -113,41 +184,12 @@ async function setupWindow(win, environmentOptions) {
113
184
  prerendered: []
114
185
  }))
115
186
  );
116
- registry.add(`${manifestOutputPath}/latest.json`);
117
- registry.add(`${manifestOutputPath}/meta/${buildId}.json`);
187
+ res.registry.add(`${manifestOutputPath}/latest.json`);
188
+ res.registry.add(`${manifestOutputPath}/meta/${buildId}.json`);
118
189
  return () => {
119
190
  console.info = consoleInfo;
120
191
  };
121
192
  }
122
- function normalizeFetchResponse(response) {
123
- if (!response.headers.has("set-cookie")) {
124
- return response;
125
- }
126
- return new Response(response.body, {
127
- status: response.status,
128
- statusText: response.statusText,
129
- headers: normalizeCookieHeaders(response.headers)
130
- });
131
- }
132
- function normalizeCookieHeader(header = "") {
133
- return splitCookiesString(joinHeaders(header));
134
- }
135
- function normalizeCookieHeaders(headers) {
136
- const outgoingHeaders = new Headers();
137
- for (const [name, header] of headers) {
138
- if (name === "set-cookie") {
139
- for (const cookie of normalizeCookieHeader(header)) {
140
- outgoingHeaders.append("set-cookie", cookie);
141
- }
142
- } else {
143
- outgoingHeaders.set(name, joinHeaders(header));
144
- }
145
- }
146
- return outgoingHeaders;
147
- }
148
- function joinHeaders(value) {
149
- return Array.isArray(value) ? value.join(", ") : String(value);
150
- }
151
193
 
152
194
  const happyDom = (async function(_, { happyDom = {} }) {
153
195
  const { Window, GlobalWindow } = await importModule("happy-dom");
@@ -195,22 +237,22 @@ const environmentMap = {
195
237
  };
196
238
  const index = {
197
239
  name: "nuxt",
198
- transformMode: "web",
240
+ viteEnvironment: "client",
199
241
  async setup(global, environmentOptions) {
200
242
  const url = joinURL(
201
- environmentOptions?.nuxt.url ?? "http://localhost:3000",
202
- environmentOptions?.nuxtRuntimeConfig.app?.baseURL || "/"
243
+ environmentOptions.nuxt?.url ?? "http://localhost:3000",
244
+ environmentOptions.nuxtRuntimeConfig?.app?.baseURL || "/"
203
245
  );
204
- const environmentName = environmentOptions.nuxt.domEnvironment;
246
+ const environmentName = environmentOptions.nuxt?.domEnvironment;
205
247
  const environment = environmentMap[environmentName] || environmentMap["happy-dom"];
206
248
  const { window: win, teardown } = await environment(global, defu(environmentOptions, {
207
249
  happyDom: { url },
208
250
  jsdom: { url }
209
251
  }));
210
- if (environmentOptions?.nuxt?.mock?.intersectionObserver) {
252
+ if (environmentOptions.nuxt?.mock?.intersectionObserver) {
211
253
  win.IntersectionObserver ||= IntersectionObserver;
212
254
  }
213
- if (environmentOptions?.nuxt?.mock?.indexedDb) {
255
+ if (environmentOptions.nuxt?.mock?.indexedDb) {
214
256
  win.indexedDB = indexedDB;
215
257
  }
216
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.22.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,7 +67,9 @@
67
67
  "dev:prepare": "nuxt prepare && unbuild --stub && pnpm -r dev:prepare"
68
68
  },
69
69
  "dependencies": {
70
- "@nuxt/kit": "^3.20.2",
70
+ "@clack/prompts": "1.0.0",
71
+ "@nuxt/devtools-kit": "^2.7.0",
72
+ "@nuxt/kit": "^3.21.0",
71
73
  "c12": "^3.3.3",
72
74
  "consola": "^3.4.2",
73
75
  "defu": "^6.1.4",
@@ -76,66 +78,69 @@
76
78
  "exsolve": "^1.0.8",
77
79
  "fake-indexeddb": "^6.2.5",
78
80
  "get-port-please": "^3.2.0",
79
- "h3": "^1.15.4",
81
+ "h3": "^1.15.5",
82
+ "h3-next": "npm:h3@2.0.1-rc.11",
80
83
  "local-pkg": "^1.1.2",
81
84
  "magic-string": "^0.30.21",
82
85
  "node-fetch-native": "^1.6.7",
83
86
  "node-mock-http": "^1.0.4",
87
+ "nypm": "^0.6.4",
84
88
  "ofetch": "^1.5.1",
85
89
  "pathe": "^2.0.3",
86
- "perfect-debounce": "^2.0.0",
90
+ "perfect-debounce": "^2.1.0",
87
91
  "radix3": "^1.1.2",
88
92
  "scule": "^1.3.0",
89
93
  "std-env": "^3.10.0",
90
94
  "tinyexec": "^1.0.2",
91
- "ufo": "^1.6.1",
92
- "unplugin": "^2.3.11",
95
+ "ufo": "^1.6.3",
96
+ "unplugin": "^3.0.0",
93
97
  "vitest-environment-nuxt": "^1.0.1",
94
- "vue": "^3.5.26"
98
+ "vue": "^3.5.27"
95
99
  },
96
100
  "devDependencies": {
97
- "@cucumber/cucumber": "12.5.0",
101
+ "@cucumber/cucumber": "12.6.0",
98
102
  "@jest/globals": "30.2.0",
99
- "@nuxt/devtools-kit": "2.7.0",
100
- "@nuxt/eslint-config": "1.12.1",
101
- "@nuxt/schema": "4.2.2",
102
- "@playwright/test": "1.57.0",
103
+ "@nuxt/eslint-config": "1.13.0",
104
+ "@nuxt/schema": "4.3.0",
105
+ "@playwright/test": "1.58.1",
103
106
  "@testing-library/vue": "8.1.0",
104
- "@types/bun": "1.3.5",
107
+ "@types/bun": "1.3.8",
105
108
  "@types/estree": "1.0.8",
106
109
  "@types/jsdom": "27.0.0",
107
110
  "@types/node": "latest",
108
111
  "@types/semver": "7.7.1",
112
+ "@vitest/browser-playwright": "4.0.18",
109
113
  "@vue/test-utils": "2.4.6",
110
114
  "changelogen": "0.6.2",
111
115
  "compatx": "0.2.0",
112
116
  "eslint": "9.39.2",
113
117
  "installed-check": "9.3.0",
114
- "knip": "5.78.0",
115
- "nitropack": "2.12.9",
116
- "nuxt": "4.2.2",
117
- "pkg-pr-new": "0.0.62",
118
- "playwright-core": "1.57.0",
119
- "rollup": "4.54.0",
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",
120
125
  "semver": "7.7.3",
121
126
  "typescript": "5.9.3",
122
127
  "unbuild": "latest",
123
128
  "unimport": "5.6.0",
124
- "vite": "7.3.0",
125
- "vitest": "3.2.4",
126
- "vue-router": "4.6.4",
127
- "vue-tsc": "3.2.1"
129
+ "vite": "7.3.1",
130
+ "vitest": "4.0.18",
131
+ "vue-router": "5.0.2",
132
+ "vue-tsc": "3.2.4"
128
133
  },
129
134
  "peerDependencies": {
130
- "@cucumber/cucumber": "^10.3.1 || >=11.0.0",
131
- "@jest/globals": "^29.5.0 || >=30.0.0",
135
+ "@cucumber/cucumber": ">=11.0.0",
136
+ "@jest/globals": ">=30.0.0",
132
137
  "@playwright/test": "^1.43.1",
133
- "@testing-library/vue": "^7.0.0 || ^8.0.1",
138
+ "@testing-library/vue": "^8.0.1",
134
139
  "@vue/test-utils": "^2.4.2",
135
- "happy-dom": "*",
136
- "jsdom": "*",
140
+ "happy-dom": ">=20.0.11",
141
+ "jsdom": ">=27.4.0",
137
142
  "playwright-core": "^1.43.1",
138
- "vitest": "^3.2.0"
143
+ "vitest": "^4.0.2"
139
144
  },
140
145
  "peerDependenciesMeta": {
141
146
  "@cucumber/cucumber": {
@@ -170,18 +175,19 @@
170
175
  }
171
176
  },
172
177
  "resolutions": {
173
- "@cucumber/cucumber": "12.5.0",
174
- "@nuxt/schema": "4.2.2",
178
+ "@cucumber/cucumber": "12.6.0",
179
+ "@nuxt/schema": "4.3.0",
175
180
  "@nuxt/test-utils": "workspace:*",
176
- "@types/node": "24.10.4",
177
- "rollup": "4.54.0",
178
- "vite": "7.3.0",
179
- "vite-node": "5.2.0",
180
- "vitest": "3.2.4",
181
- "vue": "^3.5.26"
181
+ "@types/node": "24.10.9",
182
+ "nitro": "https://pkg.pr.new/nitrojs/nitro@00598a8",
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"
182
188
  },
183
189
  "engines": {
184
- "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
190
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
185
191
  },
186
- "packageManager": "pnpm@10.26.2"
192
+ "packageManager": "pnpm@10.28.2"
187
193
  }