UNPKG

micro-test-runner

Version:
155 lines (154 loc) 6.89 kB
class MicroTestRunner { constructor(candidate) { this.log = { name: undefined, icons: ['✓', '✕'], severity: 'log' }; this.args = []; this.conditions = []; this.runs = 1; this.currentRun = 0; this.performance = { format: 'none', measurements: [] }; this.passing = []; this.result = { passed: false }; this.candidate = candidate; } get logMessage() { let performanceMessage = ''; let performanceTable = ''; if (this.result.passed && this.performance.format !== 'none' && this.performance.measurements.length > 0) { if (this.performance.format === 'table') { performanceTable = '\n ╭───────┬───────┬───────────────╮\n │ Test │ Run │ Duration (ms) │'; } const startTimestamp = this.performance.measurements[0][0].start; const endTimestamp = this.performance.measurements[this.performance.measurements.length - 1][this.performance.measurements[this.performance.measurements.length - 1].length - 1].end; const testDuration = Number(endTimestamp - startTimestamp); let averageRunDuration = 0; let totalRuns = 0; this.performance.measurements.forEach((test, testIndex) => { if (this.performance.format === 'table') { performanceTable += '\n ├───────┼───────┼───────────────┤'; } test.forEach((run, runIndex) => { const runDuration = run.end - run.start; averageRunDuration += runDuration; if (this.performance.format === 'table') { performanceTable += `\n │ ${runIndex === 0 ? (testIndex + 1).toString().padEnd(5, ' ') : ' '} │ ${(runIndex + 1).toString().padEnd(5, ' ')} │ ${runDuration.toFixed(3).padStart(13, ' ')} │`; } totalRuns++; }); }); if (this.performance.format === 'table') { performanceTable += '\n ╰───────┴───────┴───────────────╯'; } averageRunDuration = Number(averageRunDuration / totalRuns); performanceMessage = ` in ${testDuration.toFixed(3)}ms${totalRuns > 1 ? ` (x̄ ${averageRunDuration.toFixed(3)}ms per run, over ${totalRuns} runs)` : ''}`; } const wrap = typeof this.result.expected === 'string' && /[\r\n]/.test(this.result.expected); const part1 = `${this.result.passed ? this.log.icons[0] : this.log.icons[1]} ${this.log.name} test ${this.result.passed ? 'passed' : 'failed'}`; const part2 = `${performanceMessage}`; const part3 = `${this.result.passed && this.performance.format === 'table' ? ':' : '.'}${performanceTable}`; const part4 = !this.result.passed && 'expected' in this.result ? `\nExpected:${wrap ? '\n' : ' '}${this.result.expected}` : ''; const part5 = !this.result.passed && 'received' in this.result ? `\nReceived:${wrap ? '\n' : ' '}${this.result.received}` : ''; return part1 + part2 + part3 + part4 + part5; } logResult() { if (!this.result.passed) { if (this.log.severity === 'error') { throw new Error(this.logMessage); } else if (this.log.severity === 'warn') { console.warn(this.logMessage); return; } } console.info(this.logMessage); } static test(candidate) { return new MicroTestRunner(candidate); } logging(name, severity = 'log', icons, performance = 'none') { this.log.name = name; this.log.severity = severity; if (Array.isArray(icons) && icons.length === 2) { this.log.icons = icons; } if (globalThis.performance) { this.performance.format = performance; } if (typeof performance === 'string') { this.performance.format = performance; } return this; } context(context) { this.candidateContext = context; return this; } times(number) { this.runs = Math.max(Math.ceil(number), 1); return this; } with(...args) { this.args.push(args); return this; } async expect(...conditions) { this.conditions = conditions; if (!this.args.length) { this.args.push([]); } let halt = false; for (const [index, argumentGroup] of this.args.entries()) { this.currentRun = 0; this.performance.measurements.push([]); while (this.currentRun < this.runs) { try { let runDuration = undefined; if (this.performance.format !== 'none') { this.performance.measurements[index].push({ start: performance.now(), end: 0 }); } const runResult = await Promise.resolve(this.candidate.apply(this.candidateContext, argumentGroup)); if (this.performance.format !== 'none') { this.performance.measurements[index][this.currentRun].end = performance.now(); runDuration = this.performance.measurements[index][this.currentRun].end - this.performance.measurements[index][this.currentRun].start; } const condition = this.conditions[Math.min(index, this.conditions.length - 1)]; const pass = typeof condition === 'function' ? condition(runResult, this.currentRun, runDuration) : runResult === condition; this.passing.push(pass); if (!pass) { halt = true; this.result.received = runResult; if (typeof condition !== 'function') { this.result.expected = condition; } } } catch (error) { console.warn('MicroTestRunner: Run failed with error:\n', error); } this.currentRun++; if (halt) break; } if (halt) break; } this.result.passed = !this.passing.includes(false); if (this.log.name) { this.logResult(); } return this.result.passed; } } export default MicroTestRunner.test;