Last active
April 10, 2022 15:23
-
-
Save mattmccray/b6fdab28cd26ea35aea079058435de75 to your computer and use it in GitHub Desktop.
🧪 Tiny Browser Unit Testing (<1kb gzipped)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type LogEntry = { | |
messages: any[], | |
level: 'pass' | 'fail' | 'error' | |
} | |
class Logger { | |
messages: LogEntry[] = [] | |
get total() { | |
return this.messages.length | |
} | |
get passCount() { | |
return this.messages.filter(msg => msg.level === 'pass').length | |
} | |
get failCount() { | |
return this.messages.filter(msg => msg.level === 'fail').length | |
} | |
get errorCount() { | |
return this.messages.filter(msg => msg.level === 'error').length | |
} | |
constructor(public name: string) { } | |
pass(...messages: any[]) { | |
this.messages.push({ messages, level: 'pass' }) | |
} | |
fail(...messages: any[]) { | |
this.messages.push({ messages, level: 'fail' }) | |
} | |
error(...messages: any[]) { | |
this.messages.push({ messages, level: 'error' }) | |
} | |
} | |
class Assertions { | |
constructor(protected log: Logger) { } | |
assert = (description: string, assertion: boolean) => { | |
if (assertion) this.log.pass(description) | |
if (!assertion) this.log.fail(description) | |
} | |
equal = (description: string, actual: any, expected: any, strictMatch = false) => { | |
if (strictMatch) { | |
if (actual !== expected) return this.log.fail(description, "(strict equality failure)", { expected, actual }) | |
} | |
else { | |
if (actual != expected) return this.log.fail(description, "(equality failure)", { expected, actual }) | |
} | |
this.log.pass(description) | |
} | |
notEqual = (description: string, actual: any, expected: any, strictMatch = false) => { | |
if (strictMatch) { | |
if (actual === expected) return this.log.fail(description, "(strict inequality failure)", { expected, actual }) | |
} | |
else { | |
if (actual == expected) return this.log.fail(description, "(inequality failure)", { expected, actual }) | |
} | |
this.log.pass(description) | |
} | |
} | |
type Runner = (utils: Assertions, log?: Logger) => void | |
type TestCase = Record<string, Runner> | |
export function test(testCases: TestCase) { | |
return Object.keys(testCases).map(name => { | |
const fn = testCases[name] | |
const log = new Logger(name) | |
const tools = new Assertions(log) | |
try { | |
if (!!fn) { | |
fn(tools, log) | |
} | |
else { | |
log.fail("Missing Test:", name) | |
} | |
} | |
catch (ex: any) { | |
log.error("Test Exception:", ex) | |
} | |
finally { | |
reportResults(log) | |
} | |
return log | |
}) | |
} | |
export default test | |
function reportResults(log: Logger) { | |
const summary = `🧪 ${log.name} (${log.passCount}/${log.total})` | |
if (log.passCount === log.total) { | |
console.groupCollapsed(summary) | |
} | |
else { | |
console.group(summary) | |
} | |
log.messages.forEach(({ level, messages }) => { | |
switch (level) { | |
case 'pass': | |
console.log("🟩", ...messages); | |
break; | |
case 'fail': | |
console.log("🟥", ...messages); | |
break; | |
case 'error': | |
console.log("❌", ...messages); | |
break; | |
} | |
}) | |
console.groupEnd() | |
} | |
export function selfTest() { | |
test({ | |
"Self Test": ({ assert, equal, notEqual }, log) => { | |
assert("allows simple boolean-ish assertions", true) | |
equal("allows equality checking between numbers", 10, 10) | |
equal("will coerce types by default", 10, "10") | |
equal("allows strict equality checking between numbers", 10, 10, true) | |
notEqual("will not coerce types in strict mode", 10, "10", true) | |
assert("all assertions should be tracked", log!.total > 0) | |
equal("all assertions should pass", log?.passCount, log?.total) | |
}, | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment