fava
Version:
A wannabe tiny largely-drop-in replacement for ava that works in the browser too.
164 lines (163 loc) • 6.25 kB
JavaScript
/* IMPORT */
import color from 'tiny-colors';
import zeptomatch from 'zeptomatch';
import { ROOT_DESCRIBER_ID } from './constants.js';
import Describer from './describer.js';
import Env from './env.js';
import Factory from './factory.js';
import Flags from './flags.js';
import Utils from './utils.js';
/* MAIN */
class Suiter {
/* CONSTRUCTOR */
constructor(title) {
this.executed = false;
this.scheduled = false;
/* REGISTRY API */
this.registerDescriber = (describer, implementation) => {
const prev = this.current;
this.current.describers.push(describer);
this.current = describer;
this.stack.push(describer);
implementation(Factory.makeTest());
this.stack.pop();
this.current = prev;
};
this.registerTester = (tester) => {
this.current.testers.push(tester);
};
/* EXECUTION API */
this.execute = async () => {
this.executed = true;
await this.propagateFlags();
await this.propagateFlagOnly();
await this.propagateMatch();
await this.propagatePrefixes();
await this.root.execute();
await this.summary();
await this.propagateExitCode();
};
this.propagateExitCode = async () => {
if (!Env.is.cli)
return;
let isSuccess = true;
await this.root.visit({
onTester: tester => {
if (tester.passed !== 0)
return;
isSuccess = false;
}
});
const exitCode = (isSuccess ? 0 : 1);
process.exitCode = exitCode;
};
this.propagateFlags = async () => {
const propagate = (source, target) => {
var _a;
const flags = Utils.lang.keys(source.flags);
for (const flag of flags) {
(_a = target.flags)[flag] || (_a[flag] = source.flags[flag]);
}
};
const visit = (root) => {
return root.visit({
onDescriber: describer => {
propagate(root, describer);
return visit(describer);
},
onTester: tester => {
propagate(root, tester);
}
});
};
await visit(this.root);
};
this.propagateFlagOnly = async () => {
let hasOnly = false;
await this.root.visit({
onTester: tester => {
if (!tester.flags.only)
return;
hasOnly = true;
}
});
if (!hasOnly)
return;
await this.root.visit({
onTester: tester => {
if (tester.flags.only)
return;
tester.flags.skip = true;
}
});
};
this.propagateMatch = async () => {
if (!Env.options.match.length)
return;
await this.root.visit({
onTester: tester => {
if (tester.flags.skip)
return;
if (zeptomatch(Env.options.match, tester.title))
return;
tester.flags.skip = true;
}
});
};
this.propagatePrefixes = async () => {
const visit = (root, prefixes) => {
return root.visit({
recursive: false,
onDescriber: describer => {
return visit(describer, [...prefixes, describer.title]);
},
onTester: tester => {
tester.prefixes = prefixes;
}
});
};
await visit(this.root, []);
};
this.schedule = () => {
if (this.scheduled)
return;
this.scheduled = true;
setTimeout(this.execute, 0);
};
this.summary = async () => {
const assertions = { passed: 0, failed: 0, total: 0 };
const tests = { passed: 0, failed: 0, skipped: 0, total: 0 };
await this.root.visit({
onTester: tester => {
assertions.passed += tester.stats.passed;
assertions.failed += tester.stats.failed;
assertions.total += tester.stats.total;
tests.passed += (tester.passed === 1 ? 1 : 0);
tests.failed += (tester.passed === 0 ? 1 : 0);
tests.skipped += (tester.passed === -1 ? 1 : 0);
tests.total += 1;
}
});
if (Env.options.verbose || tests.failed) {
console.log();
}
if (Env.is.cli) {
const path = await import('node:path');
const filePath = process.argv[1];
const folderRe = /^(.*)([\\\/])(test|tests|__tests__)(\1)/;
const testName = folderRe.test(filePath) ? filePath.replace(folderRe, '') : path.basename(filePath);
console.log(`${color.cyan('ℹ')} File: ${testName}`);
}
if (Env.options.verbose) {
console.log(`${color.cyan('ℹ')} Asserts: ${color.green(String(assertions.passed))} passed, ${color[assertions.failed ? 'red' : 'green'](String(assertions.failed))} failed`);
}
console.log(`${color.cyan('ℹ')} Tests: ${color.green(String(tests.passed))} passed, ${color[tests.failed ? 'red' : 'green'](String(tests.failed))} failed${tests.skipped ? `, ${color.yellow(String(tests.skipped))} skipped` : ''}`);
};
this.root = new Describer(ROOT_DESCRIBER_ID, new Flags().flags);
this.current = this.root;
this.stack = [this.current];
this.title = title;
}
}
/* EXPORT */
export default Suiter;