@nuxt/test-utils-nightly 4.0.0-1699284628.89ffa77 → 4.0.0-1702810232.fba662c
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/LICENSE +21 -0
- package/README.md +18 -0
- package/config.d.ts +1 -0
- package/dist/config.d.mts +46 -0
- package/dist/config.d.ts +46 -0
- package/dist/config.mjs +156 -0
- package/dist/{index.d.mts → e2e.d.mts} +5 -0
- package/dist/{index.d.ts → e2e.d.ts} +5 -0
- package/dist/{index.mjs → e2e.mjs} +18 -8
- package/dist/experimental.mjs +1 -1
- package/dist/module.d.mts +11 -0
- package/dist/module.d.ts +11 -0
- package/dist/module.mjs +461 -0
- package/dist/runtime/entry.d.ts +1 -0
- package/dist/runtime/entry.mjs +9 -0
- package/dist/runtime/global-setup.mjs +1 -1
- package/dist/runtime/nuxt-root.d.ts +4 -0
- package/dist/runtime/nuxt-root.mjs +21 -0
- package/dist/runtime-utils/index.d.mts +150 -0
- package/dist/runtime-utils/index.d.ts +150 -0
- package/dist/runtime-utils/index.mjs +223 -0
- package/dist/vitest-environment.d.mts +22 -0
- package/dist/vitest-environment.d.ts +22 -0
- package/dist/vitest-environment.mjs +175 -0
- package/e2e.d.ts +1 -0
- package/module.d.ts +1 -0
- package/package.json +88 -28
- package/runtime.d.ts +1 -0
- package/vitest-environment.d.ts +1 -0
- /package/dist/shared/{test-utils-nightly.8f432eb9.mjs → test-utils-nightly.ddf5bsCK.mjs} +0 -0
package/dist/module.mjs
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { resolvePath, useNuxt, resolveIgnorePatterns, addVitePlugin, defineNuxtModule, createResolver, logger } from '@nuxt/kit';
|
|
3
|
+
import { mergeConfig } from 'vite';
|
|
4
|
+
import { getPort } from 'get-port-please';
|
|
5
|
+
import { h } from 'vue';
|
|
6
|
+
import { debounce } from 'perfect-debounce';
|
|
7
|
+
import { isCI } from 'std-env';
|
|
8
|
+
import { defu } from 'defu';
|
|
9
|
+
import { getVitestConfigFromNuxt } from './config.mjs';
|
|
10
|
+
import { walk } from 'estree-walker';
|
|
11
|
+
import MagicString from 'magic-string';
|
|
12
|
+
import { normalize, resolve } from 'node:path';
|
|
13
|
+
import { createUnplugin } from 'unplugin';
|
|
14
|
+
import { readFileSync } from 'node:fs';
|
|
15
|
+
import { extname, join, dirname } from 'pathe';
|
|
16
|
+
|
|
17
|
+
const PLUGIN_NAME$1 = "nuxt:vitest:mock-transform";
|
|
18
|
+
const HELPER_MOCK_IMPORT = "mockNuxtImport";
|
|
19
|
+
const HELPER_MOCK_COMPONENT = "mockComponent";
|
|
20
|
+
const HELPER_MOCK_HOIST = "__NUXT_VITEST_MOCKS";
|
|
21
|
+
const HELPERS_NAME = [HELPER_MOCK_IMPORT, HELPER_MOCK_COMPONENT];
|
|
22
|
+
const createMockPlugin = (ctx) => createUnplugin(() => {
|
|
23
|
+
let resolvedFirstSetupFile = null;
|
|
24
|
+
function transform(code, id) {
|
|
25
|
+
const isFirstSetupFile = normalize(id) === resolvedFirstSetupFile;
|
|
26
|
+
const shouldPrependMockHoist = resolvedFirstSetupFile ? isFirstSetupFile : true;
|
|
27
|
+
if (!HELPERS_NAME.some((n) => code.includes(n)))
|
|
28
|
+
return;
|
|
29
|
+
if (id.includes("/node_modules/"))
|
|
30
|
+
return;
|
|
31
|
+
let ast;
|
|
32
|
+
try {
|
|
33
|
+
ast = this.parse(code, {
|
|
34
|
+
// @ts-expect-error compatibility with rollup v3
|
|
35
|
+
sourceType: "module",
|
|
36
|
+
ecmaVersion: "latest",
|
|
37
|
+
ranges: true
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
let insertionPoint = 0;
|
|
43
|
+
let hasViImport = false;
|
|
44
|
+
const s = new MagicString(code);
|
|
45
|
+
const mocksImport = [];
|
|
46
|
+
const mocksComponent = [];
|
|
47
|
+
const importPathsList = /* @__PURE__ */ new Set();
|
|
48
|
+
walk(ast, {
|
|
49
|
+
enter: (node, parent) => {
|
|
50
|
+
if (isImportDeclaration(node)) {
|
|
51
|
+
if (node.source.value === "vitest" && !hasViImport) {
|
|
52
|
+
const viImport = node.specifiers.find(
|
|
53
|
+
(i) => isImportSpecifier(i) && i.imported.name === "vi"
|
|
54
|
+
);
|
|
55
|
+
if (viImport) {
|
|
56
|
+
insertionPoint = endOf(node);
|
|
57
|
+
hasViImport = true;
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (!isCallExpression(node))
|
|
63
|
+
return;
|
|
64
|
+
if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_IMPORT) {
|
|
65
|
+
if (node.arguments.length !== 2) {
|
|
66
|
+
return this.error(
|
|
67
|
+
new Error(
|
|
68
|
+
`${HELPER_MOCK_IMPORT}() should have exactly 2 arguments`
|
|
69
|
+
),
|
|
70
|
+
startOf(node)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
const importName = node.arguments[0];
|
|
74
|
+
if (!isLiteral(importName) || typeof importName.value !== "string") {
|
|
75
|
+
return this.error(
|
|
76
|
+
new Error(
|
|
77
|
+
`The first argument of ${HELPER_MOCK_IMPORT}() must be a string literal`
|
|
78
|
+
),
|
|
79
|
+
startOf(importName)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const name = importName.value;
|
|
83
|
+
const importItem = ctx.imports.find((_) => name === (_.as || _.name));
|
|
84
|
+
if (!importItem) {
|
|
85
|
+
console.log({ imports: ctx.imports });
|
|
86
|
+
return this.error(`Cannot find import "${name}" to mock`);
|
|
87
|
+
}
|
|
88
|
+
s.overwrite(
|
|
89
|
+
isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[0]),
|
|
90
|
+
isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
|
|
91
|
+
""
|
|
92
|
+
);
|
|
93
|
+
mocksImport.push({
|
|
94
|
+
name,
|
|
95
|
+
import: importItem,
|
|
96
|
+
factory: code.slice(
|
|
97
|
+
startOf(node.arguments[1]),
|
|
98
|
+
endOf(node.arguments[1])
|
|
99
|
+
)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_COMPONENT) {
|
|
103
|
+
if (node.arguments.length !== 2) {
|
|
104
|
+
return this.error(
|
|
105
|
+
new Error(
|
|
106
|
+
`${HELPER_MOCK_COMPONENT}() should have exactly 2 arguments`
|
|
107
|
+
),
|
|
108
|
+
startOf(node)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
const componentName = node.arguments[0];
|
|
112
|
+
if (!isLiteral(componentName) || typeof componentName.value !== "string") {
|
|
113
|
+
return this.error(
|
|
114
|
+
new Error(
|
|
115
|
+
`The first argument of ${HELPER_MOCK_COMPONENT}() must be a string literal`
|
|
116
|
+
),
|
|
117
|
+
startOf(componentName)
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
const pathOrName = componentName.value;
|
|
121
|
+
const component = ctx.components.find(
|
|
122
|
+
(_) => _.pascalName === pathOrName || _.kebabName === pathOrName
|
|
123
|
+
);
|
|
124
|
+
const path = component?.filePath || pathOrName;
|
|
125
|
+
s.overwrite(
|
|
126
|
+
isExpressionStatement(parent) ? startOf(parent) : startOf(node.arguments[1]),
|
|
127
|
+
isExpressionStatement(parent) ? endOf(parent) : endOf(node.arguments[1]),
|
|
128
|
+
""
|
|
129
|
+
);
|
|
130
|
+
mocksComponent.push({
|
|
131
|
+
path,
|
|
132
|
+
factory: code.slice(
|
|
133
|
+
startOf(node.arguments[1]),
|
|
134
|
+
endOf(node.arguments[1])
|
|
135
|
+
)
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
if (mocksImport.length === 0 && mocksComponent.length === 0)
|
|
141
|
+
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, []);
|
|
148
|
+
}
|
|
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 lines = [
|
|
156
|
+
`vi.mock(${JSON.stringify(from)}, async (importOriginal) => {`,
|
|
157
|
+
` const mocks = globalThis.${HELPER_MOCK_HOIST}`,
|
|
158
|
+
` if (!mocks[${JSON.stringify(from)}]) {`,
|
|
159
|
+
` mocks[${JSON.stringify(from)}] = { ...await importOriginal(${JSON.stringify(from)}) }`,
|
|
160
|
+
` }`
|
|
161
|
+
];
|
|
162
|
+
for (const mock of mocks) {
|
|
163
|
+
if (mock.import.name === "default") {
|
|
164
|
+
lines.push(
|
|
165
|
+
` mocks[${JSON.stringify(from)}]["default"] = await (${mock.factory})();`
|
|
166
|
+
);
|
|
167
|
+
} else {
|
|
168
|
+
lines.push(
|
|
169
|
+
` mocks[${JSON.stringify(from)}][${JSON.stringify(mock.name)}] = await (${mock.factory})();`
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
lines.push(` return mocks[${JSON.stringify(from)}] `);
|
|
174
|
+
lines.push(`});`);
|
|
175
|
+
return lines;
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (mocksComponent.length) {
|
|
181
|
+
mockLines.push(
|
|
182
|
+
...mocksComponent.flatMap((mock) => {
|
|
183
|
+
return [
|
|
184
|
+
`vi.mock(${JSON.stringify(mock.path)}, async () => {`,
|
|
185
|
+
` const factory = (${mock.factory});`,
|
|
186
|
+
` const result = typeof factory === 'function' ? await factory() : await factory`,
|
|
187
|
+
` return 'default' in result ? result : { default: result }`,
|
|
188
|
+
"});"
|
|
189
|
+
];
|
|
190
|
+
})
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (!mockLines.length)
|
|
194
|
+
return;
|
|
195
|
+
s.prepend(`vi.hoisted(() => {
|
|
196
|
+
if(!globalThis.${HELPER_MOCK_HOIST}){
|
|
197
|
+
vi.stubGlobal(${JSON.stringify(HELPER_MOCK_HOIST)}, {})
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
`);
|
|
201
|
+
if (!hasViImport)
|
|
202
|
+
s.prepend(`import {vi} from "vitest";
|
|
203
|
+
`);
|
|
204
|
+
s.appendLeft(insertionPoint, mockLines.join("\n") + "\n");
|
|
205
|
+
if (shouldPrependMockHoist) {
|
|
206
|
+
importPathsList.forEach((p) => {
|
|
207
|
+
s.append(`
|
|
208
|
+
import ${JSON.stringify(p)};`);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
code: s.toString(),
|
|
213
|
+
map: s.generateMap()
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
name: PLUGIN_NAME$1,
|
|
218
|
+
enforce: "post",
|
|
219
|
+
vite: {
|
|
220
|
+
transform,
|
|
221
|
+
// Place Vitest's mock plugin after all Nuxt plugins
|
|
222
|
+
async configResolved(config) {
|
|
223
|
+
const firstSetupFile = Array.isArray(config.test?.setupFiles) ? config.test.setupFiles.find((p) => !p.includes("runtime/entry")) : config.test?.setupFiles;
|
|
224
|
+
if (firstSetupFile) {
|
|
225
|
+
resolvedFirstSetupFile = await resolvePath(normalize(resolve(firstSetupFile)));
|
|
226
|
+
}
|
|
227
|
+
const plugins = config.plugins || [];
|
|
228
|
+
const vitestPlugins = plugins.filter((p) => (p.name === "vite:mocks" || p.name.startsWith("vitest:")) && (p.enforce || p.order) === "post");
|
|
229
|
+
const lastNuxt = findLastIndex(
|
|
230
|
+
plugins,
|
|
231
|
+
(i) => i.name?.startsWith("nuxt:")
|
|
232
|
+
);
|
|
233
|
+
if (lastNuxt === -1)
|
|
234
|
+
return;
|
|
235
|
+
for (const plugin of vitestPlugins) {
|
|
236
|
+
const index = plugins.indexOf(plugin);
|
|
237
|
+
if (index < lastNuxt) {
|
|
238
|
+
plugins.splice(index, 1);
|
|
239
|
+
plugins.splice(lastNuxt, 0, plugin);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
function findLastIndex(arr, predicate) {
|
|
247
|
+
for (let i = arr.length - 1; i >= 0; i--) {
|
|
248
|
+
if (predicate(arr[i]))
|
|
249
|
+
return i;
|
|
250
|
+
}
|
|
251
|
+
return -1;
|
|
252
|
+
}
|
|
253
|
+
function isImportDeclaration(node) {
|
|
254
|
+
return node.type === "ImportDeclaration";
|
|
255
|
+
}
|
|
256
|
+
function isImportSpecifier(node) {
|
|
257
|
+
return node.type === "ImportSpecifier";
|
|
258
|
+
}
|
|
259
|
+
function isCallExpression(node) {
|
|
260
|
+
return node.type === "CallExpression";
|
|
261
|
+
}
|
|
262
|
+
function isIdentifier(node) {
|
|
263
|
+
return node.type === "Identifier";
|
|
264
|
+
}
|
|
265
|
+
function isLiteral(node) {
|
|
266
|
+
return node.type === "Literal";
|
|
267
|
+
}
|
|
268
|
+
function isExpressionStatement(node) {
|
|
269
|
+
return node?.type === "ExpressionStatement";
|
|
270
|
+
}
|
|
271
|
+
function startOf(node) {
|
|
272
|
+
return "range" in node && node.range ? node.range[0] : node.start;
|
|
273
|
+
}
|
|
274
|
+
function endOf(node) {
|
|
275
|
+
return "range" in node && node.range ? node.range[1] : node.end;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function setupImportMocking() {
|
|
279
|
+
const nuxt = useNuxt();
|
|
280
|
+
const ctx = {
|
|
281
|
+
components: [],
|
|
282
|
+
imports: []
|
|
283
|
+
};
|
|
284
|
+
let importsCtx;
|
|
285
|
+
nuxt.hook("imports:context", async (ctx2) => {
|
|
286
|
+
importsCtx = ctx2;
|
|
287
|
+
});
|
|
288
|
+
nuxt.hook("ready", async () => {
|
|
289
|
+
ctx.imports = await importsCtx.getImports();
|
|
290
|
+
});
|
|
291
|
+
nuxt.hook("components:extend", (_) => {
|
|
292
|
+
ctx.components = _;
|
|
293
|
+
});
|
|
294
|
+
nuxt.options.ignore = nuxt.options.ignore.filter((i) => i !== "**/*.{spec,test}.{js,cts,mts,ts,jsx,tsx}");
|
|
295
|
+
if (nuxt._ignore) {
|
|
296
|
+
for (const pattern of resolveIgnorePatterns("**/*.{spec,test}.{js,cts,mts,ts,jsx,tsx}")) {
|
|
297
|
+
nuxt._ignore.add(`!${pattern}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
addVitePlugin(createMockPlugin(ctx).vite());
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const PLUGIN_NAME = "nuxt:vitest:nuxt-root-stub";
|
|
304
|
+
const STUB_ID = "nuxt-vitest-app-entry";
|
|
305
|
+
const NuxtRootStubPlugin = createUnplugin((options) => {
|
|
306
|
+
const STUB_ID_WITH_EXT = STUB_ID + extname(options.entry);
|
|
307
|
+
return {
|
|
308
|
+
name: PLUGIN_NAME,
|
|
309
|
+
enforce: "pre",
|
|
310
|
+
vite: {
|
|
311
|
+
async resolveId(id, importer) {
|
|
312
|
+
if (id.endsWith(STUB_ID) || id.endsWith(STUB_ID_WITH_EXT)) {
|
|
313
|
+
return importer?.endsWith("index.html") ? id : join(dirname(options.entry), STUB_ID_WITH_EXT);
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
async load(id) {
|
|
317
|
+
if (id.endsWith(STUB_ID) || id.endsWith(STUB_ID_WITH_EXT)) {
|
|
318
|
+
const entryContents = readFileSync(options.entry, "utf-8");
|
|
319
|
+
return entryContents.replace("#build/root-component.mjs", options.rootStubPath);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
const vitePluginBlocklist = ["vite-plugin-vue-inspector", "vite-plugin-vue-inspector:post", "vite-plugin-inspect"];
|
|
327
|
+
const module = defineNuxtModule({
|
|
328
|
+
meta: {
|
|
329
|
+
name: "@nuxt/test-utils",
|
|
330
|
+
configKey: "vitest"
|
|
331
|
+
},
|
|
332
|
+
defaults: {
|
|
333
|
+
startOnBoot: false,
|
|
334
|
+
logToConsole: false
|
|
335
|
+
},
|
|
336
|
+
async setup(options, nuxt) {
|
|
337
|
+
if (nuxt.options.test || nuxt.options.dev) {
|
|
338
|
+
setupImportMocking();
|
|
339
|
+
}
|
|
340
|
+
const resolver = createResolver(import.meta.url);
|
|
341
|
+
addVitePlugin(NuxtRootStubPlugin.vite({
|
|
342
|
+
entry: await resolvePath("#app/entry", { alias: nuxt.options.alias }),
|
|
343
|
+
rootStubPath: await resolvePath(resolver.resolve("./runtime/nuxt-root"))
|
|
344
|
+
}));
|
|
345
|
+
if (!nuxt.options.dev)
|
|
346
|
+
return;
|
|
347
|
+
if (process.env.TEST || process.env.VITE_TEST)
|
|
348
|
+
return;
|
|
349
|
+
const rawViteConfigPromise = new Promise((resolve) => {
|
|
350
|
+
nuxt.hook("app:resolve", () => {
|
|
351
|
+
nuxt.hook("vite:configResolved", (config, { isClient }) => {
|
|
352
|
+
if (isClient)
|
|
353
|
+
resolve(config);
|
|
354
|
+
});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
let loaded = false;
|
|
358
|
+
let promise;
|
|
359
|
+
let ctx = void 0;
|
|
360
|
+
let testFiles = null;
|
|
361
|
+
const updateTabs = debounce(() => {
|
|
362
|
+
nuxt.callHook("devtools:customTabs:refresh");
|
|
363
|
+
}, 100);
|
|
364
|
+
let URL;
|
|
365
|
+
async function start() {
|
|
366
|
+
const rawViteConfig = mergeConfig({}, await rawViteConfigPromise);
|
|
367
|
+
const viteConfig = await getVitestConfigFromNuxt({ nuxt, viteConfig: defu({ test: options.vitestConfig }, rawViteConfig) });
|
|
368
|
+
viteConfig.plugins = (viteConfig.plugins || []).filter((p) => {
|
|
369
|
+
return !vitePluginBlocklist.includes(p?.name);
|
|
370
|
+
});
|
|
371
|
+
process.env.__NUXT_VITEST_RESOLVED__ = "true";
|
|
372
|
+
const { startVitest } = await import(pathToFileURL(await resolvePath("vitest/node")).href);
|
|
373
|
+
const customReporter = {
|
|
374
|
+
onInit(_ctx) {
|
|
375
|
+
ctx = _ctx;
|
|
376
|
+
},
|
|
377
|
+
onTaskUpdate() {
|
|
378
|
+
testFiles = ctx.state.getFiles();
|
|
379
|
+
updateTabs();
|
|
380
|
+
},
|
|
381
|
+
onFinished() {
|
|
382
|
+
testFiles = ctx.state.getFiles();
|
|
383
|
+
updateTabs();
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
const watchMode = !process.env.NUXT_VITEST_DEV_TEST && !isCI;
|
|
387
|
+
const PORT = await getPort({ port: 15555 });
|
|
388
|
+
const PROTOCOL = nuxt.options.devServer.https ? "https" : "http";
|
|
389
|
+
URL = `${PROTOCOL}://localhost:${PORT}/__vitest__/`;
|
|
390
|
+
const overrides = watchMode ? {
|
|
391
|
+
passWithNoTests: true,
|
|
392
|
+
reporters: options.logToConsole ? [
|
|
393
|
+
...toArray(options.vitestConfig?.reporters ?? ["default"]),
|
|
394
|
+
customReporter
|
|
395
|
+
] : [customReporter],
|
|
396
|
+
// do not report to console
|
|
397
|
+
watch: true,
|
|
398
|
+
ui: true,
|
|
399
|
+
open: false,
|
|
400
|
+
api: {
|
|
401
|
+
port: PORT
|
|
402
|
+
}
|
|
403
|
+
} : { watch: false };
|
|
404
|
+
const promise2 = startVitest("test", [], defu(overrides, viteConfig.test), viteConfig);
|
|
405
|
+
promise2.catch(() => process.exit(1));
|
|
406
|
+
if (watchMode) {
|
|
407
|
+
logger.info(`Vitest UI starting on ${URL}`);
|
|
408
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
409
|
+
} else {
|
|
410
|
+
promise2.then((v) => v?.close()).then(() => process.exit());
|
|
411
|
+
promise2.catch(() => process.exit(1));
|
|
412
|
+
}
|
|
413
|
+
loaded = true;
|
|
414
|
+
}
|
|
415
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
416
|
+
const failedCount = testFiles?.filter((f) => f.result?.state === "fail").length ?? 0;
|
|
417
|
+
const passedCount = testFiles?.filter((f) => f.result?.state === "pass").length ?? 0;
|
|
418
|
+
const totalCount = testFiles?.length ?? 0;
|
|
419
|
+
tabs.push({
|
|
420
|
+
title: "Vitest",
|
|
421
|
+
name: "vitest",
|
|
422
|
+
icon: "logos-vitest",
|
|
423
|
+
view: loaded ? {
|
|
424
|
+
type: "iframe",
|
|
425
|
+
src: URL
|
|
426
|
+
} : {
|
|
427
|
+
type: "launch",
|
|
428
|
+
description: "Start tests along with Nuxt",
|
|
429
|
+
actions: [
|
|
430
|
+
{
|
|
431
|
+
label: promise ? "Starting..." : "Start Vitest",
|
|
432
|
+
pending: !!promise,
|
|
433
|
+
handle: () => {
|
|
434
|
+
promise = promise || start();
|
|
435
|
+
return promise;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
]
|
|
439
|
+
},
|
|
440
|
+
extraTabVNode: totalCount ? h("div", { style: { color: failedCount ? "orange" : "green" } }, [
|
|
441
|
+
h("span", {}, passedCount),
|
|
442
|
+
h("span", { style: { opacity: "0.5", fontSize: "0.9em" } }, "/"),
|
|
443
|
+
h(
|
|
444
|
+
"span",
|
|
445
|
+
{ style: { opacity: "0.8", fontSize: "0.9em" } },
|
|
446
|
+
totalCount
|
|
447
|
+
)
|
|
448
|
+
]) : void 0
|
|
449
|
+
});
|
|
450
|
+
});
|
|
451
|
+
if (options.startOnBoot) {
|
|
452
|
+
promise = promise || start();
|
|
453
|
+
promise.then(updateTabs);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
});
|
|
457
|
+
function toArray(value) {
|
|
458
|
+
return Array.isArray(value) ? value : [value];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
export { module as default };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
if (typeof window !== "undefined" && // @ts-expect-error undefined property
|
|
2
|
+
window.__NUXT_VITEST_ENVIRONMENT__) {
|
|
3
|
+
const { useRouter } = await import("#app/composables/router");
|
|
4
|
+
await import("#app/nuxt-vitest-app-entry").then((r) => r.default());
|
|
5
|
+
const nuxtApp = useNuxtApp();
|
|
6
|
+
await nuxtApp.callHook("page:finish");
|
|
7
|
+
useRouter().afterEach(() => nuxtApp.callHook("page:finish"));
|
|
8
|
+
}
|
|
9
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as _kit from "@nuxt/kit";
|
|
2
|
-
import { createTest, exposeContextToEnv } from "@nuxt/test-utils";
|
|
2
|
+
import { createTest, exposeContextToEnv } from "@nuxt/test-utils/e2e";
|
|
3
3
|
const kit = _kit.default || _kit;
|
|
4
4
|
const options = JSON.parse(process.env.NUXT_TEST_OPTIONS || "{}");
|
|
5
5
|
const hooks = createTest(options);
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare const _default: import("vue").DefineComponent<{}, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
|
|
2
|
+
[key: string]: any;
|
|
3
|
+
}>, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, Readonly<import("vue").ExtractPropTypes<{}>>, {}, {}>;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Suspense, defineComponent, h, onErrorCaptured, provide } from "vue";
|
|
2
|
+
import { isNuxtError, useNuxtApp, useRoute } from "#imports";
|
|
3
|
+
import { PageRouteSymbol } from "#app/components/injections";
|
|
4
|
+
export default defineComponent({
|
|
5
|
+
setup(_options, { slots }) {
|
|
6
|
+
const nuxtApp = useNuxtApp();
|
|
7
|
+
provide(PageRouteSymbol, useRoute());
|
|
8
|
+
const done = nuxtApp.deferHydration();
|
|
9
|
+
const results = nuxtApp.hooks.callHookWith((hooks) => hooks.map((hook) => hook()), "vue:setup");
|
|
10
|
+
if (import.meta.dev && results && results.some((i) => i && "then" in i)) {
|
|
11
|
+
console.error("[nuxt] Error in `vue:setup`. Callbacks must be synchronous.");
|
|
12
|
+
}
|
|
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));
|
|
15
|
+
if (isNuxtError(err) && (err.fatal || err.unhandled)) {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return () => h(Suspense, { onResolve: done }, slots.default?.());
|
|
20
|
+
}
|
|
21
|
+
});
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { EventHandler, HTTPMethod } from 'h3';
|
|
2
|
+
import { SetupContext, RenderFunction, ComputedOptions, MethodOptions, ComponentOptionsMixin, EmitsOptions, ComponentInjectOptions, ComponentOptionsWithoutProps, ComponentOptionsWithArrayProps, ComponentPropsOptions, ComponentOptionsWithObjectProps } from 'vue';
|
|
3
|
+
import { mount, ComponentMountingOptions } from '@vue/test-utils';
|
|
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';
|
|
7
|
+
|
|
8
|
+
type Awaitable<T> = T | Promise<T>;
|
|
9
|
+
type OptionalFunction<T> = T | (() => Awaitable<T>);
|
|
10
|
+
/**
|
|
11
|
+
* `registerEndpoint` allows you create Nitro endpoint that returns mocked data. It can come in handy if you want to test a component that makes requests to API to display some data.
|
|
12
|
+
* @param url - endpoint name (e.g. `/test/`).
|
|
13
|
+
* @param options - factory function that returns the mocked data or an object containing both the `handler` and the `method` properties.
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { registerEndpoint } from '@nuxt/test-utils/runtime'
|
|
17
|
+
*
|
|
18
|
+
* registerEndpoint("/test/", () => {
|
|
19
|
+
* test: "test-field"
|
|
20
|
+
* })
|
|
21
|
+
* ```
|
|
22
|
+
* @see https://github.com/danielroe/nuxt-vitest#registerendpoint
|
|
23
|
+
*/
|
|
24
|
+
declare function registerEndpoint(url: string, options: EventHandler | {
|
|
25
|
+
handler: EventHandler;
|
|
26
|
+
method: HTTPMethod;
|
|
27
|
+
}): void;
|
|
28
|
+
/**
|
|
29
|
+
* `mockNuxtImport` allows you to mock Nuxt's auto import functionality.
|
|
30
|
+
* @param _name - name of an import to mock.
|
|
31
|
+
* @param _factory - factory function that returns mocked import.
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* import { mockNuxtImport } from '@nuxt/test-utils/runtime'
|
|
35
|
+
*
|
|
36
|
+
* mockNuxtImport('useStorage', () => {
|
|
37
|
+
* return () => {
|
|
38
|
+
* return { value: 'mocked storage' }
|
|
39
|
+
* }
|
|
40
|
+
* })
|
|
41
|
+
* ```
|
|
42
|
+
* @see https://github.com/danielroe/nuxt-vitest#mocknuxtimport
|
|
43
|
+
*/
|
|
44
|
+
declare function mockNuxtImport<T = any>(_name: string, _factory: () => T | Promise<T>): void;
|
|
45
|
+
/**
|
|
46
|
+
* `mockComponent` allows you to mock Nuxt's component.
|
|
47
|
+
* @param path - component name in PascalCase, or the relative path of the component.
|
|
48
|
+
* @param setup - factory function that returns the mocked component.
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* import { mockComponent } from '@nuxt/test-utils/runtime'
|
|
52
|
+
*
|
|
53
|
+
* mockComponent('MyComponent', {
|
|
54
|
+
* props: {
|
|
55
|
+
* value: String
|
|
56
|
+
* },
|
|
57
|
+
* setup(props) {
|
|
58
|
+
* // ...
|
|
59
|
+
* }
|
|
60
|
+
* })
|
|
61
|
+
*
|
|
62
|
+
* // relative path or alias also works
|
|
63
|
+
* mockComponent('~/components/my-component.vue', async () => {
|
|
64
|
+
* // or a factory function
|
|
65
|
+
* return {
|
|
66
|
+
* setup(props) {
|
|
67
|
+
* // ...
|
|
68
|
+
* }
|
|
69
|
+
* }
|
|
70
|
+
* })
|
|
71
|
+
*
|
|
72
|
+
* // or you can use SFC for redirecting to a mock component
|
|
73
|
+
* mockComponent('MyComponent', () => import('./MockComponent.vue'))
|
|
74
|
+
* ```
|
|
75
|
+
* @see https://github.com/danielroe/nuxt-vitest#mockcomponent
|
|
76
|
+
*/
|
|
77
|
+
declare function mockComponent<Props, RawBindings = object>(path: string, setup: OptionalFunction<(props: Readonly<Props>, ctx: SetupContext) => RawBindings | RenderFunction>): void;
|
|
78
|
+
declare function mockComponent<Props = {}, RawBindings = {}, D = {}, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, I extends ComponentInjectOptions = {}, II extends string = string>(path: string, options: OptionalFunction<ComponentOptionsWithoutProps<Props, RawBindings, D, C, M, Mixin, Extends, E, EE, I, II>>): void;
|
|
79
|
+
declare function mockComponent<PropNames extends string, RawBindings, D, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, I extends ComponentInjectOptions = {}, II extends string = string>(path: string, options: OptionalFunction<ComponentOptionsWithArrayProps<PropNames, RawBindings, D, C, M, Mixin, Extends, E, EE, I, II>>): void;
|
|
80
|
+
declare function mockComponent<PropsOptions extends Readonly<ComponentPropsOptions>, RawBindings, D, C extends ComputedOptions = {}, M extends MethodOptions = {}, Mixin extends ComponentOptionsMixin = ComponentOptionsMixin, Extends extends ComponentOptionsMixin = ComponentOptionsMixin, E extends EmitsOptions = {}, EE extends string = string, I extends ComponentInjectOptions = {}, II extends string = string>(path: string, options: OptionalFunction<ComponentOptionsWithObjectProps<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE, I, II>>): void;
|
|
81
|
+
|
|
82
|
+
type MountSuspendedOptions<T> = ComponentMountingOptions<T> & {
|
|
83
|
+
route?: RouteLocationRaw;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* `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:
|
|
87
|
+
*
|
|
88
|
+
* ```ts
|
|
89
|
+
* // tests/components/SomeComponents.nuxt.spec.ts
|
|
90
|
+
* it('can mount some component', async () => {
|
|
91
|
+
* const component = await mountSuspended(SomeComponent)
|
|
92
|
+
* expect(component.text()).toMatchInlineSnapshot(
|
|
93
|
+
* 'This is an auto-imported component'
|
|
94
|
+
* )
|
|
95
|
+
* })
|
|
96
|
+
*
|
|
97
|
+
* // tests/App.nuxt.spec.ts
|
|
98
|
+
* it('can also mount an app', async () => {
|
|
99
|
+
* const component = await mountSuspended(App, { route: '/test' })
|
|
100
|
+
* expect(component.html()).toMatchInlineSnapshot(`
|
|
101
|
+
* "<div>This is an auto-imported component</div>
|
|
102
|
+
* <div> I am a global component </div>
|
|
103
|
+
* <div>/</div>
|
|
104
|
+
* <a href=\\"/test\\"> Test link </a>"
|
|
105
|
+
* `)
|
|
106
|
+
* })
|
|
107
|
+
* ```
|
|
108
|
+
* @param component the component to be tested
|
|
109
|
+
* @param options optional options to set up your component
|
|
110
|
+
*/
|
|
111
|
+
declare function mountSuspended<T>(component: T, options?: MountSuspendedOptions<T>): Promise<ReturnType<typeof mount<T>> & {
|
|
112
|
+
setupState: any;
|
|
113
|
+
}>;
|
|
114
|
+
|
|
115
|
+
type RenderOptions = RenderOptions$1 & {
|
|
116
|
+
route?: RouteLocationRaw;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* `renderSuspended` allows you to mount any vue component within the Nuxt environment, allowing async setup and access to injections from your Nuxt plugins.
|
|
120
|
+
*
|
|
121
|
+
* This is a wrapper around the `render` function from @testing-libary/vue, and should be used together with
|
|
122
|
+
* utilities from that package.
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* // tests/components/SomeComponents.nuxt.spec.ts
|
|
126
|
+
* import { renderSuspended } from '@nuxt/test-utils/runtime'
|
|
127
|
+
*
|
|
128
|
+
* it('can render some component', async () => {
|
|
129
|
+
* const { html } = await renderSuspended(SomeComponent)
|
|
130
|
+
* expect(html()).toMatchInlineSnapshot(
|
|
131
|
+
* 'This is an auto-imported component'
|
|
132
|
+
* )
|
|
133
|
+
*
|
|
134
|
+
* })
|
|
135
|
+
*
|
|
136
|
+
* // tests/App.nuxt.spec.ts
|
|
137
|
+
* import { renderSuspended } from '@nuxt/test-utils/runtime'
|
|
138
|
+
* import { screen } from '@testing-library/vue'
|
|
139
|
+
*
|
|
140
|
+
* it('can also mount an app', async () => {
|
|
141
|
+
* const { html } = await renderSuspended(App, { route: '/test' })
|
|
142
|
+
* expect(screen.getByRole('link', { name: 'Test Link' })).toBeVisible()
|
|
143
|
+
* })
|
|
144
|
+
* ```
|
|
145
|
+
* @param component the component to be tested
|
|
146
|
+
* @param options optional options to set up your component
|
|
147
|
+
*/
|
|
148
|
+
declare function renderSuspended<T>(component: T, options?: RenderOptions): Promise<_testing_library_vue.RenderResult>;
|
|
149
|
+
|
|
150
|
+
export { mockComponent, mockNuxtImport, mountSuspended, registerEndpoint, renderSuspended };
|