192 lines
4.4 KiB
JavaScript
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)
|
|
}
|