@nuxt/test-utils 4.0.0 → 4.0.1

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.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { NuxtConfig, Nuxt } from '@nuxt/schema';
1
+ import { NuxtConfig, Nuxt, ViteConfig } from '@nuxt/schema';
2
2
  import { InlineConfig } from 'vitest/node';
3
3
  import { TestProjectInlineConfiguration } from 'vitest/config';
4
4
  import { DotenvOptions } from 'c12';
@@ -6,7 +6,7 @@ import { UserConfig, UserConfigFnPromise } from 'vite';
6
6
 
7
7
  interface GetVitestConfigOptions {
8
8
  nuxt: Nuxt;
9
- viteConfig: UserConfig;
9
+ viteConfig: ViteConfig;
10
10
  }
11
11
  interface LoadNuxtOptions {
12
12
  dotenv?: Partial<DotenvOptions>;
package/dist/config.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  import process from 'node:process';
2
- import { defineConfig } from 'vite';
3
2
  import { setupDotenv } from 'c12';
4
- import { createDefu, defu } from 'defu';
3
+ import { defu, createDefu } from 'defu';
5
4
  import { createResolver, findPath } from '@nuxt/kit';
6
5
  import { resolveModulePath } from 'exsolve';
7
6
  import { getPackageInfoSync } from 'local-pkg';
@@ -107,7 +106,9 @@ async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
107
106
  break;
108
107
  }
109
108
  }
