Skip to content

Commit

Permalink
v0.0.41 upate
Browse files Browse the repository at this point in the history
  • Loading branch information
RobAndrewHurst committed Nov 13, 2024
1 parent c50f7df commit 7748961
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 76 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "codi-test-framework",
"version": "v0.0.40",
"version": "v0.0.41",
"description": "A simple test framework for JavaScript",
"main": "src/testRunner.js",
"type": "module",
Expand Down
11 changes: 5 additions & 6 deletions src/core/describe.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,19 @@ import { state } from '../state/TestState.js';
export async function describe(description, callback) {
const suite = {
description,
tests: [],
startTime: performance.now()
};

state.pushSuite(suite);
const nestedSuite = state.pushSuite(suite);

try {
await Promise.resolve(callback(description));
await Promise.resolve(callback(nestedSuite.fullPath));
} catch (error) {
console.error(chalk.red(`Suite failed: ${description}`));
console.error(chalk.red(`Suite failed: ${nestedSuite.fullPath}`));
console.error(chalk.red(error.stack));
} finally {
suite.duration = performance.now() - suite.startTime;
state.testResults.push(suite);
nestedSuite.duration = performance.now() - nestedSuite.startTime;
state.testResults.push(nestedSuite);
state.popSuite();
}
}
15 changes: 7 additions & 8 deletions src/core/it.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import chalk from 'chalk';
import { state } from '../state/TestState.js';

