From f953f607bb83b27b5febfa11542e5e736de66295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Matte?= Date: Tue, 8 Nov 2022 22:29:58 -0500 Subject: [PATCH] First implementation of the metaltest --- .gitignore | 1 + consolereporter.js | 59 ++++++++++++++++++++++++++++++++++++++ index.js | 70 ++++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 34 ++++++++++++++++++++++ package.json | 19 +++++++++++++ runifmain.js | 15 ++++++++++ test.js | 26 +++++++++++++++++ totalreporter.js | 17 +++++++++++ 8 files changed, 241 insertions(+) create mode 100644 .gitignore create mode 100644 consolereporter.js create mode 100644 index.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 runifmain.js create mode 100644 test.js create mode 100644 totalreporter.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/consolereporter.js b/consolereporter.js new file mode 100644 index 0000000..8d87bf5 --- /dev/null +++ b/consolereporter.js @@ -0,0 +1,59 @@ +import chalk from 'chalk' +const log = (...args) => process.stdout.write(...args) + +const consolereporter = () => { + + const report = { + start: (title) => { + log(chalk.cyanBright(title) + ' ') + }, + success: (test) => { + log(chalk.greenBright('✓ ')) + }, + fail: (test, e) => { + log(chalk.redBright('✗ ')) + }, + end: (stats) => { + const { success, fail, total, testFail } = stats + + log(' ' + chalk.redBright(fail)) + log(' ' + chalk.greenBright(success)) + log(' ' + chalk.gray('total:', total) + '\n') + + for (const test of testFail) { + log('\n' + chalk.redBright(test.name) + '\n') + prettyError(test.error) + log('\n') + } + } + } + + return report +} + +const prettyError = (error) => { + if (typeof error == 'string') { + log(chalk.yellowBright(error)) + log('\n') + return + } + + const msg = error.stack + const i = msg.indexOf('\n') + + const title = msg.slice(0, i) + log(chalk.yellowBright(title) + '\n') + + const at = msg.indexOf(' at ') + const diff = msg.slice(i + 1, at - 1) + if (diff != '\n ') + log(chalk.white(diff)) + + const atEnd = msg.slice(at).indexOf('\n') + const where = msg.slice(at + 3, at + atEnd) + + if (where != '') + log(chalk.gray(where) + '\n') +} + +export { consolereporter } \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..37cd892 --- /dev/null +++ b/index.js @@ -0,0 +1,70 @@ + +import { consolereporter } from './consolereporter.js' +import { totalreporter } from './totalreporter.js' +import { runifmain } from './runifmain.js' + +const noreporter = { + start: (title) => { }, + before: () => { }, + success: (test) => { }, + fail: (test, e) => { }, + after: () => { }, + end: (stats) => { }, +} + +const metaltest = (title) => { + const suite = [] + const only = [] + const before = [] + const after = [] + + let success = 0 + let fail = 0 + const testSuccess = [] + const testFail = [] + + const runner = (name, fn) => { + suite.push({ name, fn }) + } + + runner.only = (fn) => { only.push(fn) } + runner.before = (fn) => { before.push(fn) } + runner.after = (fn) => { after.push(fn) } + + runner.run = async (...reporters) => { + const rs = reporters.map(r => Object.assign({}, noreporter, r)) + + rs.forEach(r => r.start(title)) + rs.forEach(r => r.before()) + + const tests = only.length ? only : suite + for (const test of tests) { + try { + for (const fn of before) await fn() + await test.fn() + success++ + rs.forEach(r => r.success(test)) + } + catch (e) { + fail++ + testFail.push(Object.assign({}, test, { error: e })) + rs.forEach(r => r.fail(test, e)) + } + } + + for (const fn of after) await fn() + + rs.forEach(r => r.after()) + + const stats = { title, success, fail, total: success + fail, testSuccess, testFail } + + rs.forEach(r => r.end(stats)) + + return stats + } + + return runner +} + +export { metaltest, consolereporter, totalreporter, runifmain } +export default metaltest \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..f44a8fd --- /dev/null +++ b/package-lock.json @@ -0,0 +1,34 @@ +{ + "name": "metaltest", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "metaltest", + "version": "1.0.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "chalk": "^5.1.2" + } + }, + "node_modules/chalk": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", + "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + } + }, + "dependencies": { + "chalk": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.1.2.tgz", + "integrity": "sha512-E5CkT4jWURs1Vy5qGJye+XwCkNj7Od3Af7CP6SujMetSMkLs8Do2RWJK5yx1wamHV/op8Rz+9rltjaTQWDnEFQ==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..ce4efb9 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "metaltest", + "version": "1.0.0", + "description": "Clone of baretest with reporting", + "type": "module", + "exports": "./index.js", + "scripts": { + "test": "node test.js" + }, + "repository": { + "type": "git", + "url": "https://git.tranche.ca/fmatte/metaltest" + }, + "author": "Frédéric Matte ", + "license": "AGPL-3.0-or-later", + "dependencies": { + "chalk": "^5.1.2" + } +} diff --git a/runifmain.js b/runifmain.js new file mode 100644 index 0000000..d4693ef --- /dev/null +++ b/runifmain.js @@ -0,0 +1,15 @@ +import { fileURLToPath } from 'node:url' + +const isMain = (meta) => { + const path = fileURLToPath(meta.url) + + return path.includes(process.argv[1]) +} + +const runifmain = async (meta, fn) => { + if (isMain(meta)) { + await fn() + } +} + +export { runifmain } \ No newline at end of file diff --git a/test.js b/test.js new file mode 100644 index 0000000..55c9c8e --- /dev/null +++ b/test.js @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict' +import { metaltest, consolereporter } from './index.js' + +const test = metaltest('Metaltest') + +let count = 0 +test.before(() => count++) + +test('success', () => new Promise((s, f) => setTimeout(s, 500))) +test('not equal', () => assert.notEqual({ a: 1 }, { a: 2 })) +test('fail with the message', () => new Promise((s, f) => setTimeout(s, 500))) +test('throw', () => { + return assert.rejects(async () => { + throw new Error('message') + }, { name: 'Error', message: 'message' }) +}) + +export { test } + +import { runifmain } from './runifmain.js' +await runifmain(import.meta, async () => { + const stats = await test.run(consolereporter()) + assert.equal(count, 4) + assert.equal(stats.success, 4) + assert.equal(stats.fail, 0) +}) diff --git a/totalreporter.js b/totalreporter.js new file mode 100644 index 0000000..86762b6 --- /dev/null +++ b/totalreporter.js @@ -0,0 +1,17 @@ +import chalk from 'chalk' + +const totalreporter = () => { + let totalSuccess = 0, totalFail = 0 + + return { + success: (test) => { totalSuccess++ }, + fail: (test, e) => { totalFail++ }, + msg: () => ` +${chalk.redBright(`Total fail: ${totalFail}`)} +${chalk.greenBright(`Total sucess: ${totalSuccess}`)} +${chalk.yellowBright(`Ratio ${(totalSuccess / (totalSuccess + totalFail)) * 100}%`)} +` + } +} + +export { totalreporter } \ No newline at end of file