UNPKG

flagpole

Version:

Simple and fast DOM integration, headless or headful browser, and REST API testing framework.

690 lines 29.5 kB
"use strict"; 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