107 lines
3.3 KiB
JavaScript
107 lines
3.3 KiB
JavaScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import vm from "node:vm";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
import ts from "typescript";
|
|
|
|
const projectRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
const nodeRequire = createRequire(import.meta.url);
|
|
const moduleCache = new Map();
|
|
|
|
function toWindowsSafePath(value) {
|
|
return process.platform === "win32" && value.startsWith("/")
|
|
? value.slice(1)
|
|
: value;
|
|
}
|
|
|
|
function resolveModule(specifier, fromFile) {
|
|
if (specifier.startsWith("@/")) {
|
|
return resolveFile(path.join(projectRoot, specifier.slice(2)));
|
|
}
|
|
if (specifier.startsWith(".")) {
|
|
return resolveFile(path.resolve(path.dirname(fromFile), specifier));
|
|
}
|
|
return specifier;
|
|
}
|
|
|
|
function resolveFile(basePath) {
|
|
const candidates = [
|
|
basePath,
|
|
`${basePath}.ts`,
|
|
`${basePath}.tsx`,
|
|
`${basePath}.js`,
|
|
`${basePath}.jsx`,
|
|
path.join(basePath, "index.ts"),
|
|
path.join(basePath, "index.tsx"),
|
|
path.join(basePath, "index.js"),
|
|
];
|
|
const match = candidates.find((candidate) => fs.existsSync(candidate) && fs.statSync(candidate).isFile());
|
|
if (!match) {
|
|
throw new Error(`Cannot resolve module file: ${basePath}`);
|
|
}
|
|
return match;
|
|
}
|
|
|
|
function loadLocalModule(filename) {
|
|
const normalized = path.normalize(filename);
|
|
if (moduleCache.has(normalized)) return moduleCache.get(normalized).exports;
|
|
|
|
const source = fs.readFileSync(normalized, "utf8");
|
|
const transpiled = ts.transpileModule(source, {
|
|
compilerOptions: {
|
|
esModuleInterop: true,
|
|
jsx: ts.JsxEmit.ReactJSX,
|
|
module: ts.ModuleKind.CommonJS,
|
|
target: ts.ScriptTarget.ES2022,
|
|
},
|
|
fileName: normalized,
|
|
}).outputText;
|
|
|
|
const module = { exports: {} };
|
|
moduleCache.set(normalized, module);
|
|
|
|
const localRequire = (specifier) => {
|
|
const resolved = resolveModule(specifier, normalized);
|
|
if (typeof resolved === "string" && path.isAbsolute(resolved)) {
|
|
return loadLocalModule(resolved);
|
|
}
|
|
return nodeRequire(specifier);
|
|
};
|
|
|
|
const wrapper = `(function (exports, require, module, __filename, __dirname) {\n${transpiled}\n})`;
|
|
const compiled = vm.runInThisContext(wrapper, { filename: normalized });
|
|
compiled(module.exports, localRequire, module, normalized, path.dirname(normalized));
|
|
return module.exports;
|
|
}
|
|
|
|
function collectTests(dir) {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
return entries.flatMap((entry) => {
|
|
const fullPath = path.join(dir, entry.name);
|
|
if (entry.isDirectory()) return collectTests(fullPath);
|
|
return /\.test\.tsx?$/.test(entry.name) ? [fullPath] : [];
|
|
});
|
|
}
|
|
|
|
const testsRoot = path.join(projectRoot, "components");
|
|
const testFiles = fs.existsSync(testsRoot) ? collectTests(testsRoot).sort() : [];
|
|
|
|
if (!testFiles.length) {
|
|
throw new Error(`No business state tests found under ${testsRoot}`);
|
|
}
|
|
|
|
let passed = 0;
|
|
for (const file of testFiles) {
|
|
const exported = loadLocalModule(toWindowsSafePath(file));
|
|
const run = exported.runTests || exported.default;
|
|
if (typeof run !== "function") {
|
|
throw new Error(`${file} must export runTests()`);
|
|
}
|
|
await run();
|
|
passed += 1;
|
|
console.log(`✓ ${path.relative(projectRoot, file)}`);
|
|
}
|
|
|
|
console.log(`Business state tests passed: ${passed}/${testFiles.length}`);
|