| 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 part of matcher; | 5 part of matcher; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Returns a matcher that matches empty strings, maps or iterables | 8 * Returns a matcher that matches empty strings, maps or iterables |
| 9 * (including collections). | 9 * (including collections). |
| 10 */ | 10 */ |
| 11 const Matcher isEmpty = const _Empty(); | 11 const Matcher isEmpty = const _Empty(); |
| 12 | 12 |
| 13 class _Empty extends BaseMatcher { | 13 class _Empty extends Matcher { |
| 14 const _Empty(); | 14 const _Empty(); |
| 15 bool matches(item, Map matchState) { | 15 bool matches(item, Map matchState) { |
| 16 if (item is Map || item is Iterable) { | 16 if (item is Map || item is Iterable) { |
| 17 return item.isEmpty; | 17 return item.isEmpty; |
| 18 } else if (item is String) { | 18 } else if (item is String) { |
| 19 return item.length == 0; | 19 return item.length == 0; |
| 20 } else { | 20 } else { |
| 21 return false; | 21 return false; |
| 22 } | 22 } |
| 23 } | 23 } |
| 24 Description describe(Description description) => | 24 Description describe(Description description) => |
| 25 description.add('empty'); | 25 description.add('empty'); |
| 26 } | 26 } |
| 27 | 27 |
| 28 /** A matcher that matches any null value. */ | 28 /** A matcher that matches any null value. */ |
| 29 const Matcher isNull = const _IsNull(); | 29 const Matcher isNull = const _IsNull(); |
| 30 | 30 |
| 31 /** A matcher that matches any non-null value. */ | 31 /** A matcher that matches any non-null value. */ |
| 32 const Matcher isNotNull = const _IsNotNull(); | 32 const Matcher isNotNull = const _IsNotNull(); |
| 33 | 33 |
| 34 class _IsNull extends BaseMatcher { | 34 class _IsNull extends Matcher { |
| 35 const _IsNull(); | 35 const _IsNull(); |
| 36 bool matches(item, Map matchState) => item == null; | 36 bool matches(item, Map matchState) => item == null; |
| 37 Description describe(Description description) => | 37 Description describe(Description description) => |
| 38 description.add('null'); | 38 description.add('null'); |
| 39 } | 39 } |
| 40 | 40 |
| 41 class _IsNotNull extends BaseMatcher { | 41 class _IsNotNull extends Matcher { |
| 42 const _IsNotNull(); | 42 const _IsNotNull(); |
| 43 bool matches(item, Map matchState) => item != null; | 43 bool matches(item, Map matchState) => item != null; |
| 44 Description describe(Description description) => | 44 Description describe(Description description) => |
| 45 description.add('not null'); | 45 description.add('not null'); |
| 46 } | 46 } |
| 47 | 47 |
| 48 /** A matcher that matches the Boolean value true. */ | 48 /** A matcher that matches the Boolean value true. */ |
| 49 const Matcher isTrue = const _IsTrue(); | 49 const Matcher isTrue = const _IsTrue(); |
| 50 | 50 |
| 51 /** A matcher that matches anything except the Boolean value true. */ | 51 /** A matcher that matches anything except the Boolean value true. */ |
| 52 const Matcher isFalse = const _IsFalse(); | 52 const Matcher isFalse = const _IsFalse(); |
| 53 | 53 |
| 54 class _IsTrue extends BaseMatcher { | 54 class _IsTrue extends Matcher { |
| 55 const _IsTrue(); | 55 const _IsTrue(); |
| 56 bool matches(item, Map matchState) => item == true; | 56 bool matches(item, Map matchState) => item == true; |
| 57 Description describe(Description description) => | 57 Description describe(Description description) => |
| 58 description.add('true'); | 58 description.add('true'); |
| 59 } | 59 } |
| 60 | 60 |
| 61 class _IsFalse extends BaseMatcher { | 61 class _IsFalse extends Matcher { |
| 62 const _IsFalse(); | 62 const _IsFalse(); |
| 63 bool matches(item, Map matchState) => item == false; | 63 bool matches(item, Map matchState) => item == false; |
| 64 Description describe(Description description) => | 64 Description describe(Description description) => |
| 65 description.add('false'); | 65 description.add('false'); |
| 66 } | 66 } |
| 67 | 67 |
| 68 /** | 68 /** |
| 69 * Returns a matches that matches if the value is the same instance | 69 * Returns a matches that matches if the value is the same instance |
| 70 * as [object] (`===`). | 70 * as [object] (`===`). |
| 71 */ | 71 */ |
| 72 Matcher same(expected) => new _IsSameAs(expected); | 72 Matcher same(expected) => new _IsSameAs(expected); |
| 73 | 73 |
| 74 class _IsSameAs extends BaseMatcher { | 74 class _IsSameAs extends Matcher { |
| 75 final _expected; | 75 final _expected; |
| 76 const _IsSameAs(this._expected); | 76 const _IsSameAs(this._expected); |
| 77 bool matches(item, Map matchState) => identical(item, _expected); | 77 bool matches(item, Map matchState) => identical(item, _expected); |
| 78 // If all types were hashable we could show a hash here. | 78 // If all types were hashable we could show a hash here. |
| 79 Description describe(Description description) => | 79 Description describe(Description description) => |
| 80 description.add('same instance as ').addDescriptionOf(_expected); | 80 description.add('same instance as ').addDescriptionOf(_expected); |
| 81 } | 81 } |
| 82 | 82 |
| 83 /** | 83 /** |
| 84 * Returns a matcher that does a deep recursive match. This only works | 84 * Returns a matcher that does a deep recursive match. This only works |
| 85 * with scalars, Maps and Iterables. To handle cyclic structures a | 85 * with scalars, Maps and Iterables. To handle cyclic structures a |
| 86 * recursion depth [limit] can be provided. The default limit is 100. | 86 * recursion depth [limit] can be provided. The default limit is 100. |
| 87 */ | 87 */ |
| 88 Matcher equals(expected, [limit=100]) => | 88 Matcher equals(expected, [limit=100]) => |
| 89 expected is String | 89 expected is String |
| 90 ? new _StringEqualsMatcher(expected) | 90 ? new _StringEqualsMatcher(expected) |
| 91 : new _DeepMatcher(expected, limit); | 91 : new _DeepMatcher(expected, limit); |
| 92 | 92 |
| 93 class _DeepMatcher extends BaseMatcher { | 93 class _DeepMatcher extends Matcher { |
| 94 final _expected; | 94 final _expected; |
| 95 final int _limit; | 95 final int _limit; |
| 96 var count; | 96 var count; |
| 97 | 97 |
| 98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; | 98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; |
| 99 | 99 |
| 100 // Returns a pair (reason, location) | 100 // Returns a pair (reason, location) |
| 101 List _compareIterables(expected, actual, matcher, depth, location) { | 101 List _compareIterables(expected, actual, matcher, depth, location) { |
| 102 if (actual is !Iterable) { | 102 if (actual is !Iterable) { |
| 103 return ['is not Iterable', location]; | 103 return ['is not Iterable', location]; |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 227 if (reason.length == 0 && mismatchDescription.length > 0) { | 227 if (reason.length == 0 && mismatchDescription.length > 0) { |
| 228 mismatchDescription.add('is ').addDescriptionOf(item); | 228 mismatchDescription.add('is ').addDescriptionOf(item); |
| 229 } else { | 229 } else { |
| 230 mismatchDescription.add(reason); | 230 mismatchDescription.add(reason); |
| 231 } | 231 } |
| 232 return mismatchDescription; | 232 return mismatchDescription; |
| 233 } | 233 } |
| 234 } | 234 } |
| 235 | 235 |
| 236 /** A special equality matcher for strings. */ | 236 /** A special equality matcher for strings. */ |
| 237 class _StringEqualsMatcher extends BaseMatcher { | 237 class _StringEqualsMatcher extends Matcher { |
| 238 final String _value; | 238 final String _value; |
| 239 | 239 |
| 240 _StringEqualsMatcher(this._value); | 240 _StringEqualsMatcher(this._value); |
| 241 | 241 |
| 242 bool get showActualValue => true; | 242 bool get showActualValue => true; |
| 243 | 243 |
| 244 bool matches(item, Map matchState) => _value == item; | 244 bool matches(item, Map matchState) => _value == item; |
| 245 | 245 |
| 246 Description describe(Description description) => | 246 Description describe(Description description) => |
| 247 description.addDescriptionOf(_value); | 247 description.addDescriptionOf(_value); |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 307 } else { | 307 } else { |
| 308 buff.write(s.substring(start, start + 10)); | 308 buff.write(s.substring(start, start + 10)); |
| 309 buff.write(' ...'); | 309 buff.write(' ...'); |
| 310 } | 310 } |
| 311 } | 311 } |
| 312 } | 312 } |
| 313 | 313 |
| 314 /** A matcher that matches any value. */ | 314 /** A matcher that matches any value. */ |
| 315 const Matcher anything = const _IsAnything(); | 315 const Matcher anything = const _IsAnything(); |
| 316 | 316 |
| 317 class _IsAnything extends BaseMatcher { | 317 class _IsAnything extends Matcher { |
| 318 const _IsAnything(); | 318 const _IsAnything(); |
| 319 bool matches(item, Map matchState) => true; | 319 bool matches(item, Map matchState) => true; |
| 320 Description describe(Description description) => | 320 Description describe(Description description) => |
| 321 description.add('anything'); | 321 description.add('anything'); |
| 322 } | 322 } |
| 323 | 323 |
| 324 /** | 324 /** |
| 325 * Returns a matcher that matches if an object is an instance | 325 * Returns a matcher that matches if an object is an instance |
| 326 * of [type] (or a subtype). | 326 * of [type] (or a subtype). |
| 327 * | 327 * |
| 328 * As types are not first class objects in Dart we can only | 328 * As types are not first class objects in Dart we can only |
| 329 * approximate this test by using a generic wrapper class. | 329 * approximate this test by using a generic wrapper class. |
| 330 * | 330 * |
| 331 * For example, to test whether 'bar' is an instance of type | 331 * For example, to test whether 'bar' is an instance of type |
| 332 * 'Foo', we would write: | 332 * 'Foo', we would write: |
| 333 * | 333 * |
| 334 * expect(bar, new isInstanceOf<Foo>()); | 334 * expect(bar, new isInstanceOf<Foo>()); |
| 335 * | 335 * |
| 336 * To get better error message, supply a name when creating the | 336 * To get better error message, supply a name when creating the |
| 337 * Type wrapper; e.g.: | 337 * Type wrapper; e.g.: |
| 338 * | 338 * |
| 339 * expect(bar, new isInstanceOf<Foo>('Foo')); | 339 * expect(bar, new isInstanceOf<Foo>('Foo')); |
| 340 * | 340 * |
| 341 * Note that this does not currently work in dart2js; it will | 341 * Note that this does not currently work in dart2js; it will |
| 342 * match any type, and isNot(new isInstanceof<T>()) will always | 342 * match any type, and isNot(new isInstanceof<T>()) will always |
| 343 * fail. This is because dart2js currently ignores template type | 343 * fail. This is because dart2js currently ignores template type |
| 344 * parameters. | 344 * parameters. |
| 345 */ | 345 */ |
| 346 class isInstanceOf<T> extends BaseMatcher { | 346 class isInstanceOf<T> extends Matcher { |
| 347 final String _name; | 347 final String _name; |
| 348 const isInstanceOf([name = 'specified type']) : this._name = name; | 348 const isInstanceOf([name = 'specified type']) : this._name = name; |
| 349 bool matches(obj, Map matchState) => obj is T; | 349 bool matches(obj, Map matchState) => obj is T; |
| 350 // The description here is lame :-( | 350 // The description here is lame :-( |
| 351 Description describe(Description description) => | 351 Description describe(Description description) => |
| 352 description.add('an instance of ${_name}'); | 352 description.add('an instance of ${_name}'); |
| 353 } | 353 } |
| 354 | 354 |
| 355 /** | 355 /** |
| 356 * This can be used to match two kinds of objects: | 356 * This can be used to match two kinds of objects: |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 388 | 388 |
| 389 /** | 389 /** |
| 390 * A matcher that matches a function call against no exception. | 390 * A matcher that matches a function call against no exception. |
| 391 * The function will be called once. Any exceptions will be silently swallowed. | 391 * The function will be called once. Any exceptions will be silently swallowed. |
| 392 * The value passed to expect() should be a reference to the function. | 392 * The value passed to expect() should be a reference to the function. |
| 393 * Note that the function cannot take arguments; to handle this | 393 * Note that the function cannot take arguments; to handle this |
| 394 * a wrapper will have to be created. | 394 * a wrapper will have to be created. |
| 395 */ | 395 */ |
| 396 const Matcher returnsNormally = const _ReturnsNormally(); | 396 const Matcher returnsNormally = const _ReturnsNormally(); |
| 397 | 397 |
| 398 class Throws extends BaseMatcher { | 398 class Throws extends Matcher { |
| 399 final Matcher _matcher; | 399 final Matcher _matcher; |
| 400 | 400 |
| 401 const Throws([Matcher matcher]) : | 401 const Throws([Matcher matcher]) : |
| 402 this._matcher = matcher; | 402 this._matcher = matcher; |
| 403 | 403 |
| 404 bool matches(item, Map matchState) { | 404 bool matches(item, Map matchState) { |
| 405 if (item is! Function && item is! Future) return false; | 405 if (item is! Function && item is! Future) return false; |
| 406 if (item is Future) { | 406 if (item is Future) { |
| 407 var done = wrapAsync((fn) => fn()); | 407 var done = wrapAsync((fn) => fn()); |
| 408 | 408 |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 459 mismatchDescription. add('threw '). | 459 mismatchDescription. add('threw '). |
| 460 addDescriptionOf(matchState['exception']); | 460 addDescriptionOf(matchState['exception']); |
| 461 if (verbose) { | 461 if (verbose) { |
| 462 mismatchDescription.add(' at ').add(matchState['stack'].toString()); | 462 mismatchDescription.add(' at ').add(matchState['stack'].toString()); |
| 463 } | 463 } |
| 464 return mismatchDescription; | 464 return mismatchDescription; |
| 465 } | 465 } |
| 466 } | 466 } |
| 467 } | 467 } |
| 468 | 468 |
| 469 class _ReturnsNormally extends BaseMatcher { | 469 class _ReturnsNormally extends Matcher { |
| 470 const _ReturnsNormally(); | 470 const _ReturnsNormally(); |
| 471 | 471 |
| 472 bool matches(f, Map matchState) { | 472 bool matches(f, Map matchState) { |
| 473 try { | 473 try { |
| 474 f(); | 474 f(); |
| 475 return true; | 475 return true; |
| 476 } catch (e, s) { | 476 } catch (e, s) { |
| 477 addStateInfo(matchState, {'exception': e, 'stack': s}); | 477 addStateInfo(matchState, {'exception': e, 'stack': s}); |
| 478 return false; | 478 return false; |
| 479 } | 479 } |
| (...skipping 27 matching lines...) Expand all Loading... |
| 507 * | 507 * |
| 508 * bool _isException(x) => x is Exception; | 508 * bool _isException(x) => x is Exception; |
| 509 * final Matcher isException = const _Predicate(_isException, "Exception"); | 509 * final Matcher isException = const _Predicate(_isException, "Exception"); |
| 510 * final Matcher throwsException = const _Throws(isException); | 510 * final Matcher throwsException = const _Throws(isException); |
| 511 * | 511 * |
| 512 * But currently using static functions in const expressions is not supported. | 512 * But currently using static functions in const expressions is not supported. |
| 513 * For now the only solution for all platforms seems to be separate classes | 513 * For now the only solution for all platforms seems to be separate classes |
| 514 * for each exception type. | 514 * for each exception type. |
| 515 */ | 515 */ |
| 516 | 516 |
| 517 abstract class TypeMatcher extends BaseMatcher { | 517 abstract class TypeMatcher extends Matcher { |
| 518 final String _name; | 518 final String _name; |
| 519 const TypeMatcher(this._name); | 519 const TypeMatcher(this._name); |
| 520 Description describe(Description description) => | 520 Description describe(Description description) => |
| 521 description.add(_name); | 521 description.add(_name); |
| 522 } | 522 } |
| 523 | 523 |
| 524 /** A matcher for FormatExceptions. */ | 524 /** A matcher for FormatExceptions. */ |
| 525 const isFormatException = const _FormatException(); | 525 const isFormatException = const _FormatException(); |
| 526 | 526 |
| 527 /** A matcher for functions that throw FormatException. */ | 527 /** A matcher for functions that throw FormatException. */ |
| (...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 633 bool matches(item, Map matchState) => item is List; | 633 bool matches(item, Map matchState) => item is List; |
| 634 } | 634 } |
| 635 | 635 |
| 636 /** | 636 /** |
| 637 * Returns a matcher that matches if an object has a length property | 637 * Returns a matcher that matches if an object has a length property |
| 638 * that matches [matcher]. | 638 * that matches [matcher]. |
| 639 */ | 639 */ |
| 640 Matcher hasLength(matcher) => | 640 Matcher hasLength(matcher) => |
| 641 new _HasLength(wrapMatcher(matcher)); | 641 new _HasLength(wrapMatcher(matcher)); |
| 642 | 642 |
| 643 class _HasLength extends BaseMatcher { | 643 class _HasLength extends Matcher { |
| 644 final Matcher _matcher; | 644 final Matcher _matcher; |
| 645 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; | 645 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; |
| 646 | 646 |
| 647 bool matches(item, Map matchState) { | 647 bool matches(item, Map matchState) { |
| 648 try { | 648 try { |
| 649 // This is harmless code that will throw if no length property | 649 // This is harmless code that will throw if no length property |
| 650 // but subtle enough that an optimizer shouldn't strip it out. | 650 // but subtle enough that an optimizer shouldn't strip it out. |
| 651 if (item.length * item.length >= 0) { | 651 if (item.length * item.length >= 0) { |
| 652 return _matcher.matches(item.length, matchState); | 652 return _matcher.matches(item.length, matchState); |
| 653 } | 653 } |
| (...skipping 24 matching lines...) Expand all Loading... |
| 678 /** | 678 /** |
| 679 * Returns a matcher that matches if the match argument contains | 679 * Returns a matcher that matches if the match argument contains |
| 680 * the expected value. For [String]s this means substring matching; | 680 * the expected value. For [String]s this means substring matching; |
| 681 * for [Map]s it means the map has the key, and for [Iterable]s | 681 * for [Map]s it means the map has the key, and for [Iterable]s |
| 682 * (including [Iterable]s) it means the iterable has a matching | 682 * (including [Iterable]s) it means the iterable has a matching |
| 683 * element. In the case of iterables, [expected] can itself be a | 683 * element. In the case of iterables, [expected] can itself be a |
| 684 * matcher. | 684 * matcher. |
| 685 */ | 685 */ |
| 686 Matcher contains(expected) => new _Contains(expected); | 686 Matcher contains(expected) => new _Contains(expected); |
| 687 | 687 |
| 688 class _Contains extends BaseMatcher { | 688 class _Contains extends Matcher { |
| 689 | 689 |
| 690 final _expected; | 690 final _expected; |
| 691 | 691 |
| 692 const _Contains(this._expected); | 692 const _Contains(this._expected); |
| 693 | 693 |
| 694 bool matches(item, Map matchState) { | 694 bool matches(item, Map matchState) { |
| 695 if (item is String) { | 695 if (item is String) { |
| 696 return item.indexOf(_expected) >= 0; | 696 return item.indexOf(_expected) >= 0; |
| 697 } else if (item is Iterable) { | 697 } else if (item is Iterable) { |
| 698 if (_expected is Matcher) { | 698 if (_expected is Matcher) { |
| (...skipping 20 matching lines...) Expand all Loading... |
| 719 } | 719 } |
| 720 } | 720 } |
| 721 } | 721 } |
| 722 | 722 |
| 723 /** | 723 /** |
| 724 * Returns a matcher that matches if the match argument is in | 724 * Returns a matcher that matches if the match argument is in |
| 725 * the expected value. This is the converse of [contains]. | 725 * the expected value. This is the converse of [contains]. |
| 726 */ | 726 */ |
| 727 Matcher isIn(expected) => new _In(expected); | 727 Matcher isIn(expected) => new _In(expected); |
| 728 | 728 |
| 729 class _In extends BaseMatcher { | 729 class _In extends Matcher { |
| 730 | 730 |
| 731 final _expected; | 731 final _expected; |
| 732 | 732 |
| 733 const _In(this._expected); | 733 const _In(this._expected); |
| 734 | 734 |
| 735 bool matches(item, Map matchState) { | 735 bool matches(item, Map matchState) { |
| 736 if (_expected is String) { | 736 if (_expected is String) { |
| 737 return _expected.indexOf(item) >= 0; | 737 return _expected.indexOf(item) >= 0; |
| 738 } else if (_expected is Iterable) { | 738 } else if (_expected is Iterable) { |
| 739 return _expected.any((e) => e == item); | 739 return _expected.any((e) => e == item); |
| 740 } else if (_expected is Map) { | 740 } else if (_expected is Map) { |
| 741 return _expected.containsKey(item); | 741 return _expected.containsKey(item); |
| 742 } | 742 } |
| 743 return false; | 743 return false; |
| 744 } | 744 } |
| 745 | 745 |
| 746 Description describe(Description description) => | 746 Description describe(Description description) => |
| 747 description.add('is in ').addDescriptionOf(_expected); | 747 description.add('is in ').addDescriptionOf(_expected); |
| 748 } | 748 } |
| 749 | 749 |
| 750 /** | 750 /** |
| 751 * Returns a matcher that uses an arbitrary function that returns | 751 * Returns a matcher that uses an arbitrary function that returns |
| 752 * true or false for the actual value. For example: | 752 * true or false for the actual value. For example: |
| 753 * | 753 * |
| 754 * expect(v, predicate((x) => ((x % 2) == 0), "is even")) | 754 * expect(v, predicate((x) => ((x % 2) == 0), "is even")) |
| 755 */ | 755 */ |
| 756 Matcher predicate(Function f, [description ='satisfies function']) => | 756 Matcher predicate(Function f, [description ='satisfies function']) => |
| 757 new _Predicate(f, description); | 757 new _Predicate(f, description); |
| 758 | 758 |
| 759 class _Predicate extends BaseMatcher { | 759 class _Predicate extends Matcher { |
| 760 | 760 |
| 761 final Function _matcher; | 761 final Function _matcher; |
| 762 final String _description; | 762 final String _description; |
| 763 | 763 |
| 764 const _Predicate(this._matcher, this._description); | 764 const _Predicate(this._matcher, this._description); |
| 765 | 765 |
| 766 bool matches(item, Map matchState) => _matcher(item); | 766 bool matches(item, Map matchState) => _matcher(item); |
| 767 | 767 |
| 768 Description describe(Description description) => | 768 Description describe(Description description) => |
| 769 description.add(_description); | 769 description.add(_description); |
| 770 } | 770 } |
| 771 | 771 |
| 772 /** | 772 /** |
| 773 * A useful utility class for implementing other matchers through inheritance. | 773 * A useful utility class for implementing other matchers through inheritance. |
| 774 * Derived classes should call the base constructor with a feature name and | 774 * Derived classes should call the base constructor with a feature name and |
| 775 * description, and an instance matcher, and should implement the | 775 * description, and an instance matcher, and should implement the |
| 776 * [featureValueOf] abstract method. | 776 * [featureValueOf] abstract method. |
| 777 * | 777 * |
| 778 * The feature description will typically describe the item and the feature, | 778 * The feature description will typically describe the item and the feature, |
| 779 * while the feature name will just name the feature. For example, we may | 779 * while the feature name will just name the feature. For example, we may |
| 780 * have a Widget class where each Widget has a price; we could make a | 780 * have a Widget class where each Widget has a price; we could make a |
| 781 * FeatureMatcher that can make assertions about prices with: | 781 * FeatureMatcher that can make assertions about prices with: |
| 782 * | 782 * |
| 783 * class HasPrice extends FeatureMatcher { | 783 * class HasPrice extends CustomMatcher { |
| 784 * const HasPrice(matcher) : | 784 * const HasPrice(matcher) : |
| 785 * super("Widget with price that is", "price", matcher); | 785 * super("Widget with price that is", "price", matcher); |
| 786 * featureValueOf(actual) => actual.price; | 786 * featureValueOf(actual) => actual.price; |
| 787 * } | 787 * } |
| 788 * | 788 * |
| 789 * and then use this for example like: | 789 * and then use this for example like: |
| 790 * | 790 * |
| 791 * expect(inventoryItem, new HasPrice(greaterThan(0))); | 791 * expect(inventoryItem, new HasPrice(greaterThan(0))); |
| 792 */ | 792 */ |
| 793 class CustomMatcher extends BaseMatcher { | 793 class CustomMatcher extends Matcher { |
| 794 final String _featureDescription; | 794 final String _featureDescription; |
| 795 final String _featureName; | 795 final String _featureName; |
| 796 final Matcher _matcher; | 796 final Matcher _matcher; |
| 797 | 797 |
| 798 CustomMatcher(this._featureDescription, this._featureName, matcher) | 798 CustomMatcher(this._featureDescription, this._featureName, matcher) |
| 799 : this._matcher = wrapMatcher(matcher); | 799 : this._matcher = wrapMatcher(matcher); |
| 800 | 800 |
| 801 /** Override this to extract the interesting feature.*/ | 801 /** Override this to extract the interesting feature.*/ |
| 802 featureValueOf(actual) => actual; | 802 featureValueOf(actual) => actual; |
| 803 | 803 |
| (...skipping 14 matching lines...) Expand all Loading... |
| 818 var innerDescription = new StringDescription(); | 818 var innerDescription = new StringDescription(); |
| 819 _matcher.describeMismatch(matchState['feature'], innerDescription, | 819 _matcher.describeMismatch(matchState['feature'], innerDescription, |
| 820 matchState['state'], verbose); | 820 matchState['state'], verbose); |
| 821 if (innerDescription.length > 0) { | 821 if (innerDescription.length > 0) { |
| 822 mismatchDescription.add(' which ').add(innerDescription.toString()); | 822 mismatchDescription.add(' which ').add(innerDescription.toString()); |
| 823 } | 823 } |
| 824 return mismatchDescription; | 824 return mismatchDescription; |
| 825 } | 825 } |
| 826 } | 826 } |
| 827 | 827 |
| OLD | NEW |