UNPKG

petcarescript

Version:

PetCareScript - A modern, expressive programming language designed for humans

421 lines (350 loc) โ€ข 11.4 kB
/** * PetCareScript Testing Library * TDD/BDD testing framework */ class TestSuite { constructor(name) { this.name = name; this.tests = []; this.beforeHooks = []; this.afterHooks = []; this.beforeEachHooks = []; this.afterEachHooks = []; this.passed = 0; this.failed = 0; this.skipped = 0; } addTest(name, fn, skip = false) { this.tests.push({ name, fn, skip }); } before(fn) { this.beforeHooks.push(fn); } after(fn) { this.afterHooks.push(fn); } beforeEach(fn) { this.beforeEachHooks.push(fn); } afterEach(fn) { this.afterEachHooks.push(fn); } async run() { console.log(`\n๐Ÿงช Running test suite: ${this.name}`); console.log('='.repeat(50)); // Run before hooks for (const hook of this.beforeHooks) { try { await hook(); } catch (error) { console.error(`โŒ Before hook failed: ${error.message}`); return; } } // Run tests for (const test of this.tests) { if (test.skip) { console.log(`โญ๏ธ SKIPPED: ${test.name}`); this.skipped++; continue; } // Run beforeEach hooks for (const hook of this.beforeEachHooks) { try { await hook(); } catch (error) { console.error(`โŒ BeforeEach hook failed: ${error.message}`); this.failed++; continue; } } try { await test.fn(); console.log(`โœ… PASSED: ${test.name}`); this.passed++; } catch (error) { console.error(`โŒ FAILED: ${test.name}`); console.error(` ${error.message}`); this.failed++; } // Run afterEach hooks for (const hook of this.afterEachHooks) { try { await hook(); } catch (error) { console.error(`โš ๏ธ AfterEach hook failed: ${error.message}`); } } } // Run after hooks for (const hook of this.afterHooks) { try { await hook(); } catch (error) { console.error(`โš ๏ธ After hook failed: ${error.message}`); } } // Print results this.printResults(); } printResults() { console.log('\n๐Ÿ“Š Test Results:'); console.log(` Passed: ${this.passed}`); console.log(` Failed: ${this.failed}`); console.log(` Skipped: ${this.skipped}`); console.log(` Total: ${this.tests.length}`); if (this.failed === 0) { console.log('๐ŸŽ‰ All tests passed!'); } else { console.log(`๐Ÿ’ฅ ${this.failed} test(s) failed`); } } } class TestRunner { constructor() { this.suites = []; this.currentSuite = null; } describe(name, fn) { const suite = new TestSuite(name); this.suites.push(suite); const previousSuite = this.currentSuite; this.currentSuite = suite; fn(); this.currentSuite = previousSuite; return suite; } it(name, fn) { if (!this.currentSuite) { throw new Error('Test must be inside a describe block'); } this.currentSuite.addTest(name, fn); } xit(name, fn) { if (!this.currentSuite) { throw new Error('Test must be inside a describe block'); } this.currentSuite.addTest(name, fn, true); // skip = true } before(fn) { if (!this.currentSuite) { throw new Error('Hook must be inside a describe block'); } this.currentSuite.before(fn); } after(fn) { if (!this.currentSuite) { throw new Error('Hook must be inside a describe block'); } this.currentSuite.after(fn); } beforeEach(fn) { if (!this.currentSuite) { throw new Error('Hook must be inside a describe block'); } this.currentSuite.beforeEach(fn); } afterEach(fn) { if (!this.currentSuite) { throw new Error('Hook must be inside a describe block'); } this.currentSuite.afterEach(fn); } async run() { console.log('๐Ÿงช Starting PetCareScript Test Runner'); let totalPassed = 0; let totalFailed = 0; let totalSkipped = 0; for (const suite of this.suites) { await suite.run(); totalPassed += suite.passed; totalFailed += suite.failed; totalSkipped += suite.skipped; } console.log('\n๐Ÿ Final Results:'); console.log(` Total Passed: ${totalPassed}`); console.log(` Total Failed: ${totalFailed}`); console.log(` Total Skipped: ${totalSkipped}`); console.log(` Total Tests: ${totalPassed + totalFailed + totalSkipped}`); return totalFailed === 0; } } // Assertion library class Assertion { constructor(actual) { this.actual = actual; this.negated = false; } get not() { this.negated = !this.negated; return this; } toBe(expected) { const result = this.actual === expected; const shouldPass = this.negated ? !result : result; if (!shouldPass) { const message = this.negated ? `Expected ${this.actual} not to be ${expected}` : `Expected ${this.actual} to be ${expected}`; throw new Error(message); } return this; } toEqual(expected) { const result = JSON.stringify(this.actual) === JSON.stringify(expected); const shouldPass = this.negated ? !result : result; if (!shouldPass) { const message = this.negated ? `Expected ${JSON.stringify(this.actual)} not to equal ${JSON.stringify(expected)}` : `Expected ${JSON.stringify(this.actual)} to equal ${JSON.stringify(expected)}`; throw new Error(message); } return this; } toContain(expected) { let result = false; if (Array.isArray(this.actual)) { result = this.actual.includes(expected); } else if (typeof this.actual === 'string') { result = this.actual.includes(expected); } const shouldPass = this.negated ? !result : result; if (!shouldPass) { const message = this.negated ? `Expected ${this.actual} not to contain ${expected}` : `Expected ${this.actual} to contain ${expected}`; throw new Error(message); } return this; } toBeTruthy() { const result = !!this.actual; const shouldPass = this.negated ? !result : result; if (!shouldPass) { const message = this.negated ? `Expected ${this.actual} not to be truthy` : `Expected ${this.actual} to be truthy`; throw new Error(message); } return this; } toBeFalsy() { const result = !this.actual; const shouldPass = this.negated ? !result : result; if (!shouldPass) { const message = this.negated ? `Expected ${this.actual} not to be falsy` : `Expected ${this.actual} to be falsy`; throw new Error(message); } return this; } toThrow(expectedError = null) { if (typeof this.actual !== 'function') { throw new Error('toThrow can only be used with functions'); } let threw = false; let error = null; try { this.actual(); } catch (e) { threw = true; error = e; } const shouldPass = this.negated ? !threw : threw; if (!shouldPass) { const message = this.negated ? `Expected function not to throw` : `Expected function to throw`; throw new Error(message); } if (expectedError && threw) { if (typeof expectedError === 'string') { if (!error.message.includes(expectedError)) { throw new Error(`Expected error message to include "${expectedError}", got "${error.message}"`); } } } return this; } } // Mock and spy functionality class Mock { constructor() { this.calls = []; this.returnValue = undefined; this.implementation = null; } mockReturnValue(value) { this.returnValue = value; return this; } mockImplementation(fn) { this.implementation = fn; return this; } fn(...args) { this.calls.push(args); if (this.implementation) { return this.implementation(...args); } return this.returnValue; } toHaveBeenCalled() { return this.calls.length > 0; } toHaveBeenCalledWith(...args) { return this.calls.some(call => JSON.stringify(call) === JSON.stringify(args) ); } toHaveBeenCalledTimes(times) { return this.calls.length === times; } clear() { this.calls = []; this.returnValue = undefined; this.implementation = null; } } // Global test runner instance const globalRunner = new TestRunner(); const TestingLib = { // Test structure describe: (name, fn) => globalRunner.describe(name, fn), it: (name, fn) => globalRunner.it(name, fn), xit: (name, fn) => globalRunner.xit(name, fn), // Hooks before: (fn) => globalRunner.before(fn), after: (fn) => globalRunner.after(fn), beforeEach: (fn) => globalRunner.beforeEach(fn), afterEach: (fn) => globalRunner.afterEach(fn), // Assertions expect: (actual) => new Assertion(actual), assert: (condition, message = 'Assertion failed') => { if (!condition) { throw new Error(message); } }, // Mocking mock: () => new Mock(), spy: (obj, method) => { const original = obj[method]; const mock = new Mock(); obj[method] = (...args) => { mock.fn(...args); return original.apply(obj, args); }; obj[method].mockClear = () => mock.clear(); obj[method].toHaveBeenCalled = () => mock.toHaveBeenCalled(); obj[method].toHaveBeenCalledWith = (...args) => mock.toHaveBeenCalledWith(...args); obj[method].toHaveBeenCalledTimes = (times) => mock.toHaveBeenCalledTimes(times); return obj[method]; }, // Test runner run: () => globalRunner.run(), // Utilities createSuite: (name) => new TestSuite(name), createRunner: () => new TestRunner() }; module.exports = TestingLib;