Last active
September 5, 2024 14:50
-
-
Save isocroft/4f8f23b4dbb1b1324a6706fa904fe5e9 to your computer and use it in GitHub Desktop.
A toy assertion case processor for asserting on test assertion cases for a non-existent toy test framework (CommonJS) with the option to extend it
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
/* @HINT: NPM package for rendering colored text on the command-line (or standard output) */ | |
const chalk = require('chalk'); | |
/* @HINT: This uses the `chalk` NPM package to setup colors for failed test assertions as well as passed test assertions */ | |
const failStatus = chalk.red; | |
const passStatus = chalk.green; | |
/* @HINT: A helper function using the status of an assertion to color text sent to the command-line (or standard output) */ | |
const standardOutputPrettify = (isOk, statusText, prefix = "") => { | |
return isOk | |
? passStatus(chalk.white.bgGreen.bold(prefix) + ": " + statusText) | |
: failStatus(chalk.white.bgRed.bold(prefix) + ": " + statusText); | |
}; | |
/* @HINT: A helper function that uses the case condition of an assertion to determine the status message for the assertion */ | |
/** | |
* @NOTE: | |
* | |
* A case condition is used to decide how an assertion should be processed | |
* | |
* - an example is how modifiers like (.not, .andNot) which invert an assertion from a positive assertion to a negative one | |
* | |
*/ | |
const getAssertionQualifier = (invert) => { | |
/** | |
* @INFO: | |
* | |
* If an assertion is inverted when a modifier like (.not) is used, then we qualify the status message differently | |
*/ | |
return invert ? 'not to' : 'to'; | |
}; | |
/* @HINT: A helper function that turns a primitive or reference data type variable to a human-readable string */ | |
const stringifyValue = (value) => { | |
if (typeof value !== 'object' | |
&& typeof value !== 'boolean') { | |
/* @INFO: Stringify primitive data types like: number, symbols e.t.c except booleans */ | |
return String(value); | |
} | |
try { | |
if (value instanceof Error) { | |
throw new Error('do not JSON stringify'); | |
} | |
/* @INFO: Stringify booleans and other reference ddata types likes: object literals, array e.t.c */ | |
return JSON.stringify(value); | |
} catch (_) { | |
/* @INFO: Fallback: stringify error objects */ | |
return String(value); | |
} | |
}; | |
/* @HINT: Determine if a callback function is an `async` function */ | |
const isAsyncFunction = (callback) => { | |
if (typeof callback !== 'function') { | |
return false | |
} | |
const $string = callback.toString().trim() | |
return Boolean( | |
$string.match(/^async /) | |
|| callback.constructor.name === 'AsyncFunction' | |
|| callback.__proto__.constructor.name === 'AsyncFunction' | |
) | |
}; | |
/* @HINT: A helper function that determines if an object (or value) is a promise */ | |
const isPromise = (object) => { | |
if ( | |
typeof object === 'undefined' || | |
object === null || | |
!(object instanceof Object) | |
) { | |
return false; | |
} | |
return Boolean( | |
typeof object.then === 'function' || | |
Object.prototype.toString.call(object) === '[object Promise]' | |
); | |
}; | |
const isNotPromise = (object) => { | |
return !isPromise(object); | |
}; | |
/* @HINT: A helper function used to create a pub/sub (mediator) object from listeing to and firing custom events */ | |
function mitt(allEventsMap = {}) { | |
const all = Object.create(null); | |
return { | |
on(type, handler) { | |
allEventsMap[type] = -1; | |
(all[type] || (all[type] = [])).push(handler); | |
}, | |
off(type, handler) { | |
if (typeof allEventsMap[type] !== 'undefined') { | |
delete allEventsMap[type]; | |
} | |
if (all[type]) { | |
all[type].splice(all[type].indexOf(handler) >>> 0, 1); | |
} | |
}, | |
emit(type) { | |
if (allEventsMap[type]) { | |
allEventsMap[type] = 1; | |
} | |
const _len = arguments.length; | |
const evts = new Array(_len > 1 ? _len - 1 : 0); | |
for (let _key = 1; _key < _len; _key++) { | |
evts[_key - 1] = arguments[_key]; | |
} | |
(all[type] || []).slice().forEach((handler) => { | |
handler(...evts); | |
}); | |
}, | |
}; | |
} | |
/** | |
* @INFO: | |
* | |
* Assertions can be inverted by certain modifiers (e.g. .not) in a test assertion case. However, | |
* each inverted assertion maps to one and only one case condition (or expectation). | |
* | |
* This is a constant that helps the codebase keep track of proper mapping of exactly one modifier to | |
* exactly one case condition (or expectation) | |
*/ | |
const INVERTED_ASSERTION_SCAN_STATUS = { | |
ACTIVATED: 1, | |
DEACTIVATED: 2, | |
}; | |
/** @NOTE: | |
* | |
* The `AssertionCaseProcessor` is a class that lays out the blueprint for how test assertion cases are processed. | |
* | |
* Each test assertion case maps to an instance of the `AssertionCaseProcessor` class. | |
* | |
* Every test assertion case (instance of the `AssertionCaseProcessor` class) is created with an `expect(...)` call. | |
* | |
* Every test assertion case has parts and those parts a called case conditions (or expectations) | |
* | |
* Every case condition can be inverted or not inverted depending on the qualifier placed in front of it | |
* | |
* - example: | |
* expect("yes").toBe("no") // This is a test assertion that has only one case condition (or expectation) | |
* | |
* | |
* expect("no").not.toBe("yes") // This is a test assertion that has only one case condition that is inverted with (.not) | |
*/ | |
class AssertionCaseProcessor { | |
constructor(actualValue, $$emitter) { | |
/* @INFO: `actualValue` is the actual value to be asserted against */ | |
this.actualValue = actualValue; | |
/* @INFO: This is the pub/sub instance used to send messages about every test assertion to the console (standard output) */ | |
this.$$emitter = $$emitter; | |
/* @INFO: This is what helps the processor determine if the `actualValue` is a Promise or not */ | |
this.shouldAwaitActualValue = false; | |
/* @INFO: This is what helps the processor determine if a case condition for a resolved/rejected promise was met */ | |
this.awaitedActualValueExpectationMet = true; | |
/* @INFO: If only one modifier is used in a test assertion it can become globally applied to all case conditions */ | |
this.invertAssertionGlobally = false; | |
/* @INFO: Keep a list of test assertion case modifiers for each case condition (or expectation) so it can be processed */ | |
this.assertionCaseConditionModifiersList = []; | |
/* @INFO: Toggle for signaling when an assertion case moddifier has been consumed by its respective case condition */ | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
} | |
/** | |
* `toResolve` | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "verified \"0\" to be \"0\" after promise settled", "casePassed": true }'>" | |
* expect(Promise.resolve(0)).toResolve.toBe(0) | |
*/ | |
get toResolve() { | |
if (isNotPromise(this.actualValue)) { | |
throw new Error( | |
"Cannot execute this test assertion case condition `.toResolve` against an object that isn't a promise" | |
) | |
return; | |
} | |
/* @INFO: If we get here then the `actualValue` is definitely a promise */ | |
const $actualPromise = this.actualValue; | |
const caseConditionModifier = Boolean(this.assertionCaseConditionModifiersList.shift()); | |
//const isAssertionCaseConditionInverted = caseConditionModifier === true; | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
/* @INFO: Flag `actualValue` as a Promise object */ | |
this.shouldAwaitActualValue = true; | |
/* @INFO: Override `actualValue` with new promise that merges the settlement for the intial promise (resolved or rejected)*/ | |
this.actualValue = $actualPromise.then( | |
(value) => { | |
/* @HINT: If the assertion isn't inverted, then the case condition (or expectation) of a resolved promise is met */ | |
/* @HINT: If the assertion is inverted, then the case condition (or expectation) of a resolved promise is not met */ | |
this.awaitedActualValueExpectationMet = !caseConditionModifier; | |
return value; | |
}, | |
(reason) => { | |
/* @HINT: If the assertion isn't inverted, then the case condition (or expectation) of a rejected promise is not met */ | |
/* @HINT: If the assertion is inverted, then the case condition (or expectation) of a rejected promise is met */ | |
this.awaitedActualValueExpectationMet = caseConditionModifier; | |
return reason; | |
} | |
); | |
return this; | |
} | |
/** | |
* `toReject` | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "expected leading case condition on promise to be met", "casePassed": false }'>" | |
* expect(Promise.resolve(0)).toReject.toBe(0) | |
*/ | |
get toReject() { | |
if (isNotPromise(this.actualValue)) { | |
throw new Error( | |
"Cannot execute this test assertion case condition `.toReject` against an object that isn't a promise" | |
); | |
return; | |
} | |
/* @INFO: If we get here then the `actualValue` is definitely a promise */ | |
const $actualPromise = this.actualValue; | |
const caseConditionModifier = Boolean(this.assertionCaseConditionModifiersList.shift()); | |
//const isAssertionCaseConditionInverted = caseConditionModifier === true; | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
/* @INFO: Flag `actualValue` as a Promise object */ | |
this.shouldAwaitActualValue = true; | |
/* @INFO: Override `actualValue` with new promise that merges the settlement for the intial promise (resolved or rejected)*/ | |
this.actualValue = $actualPromise.then( | |
(value) => { | |
/* @HINT: If the assertion isn't inverted, then the case condition (or expectation) of a resolved promise is not met */ | |
/* @HINT: If the assertion is inverted, then the case condition (or expectation) of a resolved promise is met */ | |
this.awaitedActualValueExpectationMet = caseConditionModifier; | |
return value; | |
}, | |
(reason) => { | |
/* @HINT: If the assertion isn't inverted, then the case condition (or expectation) of a rejected promise is met */ | |
/* @HINT: If the assertion is inverted, then the case condition (or expectation) of a rejected promise is not met */ | |
this.awaitedActualValueExpectationMet = !caseConditionModifier; | |
return reason; | |
} | |
); | |
return this; | |
} | |
/** | |
* .not case condition modifier | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "expected \"0\" not to be \"0\" after promise settled", "casePassed": false }'>" | |
* expect(Promise.resolve(0)).not.toResolve.toBe(0) | |
*/ | |
get not() { | |
if (this.assertionCaseConditionModifiersList.length > 0) { | |
throw new SyntaxError( | |
'Cannot use modifier `.not` in the middle of a test assertion' | |
); | |
} | |
if ( | |
this.invertedAssertionScanStatus === | |
INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED | |
) { | |
throw new SyntaxError('Invalid test assertion case condition grammar'); | |
} | |
this.invertedAssertionScanStatus = INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED; | |
this.invertAssertionGlobally = true; | |
this.assertionCaseConditionModifiersList.push(this.invertAssertionGlobally); | |
return this; | |
} | |
/** | |
* .andNot case condition modifier | |
* | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "expected \"0\" not to be \"0\" after promise settled", "casePassed": false }'>" | |
* expect(Promise.resolve(0)).toResolve.andNot.toBe(0) | |
*/ | |
get andNot() { | |
if ( | |
!this.shouldAwaitActualValue && | |
this.assertionCaseConditionModifiersList.length === 0 | |
) { | |
throw new SyntaxError( | |
'Cannot use modifier `.andNot` at the start of a test assertion' | |
); | |
} | |
if ( | |
this.invertedAssertionScanStatus === | |
INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED | |
) { | |
throw new SyntaxError('Invalid test assertion case condition grammar'); | |
} | |
/* @INFO: If another assertion case condition is inverted then, any intial global inversion is reverted (set to "false") */ | |
this.invertAssertionGlobally = false; | |
this.invertedAssertionScanStatus = INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED; | |
this.assertionCaseConditionModifiersList.push(true); | |
return this; | |
} | |
/** | |
* .and case condition modifier | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "expected leading case condition on promise to be met", "casePassed": false }'>" | |
* expect(Promise.resolve(0)).not.toResolve.and.toBe(0) | |
*/ | |
get and() { | |
if ( | |
!this.shouldAwaitActualValue && | |
this.assertionCaseConditionModifiersList.length === 0 | |
) { | |
throw new SyntaxError( | |
'Cannot use modifier `.and` at the start of a test assertion' | |
); | |
} | |
if ( | |
this.invertedAssertionScanStatus === | |
INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED | |
) { | |
throw new SyntaxError('Invalid test assertion case condition grammar'); | |
} | |
this.invertedAssertionScanStatus = INVERTED_ASSERTION_SCAN_STATUS.ACTIVATED; | |
this.assertionCaseConditionModifiersList.push(false); | |
return this; | |
} | |
/** | |
* .toBe(...) assertion case matcher | |
* | |
* @param {*} expectedValue | |
* | |
* @example | |
* // return "Promise<'{ "statusText": "verified \"0\" not to be \"null\" after promise settled", "casePassed": true }'>" | |
* expect(Promise.resolve(0)).not.toResolve.toBe(null) | |
*/ | |
toBe (expectedValue) { | |
const processExactCompareTestAssertionCase = (awaitedActualValue) => { | |
let result = false; | |
const currentCaseConditionModifier = this.assertionCaseConditionModifiersList.shift(); | |
const invertAssertion = | |
currentCaseConditionModifier === undefined | |
? this.invertAssertionGlobally | |
: currentCaseConditionModifier; | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
if (this.shouldAwaitActualValue) { | |
if (this.awaitedActualValueExpectationMet) { | |
result = invertAssertion | |
? !Object.is(expectedValue, awaitedActualValue) | |
: Object.is(expectedValue, awaitedActualValue); | |
} else { | |
if ( | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
) { | |
result = false; | |
} | |
} | |
} else { | |
result = invertAssertion | |
? !Object.is(expectedValue, awaitedActualValue) | |
: Object.is(expectedValue, awaitedActualValue); | |
} | |
const isAssertionOk = result === true; | |
const qualifier = getAssertionQualifier(invertAssertion); | |
const finalTestAssertionState = { | |
statusText: | |
`${!isAssertionOk ? 'expected ' : 'verified '}` + | |
((this.shouldAwaitActualValue && !this.awaitedActualValueExpectationMet) && | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
? `leading case condition ${this.shouldAwaitActualValue ? 'on promise' : ''} ${qualifier} be met` | |
: `"${stringifyValue( | |
awaitedActualValue | |
)}" ${qualifier} be "${stringifyValue(expectedValue)}"${this.shouldAwaitActualValue ? ' after promise settled' : ''}`), | |
casePassed: result, | |
}; | |
/* @INFO: Emit event to dump test assertion message to command-line (or standard output) */ | |
this.$$emitter.emit('stdout:dump', finalTestAssertionState); | |
return finalTestAssertionState; | |
}; | |
if (this.shouldAwaitActualValue) { | |
return this.actualValue.then(processExactCompareTestAssertionCase); | |
} | |
return processExactCompareTestAssertionCase(this.actualValue); | |
} | |
/** | |
* .toHaveRaisedError(...) assertion case matcher | |
* | |
* @param {Error} error | |
* | |
* @example | |
* // returns '{ "statusText": "verified \"<Function>\" to have raised error > Error: hello", "casePassed": true }' | |
* expect(() => { | |
* throw new Error('hello') | |
* }).toHaveRaisedError(new Error('hello')) | |
*/ | |
toHaveRaisedError(error) { | |
if (!error || !(error instanceof Error)) { | |
throw new Error('expected value is not an error'); | |
} | |
if (typeof this.actualValue !== "function" || this.actualValue.length !== 0) { | |
throw new Error('actual callee not wrapped in caller function to track error') | |
} | |
if (isAsyncFunction(this.actualValue)) { | |
this.shouldAwaitActualValue = true | |
} | |
const processThrownErrorTestAssertionCase = (awaitedActualValue) => { | |
let result = false; | |
const currentCaseConditionModifier = this.assertionCaseConditionModifiersList.shift(); | |
const invertAssertion = | |
currentCaseConditionModifier === undefined | |
? this.invertAssertionGlobally | |
: currentCaseConditionModifier; | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
if (this.shouldAwaitActualValue) { | |
if (this.awaitedActualValueExpectationMet) { | |
result = invertAssertion | |
? !(error.name === awaitedActualValue.name && error.message === awaitedActualValue.message) | |
: error.name === awaitedActualValue.name && error.message === awaitedActualValue.message; | |
} else { | |
if ( | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
) { | |
result = false; | |
} | |
} | |
} else { | |
result = invertAssertion | |
? !(error.name === awaitedActualValue.name && error.message === awaitedActualValue.message) | |
: error.name === awaitedActualValue.name && error.message === awaitedActualValue.message; | |
} | |
const isAssertionOk = result === true; | |
const qualifier = getAssertionQualifier(invertAssertion); | |
const finalTestAssertionState = { | |
statusText: | |
`${!isAssertionOk ? 'expected ' : 'verified '}` + | |
((this.shouldAwaitActualValue && !this.awaitedActualValueExpectationMet) && | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
? `leading case condition ${this.shouldAwaitActualValue ? 'on promise' : ''} ${qualifier} be met` | |
: `"<Function>" ${qualifier} have raised error > ${stringifyValue(error)}${ | |
this.shouldAwaitActualValue ? ' after promise settled' : '' | |
}`), | |
casePassed: result, | |
}; | |
this.$$emitter.emit('stdout:dump', finalTestAssertionState); | |
return finalTestAssertionState; | |
}; | |
if (this.shouldAwaitActualValue) { | |
return this.actualValue().then(() => { | |
this.awaitedActualValueExpectationMet = false; | |
return processThrownErrorTestAssertionCase({}) | |
}).catch(processThrownErrorTestAssertionCase); | |
} | |
try { | |
this.actualValue(); | |
return processThrownErrorTestAssertionCase({}); | |
} catch ($e) { | |
return processThrownErrorTestAssertionCase($e); | |
} | |
} | |
/** | |
* .toBeThruthy(...) assertion case matcher | |
* | |
* | |
* @example | |
* // returns "Promise<'{ "statusText": "verified \"null\" not to be thruthy after promise settled", "casePassed": true }'>" | |
* expect(Promise.reject(null)).toReject.andNot.toBeTruthy() | |
*/ | |
toBeTruthy() { | |
const processTruthyTestAssertionCase = (awaitedActualValue) => { | |
let result = false; | |
const currentCaseConditionModifier = this.assertionCaseConditionModifiersList.shift(); | |
const invertAssertion = | |
currentCaseConditionModifier === undefined | |
? this.invertAssertionGlobally | |
: currentCaseConditionModifier; | |
this.invertedAssertionScanStatus = | |
INVERTED_ASSERTION_SCAN_STATUS.DEACTIVATED; | |
if (this.shouldAwaitActualValue) { | |
if (this.awaitedActualValueExpectationMet) { | |
result = invertAssertion | |
? !Boolean(awaitedActualValue) | |
: Boolean(awaitedActualValue); | |
} else { | |
if ( | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
) { | |
result = false; | |
} | |
} | |
} else { | |
result = invertAssertion | |
? !Boolean(awaitedActualValue) | |
: Boolean(awaitedActualValue); | |
} | |
const isAssertionOk = result === true; | |
const qualifier = getAssertionQualifier(invertAssertion); | |
const finalTestAssertionState = { | |
statusText: | |
`${!isAssertionOk ? 'expected ' : 'verified '}` + | |
((this.shouldAwaitActualValue && !this.awaitedActualValueExpectationMet) && | |
this.invertAssertionGlobally && | |
currentCaseConditionModifier !== undefined | |
? `leading case condition ${this.shouldAwaitActualValue ? 'on promise' : ''} ${qualifier} be met` | |
: `"${stringifyValue( | |
awaitedActualValue | |
)}" ${qualifier} be thruthy${this.shouldAwaitActualValue ? ' after promise settled' : ''}`), | |
casePassed: result, | |
}; | |
this.$$emitter.emit('stdout:dump', finalTestAssertionState); | |
return finalTestAssertionState; | |
}; | |
if (this.shouldAwaitActualValue) { | |
return this.actualValue.then(processTruthyTestAssertionCase); | |
} | |
return processTruthyTestAssertionCase(this.actualValue); | |
} | |
} | |
/** | |
* Extend the assertion case processor with custom assertion case matchers | |
* | |
* @param {Object<string, Function>} assertionCasesMap | |
* | |
* @returns void | |
* | |
* @example | |
* // returns void | |
* expect.extendAssertionProcessorWith({ | |
* toBeWithinNumberRange ( | |
* qualifier, | |
* actualValue, | |
* rangeUpperBound = Number.MAX_SAFE_INTEGER, | |
* rangeLowerBound = Number.MIN_SAFE_INTEGER | |
* ) { | |
* if (typeof actualValue !== number | |
* && typeof rangeUpperBound !== "number" | |
* && typeof rangeLowerBound !== "number") { | |
* throw new Error('Invalid arguments for this test assertion case matcher') | |
* } | |
* | |
* const result = actualValue >= rangeLowerBound && actualValue <= rangeUpperBound; | |
* const isAssertionOk = result === true; | |
* | |
* return { | |
* statusText: `${!isAssertionOk ? 'expected' : 'verified'} | |
* "${stringifyValue()}" ${qualifier} be within number range: (${rangeUpperBound} - ${rangeLowerBound}) | |
* ${this.shouldAwaitActualValue ? ' after promise settled' : ''}`, | |
* casePassed: result | |
* }; | |
* } | |
* }); | |
*/ | |
expect.extendAssertionProcessorWith = (assertionCasesMap = {}) => { | |
const createAssertionCaseProcessorMethod = (assertionCaseFunction) => | |
function () { | |
const currentCaseConditionModifier = this.assertionCaseConditionModifiersList.shift(); | |
const invertAssertion = | |
currentCaseConditionModifier === undefined | |
? this.invertAssertionGlobally | |
: currentCaseConditionModifier; | |
const qualifier = getAssertionQualifier(invertAssertion); | |
const args = Array.from(arguments); | |
const processCustomTestAssertionCase = (awaitedActualValue) => { | |
const _finalTestAssertionState = assertionCaseFunction.apply( | |
this, | |
[qualifier, awaitedActualValue].concat(args) | |
); | |
this.$$emitter.emit('stdout:dump', _finalTestAssertionState); | |
return _finalTestAssertionState; | |
}; | |
if (this.shouldAwaitActualValue) { | |
return this.actualValue.then(processCustomTestAssertionCase); | |
} | |
return processCustomTestAssertionCase(this.actualValue); | |
}; | |
for (let assertionCaseName in assertionCasesMap) { | |
if (assertionCasesMap.hasOwnProperty(assertionCaseName)) { | |
const assertionCaseFunction = assertionCasesMap[assertionCaseName]; | |
/* @INFO: JavaScript classes have a prototype that can be usedd to extend the class without syntactic sugar `extends` */ | |
/* @CHECK: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain#constructors */ | |
AssertionCaseProcessor.prototype[assertionCaseName] = | |
createAssertionCaseProcessorMethod(assertionCaseFunction); | |
} | |
} | |
}; | |
/* @INFO: Setup pub/sub object to handle custom events from within the instance of `AssertionCaseProcessor` class */ | |
expect.$$emitter = mitt(); | |
/* @INFO: Setup hanlder for custom event `stdout:dump` to log to command-line (or standard output also called console) */ | |
expect.$$emitter.on('stdout:dump', (state) => { | |
console.log( | |
standardOutputPrettify( | |
state.casePassed, | |
state.statusText, | |
state.casePassed ? "PASS" : "FAIL" | |
) | |
); | |
}); | |
/* @INFO: The `expect(...)` function that creates test assertions */ | |
function expect (actualValue) { | |
return new AssertionCaseProcessor(actualValue, expect.$$emitter); | |
} | |
module.exports = expect; |
Author
isocroft
commented
Aug 31, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment