@shedevro/assert
Version:
Assertion typescript library for better, comfortable and safe coding. See readme!
456 lines (449 loc) • 18 kB
JavaScript
import { AbstractAppError, TypeHelper, ExitFunctionError } from "@shedevro/core";
class MessageBuilder {
static expected(expected, actual) {
const gotLine = actual ? `. Got: ${actual}` : "";
return `expected ${expected.trim()}${gotLine}`;
}
static expectedValue(expected, actual) {
return this.expected(`value ${expected.trim()}`, actual);
}
static expectedToBe(expected, actual) {
return this.expectedValue(`to be ${expected.trim()}`, actual);
}
}
class WebUtilsAssertionError extends AbstractAppError {
message;
customMessageApplied;
constructor(message, customMessageApplied) {
super(message);
this.message = message;
this.customMessageApplied = customMessageApplied;
}
}
class AssertionHelper {
static valueToString(value) {
if (TypeHelper.isArray(value)) {
return `[${value.map((v => this.valueToString(v))).join(", ")}]`;
}
if (TypeHelper.isString(value)) {
return value.startsWith('"') && value.endsWith('"') ? value : `"${value}"`;
}
if (TypeHelper.isFunction(value)) {
return value.name || "Function";
}
if (TypeHelper.isObject(value)) {
return value.constructor.name;
}
return String(value);
}
}
class AssertClass {
options;
constructor(options = {}) {
this.options = options;
}
get newInstance() {
return new AssertClass;
}
get all() {
const instance = new AssertClass(Object.assign({}, this.options));
instance.options.allOperator = true;
return instance;
}
get is() {
const instance = new AssertClass(Object.assign({}, this.options));
instance.options.isOperator = true;
return instance;
}
get not() {
const instance = new AssertClass(Object.assign({}, this.options));
instance.options.notOperator = true;
return instance;
}
get nullOr() {
const instance = new AssertClass(Object.assign({}, this.options));
instance.options.nullOrOperator = true;
return instance;
}
string(value, customMessage) {
const message = MessageBuilder.expectedToBe("a string", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isString(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
emptyString(value, customMessage) {
const prefix = this.options.notOperator ? "non-empty" : "empty";
const message = MessageBuilder.expectedToBe(`${prefix} string`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => {
if (this.options.notOperator) {
return !TypeHelper.isString(v) || !v.trim();
} else {
return TypeHelper.isString(v) && !v.trim();
}
};
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
contains(value, subString, customMessage) {
const prefix = this.options.notOperator ? "not to" : "to";
const message = MessageBuilder.expectedValue(`${prefix} contain {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isString(v) && v.includes(subString);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
startsWith(value, prefix, customMessage) {
const msgPrefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedValue(`${msgPrefix} to start with {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isString(v) && v.startsWith(prefix);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
endsWith(value, suffix, customMessage) {
const prefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedValue(`${prefix} to end with {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isString(v) && v.endsWith(suffix);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
oneOf(value, values, customMessage) {
this.newInstance.array(values, "Assert.oneOf:values should be an array. Got: {1}");
const prefix = this.options.notOperator ? " not" : "";
const message = MessageBuilder.expectedToBe(`${prefix} one of {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => values.includes(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
number(value, customMessage) {
const message = MessageBuilder.expectedToBe("a number", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
natural(value, customMessage) {
const message = MessageBuilder.expectedToBe("a posistive number", "{1}");
this.greaterThanOrEqual(value, 0, customMessage ?? message);
}
greaterThan(value, limit, customMessage) {
const message = MessageBuilder.expectedToBe("greater than {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v) && v > limit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
greaterThanOrEqual(value, limit, customMessage) {
const message = MessageBuilder.expectedToBe("greater than or equal to {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v) && v >= limit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
lessThan(value, limit, customMessage) {
const message = MessageBuilder.expectedToBe("less than {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v) && v < limit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
lessThanOrEqual(value, limit, customMessage) {
const message = MessageBuilder.expectedToBe("less than or equal to {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v) && v <= limit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
range(value, min, max, customMessage) {
const prefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedToBe(`${prefix} between {2} and {3}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isNumber(v) && v >= min && v <= max;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
boolean(value, customMessage) {
const message = MessageBuilder.expectedToBe("a boolean", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isBoolean(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
true(value, customMessage) {
const message = MessageBuilder.expectedToBe("true", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => v === true;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
false(value, customMessage) {
const message = MessageBuilder.expectedToBe("false", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => v === false;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
object(value, customMessage) {
const message = MessageBuilder.expectedToBe("an object", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isObject(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
function(value, customMessage) {
const message = MessageBuilder.expectedToBe("a function", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isFunction(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
array(value, customMessage) {
const message = MessageBuilder.expectedToBe("an array", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => Array.isArray(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
arrayLength(value, expectedLength, customMessage) {
const message = MessageBuilder.expected("array length to be {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => Array.isArray(v) && v.length === expectedLength;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
arrayMinLength(value, minLimit, customMessage) {
const message = MessageBuilder.expected("array min length to be {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => Array.isArray(v) && v.length >= minLimit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
arrayMaxLength(value, maxLimit, customMessage) {
const message = MessageBuilder.expected("array max length to be {2}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => Array.isArray(v) && v.length <= maxLimit;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
arrayLengthBetween(value, min, max, customMessage) {
const message = MessageBuilder.expected("array length to be between {2} and {3}", "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => Array.isArray(v) && v.length >= min && v.length <= max;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
instanceOf(value, instanceClass, customMessage) {
const prefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedToBe(`${prefix} an instance of {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => v instanceof instanceClass;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
instanceOfAny(value, instanceClasses, customMessage) {
const assertInstance = this.newInstance;
assertInstance.array(instanceClasses, "Assert.instanceOfAny:instanceClasses should be an array. Got: {1}");
const prefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedToBe(`${prefix} an instance of {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => instanceClasses.some((instanceClass => v instanceof instanceClass));
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
match(value, regExp, customMessage) {
const prefix = this.options.notOperator ? "not" : "";
const message = MessageBuilder.expectedValue(`${prefix} to match {2}`, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => typeof v === "string" && regExp.test(v);
return this.processAssertion(value, expression, (() => this.throwError(value, String(regExp))));
}
defined(value, customMessage) {
const prefix = this.options.notOperator ? "undefined" : "defined";
const message = MessageBuilder.expectedToBe(prefix, "{1}");
this.setMessage({
message,
customMessage
});
const expression = v => TypeHelper.isDefined(v);
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
equal(value, expect, customMessage) {
const message = this.options.notOperator ? MessageBuilder.expectedValue("not equal to {2}") : MessageBuilder.expected("{1} to equal {2}");
this.setMessage({
message,
customMessage
});
const expression = v => v === expect;
return this.processAssertion(value, expression, (() => this.throwError(...arguments)));
}
throws(expression, expectedErrorClass, customMessage) {
const operatorIsApplied = this.options.isOperator ?? false;
let throwedError;
let expressionResult;
try {
const assertInstance = this.newInstance;
assertInstance.function(expression, "Assert.throws:expression must be function. Gor {1}");
expressionResult = expression();
} catch (error) {
throwedError = error;
}
if (throwedError) {
let isRightInstance = !expectedErrorClass || throwedError instanceof expectedErrorClass;
if (this.options.notOperator) {
isRightInstance = !isRightInstance;
}
if (isRightInstance) {
if (operatorIsApplied) {
return true;
}
return;
}
expressionResult = AssertionHelper.valueToString(throwedError);
if (throwedError.message?.trim()) {
expressionResult += `: ${throwedError.message}`;
}
} else {
if (this.options.notOperator) {
if (operatorIsApplied) {
return true;
}
return;
}
}
if (operatorIsApplied) {
return false;
}
const message = (() => {
const expectedErrorStr = expectedErrorClass ? "{2}" : "error";
const prefix = this.options.notOperator ? "not" : "";
return MessageBuilder.expected(`${prefix} to throw ${expectedErrorStr}`, "{1}");
})();
this.setMessage({
message,
customMessage
});
this.throwError(expressionResult, expectedErrorClass);
}
processAssertion(value, assertExpression, throwError) {
const operatorIsApplied = this.options.isOperator ?? false;
try {
if (this.options.nullOrOperator && TypeHelper.isUndefined(value)) {
return operatorIsApplied ? true : void 0;
}
this.processAllOperator(value, assertExpression, throwError);
let condition = assertExpression(value);
if (this.options.notOperator) {
condition = !condition;
}
if (!condition) {
throwError();
}
} catch (error) {
if (error instanceof ExitFunctionError) {
if (operatorIsApplied) {
return true;
}
return;
}
if (error instanceof WebUtilsAssertionError) {
if (operatorIsApplied) {
return false;
}
throwError();
}
throw error;
}
if (operatorIsApplied) {
return true;
}
}
processAllOperator(array, assertExpression, throwError) {
if (!this.options.allOperator) {
return;
}
const assertInstance = this.newInstance;
assertInstance.array(array);
array.forEach(((value, index) => {
const condition = assertExpression(value);
if (!condition) {
this.options.message += ` (item #${index})`;
throwError();
}
}));
throw new ExitFunctionError;
}
setMessage(messages) {
const {message, customMessage = null} = messages;
this.options.message = message;
this.options.customMessage = customMessage;
}
throwError(...params) {
let message = this.options.customMessage ?? this.options.message;
if (!message) {
throw new Error("assert message should be set");
}
message = message.replace(/{\d+}/g, (propPos => {
const index = Number(propPos.replace(/\D/g, "")) - 1;
return params.length > index ? AssertionHelper.valueToString(params[index]) : "";
}));
throw new WebUtilsAssertionError(message, !!this.options.customMessage);
}
}
const Assert = new AssertClass;
export { Assert, AssertionHelper, WebUtilsAssertionError };
//# sourceMappingURL=index.mjs.map