UNPKG

@olton/latte

Version:

Simple test framework for JavaScript and TypeScript with DOM supports

316 lines (269 loc) 11.7 kB
import { safeStringify } from '../helpers/json.js' import { Screen, term, termx, Progress } from '@olton/terminal' import { BOT } from '../config/index.js' const log = console.log const logExpect = (name, { result, message, expected, received }, duration = 0) => { log(` ${result ? term('🟢 ' + name + ` 🕑 ${term(`${duration} ms`, { color: 'whiteBright' })}`, { color: 'green' }) : term('🔴 ' + name + ' (' + message + ')', { color: 'red' })}`) if (!result) { log(` ${term('Expected:', { color: 'magentaBright' })} ${term(safeStringify(expected), { style: 'bold', color: 'magentaBright' })}`) log(` ${term('Received:', { color: 'cyanBright' })} ${term(safeStringify(received), { style: 'bold', color: 'cyanBright' })}`) } } const setupAndTeardown = async (funcs, type) => { if (funcs && funcs.length) { for (const fn of funcs) { try { await fn() } catch (error) { log(` The ${type} function throw error with message: ${term('🔴 ' + error.message, { color: 'red' })}`) } } } } export const runner = async (queue, options) => { const startTime = process.hrtime() const { verbose, test: testName, suite: suiteName, skip, parallel, idea, progress, showStack = false } = options let passedTests = 0 let failedTests = 0 let totalTests = 0 let totalTestCount = 0 let progressBar = null for (const q of queue) { for (const job of q[1].describes) { totalTestCount += job.it.length } totalTestCount += q[1].tests.length } if (progress !== 'none' && !verbose && !parallel) { // process.stdout.write(term(`\n\r `)) progressBar = new Progress({ total: totalTestCount, width: 30, mode: options.progress, completeMessage: 'Tests completed in {{elapsed}}s', completeMessageColor: 'gray', messageColor: 'whiteBright', message: '', unitName: 'test', barColor: 'blueBright', cursor: false, spaceBefore: 0, }) if (!idea) await progressBar.here() } else { log(' ') } const processTest = (file, count = 1) => { if (progress !== 'none' && progressBar) { for (let i = 0; i < count; i++) { progressBar.process(`${term('[{{percent}}%]', { color: 'yellow' })} ${file}`) } } } for (const [file, jobs] of queue) { // hooksRegistry.clearAllHooks() // const fileHash = await getFileHash(realpathSync(file)) const startFileTime = process.hrtime() let testFileStatus = true let testFilePassed = 0 let testFileFailed = 0 if (verbose) log(`📜 ${term('Test file:', { color: 'gray' })} ${term(file, { style: 'bold', color: 'yellow' })}...`) global.testResults[file] = { describes: [], tests: [], duration: 0, completed: true } if (jobs.describes.length) { if (verbose) log(` Tests Suites ${jobs.describes.length}:`) for (const describe of jobs.describes) { if (verbose) log(` ${term(describe.name, { color: 'blue' })} (${describe.it.length} tests):`) if (suiteName) { if (describe.name.includes(suiteName) === false) { processTest(file, describe.it.length) continue } } await setupAndTeardown(describe.beforeAll, 'beforeAll') const describes = { name: describe.name, tests: [], duration: 0 } global.testResults[file].describes.push(describes) for (const test of describe.it) { let expect = {} if (testName) { if (testName && test.name.includes(testName) === false) { processTest(file) continue } } if (skip) { if (skip && test.name.includes(skip) === true) { processTest(file) continue } } // Execute test function const startTestTime = process.hrtime() try { await setupAndTeardown(test.beforeEach, 'beforeEach') await test.fn() expect.result = true } catch (error) { global.testResults[file].completed = false expect = { result: false, message: error.message, expected: error.expected, received: error.received } if (verbose && showStack) console.log(error.stack) } finally { await setupAndTeardown(test.afterEach, 'afterEach') } describes.tests.push({ name: test.name, result: expect.result, message: expect.message || 'OK' }) const [seconds, nanoseconds] = process.hrtime(startTestTime) const testDuration = (seconds * 1e9 + nanoseconds) / 1e6 if (expect.result) { passedTests++ testFilePassed++ } else { failedTests++ testFileFailed++ testFileStatus = false } totalTests++ if (verbose) { logExpect(test.name, expect, testDuration) } else { if (progress === 'none') { process.stdout.write(term(`\r⚙️ Processed: ${file}...`)) Screen.clearRight() } else if (!parallel) { processTest(file) } } } await setupAndTeardown(describe.afterAll, 'afterAll') } } if (jobs.tests.length && !suiteName) { if (verbose) log(` Simple tests ${jobs.tests.length}:`) for (const test of jobs.tests) { // console.log(test) let expect = {} if (testName && test.name.includes(testName) === false) { processTest(file) continue } if (skip && test.name.includes(skip) === true) { processTest(file) continue } await setupAndTeardown(test.beforeEach, 'beforeEach') try { await test.fn() expect.result = true } catch (error) { global.testResults[file].completed = false expect = { result: false, message: error.message, expected: error.expected, received: error.received } } global.testResults[file].tests.push({ name: test.name, result: expect.result, message: expect.message || 'OK' }) if (expect.result) { passedTests++ testFilePassed++ } else { failedTests++ testFileFailed++ testFileStatus = false } await setupAndTeardown(test.afterEach, 'afterEach') totalTests++ if (verbose) { logExpect(test.name, expect) } else { if (progress === 'none') { process.stdout.write(term(`\r⚙️ Processed: ${file}...`)) } else if (!parallel) { processTest(file) } } } } else { if (!verbose) { processTest(file, jobs.tests.length) } } const [seconds, nanoseconds] = process.hrtime(startFileTime) global.testResults[file].duration = (seconds * 1e9 + nanoseconds) / 1e6 } if (progress === 'none') { process.stdout.write(termx.blue.write(`\r${BOT} Process completed. All tests executed!`)) Screen.clearRight() } const [seconds, nanoseconds] = process.hrtime(startTime) const duration = (seconds * 1e9 + nanoseconds) / 1e6 if (failedTests) { log('\n') } for (const [file, result] of Object.entries(global.testResults)) { if (result.completed) { continue } const fileStatus = term('🔴', { color: 'red' }) log(`${fileStatus} ${file}...${term('FAIL', { color: 'red' })} 🕑 ${term(`${result.duration} ms`, { color: 'whiteBright' })}`) for (const desc of result.describes) { const testsCount = desc.tests.length if (desc.result) { continue } let testIndex = 0 for (const test of desc.tests) { testIndex++ if (test.result) { continue } const s = testIndex === testsCount ? '└──' : '├──' log(term(` ${s} ${term(test.name, { color: 'whiteBright' })} >>> ${term(test.message, { color: 'gray' })} <<<`, { color: 'white' })) } } let testIndex = 0 for (const test of result.tests) { testIndex++ if (test.result) { continue } const s = testIndex === result.tests.length ? '└──' : '├──' log(term(` ${s} ${term(test.name, { color: 'whiteBright' })} >>> ${term(test.message, { color: 'gray' })} <<<`, { color: 'white' })) } } if (!parallel) { log(term('\n-----------------------------------------------------------------', { color: 'gray' })) log(termx.gray.write(`Total files processed: ${termx.whiteBright.write(Object.entries(global.testResults).length)}`)) log(`${term('Total Tests', { color: 'gray' })}: ${term(totalTests, { style: 'bold', color: 'blue' })}, ${term('Passed', { color: 'gray' })}: ${term(passedTests, { style: 'bold', color: 'green' })}, ${term('Failed', { color: 'gray' })}: ${term(failedTests, { style: 'bold', color: 'red' })}, ${term('Duration', { color: 'gray' })}: ${term(`${duration} ms`, { color: 'yellow' })}`) log(' ') } return failedTests }