@nuxt/test-utils
Version:
Test utilities for Nuxt
259 lines (258 loc) • 9.4 kB
JavaScript
import { a as startServer, c as createTestContext, f as setTestContext, o as stopServer, p as useTestContext, s as url } from "./server-CgIHV0e5.mjs";
import { r as loadKit } from "./utils-whMnUpQ0.mjs";
import { resolve } from "node:path";
import { defu as defu$1 } from "defu";
import { resolve as resolve$1 } from "pathe";
import { consola } from "consola";
import { existsSync, promises } from "node:fs";
import { distDir } from "#dirs";
//#region src/e2e/browser.ts
async function createBrowser() {
const ctx = useTestContext();
let playwright;
try {
playwright = await import("playwright-core");
} catch {
/* istanbul ignore next */
throw new Error(`
The dependency 'playwright-core' not found.
Please run 'yarn add --dev playwright-core' or 'npm install --save-dev playwright-core'
`);
}
const { type, launch } = ctx.options.browserOptions;
if (!playwright[type]) throw new Error(`Invalid browser '${type}'`);
ctx.browser = await playwright[type].launch(launch);
}
async function getBrowser() {
const ctx = useTestContext();
if (!ctx.browser) await createBrowser();
return ctx.browser;
}
async function createPage(path, options) {
const page = await (await getBrowser()).newPage(options);
const _goto = page.goto.bind(page);
page.goto = async (url, options) => {
const waitUntil = options?.waitUntil;
if (waitUntil && ["hydration", "route"].includes(waitUntil)) delete options.waitUntil;
const res = await _goto(url, options);
await waitForHydration(page, url, waitUntil);
return res;
};
if (path) await page.goto(url(path), options?.javaScriptEnabled === false ? {} : { waitUntil: "hydration" });
return page;
}
async function waitForHydration(page, url, waitUntil) {
if (waitUntil === "hydration") await page.waitForFunction(() => window.useNuxtApp?.().isHydrating === false);
else if (waitUntil === "route") await page.waitForFunction((route) => window.useNuxtApp?.()._route.fullPath === route, url);
}
//#endregion
//#region src/e2e/mock.ts
function mockFn() {
return useTestContext().mockFn;
}
function mockLogger() {
const mocks = {};
consola.mockTypes((type) => {
mocks[type] = mockFn();
return mocks[type];
});
return mocks;
}
//#endregion
//#region src/e2e/nuxt.ts
const isNuxtApp = (dir) => {
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")));
};
const resolveRootDir = () => {
const { options } = useTestContext();
const dirs = [
options.rootDir,
resolve(options.testDir, options.fixture),
process.cwd()
];
for (const dir of dirs) if (dir && isNuxtApp(dir)) return dir;
throw new Error("Invalid nuxt app. (Please explicitly set `options.rootDir` pointing to a valid nuxt app)");
};
async function loadFixture() {
const ctx = useTestContext();
ctx.options.rootDir = resolveRootDir();
if (!ctx.options.dev) {
const randomId = Math.random().toString(36).slice(2, 8);
const buildDir = ctx.options.buildDir || resolve(ctx.options.rootDir, ".nuxt", "test", randomId);
ctx.options.nuxtConfig = defu$1(ctx.options.nuxtConfig, {
buildDir,
nitro: { output: { dir: resolve(buildDir, "output") } }
});
}
if (ctx.options.build) {
const { loadNuxt } = await loadKit(ctx.options.rootDir);
ctx.nuxt = await loadNuxt({
cwd: ctx.options.rootDir,
dev: ctx.options.dev,
overrides: ctx.options.nuxtConfig,
configFile: ctx.options.configFile
});
const buildDir = ctx.nuxt.options.buildDir;
if (!existsSync(buildDir)) {
await promises.mkdir(buildDir, { recursive: true });
ctx.teardown = ctx.teardown || [];
ctx.teardown.push(() => promises.rm(buildDir, {
recursive: true,
force: true
}));
}
}
}
async function buildFixture() {
const ctx = useTestContext();
const { buildNuxt, logger } = await loadKit(ctx.options.rootDir);
const prevLevel = logger.level;
logger.level = ctx.options.logLevel;
await buildNuxt(ctx.nuxt);
logger.level = prevLevel;
}
//#endregion
//#region src/e2e/setup/bun.ts
async function setupBun(hooks) {
const bunTest = await import("bun:test");
hooks.ctx.mockFn = bunTest.mock;
bunTest.beforeAll(hooks.beforeAll, hooks.ctx.options.setupTimeout);
bunTest.beforeEach(hooks.beforeEach);
bunTest.afterEach(hooks.afterEach);
bunTest.afterAll(hooks.afterAll, hooks.ctx.options.teardownTimeout);
}
//#endregion
//#region src/e2e/setup/cucumber.ts
async function setupCucumber(hooks) {
const { After, AfterAll, Before, BeforeAll } = await import("@cucumber/cucumber");
BeforeAll({ timeout: hooks.ctx.options.setupTimeout }, hooks.beforeAll);
Before(hooks.beforeEach);
After(hooks.afterEach);
AfterAll(hooks.afterAll);
}
//#endregion
//#region src/e2e/setup/jest.ts
async function setupJest(hooks) {
const { jest, test, beforeEach, afterAll, afterEach } = await import("@jest/globals");
hooks.ctx.mockFn = jest.fn;
test("setup", hooks.beforeAll, hooks.ctx.options.setupTimeout);
beforeEach(hooks.beforeEach);
afterEach(hooks.afterEach);
afterAll(hooks.afterAll, hooks.ctx.options.teardownTimeout);
}
//#endregion
//#region src/e2e/setup/vitest.ts
async function setupVitest(hooks) {
const vitest = await import("vitest");
hooks.ctx.mockFn = vitest.vi.fn;
vitest.beforeAll(hooks.beforeAll, hooks.ctx.options.setupTimeout);
vitest.beforeEach(hooks.beforeEach);
vitest.afterEach(hooks.afterEach);
vitest.afterAll(hooks.afterAll, hooks.ctx.options.teardownTimeout);
}
//#endregion
//#region src/e2e/setup/index.ts
const setupMaps = {
bun: setupBun,
cucumber: setupCucumber,
jest: setupJest,
vitest: setupVitest
};
function withTimeout(label, ms, promise) {
let timedOut = false;
let timer;
const timeout = new Promise((resolve) => {
timer = setTimeout(() => {
timedOut = true;
console.warn(`[@nuxt/test-utils] Timed out after ${ms}ms ${label} during teardown. Continuing; some processes or file handles may still be held.`);
resolve(void 0);
}, ms);
});
const guarded = promise.catch((error) => {
if (timedOut) {
console.warn(`[@nuxt/test-utils] Error ${label} during teardown:`, error);
return;
}
throw error;
}).finally(() => clearTimeout(timer));
return Promise.race([guarded, timeout]);
}
function createTest(options) {
const ctx = createTestContext(options);
const beforeEach = () => {
setTestContext(ctx);
};
const afterEach = () => {
setTestContext(void 0);
};
const afterAll = async () => {
ctx.disposed = true;
const deadline = Date.now() + ctx.options.teardownTimeout;
const remaining = () => Math.max(1e3, deadline - Date.now());
if (ctx.serverProcess) {
setTestContext(ctx);
await stopServer();
setTestContext(void 0);
}
if (ctx.nuxt && ctx.nuxt.options.dev) await withTimeout("closing the Nuxt instance", remaining(), ctx.nuxt.close());
if (ctx.browser) await withTimeout("closing the browser", remaining(), ctx.browser.close());
await withTimeout("running teardown hooks", remaining(), Promise.all(!ctx.teardown ? [] : ctx.teardown.map((fn) => fn())));
};
const beforeAll = async () => {
if (ctx.options.fixture) await loadFixture();
if (ctx.options.build) await buildFixture();
if (ctx.options.server) await startServer(ctx.options.env);
if (ctx.options.waitFor) await new Promise((resolve) => setTimeout(resolve, ctx.options.waitFor));
if (ctx.options.browser) await createBrowser();
};
return {
beforeEach,
afterEach,
afterAll,
beforeAll,
setup: beforeAll,
ctx
};
}
async function setup(options = {}) {
if (typeof __NUXT_VITEST_RESOLVED__ === "boolean" && __NUXT_VITEST_RESOLVED__) console.warn("Do not use defineVitestConfig or defineVitestProject for end-to-end tests, as they only work with nuxt client-environment tests.\nPlease refer to the setup section: https://nuxt.com/docs/4.x/getting-started/testing#setup");
const hooks = createTest(options);
const setupFn = setupMaps[hooks.ctx.options.runner];
await setupFn(hooks);
}
//#endregion
//#region src/e2e/run.ts
const RunTestDefaults = {
runner: "vitest",
globalSetup: true
};
async function runTests(opts) {
opts = {
...RunTestDefaults,
...opts
};
if (opts.runner !== "vitest") throw new Error(`Unsupported runner: ${opts.runner}. Currently only vitest runner is supported.`);
if (opts.dev) process.env.NUXT_TEST_DEV = "true";
process.env.NUXT_TEST_OPTIONS = JSON.stringify(opts);
const { startVitest } = await import("vitest/node");
if (!await startVitest("test", [], {
root: opts.rootDir,
run: !opts.watch,
server: { deps: { inline: [/@nuxt\/test-utils/] } }
}, {
esbuild: { tsconfigRaw: "{}" },
test: {
dir: opts.rootDir,
server: { deps: { inline: [
distDir,
"@nuxt/test-utils",
"@nuxt/test-utils-nightly",
"@nuxt/test-utils-edge"
] } },
globals: true,
globalSetup: [...opts.globalSetup ? [resolve$1(distDir, "./runtime/global-setup")] : []]
}
})) process.exit(1);
}
//#endregion
export { buildFixture as a, mockLogger as c, getBrowser as d, waitForHydration as f, setupMaps as i, createBrowser as l, createTest as n, loadFixture as o, setup as r, mockFn as s, runTests as t, createPage as u };