123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- /*
- * Should
- * Copyright(c) 2010-2014 TJ Holowaychuk <tj@vision-media.ca>
- * MIT Licensed
- */
- var util = require('../util');
- var eql = require('should-equal');
- module.exports = function(should, Assertion) {
- var i = should.format;
- /**
- * Asserts if given object match `other` object, using some assumptions:
- * First object matched if they are equal,
- * If `other` is a regexp and given object is a string check on matching with regexp
- * If `other` is a regexp and given object is an array check if all elements matched regexp
- * If `other` is a regexp and given object is an object check values on matching regexp
- * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
- * If `other` is an object check if the same keys matched with above rules
- * All other cases failed.
- *
- * Usually it is right idea to add pre type assertions, like `.String()` or `.Object()` to be sure assertions will do what you are expecting.
- * Object iteration happen by keys (properties with enumerable: true), thus some objects can cause small pain. Typical example is js
- * Error - it by default has 2 properties `name` and `message`, but they both non-enumerable. In this case make sure you specify checking props (see examples).
- *
- * @name match
- * @memberOf Assertion
- * @category assertion matching
- * @param {*} other Object to match
- * @param {string} [description] Optional message
- * @example
- * 'foobar'.should.match(/^foo/);
- * 'foobar'.should.not.match(/^bar/);
- *
- * ({ a: 'foo', c: 'barfoo' }).should.match(/foo$/);
- *
- * ['a', 'b', 'c'].should.match(/[a-z]/);
- *
- * (5).should.not.match(function(n) {
- * return n < 0;
- * });
- * (5).should.not.match(function(it) {
- * it.should.be.an.Array();
- * });
- * ({ a: 10, b: 'abc', c: { d: 10 }, d: 0 }).should
- * .match({ a: 10, b: /c$/, c: function(it) {
- * return it.should.have.property('d', 10);
- * }});
- *
- * [10, 'abc', { d: 10 }, 0].should
- * .match({ '0': 10, '1': /c$/, '2': function(it) {
- * return it.should.have.property('d', 10);
- * }});
- *
- * var myString = 'abc';
- *
- * myString.should.be.a.String().and.match(/abc/);
- *
- * myString = {};
- *
- * myString.should.match(/abc/); //yes this will pass
- * //better to do
- * myString.should.be.an.Object().and.not.empty().and.match(/abc/);//fixed
- *
- * (new Error('boom')).should.match(/abc/);//passed because no keys
- * (new Error('boom')).should.not.match({ message: /abc/ });//check specified property
- */
- Assertion.add('match', function(other, description) {
- this.params = {operator: 'to match ' + i(other), message: description};
- if(!eql(this.obj, other).result) {
- if(other instanceof RegExp) { // something - regex
- if(typeof this.obj == 'string') {
- this.assert(other.exec(this.obj));
- } else if(util.isIndexable(this.obj)) {
- util.forEach(this.obj, function(item) {
- this.assert(other.exec(item));// should we try to convert to String and exec?
- }, this);
- } else if(null != this.obj && typeof this.obj == 'object') {
- var notMatchedProps = [], matchedProps = [];
- util.forEach(this.obj, function(value, name) {
- if(other.exec(value)) matchedProps.push(util.formatProp(name));
- else notMatchedProps.push(util.formatProp(name) + ' (' + i(value) + ')');
- }, this);
- if(notMatchedProps.length)
- this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
- if(matchedProps.length)
- this.params.operator += '\n matched properties: ' + matchedProps.join(', ');
- this.assert(notMatchedProps.length === 0);
- } // should we try to convert to String and exec?
- } else if(typeof other == 'function') {
- var res;
- res = other(this.obj);
- //if(res instanceof Assertion) {
- // this.params.operator += '\n ' + res.getMessage();
- //}
- //if we throw exception ok - it is used .should inside
- if(typeof res == 'boolean') {
- this.assert(res); // if it is just boolean function assert on it
- }
- } else if(other != null && typeof other == 'object') { // try to match properties (for Object and Array)
- notMatchedProps = [];
- matchedProps = [];
- util.forEach(other, function(value, key) {
- try {
- should(this.obj).have.property(key).which.match(value);
- matchedProps.push(util.formatProp(key));
- } catch(e) {
- if(e instanceof should.AssertionError) {
- notMatchedProps.push(util.formatProp(key) + ' (' + i(this.obj[key]) + ')');
- } else {
- throw e;
- }
- }
- }, this);
- if(notMatchedProps.length)
- this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
- if(matchedProps.length)
- this.params.operator += '\n matched properties: ' + matchedProps.join(', ');
- this.assert(notMatchedProps.length === 0);
- } else {
- this.assert(false);
- }
- }
- });
- /**
- * Asserts if given object values or array elements all match `other` object, using some assumptions:
- * First object matched if they are equal,
- * If `other` is a regexp - matching with regexp
- * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
- * All other cases check if this `other` equal to each element
- *
- * @name matchEach
- * @memberOf Assertion
- * @category assertion matching
- * @alias Assertion#matchSome
- * @param {*} other Object to match
- * @param {string} [description] Optional message
- * @example
- * [ 'a', 'b', 'c'].should.matchEach(/\w+/);
- * [ 'a', 'a', 'a'].should.matchEach('a');
- *
- * [ 'a', 'a', 'a'].should.matchEach(function(value) { value.should.be.eql('a') });
- *
- * { a: 'a', b: 'a', c: 'a' }.should.matchEach(function(value) { value.should.be.eql('a') });
- */
- Assertion.add('matchEach', function(other, description) {
- this.params = {operator: 'to match each ' + i(other), message: description};
- util.forEach(this.obj, function(value) {
- should(value).match(other);
- }, this);
- });
- /**
- * Asserts if any of given object values or array elements match `other` object, using some assumptions:
- * First object matched if they are equal,
- * If `other` is a regexp - matching with regexp
- * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
- * All other cases check if this `other` equal to each element
- *
- * @name matchAny
- * @memberOf Assertion
- * @category assertion matching
- * @param {*} other Object to match
- * @alias Assertion#matchEvery
- * @param {string} [description] Optional message
- * @example
- * [ 'a', 'b', 'c'].should.matchAny(/\w+/);
- * [ 'a', 'b', 'c'].should.matchAny('a');
- *
- * [ 'a', 'b', 'c'].should.matchAny(function(value) { value.should.be.eql('a') });
- *
- * { a: 'a', b: 'b', c: 'c' }.should.matchAny(function(value) { value.should.be.eql('a') });
- */
- Assertion.add('matchAny', function(other, description) {
- this.params = {operator: 'to match any ' + i(other), message: description};
- this.assert(util.some(this.obj, function(value) {
- try {
- should(value).match(other);
- return true;
- } catch(e) {
- if(e instanceof should.AssertionError) {
- // Caught an AssertionError, return false to the iterator
- return false;
- }
- throw e;
- }
- }));
- });
- Assertion.alias('matchAny', 'matchSome');
- Assertion.alias('matchEach', 'matchEvery');
- };
|