@sprucelabs/test-utils
Version:
Helpful utilities to make asserting more complicated conditions quick and easy! ⚡️
190 lines (189 loc) • 8.06 kB
JavaScript
import chalk from 'chalk';
import escapeRegExp from 'lodash/escapeRegExp.js';
import get from 'lodash/get.js';
import includes from 'lodash/includes.js';
import isObject from 'lodash/isObject.js';
import isObjectLike from 'lodash/isObjectLike.js';
import AssertionError from '../AssertionError.js';
export const UNDEFINED_PLACEHOLDER = '_____________undefined_____________';
export const FUNCTION_PLACEHOLDER = '_____________function_____________';
export const CIRCULAR_PLACEHOLDER = '_____________circular_____________';
export const NULL_PLACEHOLDER = '_____________null_____________';
const assertUtil = {
fail(message, stack) {
throw new AssertionError(message !== null && message !== void 0 ? message : 'Fail!', stack);
},
stringify(object) {
var _a;
let stringified;
if (Array.isArray(object)) {
stringified = `[\n ${object.map((o) => this.stringify(o).split('\n').join('\n '))}\n]`;
}
else if (typeof object === 'number') {
// this hack allows the Spruce Test Reporter to render number errors (they got eaten by terminal-kit's style regex)
stringified = chalk.bgBlack.white(` ${object} `);
}
else if (object instanceof Error) {
stringified = `${(_a = object.stack) !== null && _a !== void 0 ? _a : object.message}`;
}
else if (object instanceof RegExp) {
stringified = `${object.toString()}`;
}
else if (typeof object === 'undefined') {
stringified = 'undefined';
}
else if (typeof object === 'string') {
stringified = `"${object}"`;
}
else {
stringified = JSON.stringify(assertUtil.dropInPlaceholders(object), undefined, 2).replace(/\\/g, '');
}
if (stringified.length > 5000) {
stringified =
stringified.substr(0, 1000) +
'\n\n... big object ...\n\n' +
stringified.substr(stringified.length - 1000);
}
stringified = assertUtil.replacePlaceholders(stringified);
return `${chalk.bold(stringified)}`;
},
replacePlaceholders(str) {
return str
.replace(new RegExp(`"${UNDEFINED_PLACEHOLDER}"`, 'g'), chalk.italic('undefined'))
.replace(new RegExp(`"${FUNCTION_PLACEHOLDER}"`, 'g'), chalk.italic('Function'))
.replace(new RegExp(`"${NULL_PLACEHOLDER}"`, 'g'), chalk.italic('NULL'));
},
dropInPlaceholders(obj) {
const checkedObjects = [
{ obj, depth: 0 },
];
let updated = this.dropInPlaceholder(obj, (obj, depth) => {
if (isObject(obj) &&
checkedObjects.some((checked) => {
return checked.obj === obj && checked.depth < depth;
})) {
return true;
}
checkedObjects.push({ obj, depth });
return false;
}, CIRCULAR_PLACEHOLDER);
updated = this.dropInPlaceholder(updated, (obj) => typeof obj === 'undefined', UNDEFINED_PLACEHOLDER);
updated = this.dropInPlaceholder(updated, (obj) => typeof obj === 'function', FUNCTION_PLACEHOLDER);
updated = this.dropInPlaceholder(updated, (obj) => obj === null, NULL_PLACEHOLDER);
return updated;
},
dropInPlaceholder(obj, checker, placeholder, depth = 1) {
if (!isObject(obj)) {
return obj;
}
const updated = Array.isArray(obj)
? []
: {};
Object.keys(obj).forEach((key) => {
//@ts-ignore
updated[key] =
// @ts-ignore
checker(obj[key], depth) ? placeholder : obj[key];
//@ts-ignore
if (typeof updated[key] !== 'function' && isObject(updated[key])) {
//@ts-ignore
updated[key] = this.dropInPlaceholder(
//@ts-ignore
updated[key], checker, placeholder, depth + 1);
}
});
return updated;
},
doHaystacksPassCheck(haystacks, needle, check) {
return !!haystacks.find((haystack) => {
try {
check(haystack, needle);
return true;
}
catch {
return false;
}
});
},
assertTypeof(actual, type, message) {
if (typeof actual !== type) {
this.fail(message !== null && message !== void 0 ? message : `${JSON.stringify(actual)} is not a ${type}`);
}
},
assertErrorIncludes(matcher, err, msg) {
var _a;
const originalErrorMessage = (_a = err.message) !== null && _a !== void 0 ? _a : '';
const errorMessage = originalErrorMessage.toLowerCase();
const needle = typeof matcher === 'string' ? matcher.toLowerCase() : matcher;
if (typeof needle === 'string' &&
errorMessage.search(needle) === -1 &&
!errorMessage.includes(needle)) {
this.fail(msg !== null && msg !== void 0 ? msg : `Expected thrown error whose message contains: \n\n${chalk.bold(matcher)}\n\nbut got back:\n\n\`${chalk.bold(originalErrorMessage)}\`.`, '\n\nStack: ' + err.stack);
}
else if (needle instanceof RegExp &&
errorMessage.search(needle) === -1 &&
originalErrorMessage.search(needle) === -1) {
this.fail(msg !== null && msg !== void 0 ? msg : `Expected thrown error whose message matches the regex: \n\n${chalk.bold(matcher)}\n\nbut got back:\n\n\`${chalk.bold(originalErrorMessage)}\`.`, '\n\nStack: ' + err.stack);
}
},
partialContains(object, subObject) {
const objProps = object ? Object.getOwnPropertyNames(object) : [];
const subProps = subObject ? Object.getOwnPropertyNames(subObject) : [];
if (objProps.length == 0 || subProps.length === 0) {
return;
}
if (subProps.length > objProps.length) {
return false;
}
for (const subProp of subProps) {
if (!Object.prototype.hasOwnProperty.call(object, subProp)) {
return false;
}
if ((!isObjectLike(object[subProp]) ||
!isObjectLike(subObject[subProp])) &&
object[subProp] !== subObject[subProp]) {
return false;
}
if (isObjectLike(object[subProp]) &&
isObjectLike(subObject[subProp]) &&
!this.partialContains(object[subProp], subObject[subProp])) {
return false;
}
}
return true;
},
valueAtPath(object, path) {
return get(object, path);
},
parseIncludeNeedle(needle) {
const path = Object.keys(needle)[0];
const expected = path && needle[path];
const needleHasArrayNotation = !!(path && path.search(/\[\]\./) > -1);
return { needleHasArrayNotation, path, expected };
},
splitPathBasedOnArrayNotation(path, haystack) {
var _a;
const pathParts = path.split('[].');
const pathToFirstArray = (_a = pathParts.shift()) !== null && _a !== void 0 ? _a : '';
const pathAfterFirstArray = pathParts.join('[].');
const actualBeforeArray = this.valueAtPath(haystack, pathToFirstArray);
return { actualBeforeArray, pathAfterFirstArray };
},
foundUsing3rdPartyIncludes(haystack, needle, isHaystackObject) {
let passed = false;
const escapedNeedle = typeof needle === 'string' ? escapeRegExp(needle) : needle;
if (typeof haystack === 'string' &&
typeof needle === 'string' &&
haystack.search(escapedNeedle) > -1) {
passed = true;
}
if (isHaystackObject && includes(haystack, escapedNeedle)) {
passed = true;
}
if (isHaystackObject && this.partialContains(haystack, escapedNeedle)) {
passed = true;
}
return passed;
},
};
export default assertUtil;