UNPKG

flagpole

Version:

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

748 lines 32.1 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 assertionschema_1 = require("./assertionschema"); const util_1 = require("./util"); const imagecompare_1 = require("./imagecompare"); const assertion_is_1 = require("./assertion-is"); 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() { this._resolveAssertion(); const assertion = new Assertion(this._context, this._input, this._message ? `&& ${this._message}` : null); this._not && assertion.not; this._optional && assertion.optional; return assertion; } get type() { this._resolveAssertion(); const type = new value_1.Value(util_1.toType(this.value), this._context, `Type of ${this.subject}`); const assertion = new Assertion(this._context, type, this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } get length() { this._resolveAssertion(); const length = (() => { const thisValue = this.value; return thisValue && thisValue.length ? thisValue.length : 0; })(); const assertion = new Assertion(this._context, new value_1.Value(length, this._context, `Length of ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } get trim() { this._resolveAssertion(); const trimmedValue = this.text.trim(); const assertion = new Assertion(this._context, new value_1.Value(trimmedValue, this._context, `Trim of ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } get keys() { this._resolveAssertion(); const keys = (() => { const thisValue = this.value; return util_1.isNullOrUndefined(thisValue) ? [] : Object.keys(thisValue); })(); const assertion = new Assertion(this._context, new value_1.Value(keys, this._context, `Keys of ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } get values() { this._resolveAssertion(); const values = (() => { const thisValue = this.value; return util_1.isNullOrUndefined(thisValue) ? [] : Object.values(thisValue); })(); const assertion = new Assertion(this._context, new value_1.Value(values, this._context, `Values of ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } 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); this.setDefaultMessages(`${this.subject} does not equal ${thatValue}`, `${this.subject} equals ${thatValue}`); if (thisType == "array" && thatType == "array") { return this.execute(util_1.arrayEquals(thisValue, thatValue), thisValue); } if (thisType == "object" && thatType == "object") { return this.execute(util_1.deepEqual(thisValue, thatValue), thisValue); } return this.execute(thisValue == thatValue, thisValue); } 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 thisType = util_1.toType(thisValue); 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)); } else { const schema = assertionschema_1.generateAjvSchema(thatValue); const assertion = new assertionschema_1.AssertionSchema(); const valid = assertion.isValid(schema, thisValue); this.setDefaultMessages(`${this.subject} does not match the schema template`, `${this.subject} matches the schema template`); return this.execute(valid, thisValue); } } 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.arrayify(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) { this.setDefaultMessages(`Images do not match.`, `Images match.`); let assertionPassed = false; let details = ""; const imageCompare = new imagecompare_1.ImageCompare(this._context, this.value, 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 = 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* () { const thisValue = this.value; this.setDefaultMessages(`Some were true in ${this.subject}`, `None were true in ${this.subject}`); if (util_1.toType(thisValue) !== "array") { throw new Error("Input value must be an array."); } const result = yield util_1.asyncNone(thisValue, callback); const which = result ? undefined : `${yield util_1.asyncWhich(thisValue, 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* () { const thisValue = this.value; if (util_1.toType(thisValue) !== "array") { throw new Error("Input value must be an array."); } this.setDefaultMessages(`Every function evaluates false`, `Every function evaluates true`); const result = yield util_1.asyncEvery(thisValue, (item) => { const val = this._getCompareValue(item); return this.context.eval.apply(undefined, [js, val, ...args]); }); return this.execute(result, thisValue); }); } every(callback) { return __awaiter(this, void 0, void 0, function* () { const thisValue = this.value; this.setDefaultMessages(`Some or none were true in ${this.subject}`, `All were true in ${this.subject}`); if (util_1.toType(thisValue) !== "array") { throw new Error("Input value must be an array."); } const result = yield util_1.asyncEvery(thisValue, callback); const which = result ? undefined : `${yield util_1.asyncWhichFails(thisValue, callback)}`; return this.execute(result, which); }); } everySync(callback) { const thisValue = this.value; this.setDefaultMessages(`Some or none were true in ${this.subject}`, `All were true in ${this.subject}`); if (util_1.toType(thisValue) !== "array") { throw new Error("Input value must be an array."); } return this.execute(thisValue.every((value, index, array) => callback(value, index, array)), thisValue); } map(callback) { return __awaiter(this, void 0, void 0, function* () { const thisValue = this.value; this._resolveAssertion(); const assertion = new Assertion(this._context, new value_1.Value(yield util_1.asyncMap(thisValue, callback), this._context, `Mapped ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; }); } some(callback) { return __awaiter(this, void 0, void 0, function* () { const thisValue = this.value; this.setDefaultMessages(`None were true in ${this.subject}`, `Some were true in ${this.subject}`); if (util_1.toType(thisValue) !== "array") { throw new Error("Input value must be an array."); } return this.execute(yield util_1.asyncSome(thisValue, callback), thisValue); }); } schema(schema, simple = false) { return __awaiter(this, void 0, void 0, function* () { const thisValue = this.value; if (typeof schema === "string") { const schemaName = schema; try { schema = assertionschema_1.getSchema(schemaName); } catch (_a) { this._context.comment(`Created new schema snapshot called ${schemaName}`); schema = assertionschema_1.writeSchema(thisValue, schemaName); } } const validator = simple ? new assertionschema_1.AssertionSchema() : yield this._loadSchemaValidator(); const isValid = yield validator.validate(schema, thisValue); const errors = validator.errors; let error = ""; if (typeof errors != "undefined" && errors !== null) { errors.forEach((err) => { error += err.message + " "; }); } this.setDefaultMessages(`${this.subject} does not match schema`, `${this.subject} matches schema`); return this.execute(isValid, error); }); } 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; } _loadSchemaValidator() { return __awaiter(this, void 0, void 0, function* () { if (typeof this._ajv == "undefined") { return Promise.resolve().then(() => require("ajv")).then((Ajv) => { this._ajv = new Ajv(); return this._ajv; }) .catch(() => { this._ajv = new assertionschema_1.AssertionSchema(); return this._ajv; }); } else { return this._ajv; } }); } _returnsPromise(callback, values) { return (util_1.isAsyncCallback(callback) || (values.length > 0 && util_1.toType(callback(values[0], 0, values)) === "promise")); } _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), }; } 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) { this._resolveAssertion(); const values = Array.isArray(this.value) ? this.value.sort(compareFunc) : util_1.isNullOrUndefined(this.value) ? [] : Object.values(this.value).sort(compareFunc); const assertion = new Assertion(this._context, new value_1.Value(values, this._context, `Sorted values of ${this.subject}`), this._message); this._not && assertion.not; this._optional && assertion.optional; return assertion; } _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); } _resolveAssertion() { if (this._statement === null) { this._statement = false; this._finishedResolver(null); } } _is(method, item) { return __awaiter(this, void 0, void 0, function* () { item = item === undefined ? this._input : item; if (util_1.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 = item === undefined ? this._input : item; if (util_1.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; }); } _hasValue(method, value, item) { return __awaiter(this, void 0, void 0, function* () { item = item === undefined ? this._input : item; if (util_1.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; }); } } exports.Assertion = Assertion; //# sourceMappingURL=assertion.js.map