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;
}