@sxzz/test-utils
Version:
Collection of common test utils.
170 lines (163 loc) • 5.42 kB
JavaScript
import path from "node:path";
import process from "node:process";
import { glob } from "tinyglobby";
import { normalizePath } from "unplugin-utils";
import { describe, expect, test } from "vitest";
import { readFile } from "node:fs/promises";
//#region src/fixture.ts
let isSkip;
function testFixturesSkip(fn) {
isSkip = fn;
}
async function testFixtures(globsOrFiles, cb, { params, promise, concurrent,...globOptions } = {}) {
let files;
if (typeof globsOrFiles === "string" || Array.isArray(globsOrFiles)) files = Object.fromEntries((await glob(globsOrFiles, globOptions)).map((file) => [file, undefined]));
else files = globsOrFiles;
for (const [id, code] of Object.entries(files)) makeTests(id, code, [[normalizePath(id)], ...params || []])();
function makeTests(id, code, params$1, args = {}) {
const [currParams, ...restParams] = params$1;
const [name, values = [undefined]] = currParams;
if (restParams.length > 0) return () => {
for (const value of values) {
const currArgs = {
...args,
[name]: value
};
describe(getName(name, value), makeTests(id, code, restParams, currArgs));
}
};
else return () => {
for (const value of values) {
const testName = getName(name, value);
let testFn = test.skipIf(isSkip?.(testName));
if (concurrent) testFn = testFn.concurrent;
testFn(testName, async ({ expect: expect$1 }) => {
const currArgs = {
...args,
[name]: value
};
const execute = () => cb(currArgs, path.resolve(globOptions.cwd || process.cwd(), id), code);
if (id.includes("error")) if (promise) await expect$1(execute()).rejects.toThrowErrorMatchingSnapshot();
else expect$1(execute).toThrowErrorMatchingSnapshot();
else if (promise) await expect$1(execute().catch((error) => {
console.warn(error);
return Promise.reject(error);
})).resolves.toMatchSnapshot();
else expect$1(execute()).toMatchSnapshot();
});
}
};
}
}
function getName(name, value) {
return value !== undefined ? `${name} = ${String(value)}` : name;
}
//#endregion
//#region src/snapshot.ts
function outputToSnapshot(chunks) {
const cwd = process.cwd();
return chunks.map((file) => {
let filename, content;
if ("path" in file) {
filename = file.path;
content = file.text;
} else {
filename = file.fileName;
content = file.type === "chunk" ? file.code : typeof file.source === "string" ? file.source : "[BINARY]";
}
return `// ${filename.replaceAll("\\", "/")}\n${content.replaceAll(cwd, "[CWD]")}`;
}).sort().join("\n");
}
async function expectFilesSnapshot(sourceDir, snapshotFile, { pattern = "**/*", expect: expect$1 = expect } = {}) {
const cwd = process.cwd();
const files = (await glob(pattern, { cwd: sourceDir })).sort();
const fileMap = Object.fromEntries(await Promise.all(files.map(async (filename) => [filename.replaceAll("\\", "/"), (await readFile(path.resolve(sourceDir, filename), "utf8")).replaceAll(cwd, "[CWD]")])));
const snapshot = Object.entries(fileMap).map(([filename, contents]) => {
const ext = path.extname(filename).slice(1);
return `## ${filename}
\`\`\`${ext}
${contents}
\`\`\``;
}).join("\n");
await expect$1(snapshot).toMatchFileSnapshot(snapshotFile);
return {
files,
fileMap,
snapshot
};
}
//#endregion
//#region src/rolldown.ts
async function rolldownBuild(file, plugins = [], inputOptions = {}, outputOptions = {}) {
const { rolldown } = await import("rolldown");
const bundle = await rolldown({
input: [file],
treeshake: false,
onwarn(warning, defaultHandler) {
if (["UNUSED_EXTERNAL_IMPORT", "UNRESOLVED_IMPORT"].includes(warning.code)) return;
defaultHandler(warning);
},
...inputOptions,
plugins: [plugins, inputOptions.plugins]
});
const output = await bundle.generate({
format: "esm",
sourcemap: false,
...outputOptions
});
const chunks = output.output;
return {
chunks,
snapshot: outputToSnapshot(chunks)
};
}
//#endregion
//#region src/rollup.ts
async function rollupBuild(file, plugins = [], inputOptions = {}, outputOptions = {}) {
const { rollup } = await import("rollup");
const bundle = await rollup({
input: [file],
treeshake: false,
onwarn(warning, defaultHandler) {
if (["UNUSED_EXTERNAL_IMPORT", "UNRESOLVED_IMPORT"].includes(warning.code)) return;
defaultHandler(warning);
},
...inputOptions,
plugins: [plugins, inputOptions.plugins]
});
const output = await bundle.generate({
format: "esm",
sourcemap: false,
...outputOptions
});
const chunks = output.output;
return {
chunks,
snapshot: outputToSnapshot(chunks)
};
}
const RollupToStringPlugin = () => {
return {
name: "to-string",
transform: (code) => `export default \`${code.replaceAll("`", "\\`")}\``
};
};
const RollupEscapeNullCharacterPlugin = () => {
return {
name: "escape-null-character",
generateBundle(options, bundle) {
for (const filename of Object.keys(bundle)) {
const b = bundle[filename];
if (b.type !== "chunk") continue;
if (b.code.includes("\0")) b.code = b.code.replaceAll("\0", "[NULL]");
}
}
};
};
//#endregion
//#region src/string.ts
function removeSpaces(s) {
return s.trim().replaceAll(/[\n\r]/g, "").replaceAll(/\s+/g, " ");
}
//#endregion
export { RollupEscapeNullCharacterPlugin, RollupToStringPlugin, expectFilesSnapshot, outputToSnapshot, removeSpaces, rolldownBuild, rollupBuild, testFixtures, testFixturesSkip };