Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 | |
| 6 /** | |
| 7 * Returns a matcher that matches empty strings, maps or collections. | |
| 8 */ | |
| 9 final Matcher isEmpty = const _Empty(); | |
| 10 | |
| 11 class _Empty extends BaseMatcher { | |
| 12 const _Empty(); | |
| 13 bool matches(item) { | |
| 14 if (item is Map || item is Collection) { | |
| 15 return item.isEmpty(); | |
| 16 } else if (item is String) { | |
| 17 return item.length == 0; | |
| 18 } else { | |
| 19 return false; | |
| 20 } | |
| 21 } | |
| 22 Description describe(Description description) => | |
| 23 description.add('empty'); | |
| 24 } | |
| 25 | |
| 26 /** A matcher that matches any null value. */ | |
| 27 final Matcher isNull = const _IsNull(); | |
| 28 | |
| 29 /** A matcher that matches any non-null value. */ | |
| 30 final Matcher isNotNull = const _IsNotNull(); | |
| 31 | |
| 32 class _IsNull extends BaseMatcher { | |
| 33 const _IsNull(); | |
| 34 bool matches(item) => item == null; | |
| 35 Description describe(Description description) => | |
| 36 description.add('null'); | |
| 37 } | |
| 38 | |
| 39 class _IsNotNull extends BaseMatcher { | |
| 40 const _IsNotNull(); | |
| 41 bool matches(item) => item != null; | |
| 42 Description describe(Description description) => | |
| 43 description.add('not null'); | |
| 44 } | |
| 45 | |
| 46 /** A matcher that matches the Boolean value true. */ | |
| 47 final Matcher isTrue = const _IsTrue(); | |
| 48 | |
| 49 /** A matcher that matches anything except the Boolean value true. */ | |
| 50 final Matcher isFalse = const _IsFalse(); | |
| 51 | |
| 52 class _IsTrue extends BaseMatcher { | |
| 53 const _IsTrue(); | |
| 54 bool matches(item) => item == true; | |
| 55 Description describe(Description description) => | |
| 56 description.add('true'); | |
| 57 } | |
| 58 | |
| 59 class _IsFalse extends BaseMatcher { | |
| 60 const _IsFalse(); | |
| 61 bool matches(item) => item != true; | |
| 62 Description describe(Description description) => | |
| 63 description.add('false'); | |
| 64 } | |
| 65 | |
| 66 /** | |
| 67 * Returns a matches that matches if the value is the same instance | |
| 68 * as [object] (`===`). | |
| 69 */ | |
| 70 Matcher same(expected) => new _IsSameAs(expected); | |
| 71 | |
| 72 class _IsSameAs extends BaseMatcher { | |
| 73 final _expected; | |
| 74 const _IsSameAs(this._expected); | |
| 75 bool matches(item) => item === _expected; | |
| 76 // If all types were hashable we could show a hash here. | |
| 77 Description describe(Description description) => | |
| 78 description.add('same instance as ').addDescriptionOf(_expected); | |
| 79 } | |
| 80 | |
| 81 /** Returns a matcher that matches if two objects are equal (==). */ | |
| 82 Matcher equals(expected) => new _IsEqual(expected); | |
| 83 | |
| 84 class _IsEqual extends BaseMatcher { | |
| 85 final _expected; | |
| 86 const _IsEqual(this._expected); | |
| 87 bool matches(item) => item == _expected; | |
| 88 Description describe(Description description) => | |
| 89 description.addDescriptionOf(_expected); | |
| 90 } | |
| 91 | |
| 92 /** A matcher that matches any value. */ | |
| 93 final Matcher anything = const _IsAnything(); | |
| 94 | |
| 95 class _IsAnything extends BaseMatcher { | |
| 96 const _IsAnything(); | |
| 97 bool matches(item) => true; | |
| 98 Description describe(Description description) => | |
| 99 description.add('anything'); | |
| 100 } | |
| 101 | |
| 102 /** | |
| 103 * A matcher that matches functions that throw exceptions when called. | |
| 104 * The value passed to expect() should be a reference to the function. | |
| 105 * Note that the function cannot take arguments; to handle this | |
| 106 * a wrapper will have to be created. | |
| 107 * The function will be called once upon success, or twice upon failure | |
| 108 * (the second time to get the failure description). | |
| 109 */ | |
| 110 final Matcher throws = const _Throws(); | |
| 111 | |
| 112 /** | |
| 113 * Returns a matcher that matches a function call against an exception, | |
| 114 * which is in turn constrained by a [matcher]. | |
| 115 * The value passed to expect() should be a reference to the function. | |
| 116 * Note that the function cannot take arguments; to handle this | |
| 117 * a wrapper will have to be created. | |
| 118 * The function will be called once upon success, or twice upon failure | |
| 119 * (the second time to get the failure description). | |
| 120 */ | |
| 121 Matcher throwsA(Matcher matcher) => new _Throws(matcher); | |
| 122 | |
| 123 /** | |
| 124 * A matcher that matches a function call against no exception. | |
| 125 * The function will be called once. Any exceptions will be silently swallowed. | |
| 126 * The value passed to expect() should be a reference to the function. | |
| 127 * Note that the function cannot take arguments; to handle this | |
| 128 * a wrapper will have to be created. | |
| 129 */ | |
| 130 final Matcher returnsNormally = const _ReturnsNormally(); | |
| 131 | |
| 132 class _Throws extends BaseMatcher { | |
| 133 final Matcher _matcher; | |
| 134 | |
| 135 const _Throws([Matcher this._matcher = null]); | |
| 136 | |
| 137 bool matches(item) { | |
| 138 try { | |
| 139 item(); | |
| 140 return false; | |
| 141 } catch (final e) { | |
| 142 return _matcher == null || _matcher.matches(e); | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 Description describe(Description description) { | |
| 147 if (_matcher == null) { | |
| 148 return description.add("throws an exception"); | |
| 149 } else { | |
| 150 return description.add('throws an exception which matches '). | |
| 151 addDescriptionOf(_matcher); | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 Description describeMismatch(item, Description mismatchDescription) { | |
| 156 try { | |
| 157 item(); | |
| 158 return mismatchDescription.add(' no exception'); | |
| 159 } catch (final e) { | |
| 160 return mismatchDescription.add(' exception does not match '). | |
| 161 addDescriptionOf(_matcher); | |
| 162 } | |
| 163 } | |
| 164 } | |
| 165 | |
| 166 class _ReturnsNormally extends BaseMatcher { | |
| 167 | |
| 168 const _ReturnsNormally(); | |
| 169 | |
| 170 bool matches(f) { | |
| 171 try { | |
| 172 f(); | |
| 173 return true; | |
| 174 } catch (final e) { | |
| 175 return false; | |
| 176 } | |
| 177 } | |
| 178 | |
| 179 Description describe(Description description) => | |
| 180 description.add("return normally"); | |
| 181 | |
| 182 Description describeMismatch(item, Description mismatchDescription) { | |
| 183 return mismatchDescription.add(' threw exception'); | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 /** | |
| 188 * Returns a matcher that matches if an object is an instance | |
| 189 * of [type] (or a subtype). | |
| 190 * | |
| 191 * As types are not first class objects in Dart we can only | |
| 192 * approximate this test by using a generic wrapper class. | |
| 193 * | |
| 194 * For example, to test whether 'bar' is an instance of type | |
| 195 * 'Foo', we would write: | |
| 196 * | |
| 197 * expect(bar, new isInstanceOf<Foo>()); | |
| 198 * | |
| 199 * To get better error message, supply a name when creating the | |
| 200 * Type wrapper; e.g.: | |
| 201 * | |
| 202 * expect(bar, new isInstanceOf<Foo>('Foo')); | |
| 203 */ | |
| 204 class isInstanceOf<T> extends BaseMatcher { | |
| 205 final String _name; | |
| 206 const isInstanceOf([this._name = 'specified type']); | |
| 207 bool matches(obj) => obj is T; | |
| 208 // The description here is lame :-( | |
| 209 Description describe(Description description) => | |
| 210 description.add('an instance of ${_name}'); | |
| 211 } | |
| 212 | |
| 213 /** | |
| 214 * Returns a matcher that matches if an object has a length property | |
| 215 * that matches [matcher]. | |
| 216 */ | |
| 217 Matcher hasLength(Matcher matcher) => | |
| 218 new _HasLength(wrapMatcher(matcher)); | |
| 219 | |
| 220 class _HasLength extends BaseMatcher { | |
| 221 final Matcher _matcher; | |
| 222 const _HasLength([Matcher this._matcher = null]); | |
| 223 | |
| 224 bool matches(item) { | |
| 225 return _matcher.matches(item.length); | |
| 226 } | |
| 227 | |
| 228 Description describe(Description description) => | |
| 229 description.add('an object with length of '). | |
| 230 addDescriptionOf(_matcher); | |
| 231 | |
| 232 Description describeMismatch(item, Description mismatchDescription) { | |
| 233 super.describeMismatch(item, mismatchDescription); | |
| 234 try { | |
| 235 if (item.length * item.length >= 0) { // hack to will throw if no length | |
|
Bob Nystrom
2012/06/04 20:04:09
This comment no grammar. Why is this hack needed?
gram
2012/06/05 16:25:46
Basically we want to generate a different descript
Bob Nystrom
2012/06/05 16:50:37
OK. Can you move that comment to the preceding lin
gram
2012/06/05 16:59:53
Was done already :-)
On 2012/06/05 16:50:37, Bob N
| |
| 236 return mismatchDescription.add(' with length of '). | |
| 237 addDescriptionOf(item.length); | |
| 238 } | |
| 239 } catch (var e) { | |
| 240 return mismatchDescription.add(' has no length property'); | |
| 241 } | |
| 242 } | |
| 243 } | |
| 244 | |
| 245 /** | |
| 246 * Returns a matcher that does a deep recursive match. This only works | |
| 247 * with scalars, Maps and Iterables. To handle cyclic structures an | |
| 248 * item [limit] can be provided; if after [limit] items have been | |
| 249 * compared and the process is not complete this will be treated as | |
| 250 * a mismatch. The default limit is 1000. | |
| 251 */ | |
| 252 Matcher recursivelyMatches(expected, [limit=1000]) => | |
| 253 new _DeepMatcher(expected, limit); | |
| 254 | |
| 255 // A utility function for comparing iterators | |
| 256 | |
| 257 String _compareIterables(expected, actual, matcher) { | |
| 258 if (actual is !Iterable) { | |
| 259 return 'is not Iterable'; | |
| 260 } | |
| 261 var expectedIterator = expected.iterator(); | |
| 262 var actualIterator = actual.iterator(); | |
| 263 var position = 0; | |
| 264 String reason = null; | |
| 265 while (reason == null) { | |
| 266 if (expectedIterator.hasNext()) { | |
| 267 if (actualIterator.hasNext()) { | |
| 268 reason = matcher(expectedIterator.next(), | |
| 269 actualIterator.next(), | |
| 270 'mismatch at position ${position}'); | |
| 271 ++position; | |
| 272 } else { | |
| 273 reason = 'shorter than expected'; | |
| 274 } | |
| 275 } else if (actualIterator.hasNext()) { | |
| 276 reason = 'longer than expected'; | |
| 277 } else { | |
| 278 return null; | |
| 279 } | |
| 280 } | |
| 281 return reason; | |
| 282 } | |
| 283 | |
| 284 class _DeepMatcher extends BaseMatcher { | |
| 285 final _expected; | |
| 286 final int _limit; | |
| 287 var count; | |
| 288 | |
| 289 _DeepMatcher(this._expected, [this._limit = 1000]); | |
| 290 | |
| 291 String _recursiveMatch(expected, actual, String location) { | |
| 292 String reason = null; | |
| 293 if (++count >= _limit) { | |
| 294 reason = 'item comparison limit exceeded'; | |
| 295 } else if (expected is Iterable) { | |
| 296 reason = _compareIterables(expected, actual, _recursiveMatch); | |
| 297 } else if (expected is Map) { | |
| 298 if (actual is !Map) { | |
| 299 reason = 'expected a map'; | |
| 300 } else if (expected.length != actual.length) { | |
| 301 reason = 'different map lengths'; | |
| 302 } else { | |
| 303 for (var key in expected.getKeys()) { | |
| 304 if (!actual.containsKey(key)) { | |
| 305 reason = 'missing map key ${key}'; | |
| 306 break; | |
| 307 } | |
| 308 reason = _recursiveMatch(expected[key], actual[key], | |
| 309 'with key ${key} ${location}'); | |
| 310 if (reason != null) { | |
| 311 break; | |
| 312 } | |
| 313 } | |
| 314 } | |
| 315 } else if (expected != actual) { | |
| 316 reason = 'expected ${expected} but got ${actual}'; | |
| 317 } | |
| 318 if (reason == null) { | |
| 319 return null; | |
| 320 } else { | |
| 321 return '${reason} ${location}'; | |
| 322 } | |
| 323 } | |
| 324 | |
| 325 String _match(expected, actual) { | |
| 326 count = 0; | |
| 327 return _recursiveMatch(expected, actual, ''); | |
| 328 } | |
| 329 | |
| 330 bool matches(item) => _match(_expected, item) == null; | |
| 331 | |
| 332 Description describe(Description description) => | |
| 333 description.add('recursively matches ').addDescriptionOf(_expected); | |
| 334 | |
| 335 Description describeMismatch(item, Description mismatchDescription) => | |
| 336 mismatchDescription.add(_match(_expected, item)); | |
| 337 } | |
| 338 | |
| 339 /** | |
| 340 * Returns a matcher that matches if the match argument contains | |
| 341 * the expected value. For [String]s this means substring matching; | |
| 342 * for [Map]s is means the map has the key, and for [Collection]s it | |
| 343 * means the collection has a matching element. In the case of collections, | |
| 344 * [expected] can itself be a matcher. | |
| 345 */ | |
| 346 Matcher contains(expected) => new _Contains(expected); | |
| 347 | |
| 348 class _Contains extends BaseMatcher { | |
| 349 | |
| 350 final _expected; | |
| 351 | |
| 352 const _Contains(this._expected); | |
| 353 | |
| 354 bool matches(item) { | |
| 355 if (item is String) { | |
| 356 return item.indexOf(_expected) >= 0; | |
| 357 } else if (item is Collection) { | |
| 358 if (_expected is Matcher) { | |
| 359 return item.some((e) => _expected.matches(e)); | |
| 360 } else { | |
| 361 return item.some((e) => e == _expected); | |
| 362 } | |
| 363 } else if (item is Map) { | |
| 364 return item.containsKey(_expected); | |
| 365 } | |
| 366 return false; | |
| 367 } | |
| 368 | |
| 369 Description describe(Description description) => | |
| 370 description.add('contains ').addDescriptionOf(_expected); | |
| 371 } | |
| OLD | NEW |