/**
Expand All @@ -10,12 +9,13 @@ import { state } from '../state/TestState.js';
* @returns {Promise<void>}
* @throws {Error} If called outside a describe block
*/
export async function it(description, callback, suiteDescription) {
// Find the suite by its description
const suite = state.suiteStack.find(s => s.description === suiteDescription);
export async function it(description, callback, suitePath) {
const suite = suitePath
? state.getSuiteByPath(suitePath)
: state.getCurrentSuite();

if (!suite) {
throw new Error(`Cannot find test suite with description: ${suiteDescription}`);
throw new Error('Test case defined outside of describe block');
}

const test = {
Expand All @@ -35,6 +35,5 @@ export async function it(description, callback, suiteDescription) {
state.failedTests++;
}

// Add the test to the correct suite
suite.tests.push(test);
}
state.addTestToSuite(suite.fullPath, test);
}
159 changes: 110 additions & 49 deletions src/state/TestState.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,93 @@
import chalk from 'chalk';
import EventEmitter from 'events';

/**
* Class representing the state of test execution
* @class TestState
*/
class TestState {
/**
* Create a test state instance
* @constructor
*/
class TestState extends EventEmitter {
constructor() {
super();
/** @type {number} Number of passed tests */
this.passedTests = 0;

/** @type {number} Number of failed tests */
this.failedTests = 0;

/** @type {Array} Collection of test results */
this.testResults = [];

/** @type {Array} Stack of active test suites */
this.suiteStack = [];

/** @type {number|null} Test start time */
this.startTime = null;

/** @type {object} options */
this.options = {};
/** @type {Map} Map of suite paths to suite objects */
this.suiteMap = new Map();
}

setOptions(options) {
this.options = {
...this.options,
...options
};
this.emit('optionsUpdated', this.options);
}

/**
* Reset all counters and state
* @method
*/
resetCounters() {
this.passedTests = 0;
this.failedTests = 0;
this.testResults = [];
this.suiteStack = [];
this.suiteMap.clear();
this.emit('stateReset');
}

/**
* Start the test timer
* @method
*/
startTimer() {
this.startTime = performance.now();
this.emit('timerStarted', this.startTime);
}

/**
* Get the total execution time
* @method
* @returns {string} Execution time in seconds
*/
getExecutionTime() {
return ((performance.now() - this.startTime) / 1000).toFixed(2);
}

/**
* Add a new suite to the stack
* Get current suite path based on stack
* @returns {string} Full path of current suite stack
*/
_getFullSuitePath() {
return this.suiteStack.map(suite => suite.description).join(' > ');
}

/**
* Add a new suite to the stack and register it
* @method
* @param {object} suite - Test suite to add
*/
pushSuite(suite) {
this.suiteStack.push(suite);
// Get parent suite if exists
const parentSuite = this.suiteStack[this.suiteStack.length - 1];

// Create nested suite structure
const nestedSuite = {
...suite,
fullPath: parentSuite
? `${parentSuite.fullPath} > ${suite.description}`
: suite.description,
parent: parentSuite,
children: [],
tests: []
};

// Add to parent's children if exists
if (parentSuite) {
parentSuite.children.push(nestedSuite);
}

this.suiteStack.push(nestedSuite);
this.suiteMap.set(nestedSuite.fullPath, nestedSuite);
this.emit('suitePushed', nestedSuite);

return nestedSuite;
}

/**
Expand All @@ -78,47 +96,90 @@ class TestState {
* @returns {object|undefined} Removed test suite
*/
popSuite() {
return this.suiteStack.pop();
const suite = this.suiteStack.pop();
this.emit('suitePopped', suite);
return suite;
}

printSummary() {
/**
* Get current active suite
* @method
* @returns {object|undefined} Current suite
*/
getCurrentSuite() {
return this.suiteStack[this.suiteStack.length - 1];
}

/**
* Get suite by full path
* @method
* @param {string} path - Full suite path
* @returns {object|undefined} Found suite
*/
getSuiteByPath(path) {
return this.suiteMap.get(path);
}

/**
* Add test to a specific suite
* @method
* @param {string} suitePath - Full suite path
* @param {object} test - Test case to add
*/
addTestToSuite(suitePath, test) {
const suite = this.getSuiteByPath(suitePath);
if (!suite) {
throw new Error(`Cannot find suite: ${suitePath}`);
}
suite.tests.push(test);
this.emit('testAdded', { suite, test });
}

printSummary() {
if (this.testResults.length > 0) {
this.testResults.forEach(suite => {
// Helper function to print suite and its children
const printSuite = (suite, indent = 0) => {
const indentation = ' '.repeat(indent);

// Print suite's tests
let results = suite.tests;

if (state.options.quiet) {
if (this.options.quiet) {
results = results.filter(result => result.status === 'failed');
}

// Print suite description
if (results.length > 0) {
console.log('\n' + chalk.yellow('' + chalk.bold(suite.description)));
console.log('\n' + indentation + 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)`));
}


});
results.forEach(result => {
if (result.status === 'failed') {
console.log(indentation + chalk.red(` └─ ⛔ ${result.description} (${result.duration.toFixed(2)}ms)`));
console.log(indentation + chalk.red(` ${result.error.message}`));
} else {
console.log(indentation + chalk.green(` └─ ✅ ${result.description} (${result.duration.toFixed(2)}ms)`));
}
});

// Print child suites
if (suite.children) {
suite.children.forEach(child => printSuite(child, indent + 1));
}
};

});
// Print only top-level suites (they will handle their children)
this.testResults
.filter(suite => !suite.parent)
.forEach(suite => printSuite(suite));
}
// 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`));
}
console.log(chalk.green(` Passed: ${this.passedTests}`));
console.log(chalk.red(` Failed: ${this.failedTests}`));
console.log(chalk.blue(` Time: ${this.getExecutionTime()}s`));

this.emit('summaryPrinted');
}
}

export const state = new TestState();
2 changes: 1 addition & 1 deletion src/testRunner.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const assertFalse = assertions.assertFalse;
export const assertThrows = assertions.assertThrows;
export const assertNoDuplicates = assertions.assertNoDuplicates;

export const version = 'v0.0.40';
export const version = 'v0.0.41';

/**
* CLI entry point for running tests
Expand Down
45 changes: 34 additions & 11 deletions tests/example.test.mjs
Original file line number Diff line number Diff line change
@@ -1,47 +1,70 @@
import { describe, it, assertEqual, assertNotEqual, assertTrue, assertFalse, assertThrows, assertNoDuplicates, runTestFunction } from '../src/testRunner.js';

// First test suite
await describe('I am an Example Test Suite', (description) => {
await describe('I am an Example Test Suite', () => {

it('should pass equality assertion', () => {
assertEqual(1, 1, 'Expected 1 to equal 1');
}, description);
assertEqual(1, 2, 'Expected 1 to equal 1');
});

it('should pass inequality assertion', () => {
assertNotEqual(1, 2, 'Expected 1 not to equal 2');
}, description);
});

it('should pass true assertion', () => {
assertTrue(true, 'Expected true to be true');
}, description);
});

it('should pass false assertion', () => {
assertFalse(false, 'Expected false to be false');
}, description);
});

it('should pass error assertion', () => {
assertThrows(() => {
throw new Error('An error occurred');
}, 'An error occurred', 'Expected an error to be thrown');
}, description);
});

it('should deeply compare objects', () => {
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
assertEqual(obj1, obj2, 'Expected objects to be deeply equal');
}, description);
});

it('should check for duplicates', () => {
const array = ['field1', 'field2']
assertNoDuplicates(array, 'There should be no duplicates');
}, description);
});

});


await describe('I am the first describe', async () => {
await runTestFunction(testFunction, { showSummary: false }, 'I am the first function');
await runTestFunction(testFunction, { showSummary: false }, 'I am the second function');

await describe('I am the second describe', async () => {

it('should pass equality assertion', () => {
assertEqual(1, 2, 'Expected 1 to equal 1');
});

it('should pass equality assertion', () => {
assertEqual(1, 2, 'Expected 1 to equal 1');
});

await runTestFunction(testFunction, { showSummary: false }, 'I am the third function');

await describe('I am the third describe', async () => {

await runTestFunction(testFunction, { showSummary: false }, 'I am the fouth function');
})
})
});

await runTestFunction(testFunction, { showSummary: false });

function testFunction() {
it('First', () => {
assertEqual(1, 1, 'Expected 1 to equal 1');
}, 'Function: testFunction');
});
}

0 comments on commit 7748961

Please sign in to comment.