@untestutils/nuxt 0.5.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/LICENSE +21 -0
- package/dist/config/index.d.mts +58 -0
- package/dist/config/index.d.ts +59 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.mjs +323 -0
- package/dist/config/utils.d.mts +12 -0
- package/dist/config/utils.d.ts +13 -0
- package/dist/config/utils.d.ts.map +1 -0
- package/dist/config/utils.mjs +79 -0
- package/dist/environment/index.d.mts +22 -0
- package/dist/environment/index.d.ts +23 -0
- package/dist/environment/index.d.ts.map +1 -0
- package/dist/environment/index.mjs +129 -0
- package/dist/index.d.mts +36 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +355 -0
- package/dist/module/index.d.mts +3 -0
- package/dist/module/index.d.ts +4 -0
- package/dist/module/index.d.ts.map +1 -0
- package/dist/module/index.mjs +294 -0
- package/dist/runtime/browser-entry.d.mts +5 -0
- package/dist/runtime/browser-entry.d.ts +6 -0
- package/dist/runtime/browser-entry.d.ts.map +1 -0
- package/dist/runtime/browser-entry.mjs +2 -0
- package/dist/runtime/entry.d.mts +1 -0
- package/dist/runtime/entry.d.ts +2 -0
- package/dist/runtime/entry.d.ts.map +1 -0
- package/dist/runtime/entry.mjs +9 -0
- package/dist/runtime/index.d.mts +81 -0
- package/dist/runtime/index.d.ts +82 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.mjs +156 -0
- package/dist/runtime/mocks/vue-devtools.d.mts +12 -0
- package/dist/runtime/mocks/vue-devtools.d.ts +13 -0
- package/dist/runtime/mocks/vue-devtools.d.ts.map +1 -0
- package/dist/runtime/mocks/vue-devtools.mjs +12 -0
- package/dist/runtime/nuxt-root.d.mts +3 -0
- package/dist/runtime/nuxt-root.d.ts +4 -0
- package/dist/runtime/nuxt-root.d.ts.map +1 -0
- package/dist/runtime/nuxt-root.mjs +29 -0
- package/dist/runtime/shared/environment.d.mts +52 -0
- package/dist/runtime/shared/environment.d.ts +53 -0
- package/dist/runtime/shared/environment.d.ts.map +1 -0
- package/dist/runtime/shared/environment.mjs +79 -0
- package/dist/runtime/shared/h3-v1.d.mts +7 -0
- package/dist/runtime/shared/h3-v1.d.ts +8 -0
- package/dist/runtime/shared/h3-v1.d.ts.map +1 -0
- package/dist/runtime/shared/h3-v1.mjs +50 -0
- package/dist/runtime/shared/h3-v2.d.mts +7 -0
- package/dist/runtime/shared/h3-v2.d.ts +8 -0
- package/dist/runtime/shared/h3-v2.d.ts.map +1 -0
- package/dist/runtime/shared/h3-v2.mjs +32 -0
- package/dist/runtime/shared/h3.d.mts +4 -0
- package/dist/runtime/shared/h3.d.ts +5 -0
- package/dist/runtime/shared/h3.d.ts.map +1 -0
- package/dist/runtime/shared/h3.mjs +3 -0
- package/dist/runtime/shared/nuxt.d.mts +2 -0
- package/dist/runtime/shared/nuxt.d.ts +3 -0
- package/dist/runtime/shared/nuxt.d.ts.map +1 -0
- package/dist/runtime/shared/nuxt.mjs +17 -0
- package/dist/runtime/shared/vue-wrapper-plugin.d.mts +12 -0
- package/dist/runtime/shared/vue-wrapper-plugin.d.ts +13 -0
- package/dist/runtime/shared/vue-wrapper-plugin.d.ts.map +1 -0
- package/dist/runtime/shared/vue-wrapper-plugin.mjs +42 -0
- package/dist/runtime/suspended.d.mts +45 -0
- package/dist/runtime/suspended.d.ts +46 -0
- package/dist/runtime/suspended.d.ts.map +1 -0
- package/dist/runtime/suspended.mjs +259 -0
- package/package.json +150 -0
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
|
|
2
|
+
import { extname, dirname, join, relative } from "pathe";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { addVitePlugin, defineNuxtModule, resolveIgnorePatterns, resolvePath } from "@nuxt/kit";
|
|
5
|
+
import { walk } from "estree-walker";
|
|
6
|
+
import MagicString from "magic-string";
|
|
7
|
+
import { createUnplugin } from "unplugin";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
//#region mock transform plugin
|
|
10
|
+
const PLUGIN_NAME = "untestutils:mock-transform";
|
|
11
|
+
const HELPER_MOCK_IMPORT = "mockNuxtImport";
|
|
12
|
+
const HELPER_UNMOCK_IMPORT = "unmockNuxtImport";
|
|
13
|
+
const HELPER_MOCK_COMPONENT = "mockComponent";
|
|
14
|
+
const HELPER_MOCK_HOIST = "__NUXT_VITEST_MOCKS";
|
|
15
|
+
const HELPER_MOCK_HOIST_ORIGINAL = "__NUXT_VITEST_MOCKS_ORIGINAL";
|
|
16
|
+
const HELPER_MOCK_HOIST_PREVIOUS = "__NUXT_VITEST_MOCKS_PREVIOUS";
|
|
17
|
+
const HELPERS_NAME = [
|
|
18
|
+
HELPER_MOCK_IMPORT,
|
|
19
|
+
HELPER_UNMOCK_IMPORT,
|
|
20
|
+
HELPER_MOCK_COMPONENT
|
|
21
|
+
];
|
|
22
|
+
const createMockPlugin = (ctx) => createUnplugin(() => {
|
|
23
|
+
return {
|
|
24
|
+
name: PLUGIN_NAME,
|
|
25
|
+
enforce: "post",
|
|
26
|
+
vite: {
|
|
27
|
+
transform(code, id) {
|
|
28
|
+
if (!HELPERS_NAME.some((n) => code.includes(n))) return;
|
|
29
|
+
if (id.includes("/node_modules/")) return;
|
|
30
|
+
let ast;
|
|
31
|
+
try {
|
|
32
|
+
ast = this.parse(code);
|
|
33
|
+
} catch {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
let insertionPoint = 0;
|
|
37
|
+
let hasViImport = false;
|
|
38
|
+
const s = new MagicString(code);
|
|
39
|
+
const mocksImport = [];
|
|
40
|
+
const unmocksFrom = new Set();
|
|
41
|
+
const mocksComponent = [];
|
|
42
|
+
const importPathsList = new Set();
|
|
43
|
+
// @ts-expect-error mismatch between acorn/estree types
|
|
44
|
+
walk(ast, { enter: (node, parent) => {
|
|
45
|
+
const removeCallExpression = (start, end = start) => {
|
|
46
|
+
s.overwrite(isExpressionStatement(parent) ? startOf(parent) : startOf(start), isExpressionStatement(parent) ? endOf(parent) : endOf(end), "");
|
|
47
|
+
};
|
|
48
|
+
const parseMockImportTarget = (importTarget, helperName) => {
|
|
49
|
+
const name = isLiteral(importTarget) ? importTarget.value : isIdentifier(importTarget) ? importTarget.name : undefined;
|
|
50
|
+
if (typeof name !== "string") return this.error(new Error(`The first argument of ${helperName}() must be a string literal or mocked target`), startOf(importTarget));
|
|
51
|
+
return {
|
|
52
|
+
name,
|
|
53
|
+
importItem: ctx.imports.find((_) => name === (_.as || _.name))
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
if (isImportDeclaration(node)) {
|
|
57
|
+
if (node.source.value === "vitest" && !hasViImport) {
|
|
58
|
+
if (node.specifiers.find((i) => isImportSpecifier(i) && i.imported.type === "Identifier" && i.imported.name === "vi")) {
|
|
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) return this.error(new Error(`${HELPER_MOCK_IMPORT}() should have exactly 2 arguments`), startOf(node));
|
|
68
|
+
const { name, importItem } = parseMockImportTarget(node.arguments[0], HELPER_MOCK_IMPORT);
|
|
69
|
+
if (!importItem) return this.error(`Cannot find import "${name}" to mock`);
|
|
70
|
+
removeCallExpression(node.arguments[0], node.arguments[1]);
|
|
71
|
+
mocksImport.push({
|
|
72
|
+
name,
|
|
73
|
+
import: importItem,
|
|
74
|
+
factory: code.slice(startOf(node.arguments[1]), endOf(node.arguments[1]))
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (isIdentifier(node.callee) && node.callee.name === HELPER_UNMOCK_IMPORT) {
|
|
78
|
+
if (node.arguments.length !== 1) return this.error(new Error(`${HELPER_UNMOCK_IMPORT}() should have exactly 1 argument`), startOf(node));
|
|
79
|
+
const { name, importItem } = parseMockImportTarget(node.arguments[0], HELPER_UNMOCK_IMPORT);
|
|
80
|
+
if (!importItem) return this.error(`Cannot find import "${name}" to unmock`);
|
|
81
|
+
removeCallExpression(node.arguments[0]);
|
|
82
|
+
unmocksFrom.add(importItem.from);
|
|
83
|
+
mocksImport.push({
|
|
84
|
+
name,
|
|
85
|
+
import: importItem,
|
|
86
|
+
factory: undefined
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
if (isIdentifier(node.callee) && node.callee.name === HELPER_MOCK_COMPONENT) {
|
|
90
|
+
if (node.arguments.length !== 2) return this.error(new Error(`${HELPER_MOCK_COMPONENT}() should have exactly 2 arguments`), startOf(node));
|
|
91
|
+
const componentName = node.arguments[0];
|
|
92
|
+
if (!isLiteral(componentName) || typeof componentName.value !== "string") return this.error(new Error(`The first argument of ${HELPER_MOCK_COMPONENT}() must be a string literal`), startOf(componentName));
|
|
93
|
+
const pathOrName = componentName.value;
|
|
94
|
+
const path = ctx.components.find((_) => _.pascalName === pathOrName || _.kebabName === pathOrName)?.filePath || pathOrName;
|
|
95
|
+
removeCallExpression(node.arguments[1]);
|
|
96
|
+
mocksComponent.push({
|
|
97
|
+
path,
|
|
98
|
+
factory: code.slice(startOf(node.arguments[1]), endOf(node.arguments[1]))
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
} });
|
|
102
|
+
if (mocksImport.length === 0 && mocksComponent.length === 0) return;
|
|
103
|
+
const mockLines = [];
|
|
104
|
+
for (const from of unmocksFrom) mockLines.push(`vi.unmock(${JSON.stringify(from)});`);
|
|
105
|
+
for (const [from, mocks] of mapGroupBy(mocksImport, (mock) => mock.import.from)) {
|
|
106
|
+
importPathsList.add(from);
|
|
107
|
+
const quotedFrom = JSON.stringify(from);
|
|
108
|
+
const mockModuleEntry = `globalThis.${HELPER_MOCK_HOIST}[${quotedFrom}]`;
|
|
109
|
+
mockLines.push(`vi.mock(${quotedFrom}, async (importOriginal) => {`, ` if (!${mockModuleEntry} || ${unmocksFrom.has(from)}) {`, ` const original = await importOriginal()`, ` const previous = (${mockModuleEntry} ?? {}).${HELPER_MOCK_HOIST_PREVIOUS} ?? {}`, ` ${mockModuleEntry} = { ...original, ...previous }`, ` ${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL} = { ...original }`, ` ${mockModuleEntry}.${HELPER_MOCK_HOIST_PREVIOUS} = { ...previous }`, ` }`);
|
|
110
|
+
for (const mock of mocks) {
|
|
111
|
+
const quotedName = JSON.stringify(mock.import.name);
|
|
112
|
+
const original = `${mockModuleEntry}.${HELPER_MOCK_HOIST_ORIGINAL}[${quotedName}]`;
|
|
113
|
+
if (mock.factory === undefined) mockLines.push(` ${mockModuleEntry}[${quotedName}] = ${original}`, ` delete ${mockModuleEntry}.${HELPER_MOCK_HOIST_PREVIOUS}[${quotedName}]`);
|
|
114
|
+
else mockLines.push(` ${mockModuleEntry}[${quotedName}] = await (${mock.factory})(${original})`, ` ${mockModuleEntry}.${HELPER_MOCK_HOIST_PREVIOUS}[${quotedName}] = ${mockModuleEntry}[${quotedName}]`);
|
|
115
|
+
}
|
|
116
|
+
mockLines.push(` return ${mockModuleEntry}`);
|
|
117
|
+
mockLines.push(`});`);
|
|
118
|
+
}
|
|
119
|
+
if (mocksComponent.length) mockLines.push(...mocksComponent.flatMap((mock) => {
|
|
120
|
+
return [
|
|
121
|
+
`vi.mock(${JSON.stringify(mock.path)}, async () => {`,
|
|
122
|
+
` const factory = (${mock.factory});`,
|
|
123
|
+
` const result = typeof factory === 'function' ? await factory() : await factory`,
|
|
124
|
+
` return 'default' in result ? result : { default: result }`,
|
|
125
|
+
"});"
|
|
126
|
+
];
|
|
127
|
+
}));
|
|
128
|
+
if (!mockLines.length) return;
|
|
129
|
+
s.appendLeft(insertionPoint, [
|
|
130
|
+
``,
|
|
131
|
+
`vi.hoisted(() => {`,
|
|
132
|
+
` if(!globalThis.${HELPER_MOCK_HOIST}){`,
|
|
133
|
+
` vi.stubGlobal(${JSON.stringify(HELPER_MOCK_HOIST)}, {})`,
|
|
134
|
+
` }`,
|
|
135
|
+
`});`,
|
|
136
|
+
``
|
|
137
|
+
].join("\n"));
|
|
138
|
+
if (!hasViImport) s.prepend(`import {vi} from "vitest";\n`);
|
|
139
|
+
s.appendLeft(insertionPoint, "\n" + mockLines.join("\n") + "\n");
|
|
140
|
+
importPathsList.forEach((p) => {
|
|
141
|
+
s.append(`\n import ${JSON.stringify(p)};`);
|
|
142
|
+
});
|
|
143
|
+
return {
|
|
144
|
+
code: s.toString(),
|
|
145
|
+
map: s.generateMap({ hires: true })
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
async configResolved(config) {
|
|
149
|
+
const plugins = config.plugins;
|
|
150
|
+
const vitestPlugins = plugins.filter((p) => (p.name === "vite:mocks" || p.name?.startsWith("vitest:")) && (p.enforce || "order" in p && p.order) === "post");
|
|
151
|
+
const lastNuxt = findLastIndex(plugins, (i) => Boolean(i.name?.startsWith("nuxt:")));
|
|
152
|
+
if (lastNuxt === -1) return;
|
|
153
|
+
for (const plugin of vitestPlugins) {
|
|
154
|
+
const index = plugins.indexOf(plugin);
|
|
155
|
+
if (index < lastNuxt) {
|
|
156
|
+
plugins.splice(index, 1);
|
|
157
|
+
plugins.splice(lastNuxt, 0, plugin);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
function findLastIndex(arr, predicate) {
|
|
165
|
+
for (let i = arr.length - 1; i >= 0; i--) if (predicate(arr[i])) return i;
|
|
166
|
+
return -1;
|
|
167
|
+
}
|
|
168
|
+
const isImportDeclaration = (node) => node.type === "ImportDeclaration";
|
|
169
|
+
const isImportSpecifier = (node) => node.type === "ImportSpecifier";
|
|
170
|
+
const isCallExpression = (node) => node.type === "CallExpression";
|
|
171
|
+
const isIdentifier = (node) => node.type === "Identifier";
|
|
172
|
+
const isLiteral = (node) => node.type === "Literal";
|
|
173
|
+
const isExpressionStatement = (node) => node?.type === "ExpressionStatement";
|
|
174
|
+
const startOf = (node) => "range" in node && node.range ? node.range[0] : "start" in node ? node.start : 0;
|
|
175
|
+
const endOf = (node) => "range" in node && node.range ? node.range[1] : "end" in node ? node.end : startOf(node);
|
|
176
|
+
function mapGroupBy(items, keySelector) {
|
|
177
|
+
const map = new Map();
|
|
178
|
+
for (const item of items) {
|
|
179
|
+
const key = keySelector(item);
|
|
180
|
+
if (!map.has(key)) map.set(key, []);
|
|
181
|
+
map.get(key).push(item);
|
|
182
|
+
}
|
|
183
|
+
return map;
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region import mocking setup
|
|
187
|
+
const isTestPluginFile = (src) => src.includes(".spec.") || src.includes(".test.");
|
|
188
|
+
async function setupImportMocking(nuxt) {
|
|
189
|
+
const ctx = {
|
|
190
|
+
components: [],
|
|
191
|
+
imports: []
|
|
192
|
+
};
|
|
193
|
+
let importsCtx;
|
|
194
|
+
nuxt.hook("imports:context", async (ctx) => {
|
|
195
|
+
importsCtx = ctx;
|
|
196
|
+
});
|
|
197
|
+
nuxt.hook("ready", async () => {
|
|
198
|
+
ctx.imports = importsCtx ? await importsCtx.getImports() : [];
|
|
199
|
+
});
|
|
200
|
+
nuxt.hook("components:extend", (_) => {
|
|
201
|
+
ctx.components = _;
|
|
202
|
+
});
|
|
203
|
+
nuxt.hook("imports:sources", (presets) => {
|
|
204
|
+
const idx = presets.findIndex((p) => typeof p === "object" && p !== null && "imports" in p && p.imports?.includes("setInterval"));
|
|
205
|
+
if (idx !== -1) presets.splice(idx, 1);
|
|
206
|
+
});
|
|
207
|
+
nuxt.options.ignore = nuxt.options.ignore.filter((i) => i !== "**/*.{spec,test}.{js,cts,mts,ts,jsx,tsx}");
|
|
208
|
+
if (nuxt._ignore) for (const pattern of resolveIgnorePatterns("**/*.{spec,test}.{js,cts,mts,ts,jsx,tsx}")) nuxt._ignore.add(`!${pattern}`);
|
|
209
|
+
nuxt.hook("app:resolve", (app) => {
|
|
210
|
+
app.plugins = app.plugins.filter((plugin) => !isTestPluginFile(plugin.src));
|
|
211
|
+
});
|
|
212
|
+
addVitePlugin(createMockPlugin(ctx).vite());
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region nuxt root stub plugin
|
|
216
|
+
const STUB_PLUGIN_NAME = "untestutils:nuxt-root-stub";
|
|
217
|
+
const STUB_ID = "nuxt-vitest-app-entry";
|
|
218
|
+
const NuxtRootStubPlugin = (options) => {
|
|
219
|
+
const extension = extname(options.entry);
|
|
220
|
+
const escapedExt = extension.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
221
|
+
const entryPath = join(dirname(options.entry), STUB_ID + extension);
|
|
222
|
+
const idFilter = new RegExp(`${STUB_ID}(?:${escapedExt})?$`);
|
|
223
|
+
return {
|
|
224
|
+
name: STUB_PLUGIN_NAME,
|
|
225
|
+
enforce: "pre",
|
|
226
|
+
resolveId: {
|
|
227
|
+
filter: { id: idFilter },
|
|
228
|
+
async handler(id, importer) {
|
|
229
|
+
return importer?.endsWith("index.html") ? id : entryPath;
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
load: {
|
|
233
|
+
filter: { id: idFilter },
|
|
234
|
+
async handler() {
|
|
235
|
+
return readFileSync(options.entry, "utf-8").replace("#build/root-component.mjs", options.rootStubPath);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
//#endregion
|
|
241
|
+
function runtimeFile(subpath) {
|
|
242
|
+
const bare = subpath.replace(/^\.\//, "").replace(/\.mjs$/, "");
|
|
243
|
+
return fileURLToPath(new URL(`../runtime/${bare}.mjs`, import.meta.url));
|
|
244
|
+
}
|
|
245
|
+
//#region module
|
|
246
|
+
const untestutilsModule = defineNuxtModule({
|
|
247
|
+
meta: {
|
|
248
|
+
name: "untestutils",
|
|
249
|
+
configKey: "testUtils",
|
|
250
|
+
version: "0.1.8"
|
|
251
|
+
},
|
|
252
|
+
defaults: {},
|
|
253
|
+
async setup(_options, nuxt) {
|
|
254
|
+
if (nuxt.options.test || nuxt.options.dev) await setupImportMocking(nuxt);
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
if (nuxt.options.test && !nuxt.options.dev) nuxt.hook("app:templates", (app) => {
|
|
258
|
+
const template = app.templates.find((t) => t.filename === "paths.mjs");
|
|
259
|
+
if (!template?.getContents) return;
|
|
260
|
+
const original = template.getContents;
|
|
261
|
+
const inlineAppConfig = JSON.stringify(nuxt.options.app);
|
|
262
|
+
template.getContents = async (data) => {
|
|
263
|
+
return (await original(data)).replace(/^import \{ useRuntimeConfig \} from ['"]nitropack\/runtime['"]\n?/m, "").replace(/const getAppConfig = \(\) => useRuntimeConfig\(\)\.app/, () => `const getAppConfig = () => (${inlineAppConfig})`);
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
if (nuxt.options.test || nuxt.options.dev) addVitePlugin(NuxtRootStubPlugin({
|
|
267
|
+
entry: await resolvePath("#app/entry", { alias: nuxt.options.alias }),
|
|
268
|
+
rootStubPath: await resolvePath(runtimeFile("nuxt-root"))
|
|
269
|
+
}));
|
|
270
|
+
if (!nuxt.options.test && !nuxt.options.dev) {
|
|
271
|
+
nuxt.options.vite.define ||= {};
|
|
272
|
+
nuxt.options.vite.define["import.meta.vitest"] = "undefined";
|
|
273
|
+
}
|
|
274
|
+
nuxt.hook("prepare:types", (ctx) => {
|
|
275
|
+
ctx.references.push({ types: "vitest/import-meta" });
|
|
276
|
+
for (const tsConfig of [
|
|
277
|
+
ctx.tsConfig,
|
|
278
|
+
ctx.nodeTsConfig,
|
|
279
|
+
ctx.sharedTsConfig
|
|
280
|
+
]) {
|
|
281
|
+
if (!tsConfig) continue;
|
|
282
|
+
tsConfig.compilerOptions ||= {};
|
|
283
|
+
tsConfig.compilerOptions.allowImportingTsExtensions = true;
|
|
284
|
+
}
|
|
285
|
+
if (ctx.nodeTsConfig) {
|
|
286
|
+
ctx.nodeTsConfig.include ||= [];
|
|
287
|
+
ctx.nodeTsConfig.include.push(relative(nuxt.options.buildDir, join(nuxt.options.rootDir, "vitest.config.*")));
|
|
288
|
+
if (nuxt.options.workspaceDir !== nuxt.options.rootDir) ctx.nodeTsConfig.include.push(relative(nuxt.options.buildDir, join(nuxt.options.workspaceDir, "vitest.config.*")));
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
export default untestutilsModule;
|
|
294
|
+
//#endregion
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-entry.d.ts","sourceRoot":"","sources":["../../src/runtime/browser-entry.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../../src/runtime/entry.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { setupNuxt } from "./shared/nuxt.mjs";
|
|
2
|
+
import { beforeAll, vi } from "vitest";
|
|
3
|
+
const win = globalThis.window;
|
|
4
|
+
if (typeof globalThis !== "undefined" && win?.__NUXT_VITEST_ENVIRONMENT__) {
|
|
5
|
+
vi.resetModules();
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
await setupNuxt();
|
|
8
|
+
});
|
|
9
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { Component, VNode } from "vue";
|
|
2
|
+
import type { MountingOptions, VueWrapper } from "@vue/test-utils";
|
|
3
|
+
import type { render as testingLibraryRender } from "@testing-library/vue";
|
|
4
|
+
type Awaitable<T> = T | Promise<T>;
|
|
5
|
+
type RuntimeH3Event = {
|
|
6
|
+
method?: string;
|
|
7
|
+
path: string;
|
|
8
|
+
url?: URL;
|
|
9
|
+
};
|
|
10
|
+
type RuntimeEndpointHandler<T = unknown> = ((event: RuntimeH3Event) => Awaitable<T>) & {
|
|
11
|
+
__is_handler__?: true;
|
|
12
|
+
};
|
|
13
|
+
type MountSuspendedOptions<T extends Component> = MountingOptions<T> & {
|
|
14
|
+
route?: import("vue-router").RouteLocationRaw | false;
|
|
15
|
+
scoped?: boolean;
|
|
16
|
+
spy?: boolean;
|
|
17
|
+
};
|
|
18
|
+
type TestingLibraryRender = typeof testingLibraryRender;
|
|
19
|
+
type RenderSuspendedOptions<T extends Component> = Parameters<TestingLibraryRender>[1] & MountSuspendedOptions<T>;
|
|
20
|
+
type RenderSuspendedResult<T extends Component> = ReturnType<TestingLibraryRender> & {
|
|
21
|
+
rerender: (props?: Record<string, unknown>) => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export type SuspendedHelpers = {
|
|
24
|
+
cleanupAll: () => void;
|
|
25
|
+
patchWrapperSetProps: (wrapper: object, setProps: (props: Record<string, unknown>) => void) => void;
|
|
26
|
+
wrapperSuspended: <TWrapper>(component: Component, options: MountingOptions<Component> & {
|
|
27
|
+
route?: import("vue-router").RouteLocationRaw | false;
|
|
28
|
+
scoped?: boolean;
|
|
29
|
+
spy?: boolean;
|
|
30
|
+
}, config: {
|
|
31
|
+
wrapperFn: (component: Component, options?: unknown) => TWrapper;
|
|
32
|
+
wrappedRender?: (render: () => VNode) => () => VNode;
|
|
33
|
+
suspendedHelperName: string;
|
|
34
|
+
clonedComponentName: string;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
wrapper: TWrapper & {
|
|
37
|
+
setupState: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
setProps: (props: Record<string, unknown>) => void;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
export interface RegisterEndpointOptions {
|
|
43
|
+
handler: RuntimeEndpointHandler;
|
|
44
|
+
method?: string;
|
|
45
|
+
once?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `registerEndpoint` lets you create a Nitro endpoint that returns mocked data.
|
|
49
|
+
* Handy when a component fetches data from an API.
|
|
50
|
+
*
|
|
51
|
+
* @param url endpoint name (e.g. `/test/`)
|
|
52
|
+
* @param options factory that returns mocked data, or an object with
|
|
53
|
+
* `handler`, `method` and `once`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function registerEndpoint(url: string, options: RegisterEndpointOptions | RuntimeEndpointHandler): () => void;
|
|
56
|
+
/**
|
|
57
|
+
* `mockNuxtImport` mocks Nuxt's auto-import functionality. This is a macro that
|
|
58
|
+
* is transformed to `vi.mock()` by `untestutils/module`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: (original?: T) => T): void;
|
|
61
|
+
/**
|
|
62
|
+
* `unmockNuxtImport` reverts a previous `mockNuxtImport`. This is a macro that
|
|
63
|
+
* is transformed by `untestutils/module`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function unmockNuxtImport<T = unknown>(_target: string | T): void;
|
|
66
|
+
/**
|
|
67
|
+
* `mockComponent` replaces a component with a mock. This is a macro that is
|
|
68
|
+
* transformed by `untestutils/module`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function mockComponent(_path: string, _component: unknown): void;
|
|
71
|
+
/**
|
|
72
|
+
* `mountSuspended` mounts any Vue component within the Nuxt environment,
|
|
73
|
+
* allowing async setup and access to injections from your Nuxt plugins.
|
|
74
|
+
*/
|
|
75
|
+
export declare function mountSuspended<T extends Component>(component: T, options?: MountSuspendedOptions<T>): Promise<VueWrapper>;
|
|
76
|
+
/**
|
|
77
|
+
* `renderSuspended` renders any Vue component within the Nuxt environment using
|
|
78
|
+
* `@testing-library/vue`'s `render`. Requires `@testing-library/vue`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderSuspended<T extends Component>(component: T, options?: RenderSuspendedOptions<T>): Promise<RenderSuspendedResult<T>>;
|
|
81
|
+
export {};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { Component, VNode } from 'vue';
|
|
2
|
+
import type { MountingOptions, VueWrapper } from '@vue/test-utils';
|
|
3
|
+
import type { render as testingLibraryRender } from '@testing-library/vue';
|
|
4
|
+
type Awaitable<T> = T | Promise<T>;
|
|
5
|
+
type RuntimeH3Event = {
|
|
6
|
+
method?: string;
|
|
7
|
+
path: string;
|
|
8
|
+
url?: URL;
|
|
9
|
+
};
|
|
10
|
+
type RuntimeEndpointHandler<T = unknown> = ((event: RuntimeH3Event) => Awaitable<T>) & {
|
|
11
|
+
__is_handler__?: true;
|
|
12
|
+
};
|
|
13
|
+
type MountSuspendedOptions<T extends Component> = MountingOptions<T> & {
|
|
14
|
+
route?: import('vue-router').RouteLocationRaw | false;
|
|
15
|
+
scoped?: boolean;
|
|
16
|
+
spy?: boolean;
|
|
17
|
+
};
|
|
18
|
+
type TestingLibraryRender = typeof testingLibraryRender;
|
|
19
|
+
type RenderSuspendedOptions<T extends Component> = Parameters<TestingLibraryRender>[1] & MountSuspendedOptions<T>;
|
|
20
|
+
type RenderSuspendedResult<T extends Component> = ReturnType<TestingLibraryRender> & {
|
|
21
|
+
rerender: (props?: Record<string, unknown>) => Promise<void>;
|
|
22
|
+
};
|
|
23
|
+
export type SuspendedHelpers = {
|
|
24
|
+
cleanupAll: () => void;
|
|
25
|
+
patchWrapperSetProps: (wrapper: object, setProps: (props: Record<string, unknown>) => void) => void;
|
|
26
|
+
wrapperSuspended: <TWrapper>(component: Component, options: MountingOptions<Component> & {
|
|
27
|
+
route?: import('vue-router').RouteLocationRaw | false;
|
|
28
|
+
scoped?: boolean;
|
|
29
|
+
spy?: boolean;
|
|
30
|
+
}, config: {
|
|
31
|
+
wrapperFn: (component: Component, options?: unknown) => TWrapper;
|
|
32
|
+
wrappedRender?: (render: () => VNode) => () => VNode;
|
|
33
|
+
suspendedHelperName: string;
|
|
34
|
+
clonedComponentName: string;
|
|
35
|
+
}) => Promise<{
|
|
36
|
+
wrapper: TWrapper & {
|
|
37
|
+
setupState: Record<string, unknown>;
|
|
38
|
+
};
|
|
39
|
+
setProps: (props: Record<string, unknown>) => void;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
export interface RegisterEndpointOptions {
|
|
43
|
+
handler: RuntimeEndpointHandler;
|
|
44
|
+
method?: string;
|
|
45
|
+
once?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* `registerEndpoint` lets you create a Nitro endpoint that returns mocked data.
|
|
49
|
+
* Handy when a component fetches data from an API.
|
|
50
|
+
*
|
|
51
|
+
* @param url endpoint name (e.g. `/test/`)
|
|
52
|
+
* @param options factory that returns mocked data, or an object with
|
|
53
|
+
* `handler`, `method` and `once`.
|
|
54
|
+
*/
|
|
55
|
+
export declare function registerEndpoint(url: string, options: RegisterEndpointOptions | RuntimeEndpointHandler): () => void;
|
|
56
|
+
/**
|
|
57
|
+
* `mockNuxtImport` mocks Nuxt's auto-import functionality. This is a macro that
|
|
58
|
+
* is transformed to `vi.mock()` by `untestutils/module`.
|
|
59
|
+
*/
|
|
60
|
+
export declare function mockNuxtImport<T = unknown>(_target: string | T, _factory: (original?: T) => T): void;
|
|
61
|
+
/**
|
|
62
|
+
* `unmockNuxtImport` reverts a previous `mockNuxtImport`. This is a macro that
|
|
63
|
+
* is transformed by `untestutils/module`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function unmockNuxtImport<T = unknown>(_target: string | T): void;
|
|
66
|
+
/**
|
|
67
|
+
* `mockComponent` replaces a component with a mock. This is a macro that is
|
|
68
|
+
* transformed by `untestutils/module`.
|
|
69
|
+
*/
|
|
70
|
+
export declare function mockComponent(_path: string, _component: unknown): void;
|
|
71
|
+
/**
|
|
72
|
+
* `mountSuspended` mounts any Vue component within the Nuxt environment,
|
|
73
|
+
* allowing async setup and access to injections from your Nuxt plugins.
|
|
74
|
+
*/
|
|
75
|
+
export declare function mountSuspended<T extends Component>(component: T, options?: MountSuspendedOptions<T>): Promise<VueWrapper>;
|
|
76
|
+
/**
|
|
77
|
+
* `renderSuspended` renders any Vue component within the Nuxt environment using
|
|
78
|
+
* `@testing-library/vue`'s `render`. Requires `@testing-library/vue`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function renderSuspended<T extends Component>(component: T, options?: RenderSuspendedOptions<T>): Promise<RenderSuspendedResult<T>>;
|
|
81
|
+
export {};
|
|
82
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runtime/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
|
|
2
|
+
import { h, nextTick } from "vue";
|
|
3
|
+
import { mount } from "@vue/test-utils";
|
|
4
|
+
|
|
5
|
+
function importSuspended() {
|
|
6
|
+
return import("./suspended");
|
|
7
|
+
}
|
|
8
|
+
//#region registerEndpoint
|
|
9
|
+
function getEndpointRegistry() {
|
|
10
|
+
const app = window.__app ?? (window.__app = {});
|
|
11
|
+
return app._registeredEndpointRegistry ||= {};
|
|
12
|
+
}
|
|
13
|
+
function findEndpointRegistryHandlers(url) {
|
|
14
|
+
const endpointRegistry = getEndpointRegistry();
|
|
15
|
+
const pathname = url.replace(/[?#].*$/, "");
|
|
16
|
+
for (const [key, handlers] of Object.entries(endpointRegistry)) {
|
|
17
|
+
if (key === url || key === pathname) {
|
|
18
|
+
if (handlers?.length) return handlers;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function registerEndpoint(url, options) {
|
|
24
|
+
const app = typeof globalThis !== "undefined" && "window" in globalThis ? globalThis.window?.__app : undefined;
|
|
25
|
+
if (!app) {
|
|
26
|
+
throw new Error("registerEndpoint() can only be used in an `untestutils` runtime environment");
|
|
27
|
+
}
|
|
28
|
+
const config = typeof options === "function" ? {
|
|
29
|
+
url,
|
|
30
|
+
handler: options,
|
|
31
|
+
method: undefined,
|
|
32
|
+
once: false
|
|
33
|
+
} : {
|
|
34
|
+
...options,
|
|
35
|
+
url
|
|
36
|
+
};
|
|
37
|
+
config.handler = Object.assign(config.handler, { __is_handler__: true });
|
|
38
|
+
const endpointRegistry = getEndpointRegistry();
|
|
39
|
+
endpointRegistry[url] ||= [];
|
|
40
|
+
endpointRegistry[url].push(config);
|
|
41
|
+
window.__registry.add(url);
|
|
42
|
+
app._registered ||= registerGlobalHandler(app);
|
|
43
|
+
return () => {
|
|
44
|
+
endpointRegistry[url]?.splice(endpointRegistry[url].indexOf(config), 1);
|
|
45
|
+
if (endpointRegistry[url]?.length === 0) window.__registry.delete(url);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
const handler = Object.assign(async (event) => {
|
|
49
|
+
const registeredHandlers = findEndpointRegistryHandlers("url" in event && event.url ? (event.url.pathname + event.url.search).replace(/^\/_/, "") : event.path.replace(/^\/_/, ""));
|
|
50
|
+
const latestHandler = [...registeredHandlers || []].reverse().find((config) => config.method ? event.method === config.method : true);
|
|
51
|
+
if (!latestHandler) return;
|
|
52
|
+
const result = await latestHandler.handler(event);
|
|
53
|
+
if (!latestHandler.once) return result;
|
|
54
|
+
const index = registeredHandlers?.indexOf(latestHandler);
|
|
55
|
+
if (index === undefined || index === -1) return result;
|
|
56
|
+
registeredHandlers?.splice(index, 1);
|
|
57
|
+
if (registeredHandlers?.length === 0) window.__registry.delete(latestHandler.url);
|
|
58
|
+
return result;
|
|
59
|
+
}, { __is_handler__: true });
|
|
60
|
+
function registerGlobalHandler(app) {
|
|
61
|
+
app.use(handler, { match: (eventOrPath, _event) => {
|
|
62
|
+
const url = typeof eventOrPath === "string" ? eventOrPath.replace(/^\/_/, "") : eventOrPath.url ? (eventOrPath.url.pathname + eventOrPath.url.search).replace(/^\/_/, "") : eventOrPath.path.replace(/^\/_/, "");
|
|
63
|
+
const event = _event ?? (typeof eventOrPath === "string" ? undefined : eventOrPath);
|
|
64
|
+
return findEndpointRegistryHandlers(url)?.some((config) => config.method ? event?.method === config.method : true) ?? false;
|
|
65
|
+
} });
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region mock macros
|
|
70
|
+
|
|
71
|
+
export function mockNuxtImport(_target, _factory) {
|
|
72
|
+
throw new Error("mockNuxtImport() is a macro and it did not get transpiled. Ensure `untestutils/module` is enabled in your Nuxt test config.");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function unmockNuxtImport(_target) {
|
|
76
|
+
throw new Error("unmockNuxtImport() is a macro and it did not get transpiled. Ensure `untestutils/module` is enabled in your Nuxt test config.");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function mockComponent(_path, _component) {
|
|
80
|
+
throw new Error("mockComponent() is a macro and it did not get transpiled. Ensure `untestutils/module` is enabled in your Nuxt test config.");
|
|
81
|
+
}
|
|
82
|
+
//#endregion
|
|
83
|
+
//#region mountSuspended
|
|
84
|
+
|
|
85
|
+
export async function mountSuspended(component, options = {}) {
|
|
86
|
+
const { cleanupAll, patchWrapperSetProps, wrapperSuspended } = await importSuspended();
|
|
87
|
+
const suspendedHelperName = "MountSuspendedHelper";
|
|
88
|
+
const clonedComponentName = "MountSuspendedComponent";
|
|
89
|
+
cleanupAll();
|
|
90
|
+
const { wrapper, setProps } = await wrapperSuspended(component, options, {
|
|
91
|
+
wrapperFn: (component, options) => mount(component, options),
|
|
92
|
+
suspendedHelperName,
|
|
93
|
+
clonedComponentName
|
|
94
|
+
});
|
|
95
|
+
patchWrapperSetProps(wrapper, setProps);
|
|
96
|
+
return wrappedMountedWrapper(wrapper, wrapper.findComponent({ name: clonedComponentName }));
|
|
97
|
+
}
|
|
98
|
+
function wrappedMountedWrapper(wrapper, component) {
|
|
99
|
+
const wrapperProps = [
|
|
100
|
+
"setProps",
|
|
101
|
+
"emitted",
|
|
102
|
+
"setupState",
|
|
103
|
+
"unmount"
|
|
104
|
+
];
|
|
105
|
+
return new Proxy(wrapper, { get: (_, prop, receiver) => {
|
|
106
|
+
if (prop === "getCurrentComponent") return getCurrentComponentPatchedProxy;
|
|
107
|
+
const target = wrapperProps.includes(prop) ? wrapper : Reflect.has(component, prop) ? component : wrapper;
|
|
108
|
+
const value = Reflect.get(target, prop, receiver);
|
|
109
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
110
|
+
} });
|
|
111
|
+
function getCurrentComponentPatchedProxy() {
|
|
112
|
+
const currentComponent = component.getCurrentComponent();
|
|
113
|
+
return new Proxy(currentComponent, { get: (target, prop, receiver) => {
|
|
114
|
+
const value = Reflect.get(target, prop, receiver);
|
|
115
|
+
if (prop === "proxy" && value) return new Proxy(value, { get(o, p, r) {
|
|
116
|
+
if (!Reflect.has(currentComponent.props, p)) {
|
|
117
|
+
const setupState = wrapper.setupState;
|
|
118
|
+
if (setupState && typeof setupState === "object") {
|
|
119
|
+
if (Reflect.has(setupState, p)) return Reflect.get(setupState, p, r);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return Reflect.get(o, p, r);
|
|
123
|
+
} });
|
|
124
|
+
return value;
|
|
125
|
+
} });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region renderSuspended
|
|
130
|
+
|
|
131
|
+
export async function renderSuspended(component, options = {}) {
|
|
132
|
+
const { cleanupAll, wrapperSuspended } = await importSuspended();
|
|
133
|
+
const wrapperId = "test-wrapper";
|
|
134
|
+
const suspendedHelperName = "RenderHelper";
|
|
135
|
+
const clonedComponentName = "RenderSuspendedComponent";
|
|
136
|
+
|
|
137
|
+
const { render: wrapperFn } = await import("@testing-library/vue");
|
|
138
|
+
cleanupAll();
|
|
139
|
+
document.getElementById(wrapperId)?.remove();
|
|
140
|
+
const { wrapper, setProps } = await wrapperSuspended(component, options, {
|
|
141
|
+
wrapperFn: (component, options) => wrapperFn(component, options),
|
|
142
|
+
wrappedRender: (render) => () => h({
|
|
143
|
+
inheritAttrs: false,
|
|
144
|
+
render: () => h("div", { id: wrapperId }, render())
|
|
145
|
+
}),
|
|
146
|
+
suspendedHelperName,
|
|
147
|
+
clonedComponentName
|
|
148
|
+
});
|
|
149
|
+
const renderResult = wrapper;
|
|
150
|
+
renderResult.rerender = async (props = {}) => {
|
|
151
|
+
setProps(props);
|
|
152
|
+
await nextTick();
|
|
153
|
+
};
|
|
154
|
+
return renderResult;
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|