-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update test structure and enhancements
- Loading branch information
1 parent
36b71f9
commit abd002e
Showing
8 changed files
with
488 additions
and
174 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import chalk from 'chalk'; | ||
import { state } from '../state/TestState.js'; | ||
|
||
/** | ||
* Create a test suite | ||
* @async | ||
* @function describe | ||
* @param {string} description - Description of the test suite | ||
* @param {Function} callback - Suite callback function | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function describe(description, callback) { | ||
const suite = { | ||
description, | ||
tests: [], | ||
startTime: performance.now() | ||
}; | ||
|
||
state.pushSuite(suite); | ||
console.log(chalk.bold.cyan(`\n${description}`)); | ||
|
||
try { | ||
await Promise.resolve(callback()); | ||
} catch (error) { | ||
console.error(chalk.red(`Suite failed: ${description}`)); | ||
console.error(chalk.red(error.stack)); | ||
} finally { | ||
suite.duration = performance.now() - suite.startTime; | ||
state.testResults.push(suite); | ||
state.popSuite(); | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import chalk from 'chalk'; | ||
import { state } from '../state/TestState.js'; | ||
|
||
/** | ||
* Create a test case | ||
* @async | ||
* @function it | ||
* @param {string} description - Test case description | ||
* @param {Function} callback - Test callback function | ||
* @returns {Promise<void>} | ||
* @throws {Error} If called outside a describe block | ||
*/ | ||
export async function it(description, callback) { | ||
if (!state.currentSuite) { | ||
throw new Error('Test case defined outside of describe block'); | ||
} | ||
|
||
const test = { | ||
description, | ||
startTime: performance.now() | ||
}; | ||
|
||
try { | ||
await Promise.resolve(callback()); | ||
test.status = 'passed'; | ||
test.duration = performance.now() - test.startTime; | ||
state.passedTests++; | ||
console.log(chalk.green(` ✅ ${description} (${test.duration.toFixed(2)}ms)`)); | ||
} catch (error) { | ||
test.status = 'failed'; | ||
test.error = error; | ||
test.duration = performance.now() - test.startTime; | ||
state.failedTests++; | ||
console.error(chalk.red(` ⛔ ${description} (${test.duration.toFixed(2)}ms)`)); | ||
console.error(chalk.red(` ${error.message}`)); | ||
} | ||
|
||
state.currentSuite.tests.push(test); | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
import fs from 'fs'; | ||
import path from 'path'; | ||
import chalk from 'chalk'; | ||
import { state } from '../state/TestState.js'; | ||
import { excludePattern } from '../util/regex.js'; | ||
|
||
/** | ||
* Run a single test file | ||
* @async | ||
* @function runTestFile | ||
* @param {string} testFile - Path to test file | ||
* @returns {Promise<void>} | ||
*/ | ||
async function runTestFile(testFile) { | ||
try { | ||
const fileUrl = path.isAbsolute(testFile) | ||
? `file://${testFile}` | ||
: `file://${path.resolve(testFile)}`; | ||
|
||
await import(fileUrl); | ||
} catch (error) { | ||
console.error(chalk.red(`\nError running test file ${chalk.underline(testFile)}:`)); | ||
console.error(chalk.red(error.stack)); | ||
state.failedTests++; | ||
} | ||
} | ||
|
||
/** | ||
* Run tests in a directory | ||
* @async | ||
* @function runTests | ||
* @param {string} testDirectory - Directory containing tests | ||
* @param {boolean} [returnResults=false] - Whether to return results | ||
* @param {object} [codiConfig={}] - Configuration object | ||
* @returns {Promise<object|void>} Test results if returnResults is true | ||
*/ | ||
export async function runTests(testDirectory, returnResults = false, codiConfig = {}) { | ||
state.resetCounters(); | ||
state.startTimer(); | ||
|
||
let testFiles = fs.readdirSync(testDirectory, { recursive: true }) | ||
.filter(file => file.endsWith('.mjs')); | ||
|
||
if (codiConfig.excludeDirectories) { | ||
const matcher = excludePattern(codiConfig.excludeDirectories); | ||
testFiles = testFiles.filter(file => !matcher(file)); | ||
} | ||
|
||
console.log(chalk.bold.magenta(`\nRunning tests in directory: ${chalk.underline(testDirectory)}`)); | ||
console.log(chalk.bold.magenta(`Found ${testFiles.length} test file(s)\n`)); | ||
|
||
for (const file of testFiles) { | ||
await runTestFile(path.join(testDirectory, file)); | ||
} | ||
|
||
const summary = { | ||
passedTests: state.passedTests, | ||
failedTests: state.failedTests, | ||
testResults: state.testResults, | ||
executionTime: state.getExecutionTime() | ||
}; | ||
|
||
console.log(chalk.bold.cyan('\nTest Summary:')); | ||
console.log(chalk.green(` Passed: ${summary.passedTests}`)); | ||
console.log(chalk.red(` Failed: ${summary.failedTests}`)); | ||
console.log(chalk.blue(` Time: ${summary.executionTime}s`)); | ||
|
||
if (returnResults) return summary; | ||
|
||
if (state.failedTests > 0) { | ||
console.log(chalk.red('\nSome tests failed.')); | ||
process.exit(1); | ||
} else { | ||
console.log(chalk.green('\nAll tests passed.')); | ||
process.exit(0); | ||
} | ||
} | ||
|
||
/** | ||
* Run a single test function | ||
* @async | ||
* @function runTestFunction | ||
* @param {Function} testFn - Test function to run | ||
* @returns {Promise<object>} Test results | ||
*/ | ||
export async function runTestFunction(testFn) { | ||
const suite = { | ||
description: `Function: ${testFn.name}`, | ||
tests: [], | ||
startTime: performance.now() | ||
}; | ||
|
||
state.pushSuite(suite); | ||
|
||
try { | ||
await Promise.resolve(testFn()); | ||
} catch (error) { | ||
console.error(`Error in test ${testFn.name}:`, error); | ||
state.failedTests++; | ||
} finally { | ||
suite.duration = performance.now() - suite.startTime; | ||
state.testResults.push(suite); | ||
state.popSuite(); | ||
} | ||
|
||
return { | ||
passedTests: state.passedTests, | ||
failedTests: state.failedTests, | ||
testResults: state.testResults | ||
}; | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import { state } from '../state/TestState.js'; | ||
import chalk from 'chalk'; | ||
|
||
/** | ||
* Run a web test file | ||
* @async | ||
* @function runWebTestFile | ||
* @param {string} testFile - Path to test file | ||
* @param {object} [options={}] - Options for running the test | ||
* @returns {Promise<void>} | ||
*/ | ||
export async function runWebTestFile(testFile, options = {}) { | ||
const { | ||
timeout = 5000, | ||
silent = false | ||
} = options; | ||
|
||
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.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); | ||
state.failedTests++; | ||
} | ||
} | ||
|
||
/** | ||
* Run web tests | ||
* @async | ||
* @function runWebTests | ||
* @param {string[]} testFiles - Array of test files | ||
* @param {object} [options={}] - Options for running the tests | ||
* @returns {Promise<object>} Test results | ||
*/ | ||
export async function runWebTests(testFiles, options = {}) { | ||
const { | ||
parallel = false, | ||
timeout = 5000, | ||
silent = false, | ||
batchSize = 5 | ||
} = options; | ||
|
||
state.resetCounters(); | ||
state.startTimer(); | ||
|
||
if (!silent) { | ||
console.log(chalk.bold.magenta(`\nRunning ${testFiles.length} web test file(s)`)); | ||
} | ||
|
||
try { | ||
if (parallel) { | ||
if (batchSize > 0) { | ||
// Run tests in batches | ||
const batches = []; | ||
for (let i = 0; i < testFiles.length; i += batchSize) { | ||
batches.push(testFiles.slice(i, i + batchSize)); | ||
} | ||
|
||
for (const [index, batch] of batches.entries()) { | ||
if (!silent) { | ||
console.log(chalk.blue(`\nBatch ${index + 1}/${batches.length}`)); | ||
} | ||
|
||
await Promise.all( | ||
batch.map(file => runWebTestFile(file, { timeout, silent })) | ||
); | ||
} | ||
} else { | ||
// Run all tests in parallel | ||
await Promise.all( | ||
testFiles.map(file => runWebTestFile(file, { timeout, silent })) | ||
); | ||
} | ||
} else { | ||
// Run tests sequentially | ||
for (const file of testFiles) { | ||
await runWebTestFile(file, { timeout, silent }); | ||
} | ||
} | ||
} catch (error) { | ||
console.error(chalk.red('\nTest execution failed:')); | ||
console.error(chalk.red(error.stack)); | ||
} | ||
|
||
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`)); | ||
} | ||
|
||
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<object>} 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`)); | ||
} | ||
|
||
return summary; | ||
} |
Oops, something went wrong.