UNPKG

deep-assert

Version:

Better deep-equals object expectations, supporting dynamic bottom-up assertions using any() and satisfies().

215 lines (214 loc) 8.75 kB
"use strict"; // Based on code from <https://github.com/substack/node-deep-equal> Object.defineProperty(exports, "__esModule", { value: true }); var formatter_1 = require("./formatter"); var symbols_1 = require("./symbols"); var supportsArgumentsClass = (function () { return Object.prototype.toString.call(arguments); })() === '[object Arguments]'; function supported(object) { return Object.prototype.toString.call(object) === '[object Arguments]'; } ; function unsupported(object) { return object && typeof object === 'object' && typeof object.length === 'number' && Object.prototype.hasOwnProperty.call(object, 'callee') && !Object.prototype.propertyIsEnumerable.call(object, 'callee') || false; } ; function createMessage(message, actual, expected) { return message + ":\n Actual: " + actual + "\n Expected: " + expected; } function isUndefinedOrNull(value) { return value === null || value === undefined; } function isBuffer(x) { if (!x || typeof x !== 'object' || typeof x.length !== 'number') { return false; } if (typeof x.copy !== 'function' || typeof x.slice !== 'function') { return false; } if (x.length > 0 && typeof x[0] !== 'number') { return false; } return true; } function stripFirstLine(message) { var indexOfFirstLinebreak = message.indexOf("\n"); return message.substr(indexOfFirstLinebreak + 1); } var isArguments = supportsArgumentsClass ? supported : unsupported; function any() { var _a; return _a = {}, _a[symbols_1.$any] = true, _a; } exports.any = any; function satisfies(compare) { var _a; return _a = {}, _a[symbols_1.$compare] = function (actual) { try { var result = compare(actual); return result === true ? true : Error(createMessage("Custom value matcher failed", actual, "<custom logic>")); } catch (error) { return error; } }, _a; } exports.satisfies = satisfies; function deepEquals(actual, expected, path, opts) { if (opts === void 0) { opts = {}; } var result; var allowAdditionalProps = opts.allowAdditionalProps === true; if (expected && expected[symbols_1.$any]) { if (typeof expected === "object" && Object.keys(expected).length > 0) { // $any got in the object because of an object spread allowAdditionalProps = true; } else { return true; } } if (expected && expected[symbols_1.$compare]) { return expected[symbols_1.$compare](actual); } if (actual === expected) { result = true; } else if (actual instanceof Date && expected instanceof Date) { result = (actual.getTime() === expected.getTime()) || Error(createMessage("Dates do not match", actual, expected)); } else if (!actual || !expected || typeof actual !== 'object' && typeof expected !== 'object') { // tslint:disable-next-line result = (opts.strict ? actual === expected : actual == expected) || Error(createMessage("Values do not match", actual, expected)); } else if (isUndefinedOrNull(actual) || isUndefinedOrNull(expected)) { return actual === expected || Error(createMessage("Values do not match", actual, expected)); } else if (["bigint", "boolean", "number", "string", "symbol"].indexOf(typeof actual) > -1) { return actual === expected || Error(createMessage("Values do not match", actual, expected)); } else { result = objEquiv(actual, expected, path, allowAdditionalProps, opts); } if (result instanceof Error && result.path) { return result; } else if (result instanceof Error) { var error = path.length > 0 ? Error("Expectation failed: " + formatter_1.describePath(["$root"].concat(path)) + " does not match.\n" + stripFirstLine(result.message)) : result; // tslint:disable-next-line return Object.assign(error, { path: path }); } return true; } exports.deepEquals = deepEquals; function diffArrayElements(actual, expected, path, allowAdditionalProps, opts) { var propMatches = []; for (var index = 0; index < expected.length; index++) { var result = deepEquals(actual[index], expected[index], path.concat([index]), opts); propMatches.push([index, !(result instanceof Error)]); } if (!allowAdditionalProps && actual.length > expected.length) { for (var index = expected.length; index < actual.length; index++) { propMatches.push([index, false]); } } propMatches.sort(function (pa, pb) { return pa > pb ? 1 : -1; }); return propMatches; } function diffObjectProps(actual, expected, path, allowAdditionalProps, opts) { var actualKeys; var expectedKeys; try { actualKeys = Object.keys(actual); expectedKeys = Object.keys(expected); } catch (e) { // happens when one is a string literal and the other isn't return Error(createMessage("Actual value or expectation is a string literal, the other one is not", actual, expected)); } var propMatches = []; for (var _i = 0, expectedKeys_1 = expectedKeys; _i < expectedKeys_1.length; _i++) { var key = expectedKeys_1[_i]; var result = deepEquals(actual[key], expected[key], path.concat([key]), opts); propMatches.push([key, !(result instanceof Error)]); } if (!allowAdditionalProps) { var unexpectedKeys = actualKeys.filter(function (key) { return expectedKeys.indexOf(key) === -1; }); propMatches.push.apply(propMatches, unexpectedKeys.map(function (key) { return [key, false]; })); } propMatches.sort(function (pa, pb) { return pa > pb ? 1 : -1; }); return propMatches; } function objEquiv(actual, expected, path, allowAdditionalProps, opts) { // an identical 'prototype' property. if (actual.prototype !== expected.prototype) { return Error(createMessage("Object prototypes do not match", actual, expected)); } // ~~~I've managed to break Object.keys through screwy arguments passing. (@substack) // Converting to array solves the problem. if (isArguments(actual)) { if (!isArguments(expected)) { return Error(createMessage("Got arguments pseudo-array, but did not expect it", actual, expected)); } return deepEquals(actual.slice(), expected.slice(), path, opts); } if (isBuffer(expected)) { if (!isBuffer(actual)) { return Error(createMessage("Expected a buffer", actual, expected)); } if (actual.length !== expected.length) { return Error(createMessage("Buffer size does not match", actual.length, expected.length)); } for (var i = 0; i < actual.length; i++) { if (actual[i] !== expected[i]) { return Error(createMessage("Buffer contents do not match at offset " + i, actual[i], expected[i])); } } return true; } if (!Array.isArray(actual) && Array.isArray(expected)) { return Error(createMessage("Expected an array", actual, expected)); } else if (Array.isArray(actual) && !Array.isArray(expected)) { return Error(createMessage("Got an array, but did not expect one", actual, expected)); } var propMatches = Array.isArray(expected) ? diffArrayElements(actual, expected, path, allowAdditionalProps, opts) : diffObjectProps(actual, expected, path, allowAdditionalProps, opts); if (propMatches instanceof Error) { return propMatches; } if (propMatches.some(function (_a) { var matches = _a[1]; return !matches; })) { var mismatchingSubObjectsProp = propMatches.find(function (_a) { var prop = _a[0], matches = _a[1]; return !matches && actual[prop] && expected[prop] && typeof actual[prop] === "object" && typeof expected[prop] === "object"; }); if (mismatchingSubObjectsProp) { var prop = mismatchingSubObjectsProp[0]; return deepEquals(actual[prop], expected[prop], path.concat([prop]), opts); } else { var message = (Array.isArray(actual) ? "Arrays" : "Objects") + " do not match:"; return Object.assign(Error(formatter_1.createObjectDiffMessage(message, actual, expected, path, propMatches)), { path: path }); } } return typeof actual === typeof expected || Error(createMessage("Types of values do not match", typeof actual, typeof expected)); }