UNPKG

@applicvision/js-toolbox

Version:

A collection of tools for modern JavaScript development

241 lines (205 loc) 5.37 kB
import assert, { AssertionError } from 'node:assert/strict' export class ExpectationError extends AssertionError { file } export class Expectation { #negated = false #value #propertyName constructor(value) { this.#value = value } get to() { return this } get be() { return this } get have() { return this } get not() { this.#negated = !this.#negated return this } get #negateMessage() { return this.#negated ? 'not ' : '' } property(propertyName) { try { this.#value[propertyName] } catch (error) { const err = new ExpectationError({ message: error.message, stackStartFn: this.property }) err.file = err.stack?.match(/\((.+)\)/)?.[1] throw err } this.#propertyName = propertyName return this } get #computedValue() { if (this.#propertyName) { return this.#value[this.#propertyName] } return this.#value } #pickAssert(standard, negated) { return this.#negated ? negated : standard } get #equalAssert() { return this.#negated ? assert.notEqual : assert.equal } #runAssertion(assertFunction, { message, caller, compareValue, value = this.#computedValue }) { try { if ([assert.ok].includes(assertFunction)) { assertFunction(value, message) } else { assertFunction(value, compareValue, message) } } catch (error) { const err = new ExpectationError({ message: error.message, stackStartFn: this[caller] }) err.file = err.stack?.match(/\((.+)\)/)?.[1] throw err } } async #runAsyncAssertion(assertFunction, { message, errorMatcher }) { try { await assertFunction(this.#computedValue, errorMatcher, message) } catch (error) { const err = new ExpectationError({ message: error.message }) err.file = error.actual?.stack.match(/\(?file.+/) throw err } } equal(compareValue) { this.#runAssertion( this.#equalAssert, { caller: 'equal', compareValue }) } deepEqual(compareValue) { this.#runAssertion( this.#pickAssert(assert.deepEqual, assert.notDeepEqual), { caller: 'deepEqual', compareValue }) } length(expectedLength) { const value = this.#computedValue.length this.#runAssertion(this.#equalAssert, { caller: 'length', compareValue: expectedLength, value, message: `Expected a length ${this.#negated ? 'other than' : 'of'} ${expectedLength}, but got ${value}.` }) } greaterThan(compareLength) { const value = this.#computedValue > compareLength this.#runAssertion(this.#equalAssert, { caller: 'greaterThan', value, compareValue: true, message: `Expected ${this.#computedValue} ${this.#negateMessage}to be greater than ${compareLength}.` }) } lessThan(compareNumber) { const value = this.#computedValue < compareNumber this.#runAssertion(this.#equalAssert, { caller: 'lessThan', value, compareValue: true, message: `Expected ${this.#computedValue} ${this.#negateMessage}to be less than ${compareNumber}.` }) } size(compareValue) { this.#runAssertion(this.#equalAssert, { caller: 'size', compareValue, value: this.#computedValue.size }) } match(compareValue) { this.#runAssertion(this.#pickAssert(assert.match, assert.doesNotMatch), { caller: 'match', compareValue, }) } a(expectedType) { const value = this.#computedValue if (typeof expectedType == 'string') { this.#runAssertion(this.#equalAssert, { caller: 'a', compareValue: expectedType.toLowerCase(), value: typeof value }) } else { this.#runAssertion(this.#equalAssert, { caller: 'a', compareValue: true, value: value instanceof expectedType, message: `Expected ${this.#negateMessage}a ${expectedType.name}, but got ${value?.constructor.name ?? value}.` }) } } resolve(errorMatcher) { return this.#runAsyncAssertion(this.#pickAssert(assert.doesNotReject, assert.rejects), { errorMatcher, message: `Expected ${this.#computedValue} ${this.#negateMessage}to resolve.` }) } reject(errorMatcher = undefined) { return this.#runAsyncAssertion(this.#pickAssert(assert.rejects, assert.doesNotReject), { errorMatcher, message: `Expected ${this.#computedValue} ${this.#negateMessage}to reject.` }) } empty() { const value = this.#computedValue const lengthOrSize = value.length ?? value.size ?? Object.keys(value ?? 0).length this.#runAssertion(this.#equalAssert, { compareValue: 0, value: lengthOrSize, caller: 'empty', message: `Expected ${value} ${this.#negateMessage}to be empty.` }) } false() { this.#runAssertion(this.#equalAssert, { compareValue: false, caller: 'false', message: `Expected ${this.#computedValue} ${this.#negateMessage}to be false` }) } true() { this.#runAssertion(this.#equalAssert, { compareValue: true, caller: 'true', message: `Expected ${this.#computedValue} ${this.#negateMessage}to be true` }) } undefined() { this.#runAssertion(this.#equalAssert, { compareValue: undefined, caller: 'undefined', message: `Expected ${this.#computedValue} ${this.#negateMessage}to be undefined` }) } ok() { this.#runAssertion(assert.ok, { caller: 'ok', message: `Expected ${this.#computedValue} ${this.#negateMessage}to be truthy` }) } } Expectation.prototype.an = Expectation.prototype.a export default function expect(value) { return new Expectation(value) }