Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
378 lines (330 sloc) 12 KB
"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)})`;
}
// ensure that nothing mutates the exported message value, ref https://github.com/sinonjs/sinon/issues/2502
Object.defineProperty(m, "message", {
configurable: false,
writable: false,
value: m.message,
});
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;