@nuxt/test-utils 3.19.2 → 3.21.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/dist/config.d.mts +3 -3
- package/dist/config.mjs +13 -39
- package/dist/e2e.d.mts +2 -2
- package/dist/e2e.mjs +8 -4
- package/dist/experimental.mjs +1 -1
- package/dist/module.mjs +25 -15
- package/dist/playwright.d.mts +1 -1
- package/dist/playwright.mjs +7 -3
- package/dist/runtime/global-setup.mjs +3 -4
- package/dist/runtime/shared/environment.mjs +34 -12
- package/dist/runtime-utils/index.d.mts +48 -12
- package/dist/runtime-utils/index.mjs +163 -155
- package/dist/shared/{test-utils.DtJCg4f3.d.mts → test-utils.20kU0tZa.d.mts} +11 -11
- package/dist/shared/{test-utils.CT3RJOY3.mjs → test-utils.3NR-so9-.mjs} +10 -9
- package/dist/shared/{test-utils.B8qEdk9k.mjs → test-utils.CtwoJP76.mjs} +1 -1
- package/dist/shared/test-utils.G1ew4kEe.mjs +67 -0
- package/dist/vitest-environment.mjs +54 -22
- package/package.json +54 -61
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineEventHandler } from 'h3';
|
|
2
2
|
import { mount } from '@vue/test-utils';
|
|
3
|
-
import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance,
|
|
3
|
+
import { reactive, h as h$1, Suspense, nextTick, getCurrentInstance, effectScope, defineComponent as defineComponent$1 } from 'vue';
|
|
4
4
|
import { defu } from 'defu';
|
|
5
5
|
import { defineComponent, useRouter, h, tryUseNuxtApp } from '#imports';
|
|
6
6
|
import NuxtRoot from '#build/root-component.mjs';
|
|
@@ -11,16 +11,25 @@ function registerEndpoint(url, options) {
|
|
|
11
11
|
if (!app) {
|
|
12
12
|
throw new Error("registerEndpoint() can only be used in a `@nuxt/test-utils` runtime environment");
|
|
13
13
|
}
|
|
14
|
-
const config = typeof options === "function" ? { handler: options, method: void 0 } : options;
|
|
14
|
+
const config = typeof options === "function" ? { handler: options, method: void 0, once: false } : options;
|
|
15
15
|
config.handler = defineEventHandler(config.handler);
|
|
16
16
|
const hasBeenRegistered = window.__registry.has(url);
|
|
17
17
|
endpointRegistry[url] ||= [];
|
|
18
18
|
endpointRegistry[url].push(config);
|
|
19
19
|
if (!hasBeenRegistered) {
|
|
20
20
|
window.__registry.add(url);
|
|
21
|
-
app.use("/_" + url, defineEventHandler((event) => {
|
|
21
|
+
app.use("/_" + url, defineEventHandler(async (event) => {
|
|
22
22
|
const latestHandler = [...endpointRegistry[url] || []].reverse().find((config2) => config2.method ? event.method === config2.method : true);
|
|
23
|
-
|
|
23
|
+
if (!latestHandler) return;
|
|
24
|
+
const result = await latestHandler.handler(event);
|
|
25
|
+
if (!latestHandler.once) return result;
|
|
26
|
+
const index = endpointRegistry[url]?.indexOf(latestHandler);
|
|
27
|
+
if (index === void 0 || index === -1) return result;
|
|
28
|
+
endpointRegistry[url]?.splice(index, 1);
|
|
29
|
+
if (endpointRegistry[url]?.length === 0) {
|
|
30
|
+
window.__registry.delete(url);
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
24
33
|
}), {
|
|
25
34
|
match(_, event) {
|
|
26
35
|
return endpointRegistry[url]?.some((config2) => config2.method ? event?.method === config2.method : true) ?? false;
|
|
@@ -34,7 +43,7 @@ function registerEndpoint(url, options) {
|
|
|
34
43
|
}
|
|
35
44
|
};
|
|
36
45
|
}
|
|
37
|
-
function mockNuxtImport(
|
|
46
|
+
function mockNuxtImport(_target, _factory) {
|
|
38
47
|
throw new Error(
|
|
39
48
|
"mockNuxtImport() is a macro and it did not get transpiled. This may be an internal bug of @nuxt/test-utils."
|
|
40
49
|
);
|
|
@@ -87,16 +96,48 @@ async function mountSuspended(component, options) {
|
|
|
87
96
|
route = "/",
|
|
88
97
|
..._options
|
|
89
98
|
} = options || {};
|
|
99
|
+
for (const cleanupFunction of globalThis.__cleanup || []) {
|
|
100
|
+
cleanupFunction();
|
|
101
|
+
}
|
|
90
102
|
const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
|
|
91
|
-
const { render, setup,
|
|
103
|
+
const { render, setup, ...componentRest } = component;
|
|
104
|
+
let wrappedInstance = null;
|
|
92
105
|
let setupContext;
|
|
93
106
|
let setupState;
|
|
94
107
|
const setProps = reactive({});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
108
|
+
function patchInstanceAppContext() {
|
|
109
|
+
const app = getCurrentInstance()?.appContext.app;
|
|
110
|
+
if (!app) return;
|
|
111
|
+
for (const [key, value] of Object.entries(vueApp)) {
|
|
112
|
+
if (key in app) continue;
|
|
113
|
+
app[key] = value;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
let componentScope = null;
|
|
117
|
+
const wrappedSetup = async (props2, setupContext2, instanceContext) => {
|
|
118
|
+
const currentInstance = getCurrentInstance();
|
|
119
|
+
if (currentInstance) {
|
|
120
|
+
currentInstance.emit = (event, ...args) => {
|
|
121
|
+
setupContext2.emit(event, ...args);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
98
124
|
if (setup) {
|
|
99
|
-
|
|
125
|
+
let result;
|
|
126
|
+
if (options?.scoped) {
|
|
127
|
+
componentScope = effectScope();
|
|
128
|
+
globalThis.__cleanup ||= [];
|
|
129
|
+
globalThis.__cleanup.push(() => {
|
|
130
|
+
componentScope?.stop();
|
|
131
|
+
});
|
|
132
|
+
result = await componentScope?.run(async () => {
|
|
133
|
+
return await setup(props2, setupContext2);
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
result = await setup(props2, setupContext2);
|
|
137
|
+
}
|
|
138
|
+
if (wrappedInstance?.exposed) {
|
|
139
|
+
instanceContext.expose(wrappedInstance.exposed);
|
|
140
|
+
}
|
|
100
141
|
setupState = result && typeof result === "object" ? result : {};
|
|
101
142
|
return result;
|
|
102
143
|
}
|
|
@@ -105,15 +146,32 @@ async function mountSuspended(component, options) {
|
|
|
105
146
|
(resolve) => {
|
|
106
147
|
const vm = mount(
|
|
107
148
|
{
|
|
149
|
+
__cssModules: componentRest.__cssModules,
|
|
150
|
+
inheritAttrs: false,
|
|
108
151
|
setup: (props2, ctx) => {
|
|
152
|
+
patchInstanceAppContext();
|
|
153
|
+
wrappedInstance = getCurrentInstance();
|
|
109
154
|
setupContext = ctx;
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
155
|
+
if (options?.scoped) {
|
|
156
|
+
const scope = effectScope();
|
|
157
|
+
globalThis.__cleanup ||= [];
|
|
158
|
+
globalThis.__cleanup.push(() => {
|
|
159
|
+
scope.stop();
|
|
160
|
+
});
|
|
161
|
+
return scope.run(() => NuxtRoot.setup(props2, {
|
|
162
|
+
...ctx,
|
|
163
|
+
expose: () => {
|
|
164
|
+
}
|
|
165
|
+
}));
|
|
166
|
+
} else {
|
|
167
|
+
return NuxtRoot.setup(props2, {
|
|
168
|
+
...ctx,
|
|
169
|
+
expose: () => {
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
}
|
|
115
173
|
},
|
|
116
|
-
render: (
|
|
174
|
+
render: () => h$1(
|
|
117
175
|
Suspense,
|
|
118
176
|
{
|
|
119
177
|
onResolve: () => nextTick().then(() => {
|
|
@@ -130,66 +188,13 @@ async function mountSuspended(component, options) {
|
|
|
130
188
|
async setup() {
|
|
131
189
|
const router = useRouter();
|
|
132
190
|
await router.replace(route);
|
|
133
|
-
let interceptedEmit = null;
|
|
134
191
|
const clonedComponent = {
|
|
135
|
-
|
|
192
|
+
components: {},
|
|
136
193
|
...component,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (currentInstance && currentInstance.emit !== interceptedEmit) {
|
|
140
|
-
const oldEmit = currentInstance.emit;
|
|
141
|
-
interceptedEmit = (event, ...args2) => {
|
|
142
|
-
oldEmit(event, ...args2);
|
|
143
|
-
setupContext.emit(event, ...args2);
|
|
144
|
-
};
|
|
145
|
-
currentInstance.emit = interceptedEmit;
|
|
146
|
-
}
|
|
147
|
-
if (data && typeof data === "function") {
|
|
148
|
-
const dataObject = data();
|
|
149
|
-
for (const key in dataObject) {
|
|
150
|
-
renderContext[key] = dataObject[key];
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
for (const key in setupState || {}) {
|
|
154
|
-
const warn = console.warn;
|
|
155
|
-
console.warn = () => {
|
|
156
|
-
};
|
|
157
|
-
try {
|
|
158
|
-
renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
|
|
159
|
-
} catch {
|
|
160
|
-
} finally {
|
|
161
|
-
console.warn = warn;
|
|
162
|
-
}
|
|
163
|
-
if (key === "props") {
|
|
164
|
-
renderContext[key] = cloneProps$1(renderContext[key]);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
const propsContext = "props" in renderContext ? renderContext.props : renderContext;
|
|
168
|
-
for (const key in props || {}) {
|
|
169
|
-
propsContext[key] = _ctx[key];
|
|
170
|
-
}
|
|
171
|
-
for (const key in passedProps || {}) {
|
|
172
|
-
propsContext[key] = passedProps[key];
|
|
173
|
-
}
|
|
174
|
-
if (methods && typeof methods === "object") {
|
|
175
|
-
for (const [key, value] of Object.entries(methods)) {
|
|
176
|
-
renderContext[key] = value.bind(renderContext);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
if (computed && typeof computed === "object") {
|
|
180
|
-
for (const [key, value] of Object.entries(computed)) {
|
|
181
|
-
if ("get" in value) {
|
|
182
|
-
renderContext[key] = value.get.call(renderContext);
|
|
183
|
-
} else {
|
|
184
|
-
renderContext[key] = value.call(renderContext);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
return render.call(this, renderContext, ...args);
|
|
189
|
-
} : void 0,
|
|
190
|
-
setup: setup ? (props2) => wrappedSetup(props2, setupContext) : void 0
|
|
194
|
+
name: "MountSuspendedComponent",
|
|
195
|
+
setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
|
|
191
196
|
};
|
|
192
|
-
return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, slots);
|
|
197
|
+
return () => h$1(clonedComponent, { ...props, ...setProps, ...attrs }, setupContext.slots);
|
|
193
198
|
}
|
|
194
199
|
})
|
|
195
200
|
}
|
|
@@ -198,11 +203,18 @@ async function mountSuspended(component, options) {
|
|
|
198
203
|
defu(
|
|
199
204
|
_options,
|
|
200
205
|
{
|
|
206
|
+
props,
|
|
201
207
|
slots,
|
|
202
208
|
attrs,
|
|
203
209
|
global: {
|
|
204
210
|
config: {
|
|
205
|
-
globalProperties:
|
|
211
|
+
globalProperties: {
|
|
212
|
+
...vueApp.config.globalProperties,
|
|
213
|
+
// make all properties/keys enumerable.
|
|
214
|
+
...Object.fromEntries(
|
|
215
|
+
Object.getOwnPropertyNames(vueApp.config.globalProperties).map((key) => [key, vueApp.config.globalProperties[key]])
|
|
216
|
+
)
|
|
217
|
+
}
|
|
206
218
|
},
|
|
207
219
|
directives: vueApp._context.directives,
|
|
208
220
|
provide: vueApp._context.provides,
|
|
@@ -219,33 +231,46 @@ async function mountSuspended(component, options) {
|
|
|
219
231
|
}
|
|
220
232
|
);
|
|
221
233
|
}
|
|
222
|
-
function cloneProps$1(props) {
|
|
223
|
-
const newProps = reactive({});
|
|
224
|
-
for (const key in props) {
|
|
225
|
-
newProps[key] = props[key];
|
|
226
|
-
}
|
|
227
|
-
return newProps;
|
|
228
|
-
}
|
|
229
234
|
function wrappedMountedWrapper(wrapper) {
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
const component = wrapper.findComponent({ name: "MountSuspendedComponent" });
|
|
236
|
+
const wrapperProps = [
|
|
237
|
+
"setProps",
|
|
238
|
+
"emitted",
|
|
239
|
+
"setupState",
|
|
240
|
+
"unmount"
|
|
241
|
+
];
|
|
242
|
+
return new Proxy(wrapper, {
|
|
243
|
+
get: (_, prop, receiver) => {
|
|
244
|
+
if (prop === "getCurrentComponent") return getCurrentComponentPatchedProxy;
|
|
245
|
+
const target = wrapperProps.includes(prop) ? wrapper : Reflect.has(component, prop) ? component : wrapper;
|
|
246
|
+
const value = Reflect.get(target, prop, receiver);
|
|
247
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
238
248
|
}
|
|
239
249
|
});
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
250
|
+
function getCurrentComponentPatchedProxy() {
|
|
251
|
+
const currentComponent = component.getCurrentComponent();
|
|
252
|
+
return new Proxy(currentComponent, {
|
|
253
|
+
get: (target, prop, receiver) => {
|
|
254
|
+
const value = Reflect.get(target, prop, receiver);
|
|
255
|
+
if (prop === "proxy" && value) {
|
|
256
|
+
return new Proxy(value, {
|
|
257
|
+
get(o, p, r) {
|
|
258
|
+
if (!Reflect.has(currentComponent.props, p)) {
|
|
259
|
+
const setupState = wrapper.setupState;
|
|
260
|
+
if (setupState && typeof setupState === "object") {
|
|
261
|
+
if (Reflect.has(setupState, p)) {
|
|
262
|
+
return Reflect.get(setupState, p, r);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return Reflect.get(o, p, r);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
245
271
|
}
|
|
246
272
|
});
|
|
247
273
|
}
|
|
248
|
-
return proxy;
|
|
249
274
|
}
|
|
250
275
|
|
|
251
276
|
const WRAPPER_EL_ID = "test-wrapper";
|
|
@@ -259,19 +284,36 @@ async function renderSuspended(component, options) {
|
|
|
259
284
|
} = options || {};
|
|
260
285
|
const { render: renderFromTestingLibrary } = await import('@testing-library/vue');
|
|
261
286
|
const vueApp = tryUseNuxtApp()?.vueApp || globalThis.__unctx__.get("nuxt-app").tryUse().vueApp;
|
|
262
|
-
const { render, setup,
|
|
287
|
+
const { render, setup, ...componentRest } = component;
|
|
288
|
+
let wrappedInstance = null;
|
|
263
289
|
let setupContext;
|
|
264
290
|
let setupState;
|
|
291
|
+
const setProps = reactive({});
|
|
292
|
+
function patchInstanceAppContext() {
|
|
293
|
+
const app = getCurrentInstance()?.appContext.app;
|
|
294
|
+
if (!app) return;
|
|
295
|
+
for (const [key, value] of Object.entries(vueApp)) {
|
|
296
|
+
if (key in app) continue;
|
|
297
|
+
app[key] = value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
265
300
|
for (const fn of window.__cleanup || []) {
|
|
266
301
|
fn();
|
|
267
302
|
}
|
|
268
303
|
document.querySelector(`#${WRAPPER_EL_ID}`)?.remove();
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
304
|
+
const wrappedSetup = async (props2, setupContext2, instanceContext) => {
|
|
305
|
+
const currentInstance = getCurrentInstance();
|
|
306
|
+
if (currentInstance) {
|
|
307
|
+
currentInstance.emit = (event, ...args) => {
|
|
308
|
+
setupContext2.emit(event, ...args);
|
|
309
|
+
};
|
|
310
|
+
}
|
|
272
311
|
if (setup) {
|
|
273
312
|
const result = await setup(props2, setupContext2);
|
|
274
313
|
setupState = result && typeof result === "object" ? result : {};
|
|
314
|
+
if (wrappedInstance?.exposed) {
|
|
315
|
+
instanceContext.expose(wrappedInstance.exposed);
|
|
316
|
+
}
|
|
275
317
|
return result;
|
|
276
318
|
}
|
|
277
319
|
};
|
|
@@ -284,7 +326,11 @@ async function renderSuspended(component, options) {
|
|
|
284
326
|
return new Promise((resolve) => {
|
|
285
327
|
const utils = renderFromTestingLibrary(
|
|
286
328
|
{
|
|
329
|
+
__cssModules: componentRest.__cssModules,
|
|
330
|
+
inheritAttrs: false,
|
|
287
331
|
setup: (props2, ctx) => {
|
|
332
|
+
patchInstanceAppContext();
|
|
333
|
+
wrappedInstance = getCurrentInstance();
|
|
288
334
|
setupContext = ctx;
|
|
289
335
|
const scope = effectScope();
|
|
290
336
|
window.__cleanup ||= [];
|
|
@@ -296,7 +342,7 @@ async function renderSuspended(component, options) {
|
|
|
296
342
|
expose: () => ({})
|
|
297
343
|
}));
|
|
298
344
|
},
|
|
299
|
-
render: (
|
|
345
|
+
render: () => (
|
|
300
346
|
// See discussions in https://github.com/testing-library/vue-testing-library/issues/230
|
|
301
347
|
// we add this additional root element because otherwise testing-library breaks
|
|
302
348
|
// because there's no root element while Suspense is resolving
|
|
@@ -309,6 +355,10 @@ async function renderSuspended(component, options) {
|
|
|
309
355
|
{
|
|
310
356
|
onResolve: () => nextTick().then(() => {
|
|
311
357
|
utils.setupState = setupState;
|
|
358
|
+
utils.rerender = async (props2) => {
|
|
359
|
+
Object.assign(setProps, props2);
|
|
360
|
+
await nextTick();
|
|
361
|
+
};
|
|
312
362
|
resolve(utils);
|
|
313
363
|
})
|
|
314
364
|
},
|
|
@@ -319,55 +369,13 @@ async function renderSuspended(component, options) {
|
|
|
319
369
|
const router = useRouter();
|
|
320
370
|
await router.replace(route);
|
|
321
371
|
const clonedComponent = {
|
|
322
|
-
|
|
372
|
+
components: {},
|
|
323
373
|
...component,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
for (const key in dataObject) {
|
|
328
|
-
renderContext[key] = dataObject[key];
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
for (const key in setupState || {}) {
|
|
332
|
-
const warn = console.warn;
|
|
333
|
-
console.warn = () => {
|
|
334
|
-
};
|
|
335
|
-
try {
|
|
336
|
-
renderContext[key] = isReadonly(setupState[key]) ? unref(setupState[key]) : setupState[key];
|
|
337
|
-
} catch {
|
|
338
|
-
} finally {
|
|
339
|
-
console.warn = warn;
|
|
340
|
-
}
|
|
341
|
-
if (key === "props") {
|
|
342
|
-
renderContext[key] = cloneProps(renderContext[key]);
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
const propsContext = "props" in renderContext ? renderContext.props : renderContext;
|
|
346
|
-
for (const key in props || {}) {
|
|
347
|
-
propsContext[key] = _ctx[key];
|
|
348
|
-
}
|
|
349
|
-
for (const key in passedProps || {}) {
|
|
350
|
-
propsContext[key] = passedProps[key];
|
|
351
|
-
}
|
|
352
|
-
if (methods && typeof methods === "object") {
|
|
353
|
-
for (const [key, value] of Object.entries(methods)) {
|
|
354
|
-
renderContext[key] = value.bind(renderContext);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
if (computed && typeof computed === "object") {
|
|
358
|
-
for (const [key, value] of Object.entries(computed)) {
|
|
359
|
-
if ("get" in value) {
|
|
360
|
-
renderContext[key] = value.get.call(renderContext);
|
|
361
|
-
} else {
|
|
362
|
-
renderContext[key] = value.call(renderContext);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
return render.call(this, renderContext, ...args);
|
|
367
|
-
} : void 0,
|
|
368
|
-
setup: setup ? (props2) => wrappedSetup(props2, setupContext) : void 0
|
|
374
|
+
name: "RenderSuspendedComponent",
|
|
375
|
+
render,
|
|
376
|
+
setup: (props2, ctx) => wrappedSetup(props2, setupContext, ctx)
|
|
369
377
|
};
|
|
370
|
-
return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...attrs }, slots);
|
|
378
|
+
return () => h$1(clonedComponent, { ...props && typeof props === "object" ? props : {}, ...setProps, ...attrs }, setupContext.slots);
|
|
371
379
|
}
|
|
372
380
|
})
|
|
373
381
|
}
|
|
@@ -377,11 +385,18 @@ async function renderSuspended(component, options) {
|
|
|
377
385
|
)
|
|
378
386
|
},
|
|
379
387
|
defu(_options, {
|
|
388
|
+
props,
|
|
380
389
|
slots,
|
|
381
390
|
attrs,
|
|
382
391
|
global: {
|
|
383
392
|
config: {
|
|
384
|
-
globalProperties:
|
|
393
|
+
globalProperties: {
|
|
394
|
+
...vueApp.config.globalProperties,
|
|
395
|
+
// make all properties/keys enumerable.
|
|
396
|
+
...Object.fromEntries(
|
|
397
|
+
Object.getOwnPropertyNames(vueApp.config.globalProperties).map((key) => [key, vueApp.config.globalProperties[key]])
|
|
398
|
+
)
|
|
399
|
+
}
|
|
385
400
|
},
|
|
386
401
|
directives: vueApp._context.directives,
|
|
387
402
|
provide: vueApp._context.provides,
|
|
@@ -391,12 +406,5 @@ async function renderSuspended(component, options) {
|
|
|
391
406
|
);
|
|
392
407
|
});
|
|
393
408
|
}
|
|
394
|
-
function cloneProps(props) {
|
|
395
|
-
const newProps = reactive({});
|
|
396
|
-
for (const key in props) {
|
|
397
|
-
newProps[key] = props[key];
|
|
398
|
-
}
|
|
399
|
-
return newProps;
|
|
400
|
-
}
|
|
401
409
|
|
|
402
410
|
export { mockComponent, mockNuxtImport, mountSuspended, registerEndpoint, renderSuspended };
|
|
@@ -29,41 +29,41 @@ interface TestOptions {
|
|
|
29
29
|
fixture: string;
|
|
30
30
|
/**
|
|
31
31
|
* Name of the configuration file.
|
|
32
|
-
* @default
|
|
32
|
+
* @default 'nuxt.config'
|
|
33
33
|
*/
|
|
34
34
|
configFile: string;
|
|
35
35
|
/**
|
|
36
36
|
* Path to a directory with a Nuxt app to be put under test.
|
|
37
|
-
* @default
|
|
37
|
+
* @default '.'
|
|
38
38
|
*/
|
|
39
39
|
rootDir: string;
|
|
40
40
|
buildDir: string;
|
|
41
41
|
nuxtConfig: NuxtConfig;
|
|
42
42
|
/**
|
|
43
43
|
* Whether to run a separate build step.
|
|
44
|
-
* @default
|
|
44
|
+
* @default true // (`false` if `browser` or `server` is disabled, or if a `host` is provided)
|
|
45
45
|
*/
|
|
46
46
|
build: boolean;
|
|
47
47
|
dev: boolean;
|
|
48
48
|
/**
|
|
49
49
|
* The amount of time (in milliseconds) to allow for `setupTest` to complete its work (which could include building or generating files for a Nuxt application, depending on the options that are passed).
|
|
50
|
-
* @default
|
|
50
|
+
* @default 120000 // or `240000` on windows
|
|
51
51
|
*/
|
|
52
52
|
setupTimeout: number;
|
|
53
53
|
/**
|
|
54
54
|
* The amount of time (in milliseconds) to allow tearing down the test environment, such as closing the browser.
|
|
55
|
-
* @default
|
|
55
|
+
* @default 30000
|
|
56
56
|
*/
|
|
57
57
|
teardownTimeout: number;
|
|
58
58
|
waitFor: number;
|
|
59
59
|
/**
|
|
60
60
|
* Under the hood, Nuxt test utils uses [`playwright`](https://playwright.dev) to carry out browser testing. If this option is set, a browser will be launched and can be controlled in the subsequent test suite.
|
|
61
|
-
* @default
|
|
61
|
+
* @default false
|
|
62
62
|
*/
|
|
63
63
|
browser: boolean;
|
|
64
64
|
/**
|
|
65
65
|
* Specify the runner for the test suite. One of `'vitest' | 'jest' | 'cucumber' | 'bun'`.
|
|
66
|
-
* @default
|
|
66
|
+
* @default 'vitest'
|
|
67
67
|
*/
|
|
68
68
|
runner: TestRunner;
|
|
69
69
|
logLevel: number;
|
|
@@ -75,17 +75,17 @@ interface TestOptions {
|
|
|
75
75
|
};
|
|
76
76
|
/**
|
|
77
77
|
* Whether to launch a server to respond to requests in the test suite.
|
|
78
|
-
* @default
|
|
78
|
+
* @default true // (`false` if a `host` is provided)
|
|
79
79
|
*/
|
|
80
80
|
server: boolean;
|
|
81
81
|
/**
|
|
82
82
|
* If provided, a URL to use as the test target instead of building and running a new server. Useful for running "real" end-to-end tests against a deployed version of your application, or against an already running local server.
|
|
83
|
-
* @default
|
|
83
|
+
* @default undefined
|
|
84
84
|
*/
|
|
85
85
|
host?: string;
|
|
86
86
|
/**
|
|
87
87
|
* If provided, set the launched test server port to the value.
|
|
88
|
-
* @default
|
|
88
|
+
* @default undefined
|
|
89
89
|
*/
|
|
90
90
|
port?: number;
|
|
91
91
|
env?: StartServerOptions['env'];
|
|
@@ -109,7 +109,7 @@ interface TestHooks {
|
|
|
109
109
|
afterAll: () => Promise<void>;
|
|
110
110
|
beforeAll: () => Promise<void>;
|
|
111
111
|
/**
|
|
112
|
-
* @deprecated use `beforeAll`
|
|
112
|
+
* @deprecated use `beforeAll` instead
|
|
113
113
|
*/
|
|
114
114
|
setup: () => Promise<void>;
|
|
115
115
|
ctx: TestContext;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { u as useTestContext, d as url, c as createTestContext, a as startServer, b as stopServer, s as setTestContext } from './test-utils.
|
|
1
|
+
import { u as useTestContext, d as url, c as createTestContext, a as startServer, b as stopServer, s as setTestContext } from './test-utils.CtwoJP76.mjs';
|
|
2
2
|
import { existsSync, promises } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
4
|
import { defu } from 'defu';
|
|
5
|
-
import
|
|
5
|
+
import { l as loadKit } from './test-utils.G1ew4kEe.mjs';
|
|
6
6
|
|
|
7
7
|
async function createBrowser() {
|
|
8
8
|
const ctx = useTestContext();
|
|
@@ -57,9 +57,8 @@ async function waitForHydration(page, url2, waitUntil) {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
const kit = _kit.default || _kit;
|
|
61
60
|
const isNuxtApp = (dir) => {
|
|
62
|
-
return existsSync(dir) && (existsSync(resolve(dir, "pages")) || existsSync(resolve(dir, "nuxt.config.js")) || existsSync(resolve(dir, "nuxt.config.mjs")) || existsSync(resolve(dir, "nuxt.config.cjs")) || existsSync(resolve(dir, "nuxt.config.ts")));
|
|
61
|
+
return existsSync(dir) && (existsSync(resolve(dir, "pages")) || existsSync(resolve(dir, "nuxt.config.js")) || existsSync(resolve(dir, "nuxt.config.mjs")) || existsSync(resolve(dir, "nuxt.config.cjs")) || existsSync(resolve(dir, "nuxt.config.ts")) || existsSync(resolve(dir, ".config", "nuxt.js")) || existsSync(resolve(dir, ".config", "nuxt.mjs")) || existsSync(resolve(dir, ".config", "nuxt.cjs")) || existsSync(resolve(dir, ".config", "nuxt.ts")));
|
|
63
62
|
};
|
|
64
63
|
const resolveRootDir = () => {
|
|
65
64
|
const { options } = useTestContext();
|
|
@@ -91,7 +90,8 @@ async function loadFixture() {
|
|
|
91
90
|
});
|
|
92
91
|
}
|
|
93
92
|
if (ctx.options.build) {
|
|
94
|
-
|
|
93
|
+
const { loadNuxt } = await loadKit(ctx.options.rootDir);
|
|
94
|
+
ctx.nuxt = await loadNuxt({
|
|
95
95
|
cwd: ctx.options.rootDir,
|
|
96
96
|
dev: ctx.options.dev,
|
|
97
97
|
overrides: ctx.options.nuxtConfig,
|
|
@@ -107,10 +107,11 @@ async function loadFixture() {
|
|
|
107
107
|
}
|
|
108
108
|
async function buildFixture() {
|
|
109
109
|
const ctx = useTestContext();
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const { buildNuxt, logger } = await loadKit(ctx.options.rootDir);
|
|
111
|
+
const prevLevel = logger.level;
|
|
112
|
+
logger.level = ctx.options.logLevel;
|
|
113
|
+
await buildNuxt(ctx.nuxt);
|
|
114
|
+
logger.level = prevLevel;
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
async function setupBun(hooks) {
|
|
@@ -14,7 +14,7 @@ function createTestContext(options) {
|
|
|
14
14
|
fixture: "fixture",
|
|
15
15
|
configFile: "nuxt.config",
|
|
16
16
|
setupTimeout: isWindows ? 24e4 : 12e4,
|
|
17
|
-
teardownTimeout: 3e4,
|
|
17
|
+
teardownTimeout: isWindows ? 6e4 : 3e4,
|
|
18
18
|
dev: !!JSON.parse(process.env.NUXT_TEST_DEV || "false"),
|
|
19
19
|
logLevel: 1,
|
|
20
20
|
server: true,
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import destr from 'destr';
|
|
2
|
+
import { snakeCase } from 'scule';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
import { resolveModulePath } from 'exsolve';
|
|
5
|
+
|
|
6
|
+
function getEnv(key, opts) {
|
|
7
|
+
const env = opts.env ?? process.env;
|
|
8
|
+
const envKey = snakeCase(key).toUpperCase();
|
|
9
|
+
return destr(
|
|
10
|
+
env[opts.prefix + envKey] ?? env[opts.altPrefix + envKey]
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
function _isObject(input) {
|
|
14
|
+
return typeof input === "object" && !Array.isArray(input);
|
|
15
|
+
}
|
|
16
|
+
function applyEnv(obj, opts, parentKey = "") {
|
|
17
|
+
for (const key in obj) {
|
|
18
|
+
const subKey = parentKey ? `${parentKey}_${key}` : key;
|
|
19
|
+
const envValue = getEnv(subKey, opts);
|
|
20
|
+
if (_isObject(obj[key])) {
|
|
21
|
+
if (_isObject(envValue)) {
|
|
22
|
+
obj[key] = { ...obj[key], ...envValue };
|
|
23
|
+
applyEnv(obj[key], opts, subKey);
|
|
24
|
+
} else if (envValue === void 0) {
|
|
25
|
+
applyEnv(obj[key], opts, subKey);
|
|
26
|
+
} else {
|
|
27
|
+
obj[key] = envValue ?? obj[key];
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
obj[key] = envValue ?? obj[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return obj;
|
|
34
|
+
}
|
|
35
|
+
async function loadKit(rootDir) {
|
|
36
|
+
try {
|
|
37
|
+
const kitPath = resolveModulePath("@nuxt/kit", { from: tryResolveNuxt(rootDir) || rootDir });
|
|
38
|
+
let kit = await import(pathToFileURL(kitPath).href);
|
|
39
|
+
if (!kit.writeTypes) {
|
|
40
|
+
kit = {
|
|
41
|
+
...kit,
|
|
42
|
+
writeTypes: () => {
|
|
43
|
+
throw new Error("`writeTypes` is not available in this version of `@nuxt/kit`. Please upgrade to v3.7 or newer.");
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
return kit;
|
|
48
|
+
} catch (e) {
|
|
49
|
+
if (e.toString().includes("Cannot find module '@nuxt/kit'")) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"nuxi requires `@nuxt/kit` to be installed in your project. Try installing `nuxt` v3+ or `@nuxt/bridge` first."
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function tryResolveNuxt(rootDir) {
|
|
58
|
+
for (const pkg of ["nuxt-nightly", "nuxt", "nuxt3", "nuxt-edge"]) {
|
|
59
|
+
const path = resolveModulePath(pkg, { from: rootDir, try: true });
|
|
60
|
+
if (path) {
|
|
61
|
+
return path;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export { applyEnv as a, loadKit as l };
|