OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library matcher.core_matchers; | 5 part of matcher; |
6 | 6 |
7 import 'dart:async'; | 7 /** |
8 | 8 * Returns a matcher that matches empty strings, maps or iterables |
9 import 'description.dart'; | 9 * (including collections). |
10 import 'expect.dart'; | 10 */ |
11 import 'interfaces.dart'; | |
12 | |
13 /// Returns a matcher that matches empty strings, maps or iterables | |
14 /// (including collections). | |
15 const Matcher isEmpty = const _Empty(); | 11 const Matcher isEmpty = const _Empty(); |
16 | 12 |
17 class _Empty extends Matcher { | 13 class _Empty extends Matcher { |
18 const _Empty(); | 14 const _Empty(); |
19 bool matches(item, Map matchState) { | 15 bool matches(item, Map matchState) { |
20 if (item is Map || item is Iterable) { | 16 if (item is Map || item is Iterable) { |
21 return item.isEmpty; | 17 return item.isEmpty; |
22 } else if (item is String) { | 18 } else if (item is String) { |
23 return item.length == 0; | 19 return item.length == 0; |
24 } else { | 20 } else { |
25 return false; | 21 return false; |
26 } | 22 } |
27 } | 23 } |
28 Description describe(Description description) => description.add('empty'); | 24 Description describe(Description description) => description.add('empty'); |
29 } | 25 } |
30 | 26 |
31 /// A matcher that matches any null value. | 27 /** A matcher that matches any null value. */ |
32 const Matcher isNull = const _IsNull(); | 28 const Matcher isNull = const _IsNull(); |
33 | 29 |
34 /// A matcher that matches any non-null value. | 30 /** A matcher that matches any non-null value. */ |
35 const Matcher isNotNull = const _IsNotNull(); | 31 const Matcher isNotNull = const _IsNotNull(); |
36 | 32 |
37 class _IsNull extends Matcher { | 33 class _IsNull extends Matcher { |
38 const _IsNull(); | 34 const _IsNull(); |
39 bool matches(item, Map matchState) => item == null; | 35 bool matches(item, Map matchState) => item == null; |
40 Description describe(Description description) => description.add('null'); | 36 Description describe(Description description) => description.add('null'); |
41 } | 37 } |
42 | 38 |
43 class _IsNotNull extends Matcher { | 39 class _IsNotNull extends Matcher { |
44 const _IsNotNull(); | 40 const _IsNotNull(); |
45 bool matches(item, Map matchState) => item != null; | 41 bool matches(item, Map matchState) => item != null; |
46 Description describe(Description description) => description.add('not null'); | 42 Description describe(Description description) => description.add('not null'); |
47 } | 43 } |
48 | 44 |
49 /// A matcher that matches the Boolean value true. | 45 /** A matcher that matches the Boolean value true. */ |
50 const Matcher isTrue = const _IsTrue(); | 46 const Matcher isTrue = const _IsTrue(); |
51 | 47 |
52 /// A matcher that matches anything except the Boolean value true. | 48 /** A matcher that matches anything except the Boolean value true. */ |
53 const Matcher isFalse = const _IsFalse(); | 49 const Matcher isFalse = const _IsFalse(); |
54 | 50 |
55 class _IsTrue extends Matcher { | 51 class _IsTrue extends Matcher { |
56 const _IsTrue(); | 52 const _IsTrue(); |
57 bool matches(item, Map matchState) => item == true; | 53 bool matches(item, Map matchState) => item == true; |
58 Description describe(Description description) => description.add('true'); | 54 Description describe(Description description) => description.add('true'); |
59 } | 55 } |
60 | 56 |
61 class _IsFalse extends Matcher { | 57 class _IsFalse extends Matcher { |
62 const _IsFalse(); | 58 const _IsFalse(); |
63 bool matches(item, Map matchState) => item == false; | 59 bool matches(item, Map matchState) => item == false; |
64 Description describe(Description description) => description.add('false'); | 60 Description describe(Description description) => description.add('false'); |
65 } | 61 } |
66 | 62 |
67 /// Returns a matches that matches if the value is the same instance | 63 /** |
68 /// as [expected], using [identical]. | 64 * Returns a matches that matches if the value is the same instance |
| 65 * as [expected], using [identical]. |
| 66 */ |
69 Matcher same(expected) => new _IsSameAs(expected); | 67 Matcher same(expected) => new _IsSameAs(expected); |
70 | 68 |
71 class _IsSameAs extends Matcher { | 69 class _IsSameAs extends Matcher { |
72 final _expected; | 70 final _expected; |
73 const _IsSameAs(this._expected); | 71 const _IsSameAs(this._expected); |
74 bool matches(item, Map matchState) => identical(item, _expected); | 72 bool matches(item, Map matchState) => identical(item, _expected); |
75 // If all types were hashable we could show a hash here. | 73 // If all types were hashable we could show a hash here. |
76 Description describe(Description description) => | 74 Description describe(Description description) => |
77 description.add('same instance as ').addDescriptionOf(_expected); | 75 description.add('same instance as ').addDescriptionOf(_expected); |
78 } | 76 } |
79 | 77 |
80 /// Returns a matcher that matches if the value is structurally equal to | 78 /** |
81 /// [expected]. | 79 * Returns a matcher that matches if the value is structurally equal to |
82 /// | 80 * [expected]. |
83 /// If [expected] is a [Matcher], then it matches using that. Otherwise it tests | 81 * |
84 /// for equality using `==` on the expected value. | 82 * If [expected] is a [Matcher], then it matches using that. Otherwise it tests |
85 /// | 83 * for equality using `==` on the expected value. |
86 /// For [Iterable]s and [Map]s, this will recursively match the elements. To | 84 * |
87 /// handle cyclic structures a recursion depth [limit] can be provided. The | 85 * For [Iterable]s and [Map]s, this will recursively match the elements. To |
88 /// default limit is 100. | 86 * handle cyclic structures a recursion depth [limit] can be provided. The |
89 Matcher equals(expected, [int limit=100]) => | 87 * default limit is 100. |
| 88 */ |
| 89 Matcher equals(expected, [limit=100]) => |
90 expected is String | 90 expected is String |
91 ? new _StringEqualsMatcher(expected) | 91 ? new _StringEqualsMatcher(expected) |
92 : new _DeepMatcher(expected, limit); | 92 : new _DeepMatcher(expected, limit); |
93 | 93 |
94 class _DeepMatcher extends Matcher { | 94 class _DeepMatcher extends Matcher { |
95 final _expected; | 95 final _expected; |
96 final int _limit; | 96 final int _limit; |
97 var count; | 97 var count; |
98 | 98 |
99 _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit; | 99 _DeepMatcher(this._expected, [limit = 1000]): this._limit = limit; |
100 | 100 |
101 // Returns a pair (reason, location) | 101 // Returns a pair (reason, location) |
102 List _compareIterables(expected, actual, matcher, depth, location) { | 102 List _compareIterables(expected, actual, matcher, depth, location) { |
103 if (actual is! Iterable) return ['is not Iterable', location]; | 103 if (actual is! Iterable) return ['is not Iterable', location]; |
104 | 104 |
105 var expectedIterator = expected.iterator; | 105 var expectedIterator = expected.iterator; |
106 var actualIterator = actual.iterator; | 106 var actualIterator = actual.iterator; |
107 for (var index = 0; ; index++) { | 107 for (var index = 0; ; index++) { |
108 // Advance in lockstep. | 108 // Advance in lockstep. |
109 var expectedNext = expectedIterator.moveNext(); | 109 var expectedNext = expectedIterator.moveNext(); |
(...skipping 118 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
228 // description). | 228 // description). |
229 if (reason.length == 0 && mismatchDescription.length > 0) { | 229 if (reason.length == 0 && mismatchDescription.length > 0) { |
230 mismatchDescription.add('is ').addDescriptionOf(item); | 230 mismatchDescription.add('is ').addDescriptionOf(item); |
231 } else { | 231 } else { |
232 mismatchDescription.add(reason); | 232 mismatchDescription.add(reason); |
233 } | 233 } |
234 return mismatchDescription; | 234 return mismatchDescription; |
235 } | 235 } |
236 } | 236 } |
237 | 237 |
238 /// A special equality matcher for strings. | 238 /** A special equality matcher for strings. */ |
239 class _StringEqualsMatcher extends Matcher { | 239 class _StringEqualsMatcher extends Matcher { |
240 final String _value; | 240 final String _value; |
241 | 241 |
242 _StringEqualsMatcher(this._value); | 242 _StringEqualsMatcher(this._value); |
243 | 243 |
244 bool get showActualValue => true; | 244 bool get showActualValue => true; |
245 | 245 |
246 bool matches(item, Map matchState) => _value == item; | 246 bool matches(item, Map matchState) => _value == item; |
247 | 247 |
248 Description describe(Description description) => | 248 Description describe(Description description) => |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 static void _writeTrailing(StringBuffer buff, String s, int start) { | 306 static void _writeTrailing(StringBuffer buff, String s, int start) { |
307 if (start + 10 > s.length) { | 307 if (start + 10 > s.length) { |
308 buff.write(s.substring(start)); | 308 buff.write(s.substring(start)); |
309 } else { | 309 } else { |
310 buff.write(s.substring(start, start + 10)); | 310 buff.write(s.substring(start, start + 10)); |
311 buff.write(' ...'); | 311 buff.write(' ...'); |
312 } | 312 } |
313 } | 313 } |
314 } | 314 } |
315 | 315 |
316 /// A matcher that matches any value. | 316 /** A matcher that matches any value. */ |
317 const Matcher anything = const _IsAnything(); | 317 const Matcher anything = const _IsAnything(); |
318 | 318 |
319 class _IsAnything extends Matcher { | 319 class _IsAnything extends Matcher { |
320 const _IsAnything(); | 320 const _IsAnything(); |
321 bool matches(item, Map matchState) => true; | 321 bool matches(item, Map matchState) => true; |
322 Description describe(Description description) => description.add('anything'); | 322 Description describe(Description description) => description.add('anything'); |
323 } | 323 } |
324 | 324 |
325 /// Returns a matcher that matches if an object is an instance | 325 /** |
326 /// of [type] (or a subtype). | 326 * Returns a matcher that matches if an object is an instance |
327 /// | 327 * of [type] (or a subtype). |
328 /// As types are not first class objects in Dart we can only | 328 * |
329 /// approximate this test by using a generic wrapper class. | 329 * As types are not first class objects in Dart we can only |
330 /// | 330 * approximate this test by using a generic wrapper class. |
331 /// For example, to test whether 'bar' is an instance of type | 331 * |
332 /// 'Foo', we would write: | 332 * For example, to test whether 'bar' is an instance of type |
333 /// | 333 * 'Foo', we would write: |
334 /// expect(bar, new isInstanceOf<Foo>()); | 334 * |
335 /// | 335 * expect(bar, new isInstanceOf<Foo>()); |
336 /// To get better error message, supply a name when creating the | 336 * |
337 /// Type wrapper; e.g.: | 337 * To get better error message, supply a name when creating the |
338 /// | 338 * Type wrapper; e.g.: |
339 /// expect(bar, new isInstanceOf<Foo>('Foo')); | 339 * |
340 /// | 340 * expect(bar, new isInstanceOf<Foo>('Foo')); |
341 /// Note that this does not currently work in dart2js; it will | 341 * |
342 /// match any type, and isNot(new isInstanceof<T>()) will always | 342 * Note that this does not currently work in dart2js; it will |
343 /// fail. This is because dart2js currently ignores template type | 343 * match any type, and isNot(new isInstanceof<T>()) will always |
344 /// parameters. | 344 * fail. This is because dart2js currently ignores template type |
| 345 * parameters. |
| 346 */ |
345 class isInstanceOf<T> extends Matcher { | 347 class isInstanceOf<T> extends Matcher { |
346 final String _name; | 348 final String _name; |
347 const isInstanceOf([name = 'specified type']) : this._name = name; | 349 const isInstanceOf([name = 'specified type']): this._name = name; |
348 bool matches(obj, Map matchState) => obj is T; | 350 bool matches(obj, Map matchState) => obj is T; |
349 // The description here is lame :-( | 351 // The description here is lame :-( |
350 Description describe(Description description) => | 352 Description describe(Description description) => |
351 description.add('an instance of ${_name}'); | 353 description.add('an instance of ${_name}'); |
352 } | 354 } |
353 | 355 |
354 /// This can be used to match two kinds of objects: | 356 /** |
355 /// | 357 * This can be used to match two kinds of objects: |
356 /// * A [Function] that throws an exception when called. The function cannot | 358 * |
357 /// take any arguments. If you want to test that a function expecting | 359 * * A [Function] that throws an exception when called. The function cannot |
358 /// arguments throws, wrap it in another zero-argument function that calls | 360 * take any arguments. If you want to test that a function expecting |
359 /// the one you want to test. | 361 * arguments throws, wrap it in another zero-argument function that calls |
360 /// | 362 * the one you want to test. |
361 /// * A [Future] that completes with an exception. Note that this creates an | 363 * |
362 /// asynchronous expectation. The call to `expect()` that includes this will | 364 * * A [Future] that completes with an exception. Note that this creates an |
363 /// return immediately and execution will continue. Later, when the future | 365 * asynchronous expectation. The call to `expect()` that includes this will |
364 /// completes, the actual expectation will run. | 366 * return immediately and execution will continue. Later, when the future |
| 367 * completes, the actual expectation will run. |
| 368 */ |
365 const Matcher throws = const Throws(); | 369 const Matcher throws = const Throws(); |
366 | 370 |
367 /// This can be used to match two kinds of objects: | 371 /** |
368 /// | 372 * This can be used to match two kinds of objects: |
369 /// * A [Function] that throws an exception when called. The function cannot | 373 * |
370 /// take any arguments. If you want to test that a function expecting | 374 * * A [Function] that throws an exception when called. The function cannot |
371 /// arguments throws, wrap it in another zero-argument function that calls | 375 * take any arguments. If you want to test that a function expecting |
372 /// the one you want to test. | 376 * arguments throws, wrap it in another zero-argument function that calls |
373 /// | 377 * the one you want to test. |
374 /// * A [Future] that completes with an exception. Note that this creates an | 378 * |
375 /// asynchronous expectation. The call to `expect()` that includes this will | 379 * * A [Future] that completes with an exception. Note that this creates an |
376 /// return immediately and execution will continue. Later, when the future | 380 * asynchronous expectation. The call to `expect()` that includes this will |
377 /// completes, the actual expectation will run. | 381 * return immediately and execution will continue. Later, when the future |
378 /// | 382 * completes, the actual expectation will run. |
379 /// In both cases, when an exception is thrown, this will test that the exceptio
n | 383 * |
380 /// object matches [matcher]. If [matcher] is not an instance of [Matcher], it | 384 * In both cases, when an exception is thrown, this will test that the exception |
381 /// will implicitly be treated as `equals(matcher)`. | 385 * object matches [matcher]. If [matcher] is not an instance of [Matcher], it |
| 386 * will implicitly be treated as `equals(matcher)`. |
| 387 */ |
382 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher)); | 388 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher)); |
383 | 389 |
384 /// A matcher that matches a function call against no exception. | 390 /** |
385 /// The function will be called once. Any exceptions will be silently swallowed. | 391 * A matcher that matches a function call against no exception. |
386 /// The value passed to expect() should be a reference to the function. | 392 * The function will be called once. Any exceptions will be silently swallowed. |
387 /// Note that the function cannot take arguments; to handle this | 393 * The value passed to expect() should be a reference to the function. |
388 /// a wrapper will have to be created. | 394 * Note that the function cannot take arguments; to handle this |
| 395 * a wrapper will have to be created. |
| 396 */ |
389 const Matcher returnsNormally = const _ReturnsNormally(); | 397 const Matcher returnsNormally = const _ReturnsNormally(); |
390 | 398 |
391 class Throws extends Matcher { | 399 class Throws extends Matcher { |
392 final Matcher _matcher; | 400 final Matcher _matcher; |
393 | 401 |
394 const Throws([Matcher matcher]) : this._matcher = matcher; | 402 const Throws([Matcher matcher]): this._matcher = matcher; |
395 | 403 |
396 bool matches(item, Map matchState) { | 404 bool matches(item, Map matchState) { |
397 if (item is! Function && item is! Future) return false; | 405 if (item is! Function && item is! Future) return false; |
398 if (item is Future) { | 406 if (item is Future) { |
399 var done = wrapAsync((fn) => fn()); | 407 var done = wrapAsync((fn) => fn()); |
400 | 408 |
401 // Queue up an asynchronous expectation that validates when the future | 409 // Queue up an asynchronous expectation that validates when the future |
402 // completes. | 410 // completes. |
403 item.then((value) { | 411 item.then((value) { |
404 done(() { | 412 done(() => fail("Expected future to fail, but succeeded with '$value'.")
); |
405 fail("Expected future to fail, but succeeded with '$value'."); | |
406 }); | |
407 }, onError: (error, trace) { | 413 }, onError: (error, trace) { |
408 done(() { | 414 done(() { |
409 if (_matcher == null) return; | 415 if (_matcher == null) return; |
410 var reason; | 416 var reason; |
411 if (trace != null) { | 417 if (trace != null) { |
412 var stackTrace = trace.toString(); | 418 var stackTrace = trace.toString(); |
413 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}"; | 419 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}"; |
414 reason = "Actual exception trace:\n$stackTrace"; | 420 reason = "Actual exception trace:\n$stackTrace"; |
415 } | 421 } |
416 expect(error, _matcher, reason: reason); | 422 expect(error, _matcher, reason: reason); |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
506 * For now the only solution for all platforms seems to be separate classes | 512 * For now the only solution for all platforms seems to be separate classes |
507 * for each exception type. | 513 * for each exception type. |
508 */ | 514 */ |
509 | 515 |
510 abstract class TypeMatcher extends Matcher { | 516 abstract class TypeMatcher extends Matcher { |
511 final String _name; | 517 final String _name; |
512 const TypeMatcher(this._name); | 518 const TypeMatcher(this._name); |
513 Description describe(Description description) => description.add(_name); | 519 Description describe(Description description) => description.add(_name); |
514 } | 520 } |
515 | 521 |
516 /// A matcher for Map types. | 522 /** A matcher for FormatExceptions. */ |
517 const Matcher isMap = const _IsMap(); | 523 const isFormatException = const _FormatException(); |
| 524 |
| 525 /** A matcher for functions that throw FormatException. */ |
| 526 const Matcher throwsFormatException = const Throws(isFormatException); |
| 527 |
| 528 class _FormatException extends TypeMatcher { |
| 529 const _FormatException(): super("FormatException"); |
| 530 bool matches(item, Map matchState) => item is FormatException; |
| 531 } |
| 532 |
| 533 /** A matcher for Exceptions. */ |
| 534 const isException = const _Exception(); |
| 535 |
| 536 /** A matcher for functions that throw Exception. */ |
| 537 const Matcher throwsException = const Throws(isException); |
| 538 |
| 539 class _Exception extends TypeMatcher { |
| 540 const _Exception(): super("Exception"); |
| 541 bool matches(item, Map matchState) => item is Exception; |
| 542 } |
| 543 |
| 544 /** A matcher for ArgumentErrors. */ |
| 545 const isArgumentError = const _ArgumentError(); |
| 546 |
| 547 /** A matcher for functions that throw ArgumentError. */ |
| 548 const Matcher throwsArgumentError = const Throws(isArgumentError); |
| 549 |
| 550 class _ArgumentError extends TypeMatcher { |
| 551 const _ArgumentError(): super("ArgumentError"); |
| 552 bool matches(item, Map matchState) => item is ArgumentError; |
| 553 } |
| 554 |
| 555 /** A matcher for RangeErrors. */ |
| 556 const isRangeError = const _RangeError(); |
| 557 |
| 558 /** A matcher for functions that throw RangeError. */ |
| 559 const Matcher throwsRangeError = const Throws(isRangeError); |
| 560 |
| 561 class _RangeError extends TypeMatcher { |
| 562 const _RangeError(): super("RangeError"); |
| 563 bool matches(item, Map matchState) => item is RangeError; |
| 564 } |
| 565 |
| 566 /** A matcher for NoSuchMethodErrors. */ |
| 567 const isNoSuchMethodError = const _NoSuchMethodError(); |
| 568 |
| 569 /** A matcher for functions that throw NoSuchMethodError. */ |
| 570 const Matcher throwsNoSuchMethodError = const Throws(isNoSuchMethodError); |
| 571 |
| 572 class _NoSuchMethodError extends TypeMatcher { |
| 573 const _NoSuchMethodError(): super("NoSuchMethodError"); |
| 574 bool matches(item, Map matchState) => item is NoSuchMethodError; |
| 575 } |
| 576 |
| 577 /** A matcher for UnimplementedErrors. */ |
| 578 const isUnimplementedError = const _UnimplementedError(); |
| 579 |
| 580 /** A matcher for functions that throw Exception. */ |
| 581 const Matcher throwsUnimplementedError = const Throws(isUnimplementedError); |
| 582 |
| 583 class _UnimplementedError extends TypeMatcher { |
| 584 const _UnimplementedError(): super("UnimplementedError"); |
| 585 bool matches(item, Map matchState) => item is UnimplementedError; |
| 586 } |
| 587 |
| 588 /** A matcher for UnsupportedError. */ |
| 589 const isUnsupportedError = const _UnsupportedError(); |
| 590 |
| 591 /** A matcher for functions that throw UnsupportedError. */ |
| 592 const Matcher throwsUnsupportedError = const Throws(isUnsupportedError); |
| 593 |
| 594 class _UnsupportedError extends TypeMatcher { |
| 595 const _UnsupportedError(): super("UnsupportedError"); |
| 596 bool matches(item, Map matchState) => item is UnsupportedError; |
| 597 } |
| 598 |
| 599 /** A matcher for StateErrors. */ |
| 600 const isStateError = const _StateError(); |
| 601 |
| 602 /** A matcher for functions that throw StateError. */ |
| 603 const Matcher throwsStateError = const Throws(isStateError); |
| 604 |
| 605 class _StateError extends TypeMatcher { |
| 606 const _StateError(): super("StateError"); |
| 607 bool matches(item, Map matchState) => item is StateError; |
| 608 } |
| 609 |
| 610 /** A matcher for FallThroughError. */ |
| 611 const isFallThroughError = const _FallThroughError(); |
| 612 |
| 613 /** A matcher for functions that throw FallThroughError. */ |
| 614 const Matcher throwsFallThroughError = const Throws(isFallThroughError); |
| 615 |
| 616 class _FallThroughError extends TypeMatcher { |
| 617 const _FallThroughError(): super("FallThroughError"); |
| 618 bool matches(item, Map matchState) => item is FallThroughError; |
| 619 } |
| 620 |
| 621 /** A matcher for NullThrownError. */ |
| 622 const isNullThrownError = const _NullThrownError(); |
| 623 |
| 624 /** A matcher for functions that throw NullThrownError. */ |
| 625 const Matcher throwsNullThrownError = const Throws(isNullThrownError); |
| 626 |
| 627 class _NullThrownError extends TypeMatcher { |
| 628 const _NullThrownError(): super("NullThrownError"); |
| 629 bool matches(item, Map matchState) => item is NullThrownError; |
| 630 } |
| 631 |
| 632 /** A matcher for ConcurrentModificationError. */ |
| 633 const isConcurrentModificationError = const _ConcurrentModificationError(); |
| 634 |
| 635 /** A matcher for functions that throw ConcurrentModificationError. */ |
| 636 const Matcher throwsConcurrentModificationError = |
| 637 const Throws(isConcurrentModificationError); |
| 638 |
| 639 class _ConcurrentModificationError extends TypeMatcher { |
| 640 const _ConcurrentModificationError(): super("ConcurrentModificationError"); |
| 641 bool matches(item, Map matchState) => item is ConcurrentModificationError; |
| 642 } |
| 643 |
| 644 /** A matcher for AbstractClassInstantiationError. */ |
| 645 const isAbstractClassInstantiationError = |
| 646 const _AbstractClassInstantiationError(); |
| 647 |
| 648 /** A matcher for functions that throw AbstractClassInstantiationError. */ |
| 649 const Matcher throwsAbstractClassInstantiationError = |
| 650 const Throws(isAbstractClassInstantiationError); |
| 651 |
| 652 class _AbstractClassInstantiationError extends TypeMatcher { |
| 653 const _AbstractClassInstantiationError() : |
| 654 super("AbstractClassInstantiationError"); |
| 655 bool matches(item, Map matchState) => item is AbstractClassInstantiationError; |
| 656 } |
| 657 |
| 658 /** A matcher for CyclicInitializationError. */ |
| 659 const isCyclicInitializationError = const _CyclicInitializationError(); |
| 660 |
| 661 /** A matcher for functions that throw CyclicInitializationError. */ |
| 662 const Matcher throwsCyclicInitializationError = |
| 663 const Throws(isCyclicInitializationError); |
| 664 |
| 665 class _CyclicInitializationError extends TypeMatcher { |
| 666 const _CyclicInitializationError(): super("CyclicInitializationError"); |
| 667 bool matches(item, Map matchState) => item is CyclicInitializationError; |
| 668 } |
| 669 |
| 670 /** A matcher for Map types. */ |
| 671 const isMap = const _IsMap(); |
518 | 672 |
519 class _IsMap extends TypeMatcher { | 673 class _IsMap extends TypeMatcher { |
520 const _IsMap() : super("Map"); | 674 const _IsMap(): super("Map"); |
521 bool matches(item, Map matchState) => item is Map; | 675 bool matches(item, Map matchState) => item is Map; |
522 } | 676 } |
523 | 677 |
524 /// A matcher for List types. | 678 /** A matcher for List types. */ |
525 const Matcher isList = const _IsList(); | 679 const isList = const _IsList(); |
526 | 680 |
527 class _IsList extends TypeMatcher { | 681 class _IsList extends TypeMatcher { |
528 const _IsList() : super("List"); | 682 const _IsList(): super("List"); |
529 bool matches(item, Map matchState) => item is List; | 683 bool matches(item, Map matchState) => item is List; |
530 } | 684 } |
531 | 685 |
532 /// Returns a matcher that matches if an object has a length property | 686 /** |
533 /// that matches [matcher]. | 687 * Returns a matcher that matches if an object has a length property |
| 688 * that matches [matcher]. |
| 689 */ |
534 Matcher hasLength(matcher) => new _HasLength(wrapMatcher(matcher)); | 690 Matcher hasLength(matcher) => new _HasLength(wrapMatcher(matcher)); |
535 | 691 |
536 class _HasLength extends Matcher { | 692 class _HasLength extends Matcher { |
537 final Matcher _matcher; | 693 final Matcher _matcher; |
538 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; | 694 const _HasLength([Matcher matcher = null]): this._matcher = matcher; |
539 | 695 |
540 bool matches(item, Map matchState) { | 696 bool matches(item, Map matchState) { |
541 try { | 697 try { |
542 // This is harmless code that will throw if no length property | 698 // This is harmless code that will throw if no length property |
543 // but subtle enough that an optimizer shouldn't strip it out. | 699 // but subtle enough that an optimizer shouldn't strip it out. |
544 if (item.length * item.length >= 0) { | 700 if (item.length * item.length >= 0) { |
545 return _matcher.matches(item.length, matchState); | 701 return _matcher.matches(item.length, matchState); |
546 } | 702 } |
547 } catch (e) {} | 703 } catch (e) {} |
548 return false; | 704 return false; |
(...skipping 10 matching lines...) Expand all Loading... |
559 // property; we use the same trick as in matches(). | 715 // property; we use the same trick as in matches(). |
560 if (item.length * item.length >= 0) { | 716 if (item.length * item.length >= 0) { |
561 return mismatchDescription.add('has length of '). | 717 return mismatchDescription.add('has length of '). |
562 addDescriptionOf(item.length); | 718 addDescriptionOf(item.length); |
563 } | 719 } |
564 } catch (e) {} | 720 } catch (e) {} |
565 return mismatchDescription.add('has no length property'); | 721 return mismatchDescription.add('has no length property'); |
566 } | 722 } |
567 } | 723 } |
568 | 724 |
569 /// Returns a matcher that matches if the match argument contains | 725 /** |
570 /// the expected value. For [String]s this means substring matching; | 726 * Returns a matcher that matches if the match argument contains |
571 /// for [Map]s it means the map has the key, and for [Iterable]s | 727 * the expected value. For [String]s this means substring matching; |
572 /// (including [Iterable]s) it means the iterable has a matching | 728 * for [Map]s it means the map has the key, and for [Iterable]s |
573 /// element. In the case of iterables, [expected] can itself be a | 729 * (including [Iterable]s) it means the iterable has a matching |
574 /// matcher. | 730 * element. In the case of iterables, [expected] can itself be a |
| 731 * matcher. |
| 732 */ |
575 Matcher contains(expected) => new _Contains(expected); | 733 Matcher contains(expected) => new _Contains(expected); |
576 | 734 |
577 class _Contains extends Matcher { | 735 class _Contains extends Matcher { |
| 736 |
578 final _expected; | 737 final _expected; |
579 | 738 |
580 const _Contains(this._expected); | 739 const _Contains(this._expected); |
581 | 740 |
582 bool matches(item, Map matchState) { | 741 bool matches(item, Map matchState) { |
583 if (item is String) { | 742 if (item is String) { |
584 return item.indexOf(_expected) >= 0; | 743 return item.indexOf(_expected) >= 0; |
585 } else if (item is Iterable) { | 744 } else if (item is Iterable) { |
586 if (_expected is Matcher) { | 745 if (_expected is Matcher) { |
587 return item.any((e) => _expected.matches(e, matchState)); | 746 return item.any((e) => _expected.matches(e, matchState)); |
(...skipping 13 matching lines...) Expand all Loading... |
601 Map matchState, bool verbose) { | 760 Map matchState, bool verbose) { |
602 if (item is String || item is Iterable || item is Map) { | 761 if (item is String || item is Iterable || item is Map) { |
603 return super.describeMismatch(item, mismatchDescription, matchState, | 762 return super.describeMismatch(item, mismatchDescription, matchState, |
604 verbose); | 763 verbose); |
605 } else { | 764 } else { |
606 return mismatchDescription.add('is not a string, map or iterable'); | 765 return mismatchDescription.add('is not a string, map or iterable'); |
607 } | 766 } |
608 } | 767 } |
609 } | 768 } |
610 | 769 |
611 /// Returns a matcher that matches if the match argument is in | 770 /** |
612 /// the expected value. This is the converse of [contains]. | 771 * Returns a matcher that matches if the match argument is in |
| 772 * the expected value. This is the converse of [contains]. |
| 773 */ |
613 Matcher isIn(expected) => new _In(expected); | 774 Matcher isIn(expected) => new _In(expected); |
614 | 775 |
615 class _In extends Matcher { | 776 class _In extends Matcher { |
| 777 |
616 final _expected; | 778 final _expected; |
617 | 779 |
618 const _In(this._expected); | 780 const _In(this._expected); |
619 | 781 |
620 bool matches(item, Map matchState) { | 782 bool matches(item, Map matchState) { |
621 if (_expected is String) { | 783 if (_expected is String) { |
622 return _expected.indexOf(item) >= 0; | 784 return _expected.indexOf(item) >= 0; |
623 } else if (_expected is Iterable) { | 785 } else if (_expected is Iterable) { |
624 return _expected.any((e) => e == item); | 786 return _expected.any((e) => e == item); |
625 } else if (_expected is Map) { | 787 } else if (_expected is Map) { |
626 return _expected.containsKey(item); | 788 return _expected.containsKey(item); |
627 } | 789 } |
628 return false; | 790 return false; |
629 } | 791 } |
630 | 792 |
631 Description describe(Description description) => | 793 Description describe(Description description) => |
632 description.add('is in ').addDescriptionOf(_expected); | 794 description.add('is in ').addDescriptionOf(_expected); |
633 } | 795 } |
634 | 796 |
635 /// Returns a matcher that uses an arbitrary function that returns | 797 /** |
636 /// true or false for the actual value. For example: | 798 * Returns a matcher that uses an arbitrary function that returns |
637 /// | 799 * true or false for the actual value. For example: |
638 /// expect(v, predicate((x) => ((x % 2) == 0), "is even")) | 800 * |
639 Matcher predicate(bool f(value), [String description = 'satisfies function']) => | 801 * expect(v, predicate((x) => ((x % 2) == 0), "is even")) |
| 802 */ |
| 803 Matcher predicate(Function f, [description = 'satisfies function']) => |
640 new _Predicate(f, description); | 804 new _Predicate(f, description); |
641 | 805 |
642 typedef bool _PredicateFunction(value); | 806 class _Predicate extends Matcher { |
643 | 807 |
644 class _Predicate extends Matcher { | 808 final Function _matcher; |
645 final _PredicateFunction _matcher; | |
646 final String _description; | 809 final String _description; |
647 | 810 |
648 const _Predicate(this._matcher, this._description); | 811 const _Predicate(this._matcher, this._description); |
649 | 812 |
650 bool matches(item, Map matchState) => _matcher(item); | 813 bool matches(item, Map matchState) => _matcher(item); |
651 | 814 |
652 Description describe(Description description) => | 815 Description describe(Description description) => |
653 description.add(_description); | 816 description.add(_description); |
654 } | 817 } |
655 | 818 |
656 /// A useful utility class for implementing other matchers through inheritance. | 819 /** |
657 /// Derived classes should call the base constructor with a feature name and | 820 * A useful utility class for implementing other matchers through inheritance. |
658 /// description, and an instance matcher, and should implement the | 821 * Derived classes should call the base constructor with a feature name and |
659 /// [featureValueOf] abstract method. | 822 * description, and an instance matcher, and should implement the |
660 /// | 823 * [featureValueOf] abstract method. |
661 /// The feature description will typically describe the item and the feature, | 824 * |
662 /// while the feature name will just name the feature. For example, we may | 825 * The feature description will typically describe the item and the feature, |
663 /// have a Widget class where each Widget has a price; we could make a | 826 * while the feature name will just name the feature. For example, we may |
664 /// [CustomMatcher] that can make assertions about prices with: | 827 * have a Widget class where each Widget has a price; we could make a |
665 /// | 828 * [CustomMatcher] that can make assertions about prices with: |
666 /// class HasPrice extends CustomMatcher { | 829 * |
667 /// const HasPrice(matcher) : | 830 * class HasPrice extends CustomMatcher { |
668 /// super("Widget with price that is", "price", matcher); | 831 * const HasPrice(matcher) : |
669 /// featureValueOf(actual) => actual.price; | 832 * super("Widget with price that is", "price", matcher); |
670 /// } | 833 * featureValueOf(actual) => actual.price; |
671 /// | 834 * } |
672 /// and then use this for example like: | 835 * |
673 /// | 836 * and then use this for example like: |
674 /// expect(inventoryItem, new HasPrice(greaterThan(0))); | 837 * |
| 838 * expect(inventoryItem, new HasPrice(greaterThan(0))); |
| 839 */ |
675 class CustomMatcher extends Matcher { | 840 class CustomMatcher extends Matcher { |
676 final String _featureDescription; | 841 final String _featureDescription; |
677 final String _featureName; | 842 final String _featureName; |
678 final Matcher _matcher; | 843 final Matcher _matcher; |
679 | 844 |
680 CustomMatcher(this._featureDescription, this._featureName, matcher) | 845 CustomMatcher(this._featureDescription, this._featureName, matcher) |
681 : this._matcher = wrapMatcher(matcher); | 846 : this._matcher = wrapMatcher(matcher); |
682 | 847 |
683 /// Override this to extract the interesting feature. | 848 /** Override this to extract the interesting feature.*/ |
684 featureValueOf(actual) => actual; | 849 featureValueOf(actual) => actual; |
685 | 850 |
686 bool matches(item, Map matchState) { | 851 bool matches(item, Map matchState) { |
687 var f = featureValueOf(item); | 852 var f = featureValueOf(item); |
688 if (_matcher.matches(f, matchState)) return true; | 853 if (_matcher.matches(f, matchState)) return true; |
689 addStateInfo(matchState, {'feature': f}); | 854 addStateInfo(matchState, {'feature': f}); |
690 return false; | 855 return false; |
691 } | 856 } |
692 | 857 |
693 Description describe(Description description) => | 858 Description describe(Description description) => |
694 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); | 859 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); |
695 | 860 |
696 Description describeMismatch(item, Description mismatchDescription, | 861 Description describeMismatch(item, Description mismatchDescription, |
697 Map matchState, bool verbose) { | 862 Map matchState, bool verbose) { |
698 mismatchDescription.add('has ').add(_featureName).add(' with value '). | 863 mismatchDescription.add('has ').add(_featureName).add(' with value '). |
699 addDescriptionOf(matchState['feature']); | 864 addDescriptionOf(matchState['feature']); |
700 var innerDescription = new StringDescription(); | 865 var innerDescription = new StringDescription(); |
701 _matcher.describeMismatch(matchState['feature'], innerDescription, | 866 _matcher.describeMismatch(matchState['feature'], innerDescription, |
702 matchState['state'], verbose); | 867 matchState['state'], verbose); |
703 if (innerDescription.length > 0) { | 868 if (innerDescription.length > 0) { |
704 mismatchDescription.add(' which ').add(innerDescription.toString()); | 869 mismatchDescription.add(' which ').add(innerDescription.toString()); |
705 } | 870 } |
706 return mismatchDescription; | 871 return mismatchDescription; |
707 } | 872 } |
708 } | 873 } |
OLD | NEW |