Files
kashilo/tests/test-runner.js

192 lines
4.4 KiB
JavaScript

/**
* Minimal Test Runner for Browser
* Run tests by opening tests/index.html in browser
*/
const results = {
passed: 0,
failed: 0,
tests: []
}
/**
* Define a test
* @param {string} name - Test name
* @param {Function} fn - Test function
*/
export function test(name, fn) {
try {
fn()
results.passed++
results.tests.push({ name, status: 'passed' })
console.log(`${name}`)
} catch (error) {
results.failed++
results.tests.push({ name, status: 'failed', error: error.message })
console.error(`${name}`)
console.error(` ${error.message}`)
}
}
/**
* Group tests
* @param {string} name - Group name
* @param {Function} fn - Function containing tests
*/
export function describe(name, fn) {
console.group(name)
fn()
console.groupEnd()
}
/**
* Assert equality
* @param {*} actual
* @param {*} expected
* @param {string} [message]
*/
export function assertEquals(actual, expected, message = '') {
if (actual !== expected) {
throw new Error(
message || `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`
)
}
}
/**
* Assert truthy
* @param {*} value
* @param {string} [message]
*/
export function assertTrue(value, message = '') {
if (!value) {
throw new Error(message || `Expected truthy value, got ${value}`)
}
}
/**
* Assert falsy
* @param {*} value
* @param {string} [message]
*/
export function assertFalse(value, message = '') {
if (value) {
throw new Error(message || `Expected falsy value, got ${value}`)
}
}
/**
* Assert throws
* @param {Function} fn
* @param {string} [message]
*/
export function assertThrows(fn, message = '') {
try {
fn()
throw new Error(message || 'Expected function to throw')
} catch (e) {
if (e.message === (message || 'Expected function to throw')) {
throw e
}
}
}
/**
* Assert deep equality for objects/arrays
* @param {*} actual
* @param {*} expected
* @param {string} [message]
*/
export function assertDeepEquals(actual, expected, message = '') {
const actualStr = JSON.stringify(actual)
const expectedStr = JSON.stringify(expected)
if (actualStr !== expectedStr) {
throw new Error(
message || `Expected ${expectedStr}, got ${actualStr}`
)
}
}
/**
* Assert inequality
* @param {*} actual
* @param {*} expected
* @param {string} [message]
*/
export function assertNotEquals(actual, expected, message = '') {
if (actual === expected) {
throw new Error(
message || `Expected values to differ, but both are ${JSON.stringify(actual)}`
)
}
}
const asyncQueue = []
/**
* Define an async test
* @param {string} name - Test name
* @param {Function} fn - Async test function
*/
export function asyncTest(name, fn) {
asyncQueue.push({ name, fn })
}
/**
* Run all queued async tests sequentially
* @returns {Promise<void>}
*/
export async function runAsyncTests() {
for (const { name, fn } of asyncQueue) {
try {
await fn()
results.passed++
results.tests.push({ name, status: 'passed' })
console.log(`${name}`)
} catch (error) {
results.failed++
results.tests.push({ name, status: 'failed', error: error.message })
console.error(`${name}`)
console.error(` ${error.message}`)
}
}
asyncQueue.length = 0
}
/**
* Get test results
* @returns {Object}
*/
export function getResults() {
return { ...results }
}
/**
* Render results to DOM
* @param {HTMLElement} container
*/
export function renderResults(container) {
const summary = document.createElement('div')
summary.className = 'test-summary'
summary.innerHTML = `
<h2>Test Results</h2>
<p class="passed">Passed: ${results.passed}</p>
<p class="failed">Failed: ${results.failed}</p>
`
const list = document.createElement('ul')
list.className = 'test-list'
results.tests.forEach(t => {
const li = document.createElement('li')
li.className = t.status
li.innerHTML = t.status === 'passed'
? `${t.name}`
: `${t.name}<br><small>${t.error}</small>`
list.appendChild(li)
})
container.appendChild(summary)
container.appendChild(list)
}