| "use strict"; |
| |
| var arrayProto = require("@sinonjs/commons").prototypes.array; |
| var deepEqual = require("./deep-equal").use(createMatcher); // 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 typeOf = require("@sinonjs/commons").typeOf; |
| var valueToString = require("@sinonjs/commons").valueToString; |
| |
| var assertMatcher = require("./create-matcher/assert-matcher"); |
| var assertMethodExists = require("./create-matcher/assert-method-exists"); |
| var assertType = require("./create-matcher/assert-type"); |
| var isIterable = require("./create-matcher/is-iterable"); |
| var isMatcher = require("./create-matcher/is-matcher"); |
| |
| var matcherPrototype = require("./create-matcher/matcher-prototype"); |
| |
| var arrayIndexOf = arrayProto.indexOf; |
| var some = arrayProto.some; |
| |
| var hasOwnProperty = objectProto.hasOwnProperty; |
| var objectToString = objectProto.toString; |
| |
| var TYPE_MAP = require("./create-matcher/type-map")(createMatcher); // eslint-disable-line no-use-before-define |
| |
| /** |
| * Creates a matcher object for the passed expectation |
| * |
| * @alias module:samsam.createMatcher |
| * @param {*} expectation An expecttation |
| * @param {string} message A message for the expectation |
| * @returns {object} A matcher object |
| */ |
| function createMatcher(expectation, message) { |
| var m = Object.create(matcherPrototype); |
| var type = typeOf(expectation); |
| |
| if (message !== undefined && typeof message !== "string") { |
| throw new TypeError("Message should be a string"); |
| } |
| |
| if (arguments.length > 2) { |
| throw new TypeError( |
| `Expected 1 or 2 arguments, received ${arguments.length}` |
| ); |
| } |
| |
| 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; |
| } |
| |
| createMatcher.isMatcher = isMatcher; |
| |
| createMatcher.any = createMatcher(function () { |
| return true; |
| }, "any"); |
| |
| createMatcher.defined = createMatcher(function (actual) { |
| return actual !== null && actual !== undefined; |
| }, "defined"); |
| |
| createMatcher.truthy = createMatcher(function (actual) { |
| return Boolean(actual); |
| }, "truthy"); |
| |
| createMatcher.falsy = createMatcher(function (actual) { |
| return !actual; |
| }, "falsy"); |
| |
| createMatcher.same = function (expectation) { |
| return createMatcher(function (actual) { |
| return expectation === actual; |
| }, `same(${valueToString(expectation)})`); |
| }; |
| |
| createMatcher.in = function (arrayOfExpectations) { |
| if (typeOf(arrayOfExpectations) !== "array") { |
| throw new TypeError("array expected"); |
| } |
| |
| return createMatcher(function (actual) { |
| return some(arrayOfExpectations, function (expectation) { |
| return expectation === actual; |
| }); |
| }, `in(${valueToString(arrayOfExpectations)})`); |
| }; |
| |
| createMatcher.typeOf = function (type) { |
| assertType(type, "string", "type"); |
| return createMatcher(function (actual) { |
| return typeOf(actual) === type; |
| }, `typeOf("${type}")`); |
| }; |
| |
| createMatcher.instanceOf = function (type) { |
| /* istanbul ignore if */ |
| if ( |
| typeof Symbol === "undefined" || |
| typeof Symbol.hasInstance === "undefined" |
| ) { |
| assertType(type, "function", "type"); |
| } else { |
| assertMethodExists( |
| type, |
| Symbol.hasInstance, |
| "type", |
| "[Symbol.hasInstance]" |
| ); |
| } |
| return createMatcher(function (actual) { |
| return actual instanceof type; |
| }, `instanceOf(${functionName(type) || objectToString(type)})`); |
| }; |
| |
| /** |
| * Creates a property matcher |
| * |
| * @private |
| * @param {Function} propertyTest A function to test the property against a value |
| * @param {string} messagePrefix A prefix to use for messages generated by the matcher |
| * @returns {object} A matcher |
| */ |
| 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 createMatcher(function (actual) { |
| if ( |
| actual === undefined || |
| actual === null || |
| !propertyTest(actual, property) |
| ) { |
| return false; |
| } |
| return onlyProperty || deepEqual(actual[property], value); |
| }, message); |
| }; |
| } |
| |
| createMatcher.has = createPropertyMatcher(function (actual, property) { |
| if (typeof actual === "object") { |
| return property in actual; |
| } |
| return actual[property] !== undefined; |
| }, "has"); |
| |
| createMatcher.hasOwn = createPropertyMatcher(function (actual, property) { |
| return hasOwnProperty(actual, property); |
| }, "hasOwn"); |
| |
| createMatcher.hasNested = function (property, value) { |
| assertType(property, "string", "property"); |
| var onlyProperty = arguments.length === 1; |
| var message = `hasNested("${property}"`; |
| if (!onlyProperty) { |
| message += `, ${valueToString(value)}`; |
| } |
| message += ")"; |
| return createMatcher(function (actual) { |
| if ( |
| actual === undefined || |
| actual === null || |
| get(actual, property) === undefined |
| ) { |
| return false; |
| } |
| return onlyProperty || deepEqual(get(actual, property), value); |
| }, message); |
| }; |
| |
| var jsonParseResultTypes = { |
| null: true, |
| boolean: true, |
| number: true, |
| string: true, |
| object: true, |
| array: true, |
| }; |
| createMatcher.json = function (value) { |
| if (!jsonParseResultTypes[typeOf(value)]) { |
| throw new TypeError("Value cannot be the result of JSON.parse"); |
| } |
| var message = `json(${JSON.stringify(value, null, " ")})`; |
| return createMatcher(function (actual) { |
| var parsed; |
| try { |
| parsed = JSON.parse(actual); |
| } catch (e) { |
| return false; |
| } |
| return deepEqual(parsed, value); |
| }, message); |
| }; |
| |
| createMatcher.every = function (predicate) { |
| assertMatcher(predicate); |
| |
| return createMatcher(function (actual) { |
| if (typeOf(actual) === "object") { |
| return every(Object.keys(actual), function (key) { |
| return predicate.test(actual[key]); |
| }); |
| } |
| |
| return ( |
| isIterable(actual) && |
| every(actual, function (element) { |
| return predicate.test(element); |
| }) |
| ); |
| }, `every(${predicate.message})`); |
| }; |
| |
| createMatcher.some = function (predicate) { |
| assertMatcher(predicate); |
| |
| return createMatcher(function (actual) { |
| if (typeOf(actual) === "object") { |
| return !every(Object.keys(actual), function (key) { |
| return !predicate.test(actual[key]); |
| }); |
| } |
| |
| return ( |
| isIterable(actual) && |
| !every(actual, function (element) { |
| return !predicate.test(element); |
| }) |
| ); |
| }, `some(${predicate.message})`); |
| }; |
| |
| createMatcher.array = createMatcher.typeOf("array"); |
| |
| createMatcher.array.deepEquals = function (expectation) { |
| return createMatcher(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" |
| ? createMatcher.array.deepEquals(expected).test(element) |
| : deepEqual(expected, element); |
| }) |
| ); |
| }, `deepEquals([${iterableToString(expectation)}])`); |
| }; |
| |
| createMatcher.array.startsWith = function (expectation) { |
| return createMatcher(function (actual) { |
| return ( |
| typeOf(actual) === "array" && |
| every(expectation, function (expectedElement, index) { |
| return actual[index] === expectedElement; |
| }) |
| ); |
| }, `startsWith([${iterableToString(expectation)}])`); |
| }; |
| |
| createMatcher.array.endsWith = function (expectation) { |
| return createMatcher(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)}])`); |
| }; |
| |
| createMatcher.array.contains = function (expectation) { |
| return createMatcher(function (actual) { |
| return ( |
| typeOf(actual) === "array" && |
| every(expectation, function (expectedElement) { |
| return arrayIndexOf(actual, expectedElement) !== -1; |
| }) |
| ); |
| }, `contains([${iterableToString(expectation)}])`); |
| }; |
| |
| createMatcher.map = createMatcher.typeOf("map"); |
| |
| createMatcher.map.deepEquals = function mapDeepEquals(expectation) { |
| return createMatcher(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)}])`); |
| }; |
| |
| createMatcher.map.contains = function mapContains(expectation) { |
| return createMatcher(function (actual) { |
| return ( |
| typeOf(actual) === "map" && |
| every(expectation, function (element, key) { |
| return actual.has(key) && actual.get(key) === element; |
| }) |
| ); |
| }, `contains(Map[${iterableToString(expectation)}])`); |
| }; |
| |
| createMatcher.set = createMatcher.typeOf("set"); |
| |
| createMatcher.set.deepEquals = function setDeepEquals(expectation) { |
| return createMatcher(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)}])`); |
| }; |
| |
| createMatcher.set.contains = function setContains(expectation) { |
| return createMatcher(function (actual) { |
| return ( |
| typeOf(actual) === "set" && |
| every(expectation, function (element) { |
| return actual.has(element); |
| }) |
| ); |
| }, `contains(Set[${iterableToString(expectation)}])`); |
| }; |
| |
| createMatcher.bool = createMatcher.typeOf("boolean"); |
| createMatcher.number = createMatcher.typeOf("number"); |
| createMatcher.string = createMatcher.typeOf("string"); |
| createMatcher.object = createMatcher.typeOf("object"); |
| createMatcher.func = createMatcher.typeOf("function"); |
| createMatcher.regexp = createMatcher.typeOf("regexp"); |
| createMatcher.date = createMatcher.typeOf("date"); |
| createMatcher.symbol = createMatcher.typeOf("symbol"); |
| |
| module.exports = createMatcher; |