UNPKG

@plugjs/expect5

Version:

Unit Testing for the PlugJS Build System ========================================

615 lines (613 loc) 23.2 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // expectation/expectations.ts var expectations_exports = {}; __export(expectations_exports, { Expectations: () => Expectations, NegativeExpectations: () => NegativeExpectations }); module.exports = __toCommonJS(expectations_exports); var import_diff = require("./diff.cjs"); var import_include = require("./include.cjs"); var import_types = require("./types.cjs"); var Expectations = class _Expectations { /** * Create an {@link Expectations} instance associated with the specified * value and error remarks. * * Optionally a parent {@link Expectations} instance can be specified. */ constructor(value, remarks, parent) { this.value = value; this.remarks = remarks; this.parent = parent; this.not = new NegativeExpectations(this); } /** The {@link NegativeExpectations} associated with _this_ */ not; /** Throw an {@link ExpectationError} associated with _this_ */ _fail(details, diff2) { const error = new import_types.ExpectationError(this, details, diff2); Error.captureStackTrace(error, this._fail); throw error; } toBeA(type, assertionOrMatcher) { if ((0, import_types.typeOf)(this.value) === type) { if ((0, import_types.isMatcher)(assertionOrMatcher)) { assertionOrMatcher.expect(this.value); } else if (assertionOrMatcher) { assertionOrMatcher(this); } return this; } this._fail(`to be ${(0, import_types.prefixType)(type)}`); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `Date`, a `string` parseable into a `Date`, or a * `number` indicating the milliseconds from the epoch, _strictly after_ * the specified date. * * Negation: {@link Expectations.toBeBeforeOrEqual `toBeBeforeOrEqual(...)`} */ toBeAfter(value, deltaMs) { const after = value instanceof Date ? value.getTime() : typeof value === "number" ? value : new Date(value).getTime(); const timestamp = this.value instanceof Date ? this.value.getTime() : typeof this.value === "string" ? new Date(this.value).getTime() : typeof this.value === "number" ? this.value : void 0; if (typeof timestamp !== "number") { this._fail(`to be a string, a number or an instance of ${(0, import_types.stringifyConstructor)(Date)}`); } else if (isNaN(timestamp)) { this._fail("to be a valid date"); } if (timestamp <= after) { this._fail(`to be after ${(0, import_types.stringifyValue)(new Date(after))}`); } if (deltaMs !== void 0) return this.toBeBefore(after + deltaMs + 1); return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `Date`, a `string` parseable into a `Date`, or a * `number` indicating the milliseconds from the epoch, _after or equal_ * the specified date. * * Negation: {@link Expectations.toBeBefore `toBeBefore(...)`} */ toBeAfterOrEqual(value, deltaMs) { const after = value instanceof Date ? value.getTime() : typeof value === "number" ? value : new Date(value).getTime(); const delta = deltaMs === void 0 ? void 0 : deltaMs + 1; return this.toBeAfter(after - 1, delta); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `Date`, a `string` parseable into a `Date`, or a * `number` indicating the milliseconds from the epoch, _strictly before_ * the specified date. * * Negation: {@link Expectations.toBeAfterOrEqual `toBeAfterOrEqual(...)`} */ toBeBefore(value, deltaMs) { const before = value instanceof Date ? value.getTime() : typeof value === "number" ? value : new Date(value).getTime(); const timestamp = this.value instanceof Date ? this.value.getTime() : typeof this.value === "string" ? new Date(this.value).getTime() : typeof this.value === "number" ? this.value : void 0; if (typeof timestamp !== "number") { this._fail(`to be a string, a number or an instance of ${(0, import_types.stringifyConstructor)(Date)}`); } else if (isNaN(timestamp)) { this._fail("to be a valid date"); } if (timestamp >= before) { this._fail(`to be before ${(0, import_types.stringifyValue)(new Date(before))}`); } if (deltaMs !== void 0) return this.toBeAfter(before - deltaMs - 1); return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `Date`, a `string` parseable into a `Date`, or a * `number` indicating the milliseconds from the epoch, _before or equal_ * the specified date. * * Negation: {@link Expectations.toBeAfter `toBeAfter(...)`} */ toBeBeforeOrEqual(value, deltaMs) { const before = value instanceof Date ? value.getTime() : typeof value === "number" ? value : new Date(value).getTime(); const delta = deltaMs === void 0 ? void 0 : deltaMs + 1; return this.toBeBefore(before + 1, delta); } toBeCloseTo(value, delta) { const min = value - delta; const max = value + delta; return this.toBeWithinRange(min, max); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be neither `null` nor `undefined`. * * Negation: {@link NegativeExpectations.toBeDefined `not.toBeDefined()`} */ toBeDefined() { if (this.value !== null && this.value !== void 0) return this; this._fail(`to be neither ${(0, import_types.stringifyValue)(null)} nor ${(0, import_types.stringifyValue)(void 0)}`); } toBeError(constructorOrMessage, maybeMessage) { const [constructor, message] = typeof constructorOrMessage === "function" ? [constructorOrMessage, maybeMessage] : [Error, constructorOrMessage]; if (message === void 0) return this.toBeInstanceOf(constructor); return this.toBeInstanceOf(constructor, (assert) => { assert.toHaveProperty("message", (assertMessage) => { assertMessage.toBeA("string"); if (typeof message === "string") assertMessage.toStrictlyEqual(message); else assertMessage.toMatch(message); }); }); } /* ------------------------------------------------------------------------ */ /** Expects the value strictly equal to `false`. */ toBeFalse() { return this.toStrictlyEqual(false); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be _falsy_ (zero, empty string, `false`, ...). * * Negation: {@link Expectations.toBeTruthy `toBeTruthy()`} */ toBeFalsy() { if (!this.value) return this; this._fail("to be falsy"); } toBeGreaterThan(value) { this.toBeA(typeof value); if (this.value > value) return this; this._fail(`to be greater than ${(0, import_types.stringifyValue)(value)}`); } toBeGreaterThanOrEqual(value) { this.toBeA(typeof value); if (this.value >= value) return this; this._fail(`to be greater than or equal to ${(0, import_types.stringifyValue)(value)}`); } toBeInstanceOf(constructor, assertionOrMatcher) { if (this.value instanceof constructor) { if ((0, import_types.isMatcher)(assertionOrMatcher)) { assertionOrMatcher.expect(this.value); } else if (assertionOrMatcher) { assertionOrMatcher(this); } return this; } this._fail(`to be an instance of ${(0, import_types.stringifyConstructor)(constructor)}`); } toBeLessThan(value) { this.toBeA(typeof value); if (this.value < value) return this; this._fail(`to be less than ${(0, import_types.stringifyValue)(value)}`); } toBeLessThanOrEqual(value) { this.toBeA(typeof value); if (this.value <= value) return this; this._fail(`to be less than or equal to ${(0, import_types.stringifyValue)(value)}`); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be `NaN`. * * Negation: {@link NegativeExpectations.toBeNaN `not.toBeNaN()`} */ toBeNaN() { const expectations = this.toBeA("number"); if (isNaN(expectations.value)) return expectations; this._fail(`to be ${(0, import_types.stringifyValue)(NaN)}`); } /* ------------------------------------------------------------------------ */ /** Expects the value to strictly equal `null`. */ toBeNull() { return this.toStrictlyEqual(null); } /* ------------------------------------------------------------------------ */ /** Expects the value to strictly equal `true`. */ toBeTrue() { return this.toStrictlyEqual(true); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be _falsy_ (non-zero, non-empty string, ...). * * Negation: {@link Expectations.toBeFalsy `toBeFalsy()`} */ toBeTruthy() { if (this.value) return this; this._fail("to be truthy"); } /* ------------------------------------------------------------------------ */ /** Expects the value to strictly equal `undefined`. */ toBeUndefined() { return this.toStrictlyEqual(void 0); } toBeWithinRange(min, max) { if (max < min) { const num = max; max = min; min = num; } this.toBeA(typeof min); if (this.value < min || this.value > max) { this._fail(`to be within ${(0, import_types.stringifyValue)(min)}...${(0, import_types.stringifyValue)(max)}`); } return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be _deep equal to_ the specified expected one. * * When `strict` is `true` (defaults to `false`) enumerable keys associated * with an `undefined` value found in the _actual_ object will have to be * also defined in the _expected_ object. * * For example: * * ```ts * // non-strict mode * expect({ foo: undefined }).toEqual({}) // will pass * expect({}).toEqual({ foo: undefined }) // will pass * expect({ foo: undefined }).toEqual({ foo: undefined }) // will pass * * // strict mode * expect({ foo: undefined }).toEqual({}, true) // will fail * expect({}).toEqual({ foo: undefined }, true) // will fail * expect({}).toEqual({ foo: undefined }) // will fail ("foo" is missing, whether "strict" is true or false) * ``` * * Negation: {@link NegativeExpectations.toEqual `not.toEqual(...)`} */ toEqual(expected, strict = false) { if (this.value === expected) return this; const result = (0, import_diff.diff)(this.value, expected, strict); if (result.diff) { if ((0, import_types.isMatcher)(expected)) { this._fail("to satisfy expectation matcher", result); } else { this._fail(`to equal ${(0, import_types.stringifyValue)(expected)}`, result); } } else { return this; } } /* ------------------------------------------------------------------------ */ /** * Expects the value to have a `number` _property_ `length` with the specified * expected value. * * Negation: {@link NegativeExpectations.toHaveLength `not.toHaveLength(...)`} */ toHaveLength(length) { this.toBeDefined(); const realLength = this.value["length"]; if (typeof realLength !== "number") { this._fail('to have a numeric "length" property'); } else if (realLength !== length) { this._fail(`to have length ${length}`); } return this; } toHaveProperty(property, assertionOrMatcher) { this.toBeDefined(); const propertyValue = this.value[property]; let hasProperty; try { hasProperty = property in this.value; } catch { hasProperty = propertyValue !== void 0; } if (!hasProperty) { this._fail(`to have property "${String(property)}"`); } if (assertionOrMatcher) { const parent = { expectations: this, prop: property }; try { if ((0, import_types.isMatcher)(assertionOrMatcher)) { assertionOrMatcher.expect(propertyValue, parent); } else if (assertionOrMatcher) { const expectations = new _Expectations(propertyValue, this.remarks, parent); assertionOrMatcher(expectations); } } catch (error) { if (error instanceof import_types.ExpectationError && error.diff) { error.diff = { diff: true, value: this.value, props: { [property]: error.diff } }; } throw error; } } else if (propertyValue === void 0) { this._fail(`has property "${String(property)}" with value ${(0, import_types.stringifyValue)(void 0)}`); } return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to have a `number` _property_ `size` with the specified * expected value. * * Negation: {@link NegativeExpectations.toHaveSize `not.toHaveSize(...)`} */ toHaveSize(size) { this.toBeDefined(); const realSize = this.value["size"]; if (typeof realSize !== "number") { this._fail('to have a numeric "size" property'); } else if (realSize !== size) { this._fail(`to have size ${size}`); } return this; } toInclude(contents) { (0, import_include.toInclude)(this, false, contents); return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `string` _matching_ the specified sub-`string` * or {@link RegExp}. * * Negation: {@link NegativeExpectations.toMatch `not.toMatch(...)`} */ toMatch(matcher) { const expectations = this.toBeA("string"); if (expectations.value.match(matcher)) return expectations; this._fail(`to match ${(0, import_types.stringifyValue)(matcher)}`); } /* ------------------------------------------------------------------------ */ /** * Expect the value to be an {@link Iterable} object includind _all_ values * (and only those values) from the specified _array_ or {@link Set}, * in any order. */ toMatchContents(contents) { (0, import_include.toMatchContents)(this, contents); return this; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be _strictly equal to_ the specified expected one. * * Negation: {@link NegativeExpectations.toStrictlyEqual `not.toStrictlyEqual(...)`} */ toStrictlyEqual(expected) { if (this.value === expected) return this; const diff2 = { diff: true, value: this.value, expected }; this._fail(`to strictly equal ${(0, import_types.stringifyValue)(expected)}`, diff2); } toThrow(assertionOrMatcher) { const func = this.toBeA("function"); let passed = false; try { func.value(); passed = true; } catch (thrown) { if ((0, import_types.isMatcher)(assertionOrMatcher)) { assertionOrMatcher.expect(thrown); } else if (assertionOrMatcher) { assertionOrMatcher(new _Expectations(thrown, this.remarks)); } } if (passed) this._fail("to throw"); return this; } toThrowError(constructorOrMessage, maybeMessage) { const [constructor, message] = typeof constructorOrMessage === "function" ? [constructorOrMessage, maybeMessage] : [Error, constructorOrMessage]; return this.toThrow((assert) => assert.toBeError(constructor, message)); } }; var NegativeExpectations = class { /** * Create a {@link NegativeExpectations} instance associated with the * specified (positive) {@link Expectations}. */ constructor(_expectations) { this._expectations = _expectations; this._value = _expectations.value; } /** For convenience, the value associated with the {@link Expectations} */ _value; /** Throw an {@link ExpectationError} associated with _this_ */ _fail(details, diff2) { throw new import_types.ExpectationError(this._expectations, details, diff2); } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to be of the specified _extended_ * {@link TypeName type}. * * Negates: {@link Expectations.toBeA `toBeA(...)`} */ toBeA(type) { if ((0, import_types.typeOf)(this._value) !== type) return this._expectations; this._fail(`not to be ${(0, import_types.prefixType)(type)}`); } toBeCloseTo(value, delta) { const min = value - delta; const max = value + delta; return this.toBeWithinRange(min, max); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be either `null` or `undefined`. * * Negates: {@link Expectations.toBeDefined `toBeDefined()`} */ toBeDefined() { if (this._value === null || this._value === void 0) { return this._expectations; } this._fail(`to be ${(0, import_types.stringifyValue)(null)} or ${(0, import_types.stringifyValue)(void 0)}`); } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to be an instance of the specified * {@link Constructor}. * * Negates: {@link Expectations.toBeInstanceOf `toBeInstanceOf(...)`} */ toBeInstanceOf(constructor) { if (this._value instanceof constructor) { this._fail(`not to be an instance of ${(0, import_types.stringifyConstructor)(constructor)}`); } return this._expectations; } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to be `NaN`. * * Negates: {@link Expectations.toBeNaN `toBeNaN()`} */ toBeNaN() { const expectations = this._expectations.toBeA("number"); if (isNaN(expectations.value)) this._fail(`not to be ${(0, import_types.stringifyValue)(NaN)}`); return expectations; } toBeWithinRange(min, max) { if (max < min) { const num = max; max = min; min = num; } this._expectations.toBeA(typeof min); if (this._value >= min && this._value <= max) { this._fail(`not to be within ${(0, import_types.stringifyValue)(min)}...${(0, import_types.stringifyValue)(max)}`); } return this._expectations; } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to be _deep equal to_ the specified expected * one. * * Negates: {@link Expectations.toEqual `toEqual(...)`} */ toEqual(expected, strict = false) { let result = { diff: false, value: this._value }; if (this._value !== expected) { result = (0, import_diff.diff)(this._value, expected, strict); if (result.diff) return this._expectations; } if ((0, import_types.isMatcher)(expected)) { this._fail("not to satisfy expectation matcher", result); } else { this._fail(`not to equal ${(0, import_types.stringifyValue)(expected)}`, result); } } /* ------------------------------------------------------------------------ */ /** * Expects the value to have a `number` _property_ `length` _different_ from * the specified expected value. * * Negates: {@link Expectations.toHaveLength `toHaveLength(...)`} */ toHaveLength(length) { this._expectations.toBeDefined(); const realLength = this._value["length"]; if (typeof realLength !== "number") { this._fail('to have a numeric "length" property'); } else if (realLength === length) { this._fail(`not to have length ${length}`); } return this._expectations; } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to have the specified _property_. * * Negates: {@link Expectations.toHaveProperty `toHaveProperty(...)`} */ toHaveProperty(property) { this._expectations.toBeDefined(); let hasProperty; try { hasProperty = property in this._value; } catch { hasProperty = this._value[property] !== void 0; } if (hasProperty) this._fail(`not to have property "${String(property)}"`); return this._expectations; } /* ------------------------------------------------------------------------ */ /** * Expects the value to have a `number` _property_ `size` _different_ from * the specified expected value. * * Negates: {@link Expectations.toHaveSize `toHaveSize(...)`} */ toHaveSize(size) { this._expectations.toBeDefined(); const realSize = this._value["size"]; if (typeof realSize !== "number") { this._fail('to have a numeric "size" property'); } else if (realSize === size) { this._fail(`not to have size ${size}`); } return this._expectations; } toInclude(contents) { (0, import_include.toInclude)(this._expectations, true, contents); return this._expectations; } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `string` _**NOT MATCHING**_ the specified * sub-`string` or {@link RegExp}. * * Negates: {@link Expectations.toMatch `toMatch(...)`} */ toMatch(matcher) { const expectations = this._expectations.toBeA("string"); if (!expectations.value.match(matcher)) return expectations; this._fail(`not to match ${(0, import_types.stringifyValue)(matcher)}`); } /* ------------------------------------------------------------------------ */ /** * Expects the value to be a `function` not throwing anything. * * Negates: {@link Expectations.toThrow `toThrow(...)`} */ toThrow() { const expectations = this._expectations.toBeA("function"); try { expectations.value(); return expectations; } catch { this._fail("not to throw"); } } /* ------------------------------------------------------------------------ */ /** * Expects the value _**NOT**_ to be _strictly equal to_ the specified * expected one. * * Negates: {@link Expectations.toStrictlyEqual `toStrictlyEqual(...)`} */ toStrictlyEqual(expected) { if (this._value !== expected) return this._expectations; this._fail(`not to strictly equal ${(0, import_types.stringifyValue)(expected)}`); } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Expectations, NegativeExpectations }); //# sourceMappingURL=expectations.cjs.map