diff --git a/package.json b/package.json index 3f4321e..a4c71fd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codi-test-framework", - "version": "v0.0.37", + "version": "v0.0.38", "description": "A simple test framework for JavaScript", "main": "src/testRunner.js", "type": "module", diff --git a/src/core/describe.js b/src/core/describe.js index 68db1c4..4498405 100644 --- a/src/core/describe.js +++ b/src/core/describe.js @@ -17,9 +17,6 @@ export async function describe(description, callback) { }; state.pushSuite(suite); - if (!state.options?.quiet) { - console.log(chalk.bold.cyan(`\n${description}`)); - } try { await Promise.resolve(callback()); diff --git a/src/core/it.js b/src/core/it.js index 370b68e..7f791fa 100644 --- a/src/core/it.js +++ b/src/core/it.js @@ -25,9 +25,7 @@ export async function it(description, callback) { test.status = 'passed'; test.duration = performance.now() - test.startTime; state.passedTests++; - if (!state.options?.quiet) { - console.log(chalk.green(` ✅ ${description} (${test.duration.toFixed(2)}ms)`)); - } + } catch (error) { test.status = 'failed'; @@ -35,12 +33,6 @@ export async function it(description, callback) { test.duration = performance.now() - test.startTime; state.failedTests++; - if (!state.options?.quiet) { - - console.error(chalk.red(` ⛔ ${description} (${test.duration.toFixed(2)}ms)`)); - console.error(chalk.red(` ${error.message}`)); - - } } state.currentSuite.tests.push(test); diff --git a/src/runners/nodeRunner.js b/src/runners/nodeRunner.js index 96b26dc..f197591 100644 --- a/src/runners/nodeRunner.js +++ b/src/runners/nodeRunner.js @@ -83,9 +83,9 @@ export async function runTests(testDirectory, returnResults = false, codiConfig * @param {Function} testFn - Test function to run * @returns {Promise} Test results */ -export async function runTestFunction(testFn, options) { +export async function runTestFunction(testFn, options, description) { const suite = { - description: `Function: ${testFn.name}`, + description: description ? description : `Function: ${testFn.name}`, tests: [], startTime: performance.now() }; @@ -107,7 +107,9 @@ export async function runTestFunction(testFn, options) { state.popSuite(); } - state.printSummary(); + if (options.showSummary) { + state.printSummary(); + } return { passedTests: state.passedTests, diff --git a/src/runners/webRunner.js b/src/runners/webRunner.js index acbc25e..6f3814c 100644 --- a/src/runners/webRunner.js +++ b/src/runners/webRunner.js @@ -9,26 +9,19 @@ import chalk from 'chalk'; * @param {object} [options={}] - Options for running the test * @returns {Promise} */ -export async function runWebTestFile(testFile, options = {}) { - const { - timeout = 5000, - silent = false - } = options; +export async function runWebTestFile(testFile, options) { + + const defaults = { + silent: false + }; + + options ??= defaults; try { - const startTime = performance.now(); const testPromise = import(testFile); - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error(`Test file ${testFile} timed out after ${timeout}ms`)), timeout); - }); + await Promise.resolve(testPromise); - await Promise.race([testPromise, timeoutPromise]); - - const duration = performance.now() - startTime; - if (!silent) { - console.log(chalk.green(`✅ ${path.basename(testFile)} (${duration.toFixed(2)}ms)`)); - } } catch (error) { console.error(`Error running test file ${testFile}:`); console.error(error.stack); @@ -44,13 +37,16 @@ export async function runWebTestFile(testFile, options = {}) { * @param {object} [options={}] - Options for running the tests * @returns {Promise} Test results */ -export async function runWebTests(testFiles, options = {}) { - const { - parallel = false, - timeout = 5000, - quiet = false, - batchSize = 5 - } = options; +export async function runWebTests(testFiles, options) { + + const defaults = { + parallel: false, + quiet: false, + batchSize: 5, + showSummary: true + } + + options ??= defaults; state.resetCounters(); state.startTimer(); @@ -74,19 +70,19 @@ export async function runWebTests(testFiles, options = {}) { } await Promise.all( - batch.map(file => runWebTestFile(file, { timeout, silent: quiet })) + batch.map(file => runWebTestFile(file, options)) ); } } else { // Run all tests in parallel await Promise.all( - testFiles.map(file => runWebTestFile(file, { timeout, silent: quiet })) + testFiles.map(file => runWebTestFile(file, options)) ); } } else { // Run tests sequentially for (const file of testFiles) { - await runWebTestFile(file, { timeout, silent: quiet }); + await runWebTestFile(file, options); } } } catch (error) { @@ -102,67 +98,8 @@ export async function runWebTests(testFiles, options = {}) { testResults: state.testResults }; - state.printSummary(); - - return summary; -} - -/** - * Run a web test function - * @async - * @function runWebTestFunction - * @param {Function} testFn - Test function to run - * @param {object} [options={}] - Options for running the test - * @returns {Promise} Test results - */ -export async function runWebTestFunction(testFn, options = {}) { - const { - timeout = 5000, - silent = false - } = options; - - state.resetCounters(); - state.startTimer(); - - const suite = { - description: `Function: ${testFn.name}`, - tests: [], - startTime: performance.now() - }; - - state.pushSuite(suite); - - try { - const testPromise = Promise.resolve(testFn()); - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => reject(new Error(`Test function ${testFn.name} timed out after ${timeout}ms`)), timeout); - }); - - await Promise.race([testPromise, timeoutPromise]); - } catch (error) { - console.error(chalk.red(`Error in test ${testFn.name}:`)); - console.error(chalk.red(error.stack)); - state.failedTests++; - } finally { - suite.duration = performance.now() - suite.startTime; - state.testResults.push(suite); - state.popSuite(); - } - - const summary = { - totalTests: state.passedTests + state.failedTests, - passedTests: state.passedTests, - failedTests: state.failedTests, - executionTime: state.getExecutionTime(), - testResults: state.testResults - }; - - if (!silent) { - console.log(chalk.bold.cyan('\nTest Summary:')); - console.log(chalk.blue(` Total: ${summary.totalTests}`)); - console.log(chalk.green(` Passed: ${summary.passedTests}`)); - console.log(chalk.red(` Failed: ${summary.failedTests}`)); - console.log(chalk.blue(` Time: ${summary.executionTime}s`)); + if (options.showSummary) { + state.printSummary(); } return summary; diff --git a/src/state/TestState.js b/src/state/TestState.js index ae103c1..bab6641 100644 --- a/src/state/TestState.js +++ b/src/state/TestState.js @@ -29,7 +29,10 @@ class TestState { } setOptions(options) { - this.options = options; + this.options = { + ...this.options, + ...options + }; } /** @@ -88,35 +91,43 @@ class TestState { } printSummary() { - if (state.options.quiet) { - state.printFailures(); - } - // Always show the final summary - console.log(chalk.bold.cyan('\nTest Summary:')); - console.log(chalk.green(` Passed: ${state.passedTests}`)); - console.log(chalk.red(` Failed: ${state.failedTests}`)); - console.log(chalk.blue(` Time: ${state.getExecutionTime()}s`)); - } - - printFailures() { if (this.testResults.length > 0) { this.testResults.forEach(suite => { - const failures = suite.tests.filter(test => test.status === 'failed'); + let results = suite.tests; + + if (state.options.quiet) { + results = results.filter(result => result.status === 'failed'); + } + + if (results.length > 0) { + console.log('\n' + chalk.yellow('' + chalk.bold(suite.description))); + } + + if (results.length > 0) { + results.forEach(result => { + + if (result.status === 'failed') { + console.log(chalk.red(` └─ ⛔ ${result.description} (${result.duration.toFixed(2)}ms)`)); + console.log(chalk.red(` ${result.error.message}`)); + } else { + console.log(chalk.green(` └─ ✅ ${result.description} (${result.duration.toFixed(2)}ms)`)); + } + - if (failures.length > 0) { - console.log('\nFailed Tests:'); - failures.forEach(failure => { - console.log('\n' + chalk.red('✖ ' + chalk.bold(suite.description))); - console.log(chalk.red(` └─ ${failure.description}`)); - console.log(chalk.red(` ${failure.error.message}`)); }); } }); } + // Always show the final summary + console.log(chalk.bold.cyan('\nTest Summary:')); + console.log(chalk.green(` Passed: ${state.passedTests}`)); + console.log(chalk.red(` Failed: ${state.failedTests}`)); + console.log(chalk.blue(` Time: ${state.getExecutionTime()}s`)); } + } export const state = new TestState(); \ No newline at end of file diff --git a/src/testRunner.js b/src/testRunner.js index ddf6093..938962b 100755 --- a/src/testRunner.js +++ b/src/testRunner.js @@ -17,8 +17,7 @@ export { export { runWebTests, - runWebTestFile, - runWebTestFunction + runWebTestFile } from './runners/webRunner.js'; // Assertion exports @@ -29,7 +28,7 @@ export const assertFalse = assertions.assertFalse; export const assertThrows = assertions.assertThrows; export const assertNoDuplicates = assertions.assertNoDuplicates; -export const version = 'v0.0.37'; +export const version = 'v0.0.38'; /** * CLI entry point for running tests diff --git a/tests/example.test.mjs b/tests/example.test.mjs index 961ed11..65b372c 100644 --- a/tests/example.test.mjs +++ b/tests/example.test.mjs @@ -4,7 +4,7 @@ import { describe, it, assertEqual, assertNotEqual, assertTrue, assertFalse, ass await describe('I am an Example Test Suite', () => { it('should pass equality assertion', () => { - assertEqual(1, 2, 'Expected 1 to equal 1'); + assertEqual(1, 1, 'Expected 1 to equal 1'); }); it('should pass inequality assertion', () => { @@ -37,12 +37,11 @@ await describe('I am an Example Test Suite', () => { }); }); -// Nested describe for a new context -await describe('Running testFunction', async () => { - function testFunction() { - it('First', () => { - assertEqual(1, 2, 'Expected 1 to equal 1'); - }); - } - await runTestFunction(testFunction); -}); \ No newline at end of file + +await runTestFunction(testFunction, { showSummary: false }); + +function testFunction() { + it('First', () => { + assertEqual(1, 2, 'Expected 1 to equal 1'); + }); +} \ No newline at end of file