import fs from "fs/promises";
import * as glob from "glob";
import path from "path";
import { FILTER_PATH, FIXTURES_PATH, SNAPSHOT_EXTENSION } from "./constants";
const INPUT_EXTENSIONS = [
".js",
".cjs",
".mjs",
".ts",
".cts",
".mts",
".jsx",
".tsx",
];
export type TestFilter = {
debug: boolean;
paths: Array<string>;
};
async function exists(file: string): Promise<boolean> {
try {
await fs.access(file);
return true;
} catch {
return false;
}
}
function stripExtension(filename: string, extensions: Array<string>): string {
for (const ext of extensions) {
if (filename.endsWith(ext)) {
return filename.slice(0, -ext.length);
}
}
return filename;
}
export async function readTestFilter(): Promise<TestFilter | null> {
if (!(await exists(FILTER_PATH))) {
throw new Error(`testfilter file not found at \`${FILTER_PATH}\``);
}
const input = await fs.readFile(FILTER_PATH, "utf8");
const lines = input.trim().split("\n");
let debug: boolean = false;
const line0 = lines[0];
if (line0 != null) {
let consumedLine0 = false;
if (line0.indexOf("@only") !== -1) {
consumedLine0 = true;
}
if (line0.indexOf("@debug") !== -1) {
debug = true;
consumedLine0 = true;
}
if (consumedLine0) {
lines.shift();
}
}
return {
debug,
paths: lines.filter((line) => !line.trimStart().startsWith("//")),
};
}
export function getBasename(fixture: TestFixture): string {
return stripExtension(path.basename(fixture.inputPath), INPUT_EXTENSIONS);
}
export function isExpectError(fixture: TestFixture | string): boolean {
const basename = typeof fixture === "string" ? fixture : getBasename(fixture);
return basename.startsWith("error.") || basename.startsWith("todo.error");
}
export type TestFixture =
| {
fixturePath: string;
input: string | null;
inputPath: string;
snapshot: string | null;
snapshotPath: string;
}
| {
fixturePath: string;
input: null;
inputPath: string;
snapshot: string;
snapshotPath: string;
};
async function readInputFixtures(
rootDir: string,
filter: TestFilter | null
): Promise<Map<string, { value: string; filepath: string }>> {
let inputFiles: Array<string>;
if (filter == null) {
inputFiles = glob.sync(`**/*{${INPUT_EXTENSIONS.join(",")}}`, {
cwd: rootDir,
});
} else {
inputFiles = (
await Promise.all(
filter.paths.map((pattern) =>
glob.glob(`${pattern}{${INPUT_EXTENSIONS.join(",")}}`, {
cwd: rootDir,
})
)
)
).flat();
}
const inputs: Array<Promise<[string, { value: string; filepath: string }]>> =
[];
for (const filePath of inputFiles) {
const partialPath = stripExtension(filePath, INPUT_EXTENSIONS);
inputs.push(
fs.readFile(path.join(rootDir, filePath), "utf8").then((input) => {
return [
partialPath,
{
value: input,
filepath: filePath,
},
];
})
);
}
return new Map(await Promise.all(inputs));
}
async function readOutputFixtures(
rootDir: string,
filter: TestFilter | null
): Promise<Map<string, string>> {
let outputFiles: Array<string>;
if (filter == null) {
outputFiles = glob.sync(`**/*${SNAPSHOT_EXTENSION}`, {
cwd: rootDir,
});
} else {
outputFiles = (
await Promise.all(
filter.paths.map((pattern) =>
glob.glob(`${pattern}${SNAPSHOT_EXTENSION}`, {
cwd: rootDir,
})
)
)
).flat();
}
const outputs: Array<Promise<[string, string]>> = [];
for (const filePath of outputFiles) {
const partialPath = stripExtension(filePath, [SNAPSHOT_EXTENSION]);
const outputPath = path.join(rootDir, filePath);
const output: Promise<[string, string]> = fs
.readFile(outputPath, "utf8")
.then((output) => {
return [partialPath, output];
});
outputs.push(output);
}
return new Map(await Promise.all(outputs));
}
export async function getFixtures(
filter: TestFilter | null
): Promise<Map<string, TestFixture>> {
const inputs = await readInputFixtures(FIXTURES_PATH, filter);
const outputs = await readOutputFixtures(FIXTURES_PATH, filter);
const fixtures: Map<string, TestFixture> = new Map();
for (const [partialPath, { value, filepath }] of inputs) {
const output = outputs.get(partialPath) ?? null;
fixtures.set(partialPath, {
fixturePath: partialPath,
input: value,
inputPath: filepath,
snapshot: output,
snapshotPath: path.join(FIXTURES_PATH, partialPath) + SNAPSHOT_EXTENSION,
});
}
for (const [partialPath, output] of outputs) {
if (!fixtures.has(partialPath)) {
fixtures.set(partialPath, {
fixturePath: partialPath,
input: null,
inputPath: "none",
snapshot: output,
snapshotPath:
path.join(FIXTURES_PATH, partialPath) + SNAPSHOT_EXTENSION,
});
}
}
return fixtures;
}