| import * as assert from "node:assert/strict"; |
| import { readdir } from "fs/promises"; |
| import * as path from "path"; |
| import { pathToFileURL } from "url"; |
| |
| class Test { |
| readonly name: string; |
| readonly promise: Promise<void>; |
| |
| constructor(name: string, promise: Promise<void>) { |
| this.name = name; |
| this.promise = promise; |
| } |
| } |
| |
| class Suite { |
| tests: Test[]; |
| |
| constructor() { |
| this.tests = []; |
| } |
| |
| public addTest(name: string, f: () => Promise<void>): void { |
| const test = new Test(name, f()); |
| this.tests.push(test); |
| } |
| |
| public async run(): Promise<void> { |
| let failed = 0; |
| for (const test of this.tests) { |
| try { |
| await test.promise; |
| ok(` ✔ ${test.name}`); |
| } catch (e) { |
| assert.ok(e instanceof Error); |
| error(` ✖︎ ${test.name}\n ${e.stack}`); |
| failed += 1; |
| } |
| } |
| if (failed) { |
| const plural = failed > 1 ? "s" : ""; |
| throw new Error(`${failed} failed test${plural}`); |
| } |
| } |
| } |
| |
| export class Context { |
| public async suite(name: string, f: (ctx: Suite) => void): Promise<void> { |
| const ctx = new Suite(); |
| f(ctx); |
| try { |
| ok(`⌛︎ ${name}`); |
| await ctx.run(); |
| ok(`✔ ${name}`); |
| } catch (e) { |
| assert.ok(e instanceof Error); |
| error(`✖︎ ${name}\n ${e.stack}`); |
| throw e; |
| } |
| } |
| } |
| |
| export async function run(): Promise<void> { |
| const context = new Context(); |
| |
| const testFiles = (await readdir(path.resolve(__dirname))).filter((name) => |
| name.endsWith(".test.js"), |
| ); |
| for (const testFile of testFiles) { |
| try { |
| const testModule = await import(pathToFileURL(path.resolve(__dirname, testFile)).href); |
| await testModule.getTests(context); |
| } catch (e) { |
| error(`${e}`); |
| throw e; |
| } |
| } |
| } |
| |
| function ok(message: string): void { |
| // eslint-disable-next-line no-console |
| console.log(`\x1b[32m${message}\x1b[0m`); |
| } |
| |
| function error(message: string): void { |
| // eslint-disable-next-line no-console |
| console.error(`\x1b[31m${message}\x1b[0m`); |
| } |