flagpole
Version:
Simple and fast DOM integration, headless or headful browser, and REST API testing framework.
690 lines • 29.5 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Assertion = void 0;
const value_1 = require("../value");
const assertion_schema_1 = require("./assertion-schema");
const util_1 = require("../util");
const http_response_1 = require("../http/http-response");
const image_compare_1 = require("../visual/image-compare");
const assertion_is_1 = require("./assertion-is");
const json_to_jsonschema_1 = require("@flagpolejs/json-to-jsonschema");
class Assertion {
constructor(context, thisValue, message) {
this._not = false;
this._optional = false;
this._result = null;
this._finishedResolver = () => { };
this._statement = null;
this._assertionMade = false;
this._defaultMessages = ["True", "False"];
this._context = context;
this._input = thisValue;
this._message = typeof message == "undefined" ? null : message;
this._finishedPromise = new Promise((resolve) => {
this._finishedResolver = resolve;
});
}
get value() {
return this._getCompareValue(this._input);
}
get text() {
return String(this._getCompareValue(this._input));
}
get subject() {
const type = util_1.toType(this._input);
let name;
if (this._input && this._input.name) {
name = this._input.name;
}
else if (type == "array") {
name = "Array";
}
else if (type == "object") {
name = "Object";
}
else if (type == "domelement") {
name = "DOM Element";
}
else if (type == "cssrule") {
name = "CSS Rule";
}
else {
name = String(this._input);
}
if (String(name).length > 64) {
name = name.substr(0, 61) + "...";
}
return util_1.isNullOrUndefined(name) || String(name).length == 0
? "It"
: String(name);
}
get is() {
return new assertion_is_1.AssertionIs(this);
}
get and() {
return this._clone(this._input, `&& ${this._getMessage()}`);
}
get type() {
return this._clone(util_1.toType(this.value), `Type of ${this.subject}`);
}
get length() {
const length = (() => {
const thisValue = this.value;
return thisValue && thisValue.length ? thisValue.length : 0;
})();
return this._clone(length, `Length of ${this.subject}`);
}
get trim() {
return this._clone(this.text.trim(), `Trim of ${this.subject}`);
}
get keys() {
const keys = (() => {
const thisValue = this.value;
return util_1.isNullOrUndefined(thisValue) ? [] : Object.keys(thisValue);
})();
return this._clone(keys, `Keys of ${this.subject}`);
}
get values() {
const values = (() => {
const thisValue = this.value;
return util_1.isNullOrUndefined(thisValue) ? [] : Object.values(thisValue);
})();
return this._clone(values, `Values of ${this.subject}`);
}
get not() {
this._not = true;
return this;
}
get optional() {
this._optional = true;
return this;
}
get result() {
return this._finishedPromise;
}
get assertionMade() {
return this._assertionMade || this._finishedPromise.isResolved();
}
get isFinalized() {
return this._result !== null || this._statement !== null;
}
get name() {
return this._message || this.isFlagpoleValue
? this._input.name
: String(this._input);
}
get passed() {
return this._statement;
}
get isFlagpoleValue() {
var _a;
return !!((_a = this._input) === null || _a === void 0 ? void 0 : _a.isFlagpoleValue);
}
get context() {
return this._context;
}
static create(context, thisValue, message) {
return __awaiter(this, void 0, void 0, function* () {
return new Assertion(context, thisValue, message);
});
}
visible() {
return __awaiter(this, void 0, void 0, function* () {
const is = yield this._is("isVisible");
this.setDefaultNotMessage(`${this.subject} is not visible`);
this.setDefaultMessage(`${this.subject} is visible`);
return this.execute(is, is);
});
}
hidden() {
return __awaiter(this, void 0, void 0, function* () {
const is = yield this._is("isHidden");
this.setDefaultMessages(`${this.subject} is not hidden`, `${this.subject} is hidden`);
return this.execute(is, is);
});
}
hasValue(value) {
return __awaiter(this, void 0, void 0, function* () {
const hasValue = yield this._hasValue("hasValue", value);
const thatValue = this._getCompareValue(value);
this.setDefaultMessages(`${this.subject} does not have value ${thatValue}`, `${this.subject} has value ${thatValue}`);
return this.execute(hasValue, thatValue);
});
}
hasProperty(key, value) {
return __awaiter(this, void 0, void 0, function* () {
const hasKey = yield this._hasKeyValue("hasProperty", key, value);
this.setDefaultMessages(`${this.subject} does not have property ${key}`, `${this.subject} has property ${key}`);
return this.execute(hasKey, hasKey);
});
}
hasAttribute(key, value) {
return __awaiter(this, void 0, void 0, function* () {
const hasKey = yield this._hasKeyValue("hasAttribute", key, value);
this.setDefaultMessages(`${this.subject} does not have attribute ${key}`, `${this.subject} has attribute ${key}`);
return this.execute(hasKey, hasKey);
});
}
hasClassName(key, value) {
return __awaiter(this, void 0, void 0, function* () {
const hasKey = yield this._hasKeyValue("hasClassName", key, value);
this.setDefaultMessages(`${this.subject} does not have class named ${key}`, `${this.subject} has class named ${key}`);
return this.execute(hasKey, hasKey);
});
}
hasData(key, value) {
return __awaiter(this, void 0, void 0, function* () {
const hasKey = yield this._hasKeyValue("hasData", key, value);
this.setDefaultMessages(`${this.subject} does not have data ${key}`, `${this.subject} has data ${key}`);
return this.execute(hasKey, hasKey);
});
}
hasText(text) {
return __awaiter(this, void 0, void 0, function* () {
const hasValue = yield this._hasValue("hasText", text);
this.setDefaultMessages(`${this.subject} does not have text "${text}"`, `${this.subject} has text "${text}"`);
return this.execute(hasValue, hasValue);
});
}
hasTag(tagName) {
return __awaiter(this, void 0, void 0, function* () {
const hasValue = yield this._hasValue("hasTag", tagName);
tagName
? this.setDefaultMessages(`${this.subject} is not <${tagName}>`, `${this.subject} is <${tagName}>`)
: this.setDefaultMessages(`${this.subject} is not a tag`, `${this.subject} is a tag`);
return this.execute(hasValue, this._input.tagName || "Not a tag");
});
}
exactly(value) {
const { thisValue, thatValue } = this._getValues(value);
const thisType = util_1.toType(thisValue);
const thatType = util_1.toType(thatValue);
this.setDefaultMessages(`${this.subject} is not exactly ${thatValue}`, `${this.subject} is exactly ${thatValue}`);
if (thisType == "array" && thatType == "array") {
return this.execute(util_1.arrayExactly(thisValue, thatValue), thisValue);
}
if (thisType == "object" && thatType == "object") {
return this.execute(util_1.deepStrictEqual(thisValue, thatValue), thisValue);
}
return this.execute(thisValue === thatValue, thisValue, null);
}
equals(value) {
const { thisValue, thatValue } = this._getValues(value);
const thisType = util_1.toType(thisValue);
const thatType = util_1.toType(thatValue);
const thisStringified = JSON.stringify(thisValue, null, 2);
const thatStringified = JSON.stringify(thatValue, null, 2);
this.setDefaultMessages(`${this.subject} does not equal ${thatStringified}`, `${this.subject} equals ${thatStringified}`);
if (thisType == "array" && thatType == "array") {
return this.execute(util_1.arrayEquals(thisValue, thatValue), thisStringified, null, thatStringified);
}
if (thisType == "object" && thatType == "object") {
return this.execute(util_1.deepEqual(thisValue, thatValue), thisStringified, null, thatStringified);
}
return this.execute(thisValue == thatValue, thisValue, null, thatValue);
}
like(value) {
const thisValue = this.text.toLowerCase().trim();
const thatValue = String(this._getCompareValue(value))
.toLowerCase()
.trim();
this.setDefaultMessages(`${this.subject} is not like ${thatValue}`, `${this.subject} is like ${thatValue}`);
if (Array.isArray(thisValue) && Array.isArray(thatValue)) {
return this.execute(util_1.arrayEquals(thisValue.map((value) => String(value).toLowerCase().trim()), thatValue.map((value) => String(value).toLowerCase().trim())), thisValue);
}
return this.execute(thisValue == thatValue, thisValue);
}
greaterThan(value) {
const { thisValue, thatValue } = this._getValues(value);
this.setDefaultMessages(`${this.subject} is not greater than ${thatValue}`, `${this.subject} is greater than ${thatValue}`);
return this.execute(parseFloat(thisValue) > parseFloat(thatValue), thisValue, null, `> ${thatValue}`);
}
greaterThanOrEquals(value) {
const { thisValue, thatValue } = this._getValues(value);
this.setDefaultMessages(`${this.subject} is not greater than or equal to ${thatValue}`, `${this.subject} is greater than or equal to ${thatValue}`);
return this.execute(parseFloat(thisValue) >= parseFloat(thatValue), thisValue, null, `>= ${thatValue}`);
}
lessThan(value) {
const { thisValue, thatValue } = this._getValues(value);
this.setDefaultMessages(`${this.subject} is not less than ${thatValue}`, `${this.subject} is less than ${thatValue}`);
return this.execute(parseFloat(thisValue) < parseFloat(thatValue), thisValue, null, `< ${thatValue}`);
}
lessThanOrEquals(value) {
const { thisValue, thatValue } = this._getValues(value);
this.setDefaultMessages(`${this.subject} is not less than or equal to ${thatValue}`, `${this.subject} is less than or equal to ${thatValue}`);
return this.execute(parseFloat(thisValue) <= parseFloat(thatValue), thisValue, null, `<= ${thatValue}`);
}
between(min, max) {
const thisValue = this.value;
const thatMin = parseFloat(this._getCompareValue(min));
const thatMax = parseFloat(this._getCompareValue(max));
this.setDefaultMessages(`${this.subject} is not between ${min} and ${max}`, `${this.subject} is between ${min} and ${max}`);
return this.execute(parseFloat(thisValue) >= thatMin && parseFloat(thisValue) <= thatMax, thisValue, null, `Between ${min} and ${max}`);
}
matches(value) {
const { thisValue, thatValue } = this._getValues(value);
const thatType = util_1.toType(thatValue);
if (["string", "regexp"].includes(thatType)) {
const pattern = thatType == "regexp" ? thatValue : new RegExp(value);
this.setDefaultMessages(`${this.subject} does not match ${String(pattern)}`, `${this.subject} matches ${String(pattern)}`);
return this.execute(pattern.test(thisValue), String(thisValue));
}
return this._executeSchema(thisValue, json_to_jsonschema_1.default(thatValue), "JsonSchema");
}
in(values) {
const thisValue = this.value;
this.setDefaultMessages(`${this.subject} is not in list: ${values.join(", ")}`, `${this.subject} is in list: ${values.join(", ")}`);
return this.execute(values.indexOf(thisValue) >= 0, thisValue);
}
includes(value) {
return this.contains(value);
}
contains(value) {
const { thisValue, thatValue } = this._getValues(value), thisType = util_1.toType(thisValue), thatType = util_1.toType(thisValue);
const bool = (() => {
if (util_1.isNullOrUndefined(this._input)) {
return thisValue === thatValue;
}
else if (thisType == "array") {
return util_1.toArray(thatValue).every((val) => thisValue.includes(val));
}
else if (thisType == "object") {
if (thatType == "object") {
return util_1.objectContains(thisValue, thatValue);
}
else {
return util_1.objectContainsKeys(thisValue, thatValue);
}
}
else {
return String(thisValue).includes(thatValue);
}
})();
this.setDefaultMessages(`${this.subject} does not contain ${thatValue}`, `${this.subject} contains ${thatValue}`);
return this.execute(bool, thisValue);
}
looksLike(controlImage, allowedDifference = 0) {
const toCompare = this.value instanceof http_response_1.HttpResponse
? Buffer.from(this.value.body, "base64")
: this.value;
this.setDefaultMessages(`Images do not match.`, `Images match.`);
let assertionPassed = false;
let details = "";
const imageCompare = new image_compare_1.ImageCompare(this._context, toCompare, controlImage);
const percentDifferenceAllowed = (() => {
if (typeof allowedDifference === "number") {
return allowedDifference >= 0 && allowedDifference < 1
? allowedDifference * 100
: 0;
}
const n = parseFloat(allowedDifference);
return !isNaN(n) && n >= 0 && n < 100 ? n : 0;
})();
try {
const result = imageCompare.compare({
threshold: 0.1,
});
assertionPassed = result.percentDifferent <= percentDifferenceAllowed;
if (!assertionPassed) {
details =
result.percentDifferent.toFixed(2) +
`% of the image did not match (${result.pixelsDifferent} pixels).` +
` This is over the allowed difference of ${percentDifferenceAllowed}%.` +
` Diff image: ${result.diffPath}`;
}
}
catch (err) {
details = String(err);
}
return this.execute(assertionPassed, details);
}
startsWith(value) {
const { thisValue, thatValue } = this._getValues(value);
const bool = (() => {
if (util_1.toType(thisValue) == "array") {
return thisValue[0] == value;
}
if (!util_1.isNullOrUndefined(thisValue)) {
return String(thisValue).startsWith(thatValue);
}
return false;
})();
this.setDefaultMessages(`${this.subject} does not start with ${thatValue}`, `${this.subject} starts with ${thatValue}`);
return this.execute(bool, String(thisValue));
}
endsWith(value) {
const { thisValue, thatValue } = this._getValues(value);
const bool = (() => {
if (util_1.toType(thisValue) == "array") {
return thisValue[thisValue.length - 1] == thatValue;
}
if (!util_1.isNullOrUndefined(thisValue)) {
return String(thisValue).endsWith(thatValue);
}
return false;
})();
this.setDefaultMessages(`${this.subject} does not end with ${thatValue}`, `${this.subject} ends with ${thatValue}`);
return this.execute(bool, String(this._input));
}
exists() {
const thisValue = this.value;
this.setDefaultMessages(`${this.subject} does not exist`, `${this.subject} exists`);
return this.execute(!util_1.isNullOrUndefined(thisValue), this._getName(this._input), thisValue && thisValue.path
? thisValue.path
.split(" ")
.pop()
.replace(/[\. "'=\[\]]/g, "")
: null);
}
resolves(continueOnReject = false) {
const thisValue = this.value;
this.setDefaultMessages(`${this.subject} was not resolved`, `${this.subject} was resolved`);
return new Promise((resolve, reject) => {
const result = (bool) => {
const assertion = this.execute(bool, bool);
if (bool) {
resolve(assertion);
}
else {
continueOnReject ? resolve(assertion) : reject();
}
};
if (util_1.toType(thisValue) == "promise") {
thisValue
.then(() => {
result(true);
})
.catch(() => {
result(false);
});
}
else {
result(false);
}
});
}
rejects(continueOnReject = false) {
const thisValue = this.value;
this.setDefaultMessages(`${this.subject} was not rejected`, `${this.subject} was rejected`);
return new Promise((resolve, reject) => {
const result = (bool) => {
const assertion = this.execute(bool, bool);
if (bool) {
resolve(assertion);
}
else {
continueOnReject ? resolve(assertion) : reject();
}
};
if (util_1.toType(thisValue) == "promise") {
thisValue
.then(() => {
result(false);
})
.catch(() => {
result(true);
});
}
else {
result(false);
}
});
}
none(callback) {
return __awaiter(this, void 0, void 0, function* () {
this._mustBeArray(this.value);
this.setDefaultMessages(`Some were true in ${this.subject}`, `None were true in ${this.subject}`);
const result = yield util_1.asyncNone(this.value, callback);
const which = result
? undefined
: `${yield util_1.asyncFind(this.value, callback)}`;
return this.execute(result, which);
});
}
eval(js, ...args) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield this.context.eval.apply(undefined, [
js,
this.value,
...args,
]);
this.setDefaultMessages(`Function evaluates false`, `Function evaluates true`);
return this.execute(!!result, result);
});
}
evalEvery(js, ...args) {
return __awaiter(this, void 0, void 0, function* () {
this._mustBeArray(this.value);
this.setDefaultMessages(`Every function evaluates false`, `Every function evaluates true`);
const result = yield util_1.asyncEvery(this.value, (item) => {
const val = this._getCompareValue(item);
return !!this.context.eval.apply(undefined, [js, val, ...args]);
});
return this.execute(result, this.value);
});
}
every(callback) {
return __awaiter(this, void 0, void 0, function* () {
this._mustBeArray(this.value);
this.setDefaultMessages(`Some or none were true in ${this.subject}`, `All were true in ${this.subject}`);
const result = yield util_1.asyncEvery(this.value, callback);
const which = result
? undefined
: `${JSON.stringify(yield util_1.asyncFindNot(this.value, callback), null, 2)}`;
return this.execute(result, which);
});
}
everySync(callback) {
this._mustBeArray(this.value);
this.setDefaultMessages(`Some or none were true in ${this.subject}`, `All were true in ${this.subject}`);
return this.execute(this.value.every((value, index, array) => callback(value, index, array)), this.value);
}
map(callback) {
return __awaiter(this, void 0, void 0, function* () {
const mapped = yield util_1.asyncMap(this.value, callback);
return this._clone(mapped, `Mapped ${this.subject}`);
});
}
some(callback) {
return __awaiter(this, void 0, void 0, function* () {
this._mustBeArray(this.value);
this.setDefaultMessages(`None were true in ${this.subject}`, `Some were true in ${this.subject}`);
return this.execute(yield util_1.asyncSome(this.value, callback), this.value);
});
}
pluck(property) {
return this._clone(util_1.toArray(this.value).map((item) => item[property]), `Values of ${property} in ${this.subject}`);
}
nth(n) {
return this._clone(util_1.toArray(this.value)[n], `${util_1.toOrdinal(n + 1)} value in ${this.subject}`);
}
schema(schema, schemaType = "JsonSchema") {
return __awaiter(this, void 0, void 0, function* () {
const thisValue = this.value;
if (typeof schemaType === "boolean") {
schemaType = schemaType ? "JsonSchema" : "JTD";
}
if (typeof schema === "string") {
const schemaName = schema;
try {
schema = assertion_schema_1.getSchema(schemaName);
}
catch (_a) {
this._context.comment(`Created new schema snapshot called ${schemaName}`);
schema = assertion_schema_1.writeSchema(thisValue, schemaName, schemaType);
}
}
return this._executeSchema(thisValue, schema, schemaType);
});
}
assert(a, b) {
return arguments.length === 2
? this._context.assert(a, b)
: this._context.assert(a);
}
comment(input) {
this._context.comment(input);
return this;
}
as(aliasName) {
this._context.set(aliasName, this._input);
return this;
}
execute(bool, actualValue, highlightText = null, expectedValue) {
if (this.isFinalized) {
throw new Error("Assertion result is immutable.");
}
this._statement = this._not ? !bool : bool;
if (this._statement) {
this._result = this._context.logPassing(this._getMessage());
}
else {
this._result = this._optional
? this._context.logOptionalFailure(this._getMessage(), this._getErrorDetails(actualValue, expectedValue))
: this._context.logFailure(this._getMessage(), this._getErrorDetails(actualValue, expectedValue), this._getSourceCode(), this._getHighlightText(actualValue, highlightText));
}
this._assertionMade = true;
this._finishedResolver(this._result);
return this;
}
setDefaultMessage(message) {
this._defaultMessages[1] = message;
return this;
}
setDefaultNotMessage(message) {
this._defaultMessages[0] = message;
return this;
}
setDefaultMessages(notMessage, standardMessage) {
this._defaultMessages[0] = notMessage;
this._defaultMessages[1] = standardMessage;
return this;
}
sort(compareFunc) {
const values = Array.isArray(this.value)
? this.value.sort(compareFunc)
: util_1.isNullOrUndefined(this.value)
? []
: Object.values(this.value).sort(compareFunc);
return this._clone(values, `Sorted values of ${this.subject}`);
}
_mustBeArray(value) {
if (!Array.isArray(value)) {
throw new Error("Input value must be an array.");
}
return true;
}
_executeSchema(thisValue, schema, schemaType) {
const errors = util_1.validateSchema(thisValue, schema, schemaType);
this.setDefaultMessages(`${this.subject} does not match schema`, `${this.subject} matches schema`);
return this.execute(errors.length == 0, errors.join(". "));
}
_getMessage() {
return this._message
? this._message
: this._not
? this._defaultMessages[0]
: this._defaultMessages[1];
}
_getSourceCode() {
let source = this._input && this._input.sourceCode ? this._input.sourceCode : "";
if (source.length > 500) {
source = source.substring(0, 500);
}
return source;
}
_getHighlightText(actualValue, highlightText) {
return highlightText
? highlightText
: this._input && this._input.highlight
? this._input.highlight
: String(actualValue);
}
_getErrorDetails(actualValue, expectedValue) {
return [
`Actual: ${String(actualValue)}`,
expectedValue ? `Expected: ${expectedValue}` : "",
].filter((str) => str.length > 0);
}
_getValues(thatValue) {
return {
thisValue: this.value,
thatValue: this._getCompareValue(thatValue),
};
}
_getCompareValue(value) {
this._assertionMade = true;
return (value === null || value === void 0 ? void 0 : value.isFlagpoleValue) ? value.$ : value;
}
_getName(value) {
var _a;
if (value && value["path"]) {
return value["tagName"]
? `<${value["tagName"]}> @ ${value.path}`
: value.path;
}
return (((_a = this._input) === null || _a === void 0 ? void 0 : _a.toString) ? this._input.toString() : String(this._input)).substr(0, 255);
}
_is(method, item) {
return __awaiter(this, void 0, void 0, function* () {
item = this._thisOrThat(item);
if (Array.isArray(item)) {
return util_1.asyncEvery(item, (e) => __awaiter(this, void 0, void 0, function* () {
const is = yield this._is(method, e);
return is;
}));
}
return (item === null || item === void 0 ? void 0 : item.isFlagpoleValue) ? yield item[method]() : false;
});
}
_hasKeyValue(method, key, value, item) {
return __awaiter(this, void 0, void 0, function* () {
item = this._thisOrThat(item);
if (Array.isArray(item)) {
return util_1.asyncEvery(item, (e) => __awaiter(this, void 0, void 0, function* () { return yield this._hasKeyValue(method, key, value, e); }));
}
return (item === null || item === void 0 ? void 0 : item.isFlagpoleValue)
? yield item[method](key, value)
: value === undefined
? item[key] !== undefined && item[key] !== null
: item[key] == value;
});
}
_thisOrThat(item) {
return item === undefined ? this._input : item;
}
_hasValue(method, value, item) {
return __awaiter(this, void 0, void 0, function* () {
item = this._thisOrThat(item);
if (Array.isArray(item)) {
return util_1.asyncEvery(item, (e) => __awaiter(this, void 0, void 0, function* () { return yield this._hasValue(method, value, e); }));
}
return (item === null || item === void 0 ? void 0 : item.isFlagpoleValue)
? yield item[method](value)
: value === undefined
? item !== undefined && item !== null
: item == value;
});
}
_clone(value, name) {
if (this._statement === null) {
this._statement = false;
this._finishedResolver(null);
}
const assertion = new Assertion(this._context, new value_1.Value(value, this._context, name), this._message);
this._not && assertion.not;
this._optional && assertion.optional;
return assertion;
}
}
exports.Assertion = Assertion;
//# sourceMappingURL=assertion.js.map
;