Compare commits

...

9 Commits

Author SHA1 Message Date
Frédéric Matte ee6ef75765 Reverse test name and the line 2025-06-02 18:59:06 -04:00
Frédéric Matte d9786a78d0 Show totalError 2025-06-02 18:58:27 -04:00
Frédéric Matte 1b53b9854b Show at for all test 2025-06-02 18:56:57 -04:00
Frédéric Matte b9a01eed3f Extract showError 2025-06-02 18:55:46 -04:00
Frédéric Matte cdd8e8c8aa Use folder provided 2025-06-02 18:50:01 -04:00
Frédéric Matte ccd64a4367 Extract walkDir 2025-06-02 18:50:01 -04:00
Frédéric Matte 46282ac834 Await notify 2025-06-02 16:04:54 -04:00
Frédéric Matte 4fa6686a74 Add bin
To do npx metaltest in another projet to run all test
2025-06-02 16:03:58 -04:00
Frédéric Matte 1df35e971a Add mocking module 2025-06-02 15:51:28 -04:00
13 changed files with 155 additions and 70 deletions

View File

@ -2,8 +2,9 @@
import { suite } from 'metaltest'
const run = async () => {
await suite(process.cwd())
const run = async (folder) => {
if (!folder) folder = process.cwd()
await suite(folder)
}
const [, , folder] = process.argv

View File

@ -1,4 +1,5 @@
export * from './reporter/index.js'
export * from './lib/mock.js'
export { suite } from './lib/suite.js'
import { metaltest } from './lib/metaltest.js'

View File

@ -40,11 +40,11 @@ const metaltest = (title) => {
}
const runner = (name, fn) => {
suite.push({ name, fn })
suite.push({ name, fn, at: getAt() })
}
runner.only = (name, fn) => { only.push({ name, fn }) }
runner.skip = (name, fn) => { skipped.push({ name, fn }) }
runner.only = (name, fn) => { only.push({ name, fn, at: getAt() }) }
runner.skip = (name, fn) => { skipped.push({ name, fn, at: getAt() }) }
runner.before = (fn) => { before.push(fn) }
runner.after = (fn) => { after.push(fn) }
runner.end = (name, fn) => { end.push({ name, fn }) }
@ -74,7 +74,7 @@ const metaltest = (title) => {
for (const fn of before) await fn()
await test.fn(test)
stats('success', test)
notify(reporters, 'success', test)
await notify(reporters, 'success', test)
}
catch (error) {
stats('fail', test, error)

56
lib/mock.js Normal file
View File

@ -0,0 +1,56 @@
const capture = (actions, name, fn) => async (...args) => {
const count = actions.reduce((count, curr) => {
if (curr.name === name) return count + 1
return count
}, 1)
try {
const result = await fn(...args, { count })
actions.push({ name, args, result })
return result
} catch (error) {
actions.push({ name, args, result: error })
throw error
}
}
const mock = async (mocks, fn) => {
const olds = {}
const actions = []
for (const mock of mocks) {
if (mock.package in olds === false) olds[mock.package] = {}
const old = olds[mock.package]
for (const name in mock.funcs) {
//Save
old[name] = mock.package[name]
//Capture
mock.package[name] = capture(actions, name, mock.funcs[name])
}
}
//Restore even when an error occurs
//Like with a failed assert
try {
await fn()
} catch (error) {
throw error
} finally {
for (const mock of mocks) {
const old = olds[mock.package]
for (const name in mock.funcs) {
//Restore
mock.package[name] = old[name]
}
}
}
return actions
}
export { mock }

View File

@ -1,9 +1,13 @@
const regAssertParser = /(?<title>.*?)\n+(?<diff>.*?)\n+(?<stacktrace> (?<where>at.*?file:\/\/(?<file>.*?):(?<line>\d+):(?<column>\d+)\)?)\n.*)/s
const regStack = /(?<=^ )at.*/gm
const stackParser = (stack) => {
const m = stack.match(regAssertParser)
return m.groups
const stacks = stack.match(regStack)
return { ...m.groups, stacks }
}
export { stackParser }
export { stackParser }

View File

@ -1,47 +1,17 @@
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
const walkDir = async function* (dir = '.', exclude = []) {
const files = await readdir(dir, { withFileTypes: true })
import path from 'node:path'
import { walkDir } from './walkDir.js'
import { summaryreporter, totalreporter, totalerrorreporter } from 'metaltest'
for (const file of files) {
if (exclude.includes(file.name)) continue
if (file.isDirectory()) {
yield* walkDir(join(dir, file.name))
} else if (file.isSymbolicLink()) {
yield* walkDir(join(dir, file.name))
} else {
yield join(dir, file.name)
}
}
}
import { fileURLToPath } from 'node:url'
import { dirname } from 'node:path'
const getStackAbsolutePaths = () => {
const old = Error.prepareStackTrace
Error.prepareStackTrace = (_, stack) => stack
const stack = new Error().stack
Error.prepareStackTrace = old
return stack
.map(cs => cs.getFileName())
.filter(filename => filename?.startsWith('file://'))
.map(filename => dirname(fileURLToPath(filename)))
}
import { summaryreporter, errorreporter, totalreporter } from 'metaltest'
const suite = async (folder = '.') => {
const absolutePaths = getStackAbsolutePaths()
const absolutePath = absolutePaths[2]
const fileIgnored = []
const total = totalreporter()
const error = totalerrorreporter()
for await (const file of walkDir(folder, ['node_modules', '.git'])) {
if (!file.endsWith('.test.js')) continue
const module = join(absolutePath, file)
const module = path.resolve(folder, file)
const { test } = await import(module)
if (test === undefined) {
@ -49,13 +19,14 @@ const suite = async (folder = '.') => {
continue
}
await test.run(summaryreporter(), errorreporter(), total)
await test.run(summaryreporter(), error, total)
}
for (const file of fileIgnored) {
console.log(`The file ${file} doesn't export the metaltest object`)
}
error.msg()
console.log(total.msg())
}

24
lib/walkDir.js Normal file
View File

@ -0,0 +1,24 @@
import { readdir, stat } from 'fs/promises'
import { join } from 'path/posix'
const walkDir = async function* (dir = '.', exclude = []) {
const files = await readdir(dir)
for await (const file of files) {
if (exclude.includes(file)) continue
try {
const stats = await stat(join(dir, file))
if (stats.isDirectory()) {
yield* walkDir(join(dir, file))
} else {
yield join(dir, file)
}
}
catch (err) { } //Like file not found when symlink to a file that do not exist
}
}
export { walkDir }

View File

@ -4,6 +4,7 @@
"description": "Clone of baretest with reporting",
"type": "module",
"exports": "./index.js",
"bin": "./cli/suite.js",
"scripts": {
"test": "node suite.js"
},

View File

@ -1,37 +1,14 @@
import chalk from 'chalk'
import { log } from '../lib/log.js'
import { showError } from './showError.js'
const errorreporter = () => {
const report = {
end: (stats) => {
const { testFail } = stats
for (const test of testFail) {
log('\n' + chalk.redBright(test.name) + '\n')
prettyError(test.error)
}
showError(testFail)
}
}
return report
}
import { stackParser } from '../lib/stackparser.js'
const prettyError = (error) => {
if (typeof error == 'string') {
log(chalk.yellowBright(error))
log('\n')
return
}
const stack = stackParser(error.stack)
log(chalk.yellowBright(stack.title) + '\n')
if (stack.diff != '')
log(chalk.white(stack.diff) + '\n')
if (stack.where != '')
log(chalk.gray(stack.where) + '\n')
}
export { errorreporter }

View File

@ -2,4 +2,5 @@ export { summaryreporter } from './summaryreporter.js'
export { linereporter } from './linereporter.js'
export { totalreporter } from './totalreporter.js'
export { errorreporter } from './errorreporter.js'
export { totalerrorreporter } from './totalerrorreporter.js'
export { endreporter } from './endreporter.js'

View File

@ -15,7 +15,7 @@ const linereporter = () => {
fail: (test, e) => {
const line = e.stack.match(/at.*:(.*):/)[1]
lines.push(line)
log(chalk.redBright('✗'), line, test.name)
log(chalk.redBright('✗'), test.name, `(line ${line})`)
},
end: (stats) => {
const { success, fail, testFail } = stats

33
reporter/showError.js Normal file
View File

@ -0,0 +1,33 @@
import chalk from 'chalk'
import { log } from '../lib/log.js'
import { stackParser } from '../lib/stackparser.js'
const showError = (testsFail) => {
for (const test of testsFail) {
log('\n' + chalk.redBright(test.name) + '\n')
prettyError(test)
}
}
const prettyError = (test) => {
const { error } = test
if (typeof error == 'string') {
log(chalk.yellowBright(error))
log('\n')
return
}
const stack = stackParser(error.stack)
log(chalk.yellowBright(stack.title) + '\n')
if (stack.diff != '')
log(chalk.white(stack.diff) + '\n')
// if (stack.where != '')
// log(chalk.gray(stack.where) + '\n')
log(chalk.gray(test.at.summary) + '\n')
log(chalk.gray(stack.stacks.join('\n')) + '\n')
}
export { showError }

View File

@ -0,0 +1,16 @@
import { showError } from "./showError.js"
const totalerrorreporter = () => {
const testFails = []
const report = {
end: (stats) => testFails.push(...stats.testFail),
msg: () => {
showError(testFails)
},
}
return report
}
export { totalerrorreporter }