alsatian-fluent-assertions
Version:
Fluent assertions extension to Alsatian xUnit framework.
167 lines • 7.69 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const alsatian_1 = require("alsatian");
const nested_properties_match_error_1 = require("../errors/nested-properties-match-error");
const elements_matcher_1 = require("./elements-matcher");
class PropertiesMatcher extends elements_matcher_1.ElementsMatcher {
constructor(actualValue, nextValue, initial, prevCore, ctxt) {
super(actualValue, nextValue, initial, prevCore, ctxt);
}
hasProperties(expected, mode = "normal" /* normal */) {
this.setCurrentNode(this.hasProperties.name, null);
this._properties(this.actualValue, expected, [], mode);
return this.generateFluentState(this.actualValue, null, false);
}
hasAll(expected, mode = "normal" /* normal */) {
this.setCurrentNode(this.hasAll.name, null);
this._properties(this.actualValue, expected, [], mode);
return this.generateFluentState(this.actualValue, null, false);
}
hasKeys(expectedKeys) {
this.setCurrentNode(this.hasKeys.name, null);
if (this.nullOrUndefined(this.actualValue)) {
if (!this.invertedContext) {
this.specError(`should be defined`, undefined, undefined);
}
return; // .not.hasKeys always passes when target not defined.
}
if (!(expectedKeys instanceof Array)) {
this.specError(`param expectedKeys should be an array type`, "[an array type]", this.id(expectedKeys));
}
if (this.maybeInvert(!expectedKeys.every(k => typeof this.actualValue[k] !== "undefined"))) {
this.specError(`should${this.negation}contain all`, expectedKeys, this.actualValue);
}
return this.generateFluentState(this.actualValue, null, false);
}
// tslint:disable-next-line
/** Asserts that the actual has the expected properties (recursive). */
_properties(actualObject, expectedObject, path, mode) {
const notDefined = !this._assertNestedPropertiesDefined(actualObject, expectedObject, path);
if (this.invertedContext && notDefined) {
// notDefined when inverted is the same as saying, "it doesn't have these properties,"
// so return without error.
return;
}
const keys = Object.keys(expectedObject);
/*tslint:disable:forin*/
for (const i in keys) {
/*tslint:enable:forin*/
const k = keys[i];
const curPath = path.slice(0); // clone
curPath.push(k);
const expected = expectedObject[k];
const actual = actualObject[k];
this._assertPropertyByType(k, actual, expected, curPath, mode);
}
}
_assertNestedPropertiesDefined(actualObject, expectedObject, path) {
if (this.nullOrUndefined(actualObject)) {
this._throwIfUnnecessarilyUndefined(actualObject, expectedObject, path);
return false;
}
return true;
}
// tslint:disable-next-line
/** Only throws if undefined props/obj isn't warranted. */
_throwIfUnnecessarilyUndefined(actualObject, expectedObject, path) {
// throw iff an undefined property isn't simply satisfying a prior negation (e.g., not.has()).
if (!this.invertedContext) {
if (path.length > 0) {
const prop = path[path.length - 1];
const fpath = this.formatKeyPath(path);
const msg = `property '${prop}' should be defined at path '${fpath}'`;
this.specError(msg, undefined, undefined);
}
else {
this.specError("expected object should be defined", undefined, undefined);
}
}
}
_assertPropertyByType(k, actual, expected, curPath, mode) {
if (mode !== "literal" /* literal */ && typeof expected === "function") {
this._assertFnProperty(k, expected, actual, curPath, mode);
}
else if (mode !== "literal" /* literal */ && expected instanceof RegExp) {
this._assertRegExpProperty(k, expected, actual, curPath);
}
else if (expected !== null &&
typeof expected === "object" &&
Object.keys(expected).length > 0 // not a no-op
) {
this._properties(actual, expected, curPath, mode);
}
else if (this.maybeInvert(expected !== actual)) {
const fpath = this.formatKeyPath(curPath);
const msg = `property ${k} at path '${fpath}' should${this.negation}equal`;
this.specError(msg, expected, actual);
}
}
formatKeyPath(path) {
path.unshift("$");
return path.join(".");
}
// tslint:disable-next-line
/**
* A properties assertion function can fail by falsy return value, or by
* throwing an error (perhaps from nested assertions).
*/
_assertFnProperty(key, assertion, actual, path, matchMode) {
let check = null;
try {
if (matchMode === "asserts" /* asserts */) {
check = assertion(this.wrap(actual));
if (typeof check === "boolean") {
this._assertFnBoolean(check, assertion, actual, path);
}
}
else {
check = assertion(actual);
this._assertFnBoolean(check, assertion, actual, path);
}
}
catch (err) {
this._failFnError(err, path);
}
}
_failFnError(err, path) {
if (err instanceof alsatian_1.MatchError) {
throw new nested_properties_match_error_1.NestedPropertiesMatchError(this, "failed nested expectation", this.formatKeyPath(path), err);
}
else {
throw new nested_properties_match_error_1.NestedPropertiesMatchError(this, "threw unexpected error", this.formatKeyPath(path), err);
}
}
_assertFnBoolean(check, assertion, actual, path) {
const fpath = this.formatKeyPath(path);
let msg = `Property at path '${fpath}': `;
if (typeof check === "boolean" && this.maybeInvert(!check)) {
(msg = msg + `should${this.negation}satisfy lambda assertion`),
this.specError(msg, this.getFnString(assertion), actual);
}
else if (this.invert) {
(msg =
msg +
"expected lambda to return false, or yield a failed nested expectation or error"),
this.specError(msg, this.getFnString(assertion), actual);
}
}
_assertRegExpProperty(key, regexp, actual, path) {
const kpath = this.formatKeyPath(path);
if (actual instanceof RegExp) {
if (this.maybeInvert(actual.toString() !== regexp.toString())) {
const msg = `regular expressions at path '${kpath}' should${this.negation}be equal`;
this.specError(msg, this.id(regexp), this.id(actual));
}
}
else if (typeof actual !== "string") {
const msg = `expected type 'string' for regexp match at path '${kpath}'`;
this.specError(msg, "string", typeof actual);
}
else if (this.maybeInvert(!regexp.test(actual))) {
const msg = `regular expression at path '${kpath}' should${this.negation}match`;
this.specError(msg, this.id(regexp), actual);
}
}
}
exports.PropertiesMatcher = PropertiesMatcher;
//# sourceMappingURL=properties-matcher.js.map