| 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 const Matcher isEmpty = const _Empty(); |  | 
| 10 |  | 
| 11 class _Empty extends BaseMatcher { |  | 
| 12   const _Empty(); |  | 
| 13   bool matches(item, MatchState matchState) { |  | 
| 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 const Matcher isNull = const _IsNull(); |  | 
| 28 |  | 
| 29 /** A matcher that matches any non-null value. */ |  | 
| 30 const Matcher isNotNull = const _IsNotNull(); |  | 
| 31 |  | 
| 32 class _IsNull extends BaseMatcher { |  | 
| 33   const _IsNull(); |  | 
| 34   bool matches(item, MatchState matchState) => 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, MatchState matchState) => item != null; |  | 
| 42   Description describe(Description description) => |  | 
| 43       description.add('not null'); |  | 
| 44 } |  | 
| 45 |  | 
| 46 /** A matcher that matches the Boolean value true. */ |  | 
| 47 const Matcher isTrue = const _IsTrue(); |  | 
| 48 |  | 
| 49 /** A matcher that matches anything except the Boolean value true. */ |  | 
| 50 const Matcher isFalse = const _IsFalse(); |  | 
| 51 |  | 
| 52 class _IsTrue extends BaseMatcher { |  | 
| 53   const _IsTrue(); |  | 
| 54   bool matches(item, MatchState matchState) => 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, MatchState matchState) => 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, MatchState matchState) => 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 /** |  | 
| 82  * Returns a matcher that does a deep recursive match. This only works |  | 
| 83  * with scalars, Maps and Iterables. To handle cyclic structures a |  | 
| 84  * recursion depth [limit] can be provided. The default limit is 100. |  | 
| 85  */ |  | 
| 86 Matcher equals(expected, [limit=100]) => |  | 
| 87     new _DeepMatcher(expected, limit); |  | 
| 88 |  | 
| 89 class _DeepMatcher extends BaseMatcher { |  | 
| 90   final _expected; |  | 
| 91   final int _limit; |  | 
| 92   var count; |  | 
| 93 |  | 
| 94   _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; |  | 
| 95 |  | 
| 96   String _compareIterables(expected, actual, matcher, depth) { |  | 
| 97     if (actual is !Iterable) { |  | 
| 98       return 'is not Iterable'; |  | 
| 99     } |  | 
| 100     var expectedIterator = expected.iterator(); |  | 
| 101     var actualIterator = actual.iterator(); |  | 
| 102     var position = 0; |  | 
| 103     String reason = null; |  | 
| 104     while (reason == null) { |  | 
| 105       if (expectedIterator.hasNext()) { |  | 
| 106         if (actualIterator.hasNext()) { |  | 
| 107           Description r = matcher(expectedIterator.next(), |  | 
| 108                            actualIterator.next(), |  | 
| 109                            'mismatch at position ${position}', |  | 
| 110                            depth); |  | 
| 111           if (r != null) reason = r.toString(); |  | 
| 112           ++position; |  | 
| 113         } else { |  | 
| 114           reason = 'shorter than expected'; |  | 
| 115         } |  | 
| 116       } else if (actualIterator.hasNext()) { |  | 
| 117         reason = 'longer than expected'; |  | 
| 118       } else { |  | 
| 119         return null; |  | 
| 120       } |  | 
| 121     } |  | 
| 122     return reason; |  | 
| 123   } |  | 
| 124 |  | 
| 125   Description _recursiveMatch(expected, actual, String location, int depth) { |  | 
| 126     Description reason = null; |  | 
| 127     // If _limit is 1 we can only recurse one level into object. |  | 
| 128     bool canRecurse = depth == 0 || _limit > 1; |  | 
| 129     if (expected == actual) { |  | 
| 130       // Do nothing. |  | 
| 131     } else if (depth > _limit) { |  | 
| 132       reason = new StringDescription('recursion depth limit exceeded'); |  | 
| 133     } else { |  | 
| 134       if (expected is Iterable && canRecurse) { |  | 
| 135         String r = _compareIterables(expected, actual, |  | 
| 136           _recursiveMatch, depth+1); |  | 
| 137         if (r != null) reason = new StringDescription(r); |  | 
| 138       } else if (expected is Map && canRecurse) { |  | 
| 139         if (actual is !Map) { |  | 
| 140           reason = new StringDescription('expected a map'); |  | 
| 141         } else if (expected.length != actual.length) { |  | 
| 142           reason = new StringDescription('different map lengths'); |  | 
| 143         } else { |  | 
| 144           for (var key in expected.getKeys()) { |  | 
| 145             if (!actual.containsKey(key)) { |  | 
| 146               reason = new StringDescription('missing map key '); |  | 
| 147               reason.addDescriptionOf(key); |  | 
| 148               break; |  | 
| 149             } |  | 
| 150             reason = _recursiveMatch(expected[key], actual[key], |  | 
| 151                 'with key <${key}> ${location}', depth+1); |  | 
| 152             if (reason != null) { |  | 
| 153               break; |  | 
| 154             } |  | 
| 155           } |  | 
| 156         } |  | 
| 157       } else { |  | 
| 158         // If we have recursed, show the expected value too; if not, |  | 
| 159         // expect() will show it for us. |  | 
| 160         reason = new StringDescription(); |  | 
| 161         if (depth > 1) { |  | 
| 162           reason.add('expected ').addDescriptionOf(expected).add(' but was '). |  | 
| 163               addDescriptionOf(actual); |  | 
| 164         } else { |  | 
| 165           reason.add('was ').addDescriptionOf(actual); |  | 
| 166         } |  | 
| 167       } |  | 
| 168     } |  | 
| 169     if (reason != null && location.length > 0) { |  | 
| 170       reason.add(' ').add(location); |  | 
| 171     } |  | 
| 172     return reason; |  | 
| 173   } |  | 
| 174 |  | 
| 175   String _match(expected, actual) { |  | 
| 176     Description reason = _recursiveMatch(expected, actual, '', 0); |  | 
| 177     return reason == null ? null : reason.toString(); |  | 
| 178   } |  | 
| 179 |  | 
| 180   // TODO(gram) - see if we can make use of matchState here to avoid |  | 
| 181   // recursing again in describeMismatch. |  | 
| 182   bool matches(item, MatchState matchState) => _match(_expected, item) == null; |  | 
| 183 |  | 
| 184   Description describe(Description description) => |  | 
| 185     description.addDescriptionOf(_expected); |  | 
| 186 |  | 
| 187   Description describeMismatch(item, Description mismatchDescription, |  | 
| 188                                MatchState matchState, bool verbose) => |  | 
| 189     mismatchDescription.add(_match(_expected, item)); |  | 
| 190 } |  | 
| 191 |  | 
| 192 /** A matcher that matches any value. */ |  | 
| 193 const Matcher anything = const _IsAnything(); |  | 
| 194 |  | 
| 195 class _IsAnything extends BaseMatcher { |  | 
| 196   const _IsAnything(); |  | 
| 197   bool matches(item, MatchState matchState) => true; |  | 
| 198   Description describe(Description description) => |  | 
| 199       description.add('anything'); |  | 
| 200 } |  | 
| 201 |  | 
| 202 /** |  | 
| 203  * Returns a matcher that matches if an object is an instance |  | 
| 204  * of [type] (or a subtype). |  | 
| 205  * |  | 
| 206  * As types are not first class objects in Dart we can only |  | 
| 207  * approximate this test by using a generic wrapper class. |  | 
| 208  * |  | 
| 209  * For example, to test whether 'bar' is an instance of type |  | 
| 210  * 'Foo', we would write: |  | 
| 211  * |  | 
| 212  *     expect(bar, new isInstanceOf<Foo>()); |  | 
| 213  * |  | 
| 214  * To get better error message, supply a name when creating the |  | 
| 215  * Type wrapper; e.g.: |  | 
| 216  * |  | 
| 217  *     expect(bar, new isInstanceOf<Foo>('Foo')); |  | 
| 218  */ |  | 
| 219 class isInstanceOf<T> extends BaseMatcher { |  | 
| 220   final String _name; |  | 
| 221   const isInstanceOf([name = 'specified type']) : this._name = name; |  | 
| 222   bool matches(obj, MatchState matchState) => obj is T; |  | 
| 223   // The description here is lame :-( |  | 
| 224   Description describe(Description description) => |  | 
| 225       description.add('an instance of ${_name}'); |  | 
| 226 } |  | 
| 227 |  | 
| 228 /** |  | 
| 229  * This can be used to match two kinds of objects: |  | 
| 230  * |  | 
| 231  *   * A [Function] that throws an exception when called. The function cannot |  | 
| 232  *     take any arguments. If you want to test that a function expecting |  | 
| 233  *     arguments throws, wrap it in another zero-argument function that calls |  | 
| 234  *     the one you want to test. |  | 
| 235  * |  | 
| 236  *   * A [Future] that completes with an exception. Note that this creates an |  | 
| 237  *     asynchronous expectation. The call to `expect()` that includes this will |  | 
| 238  *     return immediately and execution will continue. Later, when the future |  | 
| 239  *     completes, the actual expectation will run. |  | 
| 240  */ |  | 
| 241 const Matcher throws = const _Throws(); |  | 
| 242 |  | 
| 243 /** |  | 
| 244  * This can be used to match two kinds of objects: |  | 
| 245  * |  | 
| 246  *   * A [Function] that throws an exception when called. The function cannot |  | 
| 247  *     take any arguments. If you want to test that a function expecting |  | 
| 248  *     arguments throws, wrap it in another zero-argument function that calls |  | 
| 249  *     the one you want to test. |  | 
| 250  * |  | 
| 251  *   * A [Future] that completes with an exception. Note that this creates an |  | 
| 252  *     asynchronous expectation. The call to `expect()` that includes this will |  | 
| 253  *     return immediately and execution will continue. Later, when the future |  | 
| 254  *     completes, the actual expectation will run. |  | 
| 255  * |  | 
| 256  * In both cases, when an exception is thrown, this will test that the exception |  | 
| 257  * object matches [matcher]. If [matcher] is not an instance of [Matcher], it |  | 
| 258  * will implicitly be treated as `equals(matcher)`. |  | 
| 259  */ |  | 
| 260 Matcher throwsA(matcher) => new _Throws(wrapMatcher(matcher)); |  | 
| 261 |  | 
| 262 /** |  | 
| 263  * A matcher that matches a function call against no exception. |  | 
| 264  * The function will be called once. Any exceptions will be silently swallowed. |  | 
| 265  * The value passed to expect() should be a reference to the function. |  | 
| 266  * Note that the function cannot take arguments; to handle this |  | 
| 267  * a wrapper will have to be created. |  | 
| 268  */ |  | 
| 269 const Matcher returnsNormally = const _ReturnsNormally(); |  | 
| 270 |  | 
| 271 class _Throws extends BaseMatcher { |  | 
| 272   final Matcher _matcher; |  | 
| 273 |  | 
| 274   const _Throws([Matcher matcher]) : |  | 
| 275     this._matcher = matcher; |  | 
| 276 |  | 
| 277   bool matches(item, MatchState matchState) { |  | 
| 278     if (item is Future) { |  | 
| 279       // Queue up an asynchronous expectation that validates when the future |  | 
| 280       // completes. |  | 
| 281       item.onComplete(expectAsync1((future) { |  | 
| 282         if (future.hasValue) { |  | 
| 283           expect(false, reason: |  | 
| 284               "Expected future to fail, but succeeded with '${future.value}'."); |  | 
| 285         } else if (_matcher != null) { |  | 
| 286           var reason; |  | 
| 287           if (future.stackTrace != null) { |  | 
| 288             var stackTrace = future.stackTrace.toString(); |  | 
| 289             stackTrace = "  ${stackTrace.replaceAll("\n", "\n  ")}"; |  | 
| 290             reason = "Actual exception trace:\n$stackTrace"; |  | 
| 291           } |  | 
| 292           expect(future.exception, _matcher, reason: reason); |  | 
| 293         } |  | 
| 294       })); |  | 
| 295 |  | 
| 296       // It hasn't failed yet. |  | 
| 297       return true; |  | 
| 298     } |  | 
| 299 |  | 
| 300     try { |  | 
| 301       item(); |  | 
| 302       return false; |  | 
| 303     } catch (e, s) { |  | 
| 304       if (_matcher == null ||_matcher.matches(e, matchState)) { |  | 
| 305         return true; |  | 
| 306       } else { |  | 
| 307         matchState.state = { |  | 
| 308             'exception' :e, |  | 
| 309             'stack': s |  | 
| 310         }; |  | 
| 311         return false; |  | 
| 312       } |  | 
| 313     } |  | 
| 314   } |  | 
| 315 |  | 
| 316   Description describe(Description description) { |  | 
| 317     if (_matcher == null) { |  | 
| 318       return description.add("throws an exception"); |  | 
| 319     } else { |  | 
| 320       return description.add('throws an exception which matches '). |  | 
| 321           addDescriptionOf(_matcher); |  | 
| 322     } |  | 
| 323   } |  | 
| 324 |  | 
| 325   Description describeMismatch(item, Description mismatchDescription, |  | 
| 326                                MatchState matchState, |  | 
| 327                                bool verbose) { |  | 
| 328     if (_matcher == null ||  matchState.state == null) { |  | 
| 329       return mismatchDescription.add(' no exception'); |  | 
| 330     } else { |  | 
| 331       mismatchDescription. |  | 
| 332           add(' exception ').addDescriptionOf(matchState.state['exception']); |  | 
| 333       if (verbose) { |  | 
| 334           mismatchDescription.add(' at '). |  | 
| 335           add(matchState.state['stack'].toString()); |  | 
| 336       } |  | 
| 337        mismatchDescription.add(' does not match ').addDescriptionOf(_matcher); |  | 
| 338        return mismatchDescription; |  | 
| 339     } |  | 
| 340   } |  | 
| 341 } |  | 
| 342 |  | 
| 343 class _ReturnsNormally extends BaseMatcher { |  | 
| 344   const _ReturnsNormally(); |  | 
| 345 |  | 
| 346   bool matches(f, MatchState matchState) { |  | 
| 347     try { |  | 
| 348       f(); |  | 
| 349       return true; |  | 
| 350     } catch (e, s) { |  | 
| 351       matchState.state = { |  | 
| 352           'exception' : e, |  | 
| 353           'stack': s |  | 
| 354       }; |  | 
| 355       return false; |  | 
| 356     } |  | 
| 357   } |  | 
| 358 |  | 
| 359   Description describe(Description description) => |  | 
| 360       description.add("return normally"); |  | 
| 361 |  | 
| 362   Description describeMismatch(item, Description mismatchDescription, |  | 
| 363                                MatchState matchState, |  | 
| 364                                bool verbose) { |  | 
| 365       mismatchDescription.add(' threw '). |  | 
| 366           addDescriptionOf(matchState.state['exception']); |  | 
| 367       if (verbose) { |  | 
| 368         mismatchDescription.add(' at '). |  | 
| 369         add(matchState.state['stack'].toString()); |  | 
| 370       } |  | 
| 371       return mismatchDescription; |  | 
| 372   } |  | 
| 373 } |  | 
| 374 |  | 
| 375 /* |  | 
| 376  * Matchers for different exception types. Ideally we should just be able to |  | 
| 377  * use something like: |  | 
| 378  * |  | 
| 379  * final Matcher throwsException = |  | 
| 380  *     const _Throws(const isInstanceOf<Exception>()); |  | 
| 381  * |  | 
| 382  * Unfortunately instanceOf is not working with dart2js. |  | 
| 383  * |  | 
| 384  * Alternatively, if static functions could be used in const expressions, |  | 
| 385  * we could use: |  | 
| 386  * |  | 
| 387  * bool _isException(x) => x is Exception; |  | 
| 388  * final Matcher isException = const _Predicate(_isException, "Exception"); |  | 
| 389  * final Matcher throwsException = const _Throws(isException); |  | 
| 390  * |  | 
| 391  * But currently using static functions in const expressions is not supported. |  | 
| 392  * For now the only solution for all platforms seems to be separate classes |  | 
| 393  * for each exception type. |  | 
| 394  */ |  | 
| 395 |  | 
| 396 /* abstract */ class _ExceptionMatcher extends BaseMatcher { |  | 
| 397   final String _name; |  | 
| 398   const _ExceptionMatcher(this._name); |  | 
| 399   Description describe(Description description) => |  | 
| 400       description.add(_name); |  | 
| 401 } |  | 
| 402 |  | 
| 403 /** A matcher for FormatExceptions. */ |  | 
| 404 const isFormatException = const _FormatException(); |  | 
| 405 |  | 
| 406 /** A matcher for functions that throw FormatException */ |  | 
| 407 const Matcher throwsFormatException = |  | 
| 408     const _Throws(isFormatException); |  | 
| 409 |  | 
| 410 class _FormatException extends _ExceptionMatcher { |  | 
| 411   const _FormatException() : super("FormatException"); |  | 
| 412   bool matches(item, MatchState matchState) => item is FormatException; |  | 
| 413 } |  | 
| 414 |  | 
| 415 /** A matcher for Exceptions. */ |  | 
| 416 const isException = const _Exception(); |  | 
| 417 |  | 
| 418 /** A matcher for functions that throw Exception */ |  | 
| 419 const Matcher throwsException = const _Throws(isException); |  | 
| 420 |  | 
| 421 class _Exception extends _ExceptionMatcher { |  | 
| 422   const _Exception() : super("Exception"); |  | 
| 423   bool matches(item, MatchState matchState) => item is Exception; |  | 
| 424 } |  | 
| 425 |  | 
| 426 /** A matcher for IllegalArgumentExceptions. */ |  | 
| 427 const isIllegalArgumentException = const _IllegalArgumentException(); |  | 
| 428 |  | 
| 429 /** A matcher for functions that throw IllegalArgumentException */ |  | 
| 430 const Matcher throwsIllegalArgumentException = |  | 
| 431     const _Throws(isIllegalArgumentException); |  | 
| 432 |  | 
| 433 class _IllegalArgumentException extends _ExceptionMatcher { |  | 
| 434   const _IllegalArgumentException() : super("IllegalArgumentException"); |  | 
| 435   bool matches(item, MatchState matchState) => item is IllegalArgumentException; |  | 
| 436 } |  | 
| 437 |  | 
| 438 /** A matcher for IllegalJSRegExpExceptions. */ |  | 
| 439 const isIllegalJSRegExpException = const _IllegalJSRegExpException(); |  | 
| 440 |  | 
| 441 /** A matcher for functions that throw IllegalJSRegExpException */ |  | 
| 442 const Matcher throwsIllegalJSRegExpException = |  | 
| 443     const _Throws(isIllegalJSRegExpException); |  | 
| 444 |  | 
| 445 class _IllegalJSRegExpException extends _ExceptionMatcher { |  | 
| 446   const _IllegalJSRegExpException() : super("IllegalJSRegExpException"); |  | 
| 447   bool matches(item, MatchState matchState) => item is IllegalJSRegExpException; |  | 
| 448 } |  | 
| 449 |  | 
| 450 /** A matcher for IndexOutOfRangeExceptions. */ |  | 
| 451 const isIndexOutOfRangeException = const _IndexOutOfRangeException(); |  | 
| 452 |  | 
| 453 /** A matcher for functions that throw IndexOutOfRangeException */ |  | 
| 454 const Matcher throwsIndexOutOfRangeException = |  | 
| 455     const _Throws(isIndexOutOfRangeException); |  | 
| 456 |  | 
| 457 class _IndexOutOfRangeException extends _ExceptionMatcher { |  | 
| 458   const _IndexOutOfRangeException() : super("IndexOutOfRangeException"); |  | 
| 459   bool matches(item, MatchState matchState) => item is IndexOutOfRangeException; |  | 
| 460 } |  | 
| 461 |  | 
| 462 /** A matcher for NoSuchMethodErrors. */ |  | 
| 463 const isNoSuchMethodError = const _NoSuchMethodError(); |  | 
| 464 |  | 
| 465 /** A matcher for functions that throw NoSuchMethodError */ |  | 
| 466 const Matcher throwsNoSuchMethodError = |  | 
| 467     const _Throws(isNoSuchMethodError); |  | 
| 468 |  | 
| 469 class _NoSuchMethodError extends _ExceptionMatcher { |  | 
| 470   const _NoSuchMethodError() : super("NoSuchMethodError"); |  | 
| 471   bool matches(item, MatchState matchState) => item is NoSuchMethodError; |  | 
| 472 } |  | 
| 473 |  | 
| 474 /** A matcher for NotImplementedExceptions. */ |  | 
| 475 const isNotImplementedException = const _NotImplementedException(); |  | 
| 476 |  | 
| 477 /** A matcher for functions that throw Exception */ |  | 
| 478 const Matcher throwsNotImplementedException = |  | 
| 479     const _Throws(isNotImplementedException); |  | 
| 480 |  | 
| 481 class _NotImplementedException extends _ExceptionMatcher { |  | 
| 482   const _NotImplementedException() : super("NotImplementedException"); |  | 
| 483   bool matches(item, MatchState matchState) => item is NotImplementedException; |  | 
| 484 } |  | 
| 485 |  | 
| 486 /** A matcher for NullPointerExceptions. */ |  | 
| 487 const isNullPointerException = const _NullPointerException(); |  | 
| 488 |  | 
| 489 /** A matcher for functions that throw NotNullPointerException */ |  | 
| 490 const Matcher throwsNullPointerException = |  | 
| 491     const _Throws(isNullPointerException); |  | 
| 492 |  | 
| 493 class _NullPointerException extends _ExceptionMatcher { |  | 
| 494   const _NullPointerException() : super("NullPointerException"); |  | 
| 495   bool matches(item, MatchState matchState) => item is NullPointerException; |  | 
| 496 } |  | 
| 497 |  | 
| 498 /** A matcher for UnsupportedOperationExceptions. */ |  | 
| 499 const isUnsupportedOperationException = const _UnsupportedOperationException(); |  | 
| 500 |  | 
| 501 /** A matcher for functions that throw UnsupportedOperationException */ |  | 
| 502 const Matcher throwsUnsupportedOperationException = |  | 
| 503     const _Throws(isUnsupportedOperationException); |  | 
| 504 |  | 
| 505 class _UnsupportedOperationException extends _ExceptionMatcher { |  | 
| 506   const _UnsupportedOperationException() : |  | 
| 507       super("UnsupportedOperationException"); |  | 
| 508   bool matches(item, MatchState matchState) => item is UnsupportedOperationExcep
     tion; |  | 
| 509 } |  | 
| 510 |  | 
| 511 /** |  | 
| 512  * Returns a matcher that matches if an object has a length property |  | 
| 513  * that matches [matcher]. |  | 
| 514  */ |  | 
| 515 Matcher hasLength(matcher) => |  | 
| 516     new _HasLength(wrapMatcher(matcher)); |  | 
| 517 |  | 
| 518 class _HasLength extends BaseMatcher { |  | 
| 519   final Matcher _matcher; |  | 
| 520   const _HasLength([Matcher matcher = null]) : this._matcher = matcher; |  | 
| 521 |  | 
| 522   bool matches(item, MatchState matchState) { |  | 
| 523     return _matcher.matches(item.length, matchState); |  | 
| 524   } |  | 
| 525 |  | 
| 526   Description describe(Description description) => |  | 
| 527     description.add('an object with length of '). |  | 
| 528         addDescriptionOf(_matcher); |  | 
| 529 |  | 
| 530   Description describeMismatch(item, Description mismatchDescription, |  | 
| 531                                MatchState matchState, bool verbose) { |  | 
| 532     super.describeMismatch(item, mismatchDescription, matchState, verbose); |  | 
| 533     try { |  | 
| 534       // We want to generate a different description if there is no length |  | 
| 535       // property. This is harmless code that will throw if no length property |  | 
| 536       // but subtle enough that an optimizer shouldn't strip it out. |  | 
| 537       if (item.length * item.length >= 0) { |  | 
| 538         return mismatchDescription.add(' with length of '). |  | 
| 539             addDescriptionOf(item.length); |  | 
| 540       } |  | 
| 541     } catch (e) { |  | 
| 542       return mismatchDescription.add(' has no length property'); |  | 
| 543     } |  | 
| 544   } |  | 
| 545 } |  | 
| 546 |  | 
| 547 /** |  | 
| 548  * Returns a matcher that matches if the match argument contains |  | 
| 549  * the expected value. For [String]s this means substring matching; |  | 
| 550  * for [Map]s is means the map has the key, and for [Collection]s it |  | 
| 551  * means the collection has a matching element. In the case of collections, |  | 
| 552  * [expected] can itself be a matcher. |  | 
| 553  */ |  | 
| 554 Matcher contains(expected) => new _Contains(expected); |  | 
| 555 |  | 
| 556 class _Contains extends BaseMatcher { |  | 
| 557 |  | 
| 558   final _expected; |  | 
| 559 |  | 
| 560   const _Contains(this._expected); |  | 
| 561 |  | 
| 562   bool matches(item, MatchState matchState) { |  | 
| 563     if (item is String) { |  | 
| 564       return item.indexOf(_expected) >= 0; |  | 
| 565     } else if (item is Collection) { |  | 
| 566       if (_expected is Matcher) { |  | 
| 567         return item.some((e) => _expected.matches(e, matchState)); |  | 
| 568       } else { |  | 
| 569         return item.some((e) => e == _expected); |  | 
| 570       } |  | 
| 571     } else if (item is Map) { |  | 
| 572       return item.containsKey(_expected); |  | 
| 573     } |  | 
| 574     return false; |  | 
| 575   } |  | 
| 576 |  | 
| 577   Description describe(Description description) => |  | 
| 578       description.add('contains ').addDescriptionOf(_expected); |  | 
| 579 } |  | 
| 580 |  | 
| 581 /** |  | 
| 582  * Returns a matcher that matches if the match argument is in |  | 
| 583  * the expected value. This is the converse of [contains]. |  | 
| 584  */ |  | 
| 585 Matcher isIn(expected) => new _In(expected); |  | 
| 586 |  | 
| 587 class _In extends BaseMatcher { |  | 
| 588 |  | 
| 589   final _expected; |  | 
| 590 |  | 
| 591   const _In(this._expected); |  | 
| 592 |  | 
| 593   bool matches(item, MatchState matchState) { |  | 
| 594     if (_expected is String) { |  | 
| 595       return _expected.indexOf(item) >= 0; |  | 
| 596     } else if (_expected is Collection) { |  | 
| 597       return _expected.some((e) => e == item); |  | 
| 598     } else if (_expected is Map) { |  | 
| 599       return _expected.containsKey(item); |  | 
| 600     } |  | 
| 601     return false; |  | 
| 602   } |  | 
| 603 |  | 
| 604   Description describe(Description description) => |  | 
| 605       description.add('is in ').addDescriptionOf(_expected); |  | 
| 606 } |  | 
| 607 |  | 
| 608 /** |  | 
| 609  * Returns a matcher that uses an arbitrary function that returns |  | 
| 610  * true or false for the actual value. |  | 
| 611  */ |  | 
| 612 Matcher predicate(f, [description = 'satisfies function']) => |  | 
| 613     new _Predicate(f, description); |  | 
| 614 |  | 
| 615 class _Predicate extends BaseMatcher { |  | 
| 616 |  | 
| 617   final _matcher; |  | 
| 618   final String _description; |  | 
| 619 |  | 
| 620   const _Predicate(this._matcher, this._description); |  | 
| 621 |  | 
| 622   bool matches(item, MatchState matchState) => _matcher(item); |  | 
| 623 |  | 
| 624   Description describe(Description description) => |  | 
| 625       description.add(_description); |  | 
| 626 } |  | 
| OLD | NEW | 
|---|