110
- const h3Info = getPackageInfoSync("h3", {
109
+ const projectH3Path = resolveModulePath("h3/package.json", { from: rootDir, try: true });
110
+ const projectH3Info = projectH3Path ? getPackageInfoSync("h3", { paths: [projectH3Path] }) : void 0;
111
+ const h3Info = projectH3Info || getPackageInfoSync("h3", {
111
112
  paths: nitroPath ? [nitroPath] : options.nuxt.options.modulesDir
112
113
  });
113
114
  const resolver = createResolver(import.meta.url);
@@ -160,7 +161,7 @@ async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
160
161
  },
161
162
  deps: {
162
163
  optimizer: {
163
- web: {
164
+ client: {
164
165
  enabled: false
165
166
  }
166
167
  }
@@ -216,6 +217,7 @@ async function getVitestConfigFromNuxt(options, loadNuxtOptions = {}) {
216
217
  );
217
218
  delete resolvedConfig.define["process.browser"];
218
219
  delete resolvedConfig.customLogger;
220
+ delete resolvedConfig.ssr;
219
221
  if (!Array.isArray(resolvedConfig.test.setupFiles)) {
220
222
  resolvedConfig.test.setupFiles = [resolvedConfig.test.setupFiles].filter(Boolean);
221
223
  }
@@ -228,8 +230,9 @@ async function defineVitestProject(config) {
228
230
  resolvedConfig.test.environment = "nuxt";
229
231
  return resolvedConfig;
230
232
  }
233
+ const defineViteConfig = (config) => config;
231
234
  function defineVitestConfig(config = {}) {
232
- return defineConfig(async () => {
235
+ return defineViteConfig(async () => {
233
236
  const resolvedConfig = await resolveConfig(config);
234
237
  if (resolvedConfig.test.browser?.enabled) {
235
238
  return resolvedConfig;
@@ -277,9 +280,11 @@ function defineVitestConfig(config = {}) {
277
280
  ]
278
281
  }
279
282
  }, resolvedConfig);
280
- resolvedConfig.test = {
281
- projects: [nuxtProject, defaultProject]
282
- };
283
+ delete resolvedConfig.test.name;
284
+ delete resolvedConfig.test.environment;
285
+ delete resolvedConfig.test.include;
286
+ delete resolvedConfig.test.exclude;
287
+ resolvedConfig.test.projects = [nuxtProject, defaultProject];
283
288
  }
284
289
  return resolvedConfig;
285
290
  });
package/dist/module.mjs CHANGED
@@ -28,191 +28,190 @@ const HELPER_MOCK_HOIST = "__NUXT_VITEST_MOCKS";
28
28
  const HELPER_MOCK_HOIST_ORIGINAL = "__NUXT_VITEST_MOCKS_ORIGINAL";
29
29
  const HELPERS_NAME = [HELPER_MOCK_IMPORT, HELPER_MOCK_COMPONENT];
30
30
  const createMockPlugin = (ctx) => createUnplugin(() => {
31
- function transform(code, id) {
32
- if (!HELPERS_NAME.some((n) => code.includes(n))) return;
33
- if (id.includes("/node_modules/")) return;
34
- let ast;
35
- try {
36
- ast = this.parse(code, {
37
- // @ts-expect-error compatibility with rollup v3
38
- sourceType: "module",
39
- ecmaVersion: "latest",
40
- ranges: true
41
- });
42
- } catch {
43
- return;
44
- }
45
- let insertionPoint = 0;
46
- let hasViImport = false;
47
- const s = new MagicString(code);
48
- const mocksImport = [];
49
- const mocksComponent = [];
50
- const importPathsList = /* @__PURE__ */ new Set();
51
- walk(ast, {
52
- enter: (node, parent) => {
53
- if (isImportDeclaration(node)) {
54
- if (node.source.value === "vitest" && !hasViImport) {
55
- const viImport = node.specifiers.find(
56
- (i) => isImportSpecifier(i) && i.imported.type === "Identifier" && i.imported.name === "vi"
57
- );
58
- if (viImport) {
59
- insertionPoint = endOf(node);
60
- hasViImport = true;
61
- }
62
- return;
63
- }
64
- }
65
- if (!isCallExpression(node)) return;
66
- if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_IMPORT) {
67
- if (node.arguments.length !== 2) {
68
- return this.error(
69
- new Error(
70
- `${HELPER_MOCK_IMPORT}() should have exactly 2 arguments`
71
- ),
72
- startOf(node)
73
- );
74
- }
75
- const importTarget = node.arguments[0];
76
- const name = isLiteral(importTarget) ? importTarget.value : isIdentifier(importTarget) ? importTarget.name : void 0;
77
- if (typeof name !== "string") {
78
- return this.error(
79
- new Error(
80
- `The first argument of ${HELPER_MOCK_IMPORT}() must be a string literal or mocked target`
81
- ),
82
- startOf(importTarget)
83
- );
84
- }
85
- const importItem = ctx.imports.find((_) => name === (_.as || _.name));
86
- if (!importItem) {
87
- return this.error(`Cannot find import "${name}" to mock`);
88
- }
89
- s.overwrite(
90
- isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[0]),
91
- isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
92
- ""
93
- );
94
- mocksImport.push({
95
- name,
96
- import: importItem,
97
- factory: code.slice(
98
- startOf(node.arguments[1]),
99
- endOf(node.arguments[1])
100
- )
31
+ return {
32
+ name: PLUGIN_NAME$1,
33
+ enforce: "post",
34
+ vite: {
35
+ transform(code, id) {
36
+ if (!HELPERS_NAME.some((n) => code.includes(n))) return;
37
+ if (id.includes("/node_modules/")) return;
38
+ let ast;
39
+ try {
40
+ ast = this.parse(code, {
41
+ // @ts-expect-error compatibility with rollup v3
42
+ sourceType: "module",
43
+ ecmaVersion: "latest",
44
+ ranges: true
101
45
  });
46
+ } catch {
47
+ return;
102
48
  }
103
- if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_COMPONENT) {
104
- if (node.arguments.length !== 2) {
105
- return this.error(
106
- new Error(
107
- `${HELPER_MOCK_COMPONENT}() should have exactly 2 arguments`
108
- ),
109
- startOf(node)
110
- );
49
+ let insertionPoint = 0;
50
+ let hasViImport = false;
51
+ const s = new MagicString(code);
52
+ const mocksImport = [];
53
+ const mocksComponent = [];
54
+ const importPathsList = /* @__PURE__ */ new Set();
55
+ walk(ast, {
56
+ enter: (node, parent) => {
57
+ if (isImportDeclaration(node)) {
58
+ if (node.source.value === "vitest" && !hasViImport) {
59
+ const viImport = node.specifiers.find(
60
+ (i) => isImportSpecifier(i) && i.imported.type === "Identifier" && i.imported.name === "vi"
61
+ );
62
+ if (viImport) {
63
+ insertionPoint = endOf(node);
64
+ hasViImport = true;
65
+ }
66
+ return;
67
+ }
68
+ }
69
+ if (!isCallExpression(node)) return;
70
+ if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_IMPORT) {
71
+ if (node.arguments.length !== 2) {
72
+ return this.error(
73
+ new Error(
74
+ `${HELPER_MOCK_IMPORT}() should have exactly 2 arguments`
75
+ ),
76
+ startOf(node)
77
+ );
78
+ }
79
+ const importTarget = node.arguments[0];
80
+ const name = isLiteral(importTarget) ? importTarget.value : isIdentifier(importTarget) ? importTarget.name : void 0;
81
+ if (typeof name !== "string") {
82
+ return this.error(
83
+ new Error(
84
+ `The first argument of ${HELPER_MOCK_IMPORT}() must be a string literal or mocked target`
85
+ ),
86
+ startOf(importTarget)
87
+ );
88
+ }
89
+ const importItem = ctx.imports.find((_) => name === (_.as || _.name));
90
+ if (!importItem) {
91
+ return this.error(`Cannot find import "${name}" to mock`);
92
+ }
93
+ s.overwrite(
94
+ isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[0]),
95
+ isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
96
+ ""
97
+ );
98
+ mocksImport.push({
99
+ name,
100
+ import: importItem,
101
+ factory: code.slice(
102
+ startOf(node.arguments[1]),
103
+ endOf(node.arguments[1])
104
+ )
105
+ });
106
+ }
107
+ if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_COMPONENT) {
108
+ if (node.arguments.length !== 2) {
109
+ return this.error(
110
+ new Error(
111
+ `${HELPER_MOCK_COMPONENT}() should have exactly 2 arguments`
112
+ ),
113
+ startOf(node)
114
+ );
115
+ }
116
+ const componentName = node.arguments[0];
117
+ if (!isLiteral(componentName) || typeof componentName.value !== "string") {
118
+ return this.error(
119
+ new Error(
120
+ `The first argument of ${HELPER_MOCK_COMPONENT}() must be a string literal`
121
+ ),
122
+ startOf(componentName)
123
+ );
124
+ }
125
+ const pathOrName = componentName.value;
126
+ const component = ctx.components.find(
127
+ (_) => _.pascalName === pathOrName || _.kebabName === pathOrName
128
+ );
129
+ const path = component?.filePath || pathOrName;
130
+ s.overwrite(
131
+ isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[1]),
132
+ isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
133
+ ""
134
+ );
135
+ mocksComponent.push({
136
+ path,
137
+ factory: code.slice(
138
+ startOf(node.arguments[1]),
139
+ endOf(node.arguments[1])
140
+ )
141
+ });
142
+ }
111
143
  }
112
- const componentName = node.arguments[0];
113
- if (!isLiteral(componentName) || typeof componentName.value !== "string") {
114
- return this.error(
115
- new Error(
116
- `The first argument of ${HELPER_MOCK_COMPONENT}() must be a string literal`
117
- ),
118
- startOf(componentName)
119
- );
144
+ });
145
+ if (mocksImport.length === 0 && mocksComponent.length === 0) return;
146
+ const mockLines = [];
147
+ if (mocksImport.length) {
148
+ const mockImportMap = /* @__PURE__ */ new Map();
149
+ for (const mock of mocksImport) {
150
+ if (!mockImportMap.has(mock.import.from)) {
151
+ mockImportMap.set(mock.import.from, []);
152
+ }
153
+ mockImportMap.get(mock.import.from).push(mock);
120
154
  }
121
- const pathOrName = componentName.value;
122
- const component = ctx.components.find(
123
- (_) => _.pascalName === pathOrName || _.kebabName === pathOrName
124
- );
125
- const path = component?.filePath || pathOrName;
126
- s.overwrite(
127
- isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[1]),
128
- isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
129
- ""
130
- );
131
- mocksComponent.push({
132
- path,
133
- factory: code.slice(
134
- startOf(node.arguments[1]),
135
- endOf(node.arguments[1])
155
+ mockLines.push(
156
+ ...Array.from(mockImportMap.entries()).flatMap(
157
+ ([from, mocks]) => {
158
+ importPathsList.add(from);
159
+ const quotedFrom = JSON.stringify(from);
160
+ const mockModuleEntry = `globalThis.${HELPER_MOCK_HOIST}[${quotedFrom}]`;
161
+ const lines = [
162
+ `vi.mock(${quotedFrom}, async (importOriginal) => {`,
163
+ ` if (!${mockModuleEntry}) {`,
164
+ ` const original = await importOriginal(${quotedFrom})`,
165
+ ` ${mockModuleEntry} = { ...original }`,
166
+ ` ${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL} = { ...original }`,
167
+ ` }`
168
+ ];
169
+ for (const mock of mocks) {
170
+ const quotedName = JSON.stringify(mock.import.name === "default" ? "default" : mock.name);
171
+ lines.push(
172
+ ` ${mockModuleEntry}[${quotedName}] = await (${mock.factory})(${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL}[${quotedName}]);`
173
+ );
174
+ }
175
+ lines.push(` return ${mockModuleEntry} `);
176
+ lines.push(`});`);
177
+ return lines;
178
+ }
136
179
  )
137
- });
180
+ );
138
181
  }
139
- }
140
- });
141
- if (mocksImport.length === 0 && mocksComponent.length === 0) return;
142
- const mockLines = [];
143
- if (mocksImport.length) {
144
- const mockImportMap = /* @__PURE__ */ new Map();
145
- for (const mock of mocksImport) {
146
- if (!mockImportMap.has(mock.import.from)) {
147
- mockImportMap.set(mock.import.from, []);
182
+ if (mocksComponent.length) {
183
+ mockLines.push(
184
+ ...mocksComponent.flatMap((mock) => {
185
+ return [
186
+ `vi.mock(${JSON.stringify(mock.path)}, async () => {`,
187
+ ` const factory = (${mock.factory});`,
188
+ ` const result = typeof factory === 'function' ? await factory() : await factory`,
189
+ ` return 'default' in result ? result : { default: result }`,
190
+ "});"
191
+ ];
192
+ })
193
+ );
148
194
  }
149
- mockImportMap.get(mock.import.from).push(mock);
150
- }
151
- mockLines.push(
152
- ...Array.from(mockImportMap.entries()).flatMap(
153
- ([from, mocks]) => {
154
- importPathsList.add(from);
155
- const quotedFrom = JSON.stringify(from);
156
- const mockModuleEntry = `globalThis.${HELPER_MOCK_HOIST}[${quotedFrom}]`;
157
- const lines = [
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 }`,
163
- ` }`
164
- ];
165
- for (const mock of mocks) {
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
- }
171
- lines.push(` return ${mockModuleEntry} `);
172
- lines.push(`});`);
173
- return lines;
174
- }
175
- )
176
- );
177
- }
178
- if (mocksComponent.length) {
179
- mockLines.push(
180
- ...mocksComponent.flatMap((mock) => {
181
- return [
182
- `vi.mock(${JSON.stringify(mock.path)}, async () => {`,
183
- ` const factory = (${mock.factory});`,
184
- ` const result = typeof factory === 'function' ? await factory() : await factory`,
185
- ` return 'default' in result ? result : { default: result }`,
186
- "});"
187
- ];
188
- })
189
- );
190
- }
191
- if (!mockLines.length) return;
192
- s.appendLeft(insertionPoint, `
195
+ if (!mockLines.length) return;
196
+ s.appendLeft(insertionPoint, `
193
197
  vi.hoisted(() => {
194
198
  if(!globalThis.${HELPER_MOCK_HOIST}){
195
199
  vi.stubGlobal(${JSON.stringify(HELPER_MOCK_HOIST)}, {})
196
200
  }
197
201
  });
198
202
  `);
199
- if (!hasViImport) s.prepend(`import {vi} from "vitest";
203
+ if (!hasViImport) s.prepend(`import {vi} from "vitest";
200
204
  `);
201
- s.appendLeft(insertionPoint, "\n" + mockLines.join("\n") + "\n");
202
- importPathsList.forEach((p) => {
203
- s.append(`
205
+ s.appendLeft(insertionPoint, "\n" + mockLines.join("\n") + "\n");
206
+ importPathsList.forEach((p) => {
207
+ s.append(`
204
208
  import ${JSON.stringify(p)};`);
205
- });
206
- return {
207
- code: s.toString(),
208
- map: s.generateMap()
209
- };
210
- }
211
- return {
212
- name: PLUGIN_NAME$1,
213
- enforce: "post",
214
- vite: {
215
- transform,
209
+ });
210
+ return {
211
+ code: s.toString(),
212
+ map: s.generateMap()
213
+ };
214
+ },
216
215
  // Place Vitest's mock plugin after all Nuxt plugins
217
216
  async configResolved(config) {
218
217
  const plugins = config.plugins || [];
@@ -953,7 +952,7 @@ function vitestWrapper(options) {
953
952
  };
954
953
  }
955
954
 
956
- const version = "4.0.0";
955
+ const version = "4.0.1";
957
956
  const pkg = {
958
957
  version: version};
959
958
 
@@ -6,12 +6,12 @@ export default defineComponent({
6
6
  const nuxtApp = useNuxtApp();
7
7
  provide(PageRouteSymbol, useRoute());
8
8
  const done = nuxtApp.deferHydration();
9
- const results = nuxtApp.hooks.callHookWith((hooks) => hooks.map((hook) => hook()), "vue:setup");
9
+ const results = nuxtApp.hooks.callHookWith((hooks) => hooks.map((hook) => hook()), "vue:setup", []);
10
10
  if (import.meta.dev && results && results.some((i) => i && "then" in i)) {
11
11
  console.error("[nuxt] Error in `vue:setup`. Callbacks must be synchronous.");
12
12
  }
13
13
  onErrorCaptured((err, target, info) => {
14
- nuxtApp.hooks.callHook("vue:error", err, target, info).catch((hookError) => console.error("[nuxt] Error in `vue:error` hook", hookError));
14
+ nuxtApp.hooks.callHook("vue:error", err, target, info)?.catch((hookError) => console.error("[nuxt] Error in `vue:error` hook", hookError));
15
15
  if (isNuxtError(err) && (err.fatal || err.unhandled)) {
16
16
  return false;
17
17
  }
@@ -1,50 +1,5 @@
1
- import type { VueWrapper } from '@vue/test-utils';
2
- type Options = ReturnType<typeof createPluginOptions>;
3
- export declare function getVueWrapperPlugin(): Options;
4
- declare function createPluginOptions(): {
5
- _name: string;
6
- _instances: WeakRef<VueWrapper>[];
7
- readonly instances: VueWrapper<unknown, {
8
- $: import("vue").ComponentInternalInstance;
9
- $data: {};
10
- $props: {};
11
- $attrs: {
12
- [x: string]: unknown;
13
- };
14
- $refs: {
15
- [x: string]: unknown;
16
- };
17
- $slots: Readonly<{
18
- [name: string]: import("vue").Slot<any> | undefined;
19
- }>;
20
- $root: import("vue").ComponentPublicInstance | null;
21
- $parent: import("vue").ComponentPublicInstance | null;
22
- $host: Element | null;
23
- $emit: (event: string, ...args: any[]) => void;
24
- $el: any;
25
- $options: import("vue").ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}, {}, {}, string, import("vue").ComponentProvideOptions> & {
26
- beforeCreate?: (() => void) | (() => void)[];
27
- created?: (() => void) | (() => void)[];
28
- beforeMount?: (() => void) | (() => void)[];
29
- mounted?: (() => void) | (() => void)[];
30
- beforeUpdate?: (() => void) | (() => void)[];
31
- updated?: (() => void) | (() => void)[];
32
- activated?: (() => void) | (() => void)[];
33
- deactivated?: (() => void) | (() => void)[];
34
- beforeDestroy?: (() => void) | (() => void)[];
35
- beforeUnmount?: (() => void) | (() => void)[];
36
- destroyed?: (() => void) | (() => void)[];
37
- unmounted?: (() => void) | (() => void)[];
38
- renderTracked?: ((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[];
39
- renderTriggered?: ((e: import("vue").DebuggerEvent) => void) | ((e: import("vue").DebuggerEvent) => void)[];
40
- errorCaptured?: ((err: unknown, instance: import("vue").ComponentPublicInstance | null, info: string) => boolean | void) | ((err: unknown, instance: import("vue").ComponentPublicInstance | null, info: string) => boolean | void)[];
41
- };
42
- $forceUpdate: () => void;
43
- $nextTick: typeof import("vue").nextTick;
44
- $watch<T extends string | ((...args: any) => any)>(source: T, cb: T extends (...args: any) => infer R ? (...args: [R, R, import("@vue/reactivity").OnCleanup]) => any : (...args: [any, any, import("@vue/reactivity").OnCleanup]) => any, options?: import("vue").WatchOptions): import("vue").WatchStopHandle;
45
- } & Readonly<{}> & Omit<{}, never> & import("vue").ShallowUnwrapRef<{}> & {} & import("vue").ComponentCustomProperties & {}>[];
46
- addInstance(instance: VueWrapper): void;
1
+ interface Options {
47
2
  hasNuxtPage(): boolean;
48
- _hasComponent(componentName: string): boolean;
49
- };
3
+ }
4
+ export declare function getVueWrapperPlugin(): Options;
50
5
  export {};
@@ -1,6 +1,11 @@
1
- import { config } from "@vue/test-utils";
2
1
  const PLUGIN_NAME = "nuxt-test-utils";
2
+ const config = await import("@vue/test-utils").then((r) => r.config).catch(() => void 0);
3
3
  export function getVueWrapperPlugin() {
4
+ if (!config) {
5
+ return {
6
+ hasNuxtPage: () => false
7
+ };
8
+ }
4
9
  const installed = config.plugins.VueWrapper.installedPlugins.find(({ options: options2 }) => options2?._name === PLUGIN_NAME);
5
10
  if (installed) return installed.options;
6
11
  const options = createPluginOptions();
@@ -56,9 +56,23 @@ declare function registerEndpoint(url: string, options: EventHandler | {
56
56
  * }
57
57
  * })
58
58
  * ```
59
+ * @example
60
+ * ```ts
61
+ * // Making partial mock with original implementation
62
+ * mockNuxtImport(useRoute, original => vi.fn(original))
63
+ * // or (with name based)
64
+ * mockNuxtImport('useRoute', original => vi.fn(original))
65
+ * // or (with name based, type parameter)
66
+ * mockNuxtImport<typeof useRoute>('useRoute', original => vi.fn(original))
67
+ *
68
+ * // Override in test
69
+ * vi.mocked(useRoute).mockImplementation(
70
+ * (...args) => ({ ...vi.mocked(useRoute).getMockImplementation()!(...args), path: '/mocked' }),
71
+ * )
72
+ * ```
59
73
  * @see https://nuxt.com/docs/getting-started/testing#mocknuxtimport
60
74
  */
61
- declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: (original: T) => T | Promise<T>): void;
75
+ declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: T extends string ? (original: any) => any : (original: T) => T | Promise<T>): void;
62
76
  /**
63
77
  * `mockComponent` allows you to mock Nuxt's component.
64
78
  * @param path - component name in PascalCase, or the relative path of the component.
@@ -1,6 +1,5 @@
1
1
  import { mount } from '@vue/test-utils';
2
2
  import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance, onErrorCaptured, effectScope } from 'vue';
3
- import { defu } from 'defu';
4
3
  import { defineComponent, useRouter, h, tryUseNuxtApp } from '#imports';
5
4
  import NuxtRoot from '#build/root-component.mjs';
6
5
 
@@ -231,8 +230,9 @@ function wrapperSuspended(component, options, {
231
230
  }
232
231
  ))
233
232
  },
234
- defu(wrapperFnOptions, {
235
- global: {
233
+ {
234
+ ...wrapperFnOptions,
235
+ global: mergeComponentMountingGlobalOptions(wrapperFnOptions.global, {
236
236
  config: {
237
237
  globalProperties: makeAllPropertiesEnumerable(
238
238
  vueApp.config.globalProperties
@@ -246,11 +246,38 @@ function wrapperSuspended(component, options, {
246
246
  [ClonedComponent.name]: false
247
247
  },
248
248
  components: { ...vueApp._context.components, RouterLink }
249
- }
250
- })
249
+ })
250
+ }
251
251
  );
252
252
  });
253
253
  }
254
+ function mergeComponentMountingGlobalOptions(options = {}, defaults = {}) {
255
+ return {
256
+ ...options,
257
+ mixins: [...defaults.mixins || [], ...options.mixins || []],
258
+ stubs: {
259
+ ...defaults.stubs,
260
+ ...Array.isArray(options.stubs) ? Object.fromEntries(options.stubs.map((n) => [n, true])) : options.stubs
261
+ },
262
+ plugins: [...defaults.plugins || [], ...options.plugins || []],
263
+ components: { ...defaults.components, ...options.components },
264
+ provide: { ...defaults.provide, ...options.provide },
265
+ mocks: { ...defaults.mocks, ...options.mocks },
266
+ config: {
267
+ ...defaults.config,
268
+ ...options.config,
269
+ compilerOptions: {
270
+ ...defaults.config?.compilerOptions,
271
+ ...options.config?.compilerOptions
272
+ },
273
+ globalProperties: {
274
+ ...defaults.config?.globalProperties,
275
+ ...options.config?.globalProperties
276
+ }
277
+ },
278
+ directives: { ...defaults.directives, ...options.directives }
279
+ };
280
+ }
254
281
  function makeAllPropertiesEnumerable(target) {
255
282
  return {
256
283
  ...target,
@@ -1,10 +1,9 @@
1
+ import { resolveModulePath } from 'exsolve';
1
2
  import { indexedDB } from 'fake-indexeddb';
2
3
  import { joinURL } from 'ufo';
3
4
  import defu from 'defu';
4
- import { populateGlobal } from 'vitest/environments';
5
5
  import { createFetch } from 'ofetch';
6
6
  import { toRouteMatcher, createRouter, exportMatcher } from 'radix3';
7
- import { importModule } from 'local-pkg';
8
7
 
9
8
  function defineEventHandler(handler) {
10
9
  return Object.assign(handler, { __is_handler__: true });
@@ -192,7 +191,7 @@ async function setupWindow(win, environmentOptions) {
192
191
  }
193
192
 
194
193
  const happyDom = (async function(_, { happyDom = {} }) {
195
- const { Window, GlobalWindow } = await importModule("happy-dom");
194
+ const { Window, GlobalWindow } = await import('happy-dom');
196
195
  const window = new (GlobalWindow || Window)(happyDom);
197
196
  return {
198
197
  window,
@@ -203,7 +202,7 @@ const happyDom = (async function(_, { happyDom = {} }) {
203
202
  });
204
203
 
205
204
  const jsdom = (async function(global, { jsdom = {} }) {
206
- const { CookieJar, JSDOM, ResourceLoader, VirtualConsole } = await importModule("jsdom");
205
+ const { CookieJar, JSDOM, ResourceLoader, VirtualConsole } = await import('jsdom');
207
206
  const jsdomOptions = defu(jsdom, {
208
207
  html: "<!DOCTYPE html>",
209
208
  url: "http://localhost:3000",
@@ -217,7 +216,7 @@ const jsdom = (async function(global, { jsdom = {} }) {
217
216
  const virtualConsole = jsdomOptions.console && global.console ? new VirtualConsole() : void 0;
218
217
  const window = new JSDOM(jsdomOptions.html, {
219
218
  ...jsdomOptions,
220
- resources: jsdomOptions.resources ?? (jsdomOptions.userAgent ? new ResourceLoader({ userAgent: jsdomOptions.userAgent }) : void 0),
219
+ resources: jsdomOptions.resources ?? (jsdomOptions.userAgent ? ResourceLoader ? new ResourceLoader({ userAgent: jsdomOptions.userAgent }) : { userAgent: jsdomOptions.userAgent } : void 0),
221
220
  virtualConsole: virtualConsole ? "sendTo" in virtualConsole ? virtualConsole.sendTo(global.console) : virtualConsole.forwardTo(global.console) : void 0,
222
221
  cookieJar: jsdomOptions.cookieJar ? new CookieJar() : void 0
223
222
  }).window;
@@ -239,6 +238,7 @@ const index = {
239
238
  name: "nuxt",
240
239
  viteEnvironment: "client",
241
240
  async setup(global, environmentOptions) {
241
+ const { populateGlobal } = await importVitestEnvironments();
242
242
  const url = joinURL(
243
243
  environmentOptions.nuxt?.url ?? "http://localhost:3000",
244
244
  environmentOptions.nuxtRuntimeConfig?.app?.baseURL || "/"
@@ -274,6 +274,10 @@ const index = {
274
274
  };
275
275
  }
276
276
  };
277
+ async function importVitestEnvironments() {
278
+ const pkg = resolveModulePath("vitest/runtime", { try: true }) ? "vitest/runtime" : "vitest/environments";
279
+ return await import(pkg);
280
+ }
277
281
  class IntersectionObserver {
278
282
  observe() {
279
283
  }
@@ -1,4 +1,3 @@
1
- import { importModule } from 'local-pkg';
2
1
  import { getPort } from 'get-port-please';
3
2
  import { a as listenHostMessages, b as sendMessageToHost } from '../shared/test-utils.DDUpsMYL.mjs';
4
3
 
@@ -56,7 +55,7 @@ async function main() {
56
55
  });
57
56
  });
58
57
  const port = apiPorts ? await getPort({ ports: apiPorts }) : void 0;
59
- const { startVitest } = await importModule("vitest/node");
58
+ const { startVitest } = await import('vitest/node');
60
59
  const customReporter = createCustomReporter((vitest2) => {
61
60
  listenHostMessages(async ({ type, payload }) => {
62
61
  if (type === "stop") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxt/test-utils",
3
- "version": "4.0.0",
3
+ "version": "4.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/nuxt/test-utils.git"
@@ -60,17 +60,17 @@
60
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
- "test:types": "vue-tsc --noEmit",
64
- "test:unit": "vitest test/unit --run",
63
+ "test:types": "vue-tsc --noEmit && vitest --dir ./test/types --typecheck.only --run",
64
+ "test:unit": "vitest --dir ./test/unit --run",
65
65
  "build": "unbuild",
66
66
  "prepack": "unbuild",
67
67
  "dev:prepare": "nuxt prepare && unbuild --stub && pnpm -r dev:prepare"
68
68
  },
69
69
  "dependencies": {
70
- "@clack/prompts": "1.0.0",
70
+ "@clack/prompts": "1.2.0",
71
71
  "@nuxt/devtools-kit": "^2.7.0",
72
- "@nuxt/kit": "^3.21.0",
73
- "c12": "^3.3.3",
72
+ "@nuxt/kit": "^3.21.2",
73
+ "c12": "^3.3.4",
74
74
  "consola": "^3.4.2",
75
75
  "defu": "^6.1.4",
76
76
  "destr": "^2.0.5",
@@ -78,58 +78,58 @@
78
78
  "exsolve": "^1.0.8",
79
79
  "fake-indexeddb": "^6.2.5",
80
80
  "get-port-please": "^3.2.0",
81
- "h3": "^1.15.5",
82
- "h3-next": "npm:h3@2.0.1-rc.11",
81
+ "h3": "^1.15.11",
82
+ "h3-next": "npm:h3@2.0.1-rc.20",
83
83
  "local-pkg": "^1.1.2",
84
84
  "magic-string": "^0.30.21",
85
85
  "node-fetch-native": "^1.6.7",
86
86
  "node-mock-http": "^1.0.4",
87
- "nypm": "^0.6.4",
87
+ "nypm": "^0.6.5",
88
88
  "ofetch": "^1.5.1",
89
89
  "pathe": "^2.0.3",
90
90
  "perfect-debounce": "^2.1.0",
91
91
  "radix3": "^1.1.2",
92
92
  "scule": "^1.3.0",
93
- "std-env": "^3.10.0",
94
- "tinyexec": "^1.0.2",
93
+ "std-env": "^4.0.0",
94
+ "tinyexec": "^1.1.1",
95
95
  "ufo": "^1.6.3",
96
96
  "unplugin": "^3.0.0",
97
- "vitest-environment-nuxt": "^1.0.1",
98
- "vue": "^3.5.27"
97
+ "vitest-environment-nuxt": "workspace:*",
98
+ "vue": "^3.5.32"
99
99
  },
100
100
  "devDependencies": {
101
- "@cucumber/cucumber": "12.6.0",
102
- "@jest/globals": "30.2.0",
103
- "@nuxt/eslint-config": "1.13.0",
104
- "@nuxt/schema": "4.3.0",
105
- "@playwright/test": "1.58.1",
101
+ "@cucumber/cucumber": "12.7.0",
102
+ "@jest/globals": "30.3.0",
103
+ "@nuxt/eslint-config": "1.15.2",
104
+ "@nuxt/schema": "4.4.2",
105
+ "@playwright/test": "1.59.1",
106
106
  "@testing-library/vue": "8.1.0",
107
- "@types/bun": "1.3.8",
107
+ "@types/bun": "1.3.11",
108
108
  "@types/estree": "1.0.8",
109
- "@types/jsdom": "27.0.0",
109
+ "@types/jsdom": "28.0.1",
110
110
  "@types/node": "latest",
111
111
  "@types/semver": "7.7.1",
112
- "@vitest/browser-playwright": "4.0.18",
112
+ "@vitest/browser-playwright": "4.1.2",
113
113
  "@vue/test-utils": "2.4.6",
114
114
  "changelogen": "0.6.2",
115
115
  "compatx": "0.2.0",
116
- "eslint": "9.39.2",
117
- "installed-check": "9.3.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",
125
- "semver": "7.7.3",
126
- "typescript": "5.9.3",
116
+ "eslint": "10.2.0",
117
+ "installed-check": "10.0.1",
118
+ "knip": "6.3.0",
119
+ "nitropack": "2.13.3",
120
+ "nuxt": "4.4.2",
121
+ "oxc-parser": "0.124.0",
122
+ "pkg-pr-new": "0.0.66",
123
+ "playwright-core": "1.59.1",
124
+ "rollup": "4.60.1",
125
+ "semver": "7.7.4",
126
+ "typescript": "6.0.2",
127
127
  "unbuild": "latest",
128
- "unimport": "5.6.0",
129
- "vite": "7.3.1",
130
- "vitest": "4.0.18",
131
- "vue-router": "5.0.2",
132
- "vue-tsc": "3.2.4"
128
+ "unimport": "6.0.2",
129
+ "vite": "8.0.5",
130
+ "vitest": "4.1.2",
131
+ "vue-router": "5.0.4",
132
+ "vue-tsc": "3.2.6"
133
133
  },
134
134
  "peerDependencies": {
135
135
  "@cucumber/cucumber": ">=11.0.0",
@@ -175,19 +175,19 @@
175
175
  }
176
176
  },
177
177
  "resolutions": {
178
- "@cucumber/cucumber": "12.6.0",
179
- "@nuxt/schema": "4.3.0",
178
+ "@cucumber/cucumber": "12.7.0",
179
+ "@nuxt/schema": "4.4.2",
180
180
  "@nuxt/test-utils": "workspace:*",
181
- "@types/node": "24.10.9",
181
+ "@types/node": "24.12.2",
182
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"
183
+ "rollup": "4.60.1",
184
+ "vite": "8.0.5",
185
+ "vite-node": "6.0.0",
186
+ "vitest": "4.1.2",
187
+ "vue": "^3.5.32"
188
188
  },
189
189
  "engines": {
190
190
  "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
191
191
  },
192
- "packageManager": "pnpm@10.28.2"
192
+ "packageManager": "pnpm@10.33.0"
193
193
  }