123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- "use strict";
- var arrayProto = require("@sinonjs/commons").prototypes.array;
- var deepEqual = require("./deep-equal").use(match); // eslint-disable-line no-use-before-define
- var every = require("@sinonjs/commons").every;
- var functionName = require("@sinonjs/commons").functionName;
- var get = require("lodash").get;
- var iterableToString = require("./iterable-to-string");
- var objectProto = require("@sinonjs/commons").prototypes.object;
- var stringProto = require("@sinonjs/commons").prototypes.string;
- var typeOf = require("@sinonjs/commons").typeOf;
- var valueToString = require("@sinonjs/commons").valueToString;
- var arrayIndexOf = arrayProto.indexOf;
- var arrayEvery = arrayProto.every;
- var join = arrayProto.join;
- var map = arrayProto.map;
- var some = arrayProto.some;
- var hasOwnProperty = objectProto.hasOwnProperty;
- var isPrototypeOf = objectProto.isPrototypeOf;
- var stringIndexOf = stringProto.indexOf;
- function assertType(value, type, name) {
- var actual = typeOf(value);
- if (actual !== type) {
- throw new TypeError(
- "Expected type of " +
- name +
- " to be " +
- type +
- ", but was " +
- actual
- );
- }
- }
- function assertMethodExists(value, method, name, methodPath) {
- if (value[method] == null) {
- throw new TypeError(
- "Expected " + name + " to have method " + methodPath
- );
- }
- }
- var matcher = {
- toString: function() {
- return this.message;
- }
- };
- function isMatcher(object) {
- return isPrototypeOf(matcher, object);
- }
- function matchObject(actual, expectation) {
- if (actual === null || actual === undefined) {
- return false;
- }
- return arrayEvery(Object.keys(expectation), function(key) {
- var exp = expectation[key];
- var act = actual[key];
- if (isMatcher(exp)) {
- if (!exp.test(act)) {
- return false;
- }
- } else if (typeOf(exp) === "object") {
- if (!matchObject(act, exp)) {
- return false;
- }
- } else if (!deepEqual(act, exp)) {
- return false;
- }
- return true;
- });
- }
- var TYPE_MAP = {
- function: function(m, expectation, message) {
- m.test = expectation;
- m.message = message || "match(" + functionName(expectation) + ")";
- },
- number: function(m, expectation) {
- m.test = function(actual) {
- // we need type coercion here
- return expectation == actual; // eslint-disable-line eqeqeq
- };
- },
- object: function(m, expectation) {
- var array = [];
- if (typeof expectation.test === "function") {
- m.test = function(actual) {
- return expectation.test(actual) === true;
- };
- m.message = "match(" + functionName(expectation.test) + ")";
- return m;
- }
- array = map(Object.keys(expectation), function(key) {
- return key + ": " + valueToString(expectation[key]);
- });
- m.test = function(actual) {
- return matchObject(actual, expectation);
- };
- m.message = "match(" + join(array, ", ") + ")";
- return m;
- },
- regexp: function(m, expectation) {
- m.test = function(actual) {
- return typeof actual === "string" && expectation.test(actual);
- };
- },
- string: function(m, expectation) {
- m.test = function(actual) {
- return (
- typeof actual === "string" &&
- stringIndexOf(actual, expectation) !== -1
- );
- };
- m.message = 'match("' + expectation + '")';
- }
- };
- function match(expectation, message) {
- var m = Object.create(matcher);
- var type = typeOf(expectation);
- if (type in TYPE_MAP) {
- TYPE_MAP[type](m, expectation, message);
- } else {
- m.test = function(actual) {
- return deepEqual(actual, expectation);
- };
- }
- if (!m.message) {
- m.message = "match(" + valueToString(expectation) + ")";
- }
- return m;
- }
- matcher.or = function(m2) {
- if (!arguments.length) {
- throw new TypeError("Matcher expected");
- } else if (!isMatcher(m2)) {
- m2 = match(m2);
- }
- var m1 = this;
- var or = Object.create(matcher);
- or.test = function(actual) {
- return m1.test(actual) || m2.test(actual);
- };
- or.message = m1.message + ".or(" + m2.message + ")";
- return or;
- };
- matcher.and = function(m2) {
- if (!arguments.length) {
- throw new TypeError("Matcher expected");
- } else if (!isMatcher(m2)) {
- m2 = match(m2);
- }
- var m1 = this;
- var and = Object.create(matcher);
- and.test = function(actual) {
- return m1.test(actual) && m2.test(actual);
- };
- and.message = m1.message + ".and(" + m2.message + ")";
- return and;
- };
- match.isMatcher = isMatcher;
- match.any = match(function() {
- return true;
- }, "any");
- match.defined = match(function(actual) {
- return actual !== null && actual !== undefined;
- }, "defined");
- match.truthy = match(function(actual) {
- return !!actual;
- }, "truthy");
- match.falsy = match(function(actual) {
- return !actual;
- }, "falsy");
- match.same = function(expectation) {
- return match(function(actual) {
- return expectation === actual;
- }, "same(" + valueToString(expectation) + ")");
- };
- match.in = function(arrayOfExpectations) {
- if (typeOf(arrayOfExpectations) !== "array") {
- throw new TypeError("array expected");
- }
- return match(function(actual) {
- return some(arrayOfExpectations, function(expectation) {
- return expectation === actual;
- });
- }, "in(" + valueToString(arrayOfExpectations) + ")");
- };
- match.typeOf = function(type) {
- assertType(type, "string", "type");
- return match(function(actual) {
- return typeOf(actual) === type;
- }, 'typeOf("' + type + '")');
- };
- match.instanceOf = function(type) {
- if (
- typeof Symbol === "undefined" ||
- typeof Symbol.hasInstance === "undefined"
- ) {
- assertType(type, "function", "type");
- } else {
- assertMethodExists(
- type,
- Symbol.hasInstance,
- "type",
- "[Symbol.hasInstance]"
- );
- }
- return match(function(actual) {
- return actual instanceof type;
- }, "instanceOf(" +
- (functionName(type) || Object.prototype.toString.call(type)) +
- ")");
- };
- function createPropertyMatcher(propertyTest, messagePrefix) {
- return function(property, value) {
- assertType(property, "string", "property");
- var onlyProperty = arguments.length === 1;
- var message = messagePrefix + '("' + property + '"';
- if (!onlyProperty) {
- message += ", " + valueToString(value);
- }
- message += ")";
- return match(function(actual) {
- if (
- actual === undefined ||
- actual === null ||
- !propertyTest(actual, property)
- ) {
- return false;
- }
- return onlyProperty || deepEqual(actual[property], value);
- }, message);
- };
- }
- match.has = createPropertyMatcher(function(actual, property) {
- if (typeof actual === "object") {
- return property in actual;
- }
- return actual[property] !== undefined;
- }, "has");
- match.hasOwn = createPropertyMatcher(function(actual, property) {
- return hasOwnProperty(actual, property);
- }, "hasOwn");
- match.hasNested = function(property, value) {
- assertType(property, "string", "property");
- var onlyProperty = arguments.length === 1;
- var message = 'hasNested("' + property + '"';
- if (!onlyProperty) {
- message += ", " + valueToString(value);
- }
- message += ")";
- return match(function(actual) {
- if (
- actual === undefined ||
- actual === null ||
- get(actual, property) === undefined
- ) {
- return false;
- }
- return onlyProperty || deepEqual(get(actual, property), value);
- }, message);
- };
- match.every = function(predicate) {
- if (!isMatcher(predicate)) {
- throw new TypeError("Matcher expected");
- }
- return match(function(actual) {
- if (typeOf(actual) === "object") {
- return every(Object.keys(actual), function(key) {
- return predicate.test(actual[key]);
- });
- }
- return (
- !!actual &&
- typeOf(actual.forEach) === "function" &&
- every(actual, function(element) {
- return predicate.test(element);
- })
- );
- }, "every(" + predicate.message + ")");
- };
- match.some = function(predicate) {
- if (!isMatcher(predicate)) {
- throw new TypeError("Matcher expected");
- }
- return match(function(actual) {
- if (typeOf(actual) === "object") {
- return !every(Object.keys(actual), function(key) {
- return !predicate.test(actual[key]);
- });
- }
- return (
- !!actual &&
- typeOf(actual.forEach) === "function" &&
- !every(actual, function(element) {
- return !predicate.test(element);
- })
- );
- }, "some(" + predicate.message + ")");
- };
- match.array = match.typeOf("array");
- match.array.deepEquals = function(expectation) {
- return match(function(actual) {
- // Comparing lengths is the fastest way to spot a difference before iterating through every item
- var sameLength = actual.length === expectation.length;
- return (
- typeOf(actual) === "array" &&
- sameLength &&
- every(actual, function(element, index) {
- var expected = expectation[index];
- return typeOf(expected) === "array" &&
- typeOf(element) === "array"
- ? match.array.deepEquals(expected).test(element)
- : deepEqual(expected, element);
- })
- );
- }, "deepEquals([" + iterableToString(expectation) + "])");
- };
- match.array.startsWith = function(expectation) {
- return match(function(actual) {
- return (
- typeOf(actual) === "array" &&
- every(expectation, function(expectedElement, index) {
- return actual[index] === expectedElement;
- })
- );
- }, "startsWith([" + iterableToString(expectation) + "])");
- };
- match.array.endsWith = function(expectation) {
- return match(function(actual) {
- // This indicates the index in which we should start matching
- var offset = actual.length - expectation.length;
- return (
- typeOf(actual) === "array" &&
- every(expectation, function(expectedElement, index) {
- return actual[offset + index] === expectedElement;
- })
- );
- }, "endsWith([" + iterableToString(expectation) + "])");
- };
- match.array.contains = function(expectation) {
- return match(function(actual) {
- return (
- typeOf(actual) === "array" &&
- every(expectation, function(expectedElement) {
- return arrayIndexOf(actual, expectedElement) !== -1;
- })
- );
- }, "contains([" + iterableToString(expectation) + "])");
- };
- match.map = match.typeOf("map");
- match.map.deepEquals = function mapDeepEquals(expectation) {
- return match(function(actual) {
- // Comparing lengths is the fastest way to spot a difference before iterating through every item
- var sameLength = actual.size === expectation.size;
- return (
- typeOf(actual) === "map" &&
- sameLength &&
- every(actual, function(element, key) {
- return expectation.has(key) && expectation.get(key) === element;
- })
- );
- }, "deepEquals(Map[" + iterableToString(expectation) + "])");
- };
- match.map.contains = function mapContains(expectation) {
- return match(function(actual) {
- return (
- typeOf(actual) === "map" &&
- every(expectation, function(element, key) {
- return actual.has(key) && actual.get(key) === element;
- })
- );
- }, "contains(Map[" + iterableToString(expectation) + "])");
- };
- match.set = match.typeOf("set");
- match.set.deepEquals = function setDeepEquals(expectation) {
- return match(function(actual) {
- // Comparing lengths is the fastest way to spot a difference before iterating through every item
- var sameLength = actual.size === expectation.size;
- return (
- typeOf(actual) === "set" &&
- sameLength &&
- every(actual, function(element) {
- return expectation.has(element);
- })
- );
- }, "deepEquals(Set[" + iterableToString(expectation) + "])");
- };
- match.set.contains = function setContains(expectation) {
- return match(function(actual) {
- return (
- typeOf(actual) === "set" &&
- every(expectation, function(element) {
- return actual.has(element);
- })
- );
- }, "contains(Set[" + iterableToString(expectation) + "])");
- };
- match.bool = match.typeOf("boolean");
- match.number = match.typeOf("number");
- match.string = match.typeOf("string");
- match.object = match.typeOf("object");
- match.func = match.typeOf("function");
- match.regexp = match.typeOf("regexp");
- match.date = match.typeOf("date");
- match.symbol = match.typeOf("symbol");
- module.exports = match;
|