match.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. /*
  2. * Should
  3. * Copyright(c) 2010-2014 TJ Holowaychuk <tj@vision-media.ca>
  4. * MIT Licensed
  5. */
  6. var util = require('../util');
  7. var eql = require('should-equal');
  8. module.exports = function(should, Assertion) {
  9. var i = should.format;
  10. /**
  11. * Asserts if given object match `other` object, using some assumptions:
  12. * First object matched if they are equal,
  13. * If `other` is a regexp and given object is a string check on matching with regexp
  14. * If `other` is a regexp and given object is an array check if all elements matched regexp
  15. * If `other` is a regexp and given object is an object check values on matching regexp
  16. * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
  17. * If `other` is an object check if the same keys matched with above rules
  18. * All other cases failed.
  19. *
  20. * Usually it is right idea to add pre type assertions, like `.String()` or `.Object()` to be sure assertions will do what you are expecting.
  21. * Object iteration happen by keys (properties with enumerable: true), thus some objects can cause small pain. Typical example is js
  22. * 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).
  23. *
  24. * @name match
  25. * @memberOf Assertion
  26. * @category assertion matching
  27. * @param {*} other Object to match
  28. * @param {string} [description] Optional message
  29. * @example
  30. * 'foobar'.should.match(/^foo/);
  31. * 'foobar'.should.not.match(/^bar/);
  32. *
  33. * ({ a: 'foo', c: 'barfoo' }).should.match(/foo$/);
  34. *
  35. * ['a', 'b', 'c'].should.match(/[a-z]/);
  36. *
  37. * (5).should.not.match(function(n) {
  38. * return n < 0;
  39. * });
  40. * (5).should.not.match(function(it) {
  41. * it.should.be.an.Array();
  42. * });
  43. * ({ a: 10, b: 'abc', c: { d: 10 }, d: 0 }).should
  44. * .match({ a: 10, b: /c$/, c: function(it) {
  45. * return it.should.have.property('d', 10);
  46. * }});
  47. *
  48. * [10, 'abc', { d: 10 }, 0].should
  49. * .match({ '0': 10, '1': /c$/, '2': function(it) {
  50. * return it.should.have.property('d', 10);
  51. * }});
  52. *
  53. * var myString = 'abc';
  54. *
  55. * myString.should.be.a.String().and.match(/abc/);
  56. *
  57. * myString = {};
  58. *
  59. * myString.should.match(/abc/); //yes this will pass
  60. * //better to do
  61. * myString.should.be.an.Object().and.not.empty().and.match(/abc/);//fixed
  62. *
  63. * (new Error('boom')).should.match(/abc/);//passed because no keys
  64. * (new Error('boom')).should.not.match({ message: /abc/ });//check specified property
  65. */
  66. Assertion.add('match', function(other, description) {
  67. this.params = {operator: 'to match ' + i(other), message: description};
  68. if(!eql(this.obj, other).result) {
  69. if(other instanceof RegExp) { // something - regex
  70. if(typeof this.obj == 'string') {
  71. this.assert(other.exec(this.obj));
  72. } else if(util.isIndexable(this.obj)) {
  73. util.forEach(this.obj, function(item) {
  74. this.assert(other.exec(item));// should we try to convert to String and exec?
  75. }, this);
  76. } else if(null != this.obj && typeof this.obj == 'object') {
  77. var notMatchedProps = [], matchedProps = [];
  78. util.forEach(this.obj, function(value, name) {
  79. if(other.exec(value)) matchedProps.push(util.formatProp(name));
  80. else notMatchedProps.push(util.formatProp(name) + ' (' + i(value) + ')');
  81. }, this);
  82. if(notMatchedProps.length)
  83. this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
  84. if(matchedProps.length)
  85. this.params.operator += '\n matched properties: ' + matchedProps.join(', ');
  86. this.assert(notMatchedProps.length === 0);
  87. } // should we try to convert to String and exec?
  88. } else if(typeof other == 'function') {
  89. var res;
  90. res = other(this.obj);
  91. //if(res instanceof Assertion) {
  92. // this.params.operator += '\n ' + res.getMessage();
  93. //}
  94. //if we throw exception ok - it is used .should inside
  95. if(typeof res == 'boolean') {
  96. this.assert(res); // if it is just boolean function assert on it
  97. }
  98. } else if(other != null && typeof other == 'object') { // try to match properties (for Object and Array)
  99. notMatchedProps = [];
  100. matchedProps = [];
  101. util.forEach(other, function(value, key) {
  102. try {
  103. should(this.obj).have.property(key).which.match(value);
  104. matchedProps.push(util.formatProp(key));
  105. } catch(e) {
  106. if(e instanceof should.AssertionError) {
  107. notMatchedProps.push(util.formatProp(key) + ' (' + i(this.obj[key]) + ')');
  108. } else {
  109. throw e;
  110. }
  111. }
  112. }, this);
  113. if(notMatchedProps.length)
  114. this.params.operator += '\n not matched properties: ' + notMatchedProps.join(', ');
  115. if(matchedProps.length)
  116. this.params.operator += '\n matched properties: ' + matchedProps.join(', ');
  117. this.assert(notMatchedProps.length === 0);
  118. } else {
  119. this.assert(false);
  120. }
  121. }
  122. });
  123. /**
  124. * Asserts if given object values or array elements all match `other` object, using some assumptions:
  125. * First object matched if they are equal,
  126. * If `other` is a regexp - matching with regexp
  127. * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
  128. * All other cases check if this `other` equal to each element
  129. *
  130. * @name matchEach
  131. * @memberOf Assertion
  132. * @category assertion matching
  133. * @alias Assertion#matchSome
  134. * @param {*} other Object to match
  135. * @param {string} [description] Optional message
  136. * @example
  137. * [ 'a', 'b', 'c'].should.matchEach(/\w+/);
  138. * [ 'a', 'a', 'a'].should.matchEach('a');
  139. *
  140. * [ 'a', 'a', 'a'].should.matchEach(function(value) { value.should.be.eql('a') });
  141. *
  142. * { a: 'a', b: 'a', c: 'a' }.should.matchEach(function(value) { value.should.be.eql('a') });
  143. */
  144. Assertion.add('matchEach', function(other, description) {
  145. this.params = {operator: 'to match each ' + i(other), message: description};
  146. util.forEach(this.obj, function(value) {
  147. should(value).match(other);
  148. }, this);
  149. });
  150. /**
  151. * Asserts if any of given object values or array elements match `other` object, using some assumptions:
  152. * First object matched if they are equal,
  153. * If `other` is a regexp - matching with regexp
  154. * If `other` is a function check if this function throws AssertionError on given object or return false - it will be assumed as not matched
  155. * All other cases check if this `other` equal to each element
  156. *
  157. * @name matchAny
  158. * @memberOf Assertion
  159. * @category assertion matching
  160. * @param {*} other Object to match
  161. * @alias Assertion#matchEvery
  162. * @param {string} [description] Optional message
  163. * @example
  164. * [ 'a', 'b', 'c'].should.matchAny(/\w+/);
  165. * [ 'a', 'b', 'c'].should.matchAny('a');
  166. *
  167. * [ 'a', 'b', 'c'].should.matchAny(function(value) { value.should.be.eql('a') });
  168. *
  169. * { a: 'a', b: 'b', c: 'c' }.should.matchAny(function(value) { value.should.be.eql('a') });
  170. */
  171. Assertion.add('matchAny', function(other, description) {
  172. this.params = {operator: 'to match any ' + i(other), message: description};
  173. this.assert(util.some(this.obj, function(value) {
  174. try {
  175. should(value).match(other);
  176. return true;
  177. } catch(e) {
  178. if(e instanceof should.AssertionError) {
  179. // Caught an AssertionError, return false to the iterator
  180. return false;
  181. }
  182. throw e;
  183. }
  184. }));
  185. });
  186. Assertion.alias('matchAny', 'matchSome');
  187. Assertion.alias('matchEach', 'matchEvery');
  188. };