add a default terminal test runner
This commit is contained in:
parent
b130e6fd18
commit
be1930779e
3 changed files with 172 additions and 0 deletions
55
src/ansi.js
Normal file
55
src/ansi.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { mapObj, pipe } from './fn.js'
|
||||||
|
|
||||||
|
export const Reset = Object.freeze({
|
||||||
|
All: 0,
|
||||||
|
Intensity: 22,
|
||||||
|
Italic: 23,
|
||||||
|
Underline: 24,
|
||||||
|
Blink: 25,
|
||||||
|
Reverse: 27,
|
||||||
|
Conceal: 28,
|
||||||
|
Strike: 29
|
||||||
|
})
|
||||||
|
|
||||||
|
export const Style = Object.freeze({
|
||||||
|
Bold: 1,
|
||||||
|
Faint: 2,
|
||||||
|
Italic: 3,
|
||||||
|
Underline: 4,
|
||||||
|
SlowBlink: 5,
|
||||||
|
RapidBlink: 6,
|
||||||
|
ReverseVideo: 7,
|
||||||
|
Conceal: 8,
|
||||||
|
Strike: 9
|
||||||
|
})
|
||||||
|
|
||||||
|
const ForegroundColor = Object.freeze({
|
||||||
|
Black: 30,
|
||||||
|
Red: 31,
|
||||||
|
Green: 32,
|
||||||
|
Yellow: 33,
|
||||||
|
Blue: 34,
|
||||||
|
Magenta: 35,
|
||||||
|
Cyan: 36,
|
||||||
|
White: 37,
|
||||||
|
Default: 39
|
||||||
|
})
|
||||||
|
|
||||||
|
const BackgroundColor = mapObj(
|
||||||
|
([k, v]) => [k, v + 10]
|
||||||
|
)(ForegroundColor)
|
||||||
|
|
||||||
|
const sgr = args => `\x1b[${args.join(';')}m`
|
||||||
|
|
||||||
|
export const Color = Object.freeze({
|
||||||
|
Foreground: ForegroundColor,
|
||||||
|
Background: BackgroundColor,
|
||||||
|
|
||||||
|
get fg() { return this.Foreground },
|
||||||
|
get bg() { return this.Background }
|
||||||
|
})
|
||||||
|
|
||||||
|
export const prepend = args => s => `${sgr(args)}${s}`
|
||||||
|
export const append = args => s => `${s}${sgr(args)}`
|
||||||
|
export const wrap = (start, end) => pipe(prepend(start), append(end))
|
||||||
|
|
22
src/fn.js
Normal file
22
src/fn.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
|
||||||
|
export const exec = f => f()
|
||||||
|
export const pipe = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
|
||||||
|
export const map = f => xs => xs.map(f)
|
||||||
|
export const mapObj = f => pipe(Object.entries, map(f), Object.fromEntries)
|
||||||
|
|
||||||
|
export const partition = (predicate, xs) => {
|
||||||
|
const passed = []
|
||||||
|
const failed = []
|
||||||
|
|
||||||
|
const len = xs.length
|
||||||
|
for (let i = 0; i < len; i++) {
|
||||||
|
const value = xs[i]
|
||||||
|
if (predicate(value)) {
|
||||||
|
passed.push(value)
|
||||||
|
} else {
|
||||||
|
failed.push(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [passed, failed]
|
||||||
|
}
|
95
src/runner.js
Normal file
95
src/runner.js
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
import { Color, Style, Reset, wrap } from './ansi.js'
|
||||||
|
import { exec, partition } from './fn.js'
|
||||||
|
|
||||||
|
const { Yellow, Green, Red } = Color.Foreground
|
||||||
|
const { Bold } = Style
|
||||||
|
|
||||||
|
const reset = [Reset.All]
|
||||||
|
const style = (s, ...args) => wrap(args, reset)(s)
|
||||||
|
const fail = wrap([Red, Bold], reset)
|
||||||
|
|
||||||
|
const process = ([name, tests]) => {
|
||||||
|
const results = tests.map(exec)
|
||||||
|
|
||||||
|
const [passed, failed] = partition(test => test.success, results)
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
passed,
|
||||||
|
failed,
|
||||||
|
results,
|
||||||
|
toString: () => {
|
||||||
|
const len = results.length
|
||||||
|
const passLen = passed.length
|
||||||
|
const failLen = failed.length
|
||||||
|
|
||||||
|
let color = Yellow
|
||||||
|
|
||||||
|
if (passLen === len) {
|
||||||
|
color = Green
|
||||||
|
} else if (failLen === len) {
|
||||||
|
color = Red
|
||||||
|
}
|
||||||
|
const header = `${style(name, Bold, color)} (${passLen}/${len})`
|
||||||
|
|
||||||
|
const text = results.map(result => {
|
||||||
|
const status = result.success ? 'pass' : fail('fail')
|
||||||
|
return ` ${result.description}... ${status}`
|
||||||
|
}).join('\n')
|
||||||
|
|
||||||
|
return `${header}\n${text}\n`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeSummary = results => {
|
||||||
|
const [passed, failed] = results.reduce(
|
||||||
|
([pass, fail], result) => ([
|
||||||
|
pass + result.passed.length,
|
||||||
|
fail + result.failed.length,
|
||||||
|
]),
|
||||||
|
[0, 0]
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
passed,
|
||||||
|
failed,
|
||||||
|
toString() {
|
||||||
|
const failures = results
|
||||||
|
.filter(result => result.failed.length)
|
||||||
|
.flatMap(result => {
|
||||||
|
return result.failed.map(({ description, error }) => {
|
||||||
|
return ` ${result.name}::${description}\n${error.stack}`
|
||||||
|
}).join('\n\n')
|
||||||
|
}).join('\n\n')
|
||||||
|
|
||||||
|
const summary = `test result: ${failed}. ${passed} passed; ${failed} failed`
|
||||||
|
|
||||||
|
return `failures:\n${failures}\n\n${summary}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export const term = tests => {
|
||||||
|
const suites = Object.entries(tests).map(process)
|
||||||
|
suites.forEach(s => console.log(s.toString()))
|
||||||
|
console.log(makeSummary(suites).toString())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mock = (success, description, error) => () => ({ success, description, error })
|
||||||
|
|
||||||
|
term({
|
||||||
|
SuccessTest: [
|
||||||
|
mock(true, 'Unit 1'),
|
||||||
|
mock(true, 'Unit 2')
|
||||||
|
],
|
||||||
|
MixedTest: [
|
||||||
|
mock(false, 'Unit 3', new Error('bb')),
|
||||||
|
mock(true, 'Unit 4'),
|
||||||
|
mock(true, 'Unit 5')
|
||||||
|
],
|
||||||
|
FailTest: [
|
||||||
|
mock(false, 'Unit 6', new Error('aa')),
|
||||||
|
mock(false, 'Unit 7', new Error('cc')),
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
Loading…
Add table
Reference in a new issue