@plugjs/expect5
Version:
Unit Testing for the PlugJS Build System ========================================
615 lines (613 loc) • 23.2 kB
JavaScript
"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