Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(290)

Side by Side Diff: pkg/matcher/lib/src/core_matchers.dart

Issue 306283002: pkg/matcher: cleanup, updates, deprecations, fixes (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 library matcher.core_matchers;
6 6
7 /** 7 import 'dart:async';
8 * Returns a matcher that matches empty strings, maps or iterables 8
9 * (including collections). 9 import 'description.dart';
10 */ 10 import 'expect.dart';
11 import 'interfaces.dart';
12
13 /// Returns a matcher that matches empty strings, maps or iterables
14 /// (including collections).
11 const Matcher isEmpty = const _Empty(); 15 const Matcher isEmpty = const _Empty();
12 16
13 class _Empty extends Matcher { 17 class _Empty extends Matcher {
14 const _Empty(); 18 const _Empty();
15 bool matches(item, Map matchState) { 19 bool matches(item, Map matchState) {
16 if (item is Map || item is Iterable) { 20 if (item is Map || item is Iterable) {
17 return item.isEmpty; 21 return item.isEmpty;
18 } else if (item is String) { 22 } else if (item is String) {
19 return item.length == 0; 23 return item.length == 0;
20 } else { 24 } else {
21 return false; 25 return false;
22 } 26 }
23 } 27 }
24 Description describe(Description description) => description.add('empty'); 28 Description describe(Description description) => description.add('empty');
25 } 29 }
26 30
27 /** A matcher that matches any null value. */ 31 /// A matcher that matches any null value.
28 const Matcher isNull = const _IsNull(); 32 const Matcher isNull = const _IsNull();
29 33
30 /** A matcher that matches any non-null value. */ 34 /// A matcher that matches any non-null value.
31 const Matcher isNotNull = const _IsNotNull(); 35 const Matcher isNotNull = const _IsNotNull();
32 36
33 class _IsNull extends Matcher { 37 class _IsNull extends Matcher {
34 const _IsNull(); 38 const _IsNull();
35 bool matches(item, Map matchState) => item == null; 39 bool matches(item, Map matchState) => item == null;
36 Description describe(Description description) => description.add('null'); 40 Description describe(Description description) => description.add('null');
37 } 41 }
38 42
39 class _IsNotNull extends Matcher { 43 class _IsNotNull extends Matcher {
40 const _IsNotNull(); 44 const _IsNotNull();
41 bool matches(item, Map matchState) => item != null; 45 bool matches(item, Map matchState) => item != null;
42 Description describe(Description description) => description.add('not null'); 46 Description describe(Description description) => description.add('not null');
43 } 47 }
44 48
45 /** A matcher that matches the Boolean value true. */ 49 /// A matcher that matches the Boolean value true.
46 const Matcher isTrue = const _IsTrue(); 50 const Matcher isTrue = const _IsTrue();
47 51
48 /** A matcher that matches anything except the Boolean value true. */ 52 /// A matcher that matches anything except the Boolean value true.
49 const Matcher isFalse = const _IsFalse(); 53 const Matcher isFalse = const _IsFalse();
50 54
51 class _IsTrue extends Matcher { 55 class _IsTrue extends Matcher {
52 const _IsTrue(); 56 const _IsTrue();
53 bool matches(item, Map matchState) => item == true; 57 bool matches(item, Map matchState) => item == true;
54 Description describe(Description description) => description.add('true'); 58 Description describe(Description description) => description.add('true');
55 } 59 }
56 60
57 class _IsFalse extends Matcher { 61 class _IsFalse extends Matcher {
58 const _IsFalse(); 62 const _IsFalse();
59 bool matches(item, Map matchState) => item == false; 63 bool matches(item, Map matchState) => item == false;
60 Description describe(Description description) => description.add('false'); 64 Description describe(Description description) => description.add('false');
61 } 65 }
62 66
63 /** 67 /// Returns a matches that matches if the value is the same instance
64 * Returns a matches that matches if the value is the same instance 68 /// as [expected], using [identical].
65 * as [expected], using [identical].
66 */
67 Matcher same(expected) => new _IsSameAs(expected); 69 Matcher same(expected) => new _IsSameAs(expected);
68 70
69 class _IsSameAs extends Matcher { 71 class _IsSameAs extends Matcher {
70 final _expected; 72 final _expected;
71 const _IsSameAs(this._expected); 73 const _IsSameAs(this._expected);
72 bool matches(item, Map matchState) => identical(item, _expected); 74 bool matches(item, Map matchState) => identical(item, _expected);
73 // If all types were hashable we could show a hash here. 75 // If all types were hashable we could show a hash here.
74 Description describe(Description description) => 76 Description describe(Description description) =>
75 description.add('same instance as ').addDescriptionOf(_expected); 77 description.add('same instance as ').addDescriptionOf(_expected);
76 } 78 }
77 79
78 /** 80 /// Returns a matcher that matches if the value is structurally equal to
79 * Returns a matcher that matches if the value is structurally equal to 81 /// [expected].
80 * [expected]. 82 ///
81 * 83 /// If [expected] is a [Matcher], then it matches using that. Otherwise it tests
82 * If [expected] is a [Matcher], then it matches using that. Otherwise it tests 84 /// for equality using `==` on the expected value.
83 * for equality using `==` on the expected value. 85 ///
84 * 86 /// For [Iterable]s and [Map]s, this will recursively match the elements. To
85 * For [Iterable]s and [Map]s, this will recursively match the elements. To 87 /// handle cyclic structures a recursion depth [limit] can be provided. The
86 * handle cyclic structures a recursion depth [limit] can be provided. The 88 /// default limit is 100.
87 * default limit is 100. 89 Matcher equals(expected, [int limit=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, [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
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
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 /** 325 /// Returns a matcher that matches if an object is an instance
326 * Returns a matcher that matches if an object is an instance 326 /// of [type] (or a subtype).
327 * of [type] (or a subtype). 327 ///
328 * 328 /// As types are not first class objects in Dart we can only
329 * As types are not first class objects in Dart we can only 329 /// approximate this test by using a generic wrapper class.
330 * approximate this test by using a generic wrapper class. 330 ///
331 * 331 /// For example, to test whether 'bar' is an instance of type
332 * For example, to test whether 'bar' is an instance of type 332 /// 'Foo', we would write:
333 * 'Foo', we would write: 333 ///
334 * 334 /// expect(bar, new isInstanceOf<Foo>());
335 * expect(bar, new isInstanceOf<Foo>()); 335 ///
336 * 336 /// To get better error message, supply a name when creating the
337 * To get better error message, supply a name when creating the 337 /// Type wrapper; e.g.:
338 * Type wrapper; e.g.: 338 ///
339 * 339 /// expect(bar, new isInstanceOf<Foo>('Foo'));
340 * expect(bar, new isInstanceOf<Foo>('Foo')); 340 ///
341 * 341 /// Note that this does not currently work in dart2js; it will
342 * Note that this does not currently work in dart2js; it will 342 /// match any type, and isNot(new isInstanceof<T>()) will always
343 * match any type, and isNot(new isInstanceof<T>()) will always 343 /// fail. This is because dart2js currently ignores template type
344 * fail. This is because dart2js currently ignores template type 344 /// parameters.
345 * parameters.
346 */
347 class isInstanceOf<T> extends Matcher { 345 class isInstanceOf<T> extends Matcher {
348 final String _name; 346 final String _name;
349 const isInstanceOf([name = 'specified type']): this._name = name; 347 const isInstanceOf([name = 'specified type']) : this._name = name;
350 bool matches(obj, Map matchState) => obj is T; 348 bool matches(obj, Map matchState) => obj is T;
351 // The description here is lame :-( 349 // The description here is lame :-(
352 Description describe(Description description) => 350 Description describe(Description description) =>
353 description.add('an instance of ${_name}'); 351 description.add('an instance of ${_name}');
354 } 352 }
355 353
356 /** 354 /// This can be used to match two kinds of objects:
357 * This can be used to match two kinds of objects: 355 ///
358 * 356 /// * A [Function] that throws an exception when called. The function cannot
359 * * A [Function] that throws an exception when called. The function cannot 357 /// take any arguments. If you want to test that a function expecting
360 * take any arguments. If you want to test that a function expecting 358 /// arguments throws, wrap it in another zero-argument function that calls
361 * arguments throws, wrap it in another zero-argument function that calls 359 /// the one you want to test.
362 * the one you want to test. 360 ///
363 * 361 /// * A [Future] that completes with an exception. Note that this creates an
364 * * A [Future] that completes with an exception. Note that this creates an 362 /// asynchronous expectation. The call to `expect()` that includes this will
365 * asynchronous expectation. The call to `expect()` that includes this will 363 /// return immediately and execution will continue. Later, when the future
366 * return immediately and execution will continue. Later, when the future 364 /// completes, the actual expectation will run.
367 * completes, the actual expectation will run.
368 */
369 const Matcher throws = const Throws(); 365 const Matcher throws = const Throws();
370 366
371 /** 367 /// This can be used to match two kinds of objects:
372 * This can be used to match two kinds of objects: 368 ///
373 * 369 /// * A [Function] that throws an exception when called. The function cannot
374 * * A [Function] that throws an exception when called. The function cannot 370 /// take any arguments. If you want to test that a function expecting
375 * take any arguments. If you want to test that a function expecting 371 /// arguments throws, wrap it in another zero-argument function that calls
376 * arguments throws, wrap it in another zero-argument function that calls 372 /// the one you want to test.
377 * the one you want to test. 373 ///
378 * 374 /// * A [Future] that completes with an exception. Note that this creates an
379 * * A [Future] that completes with an exception. Note that this creates an 375 /// asynchronous expectation. The call to `expect()` that includes this will
380 * asynchronous expectation. The call to `expect()` that includes this will 376 /// return immediately and execution will continue. Later, when the future
381 * return immediately and execution will continue. Later, when the future 377 /// completes, the actual expectation will run.
382 * completes, the actual expectation will run. 378 ///
383 * 379 /// In both cases, when an exception is thrown, this will test that the exceptio n
384 * In both cases, when an exception is thrown, this will test that the exception 380 /// object matches [matcher]. If [matcher] is not an instance of [Matcher], it
385 * object matches [matcher]. If [matcher] is not an instance of [Matcher], it 381 /// will implicitly be treated as `equals(matcher)`.
386 * will implicitly be treated as `equals(matcher)`.
387 */
388 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher)); 382 Matcher throwsA(matcher) => new Throws(wrapMatcher(matcher));
389 383
390 /** 384 /// A matcher that matches a function call against no exception.
391 * A matcher that matches a function call against no exception. 385 /// The function will be called once. Any exceptions will be silently swallowed.
392 * The function will be called once. Any exceptions will be silently swallowed. 386 /// The value passed to expect() should be a reference to the function.
393 * The value passed to expect() should be a reference to the function. 387 /// Note that the function cannot take arguments; to handle this
394 * Note that the function cannot take arguments; to handle this 388 /// a wrapper will have to be created.
395 * a wrapper will have to be created.
396 */
397 const Matcher returnsNormally = const _ReturnsNormally(); 389 const Matcher returnsNormally = const _ReturnsNormally();
398 390
399 class Throws extends Matcher { 391 class Throws extends Matcher {
400 final Matcher _matcher; 392 final Matcher _matcher;
401 393
402 const Throws([Matcher matcher]): this._matcher = matcher; 394 const Throws([Matcher matcher]) : this._matcher = matcher;
403 395
404 bool matches(item, Map matchState) { 396 bool matches(item, Map matchState) {
405 if (item is! Function && item is! Future) return false; 397 if (item is! Function && item is! Future) return false;
406 if (item is Future) { 398 if (item is Future) {
407 var done = wrapAsync((fn) => fn()); 399 var done = wrapAsync((fn) => fn());
408 400
409 // Queue up an asynchronous expectation that validates when the future 401 // Queue up an asynchronous expectation that validates when the future
410 // completes. 402 // completes.
411 item.then((value) { 403 item.then((value) {
412 done(() => fail("Expected future to fail, but succeeded with '$value'.") ); 404 done(() {
405 fail("Expected future to fail, but succeeded with '$value'.");
406 });
413 }, onError: (error, trace) { 407 }, onError: (error, trace) {
414 done(() { 408 done(() {
415 if (_matcher == null) return; 409 if (_matcher == null) return;
416 var reason; 410 var reason;
417 if (trace != null) { 411 if (trace != null) {
418 var stackTrace = trace.toString(); 412 var stackTrace = trace.toString();
419 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}"; 413 stackTrace = " ${stackTrace.replaceAll("\n", "\n ")}";
420 reason = "Actual exception trace:\n$stackTrace"; 414 reason = "Actual exception trace:\n$stackTrace";
421 } 415 }
422 expect(error, _matcher, reason: reason); 416 expect(error, _matcher, reason: reason);
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
512 * For now the only solution for all platforms seems to be separate classes 506 * For now the only solution for all platforms seems to be separate classes
513 * for each exception type. 507 * for each exception type.
514 */ 508 */
515 509
516 abstract class TypeMatcher extends Matcher { 510 abstract class TypeMatcher extends Matcher {
517 final String _name; 511 final String _name;
518 const TypeMatcher(this._name); 512 const TypeMatcher(this._name);
519 Description describe(Description description) => description.add(_name); 513 Description describe(Description description) => description.add(_name);
520 } 514 }
521 515
522 /** A matcher for FormatExceptions. */
523 const isFormatException = const _FormatException();
524 516
525 /** A matcher for functions that throw FormatException. */
526 const Matcher throwsFormatException = const Throws(isFormatException);
527 517
528 class _FormatException extends TypeMatcher { 518 /// A matcher for Map types.
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(); 519 const isMap = const _IsMap();
672 520
673 class _IsMap extends TypeMatcher { 521 class _IsMap extends TypeMatcher {
674 const _IsMap(): super("Map"); 522 const _IsMap() : super("Map");
675 bool matches(item, Map matchState) => item is Map; 523 bool matches(item, Map matchState) => item is Map;
676 } 524 }
677 525
678 /** A matcher for List types. */ 526 /// A matcher for List types.
679 const isList = const _IsList(); 527 const isList = const _IsList();
680 528
681 class _IsList extends TypeMatcher { 529 class _IsList extends TypeMatcher {
682 const _IsList(): super("List"); 530 const _IsList() : super("List");
683 bool matches(item, Map matchState) => item is List; 531 bool matches(item, Map matchState) => item is List;
684 } 532 }
685 533
686 /** 534 /// Returns a matcher that matches if an object has a length property
687 * Returns a matcher that matches if an object has a length property 535 /// that matches [matcher].
688 * that matches [matcher].
689 */
690 Matcher hasLength(matcher) => new _HasLength(wrapMatcher(matcher)); 536 Matcher hasLength(matcher) => new _HasLength(wrapMatcher(matcher));
691 537
692 class _HasLength extends Matcher { 538 class _HasLength extends Matcher {
693 final Matcher _matcher; 539 final Matcher _matcher;
694 const _HasLength([Matcher matcher = null]): this._matcher = matcher; 540 const _HasLength([Matcher matcher = null]) : this._matcher = matcher;
695 541
696 bool matches(item, Map matchState) { 542 bool matches(item, Map matchState) {
697 try { 543 try {
698 // This is harmless code that will throw if no length property 544 // This is harmless code that will throw if no length property
699 // but subtle enough that an optimizer shouldn't strip it out. 545 // but subtle enough that an optimizer shouldn't strip it out.
700 if (item.length * item.length >= 0) { 546 if (item.length * item.length >= 0) {
701 return _matcher.matches(item.length, matchState); 547 return _matcher.matches(item.length, matchState);
702 } 548 }
703 } catch (e) {} 549 } catch (e) {}
704 return false; 550 return false;
(...skipping 10 matching lines...) Expand all
715 // property; we use the same trick as in matches(). 561 // property; we use the same trick as in matches().
716 if (item.length * item.length >= 0) { 562 if (item.length * item.length >= 0) {
717 return mismatchDescription.add('has length of '). 563 return mismatchDescription.add('has length of ').
718 addDescriptionOf(item.length); 564 addDescriptionOf(item.length);
719 } 565 }
720 } catch (e) {} 566 } catch (e) {}
721 return mismatchDescription.add('has no length property'); 567 return mismatchDescription.add('has no length property');
722 } 568 }
723 } 569 }
724 570
725 /** 571 /// Returns a matcher that matches if the match argument contains
726 * Returns a matcher that matches if the match argument contains 572 /// the expected value. For [String]s this means substring matching;
727 * the expected value. For [String]s this means substring matching; 573 /// for [Map]s it means the map has the key, and for [Iterable]s
728 * for [Map]s it means the map has the key, and for [Iterable]s 574 /// (including [Iterable]s) it means the iterable has a matching
729 * (including [Iterable]s) it means the iterable has a matching 575 /// element. In the case of iterables, [expected] can itself be a
730 * element. In the case of iterables, [expected] can itself be a 576 /// matcher.
731 * matcher.
732 */
733 Matcher contains(expected) => new _Contains(expected); 577 Matcher contains(expected) => new _Contains(expected);
734 578
735 class _Contains extends Matcher { 579 class _Contains extends Matcher {
736
737 final _expected; 580 final _expected;
738 581
739 const _Contains(this._expected); 582 const _Contains(this._expected);
740 583
741 bool matches(item, Map matchState) { 584 bool matches(item, Map matchState) {
742 if (item is String) { 585 if (item is String) {
743 return item.indexOf(_expected) >= 0; 586 return item.indexOf(_expected) >= 0;
744 } else if (item is Iterable) { 587 } else if (item is Iterable) {
745 if (_expected is Matcher) { 588 if (_expected is Matcher) {
746 return item.any((e) => _expected.matches(e, matchState)); 589 return item.any((e) => _expected.matches(e, matchState));
(...skipping 13 matching lines...) Expand all
760 Map matchState, bool verbose) { 603 Map matchState, bool verbose) {
761 if (item is String || item is Iterable || item is Map) { 604 if (item is String || item is Iterable || item is Map) {
762 return super.describeMismatch(item, mismatchDescription, matchState, 605 return super.describeMismatch(item, mismatchDescription, matchState,
763 verbose); 606 verbose);
764 } else { 607 } else {
765 return mismatchDescription.add('is not a string, map or iterable'); 608 return mismatchDescription.add('is not a string, map or iterable');
766 } 609 }
767 } 610 }
768 } 611 }
769 612
770 /** 613 /// Returns a matcher that matches if the match argument is in
771 * Returns a matcher that matches if the match argument is in 614 /// the expected value. This is the converse of [contains].
772 * the expected value. This is the converse of [contains].
773 */
774 Matcher isIn(expected) => new _In(expected); 615 Matcher isIn(expected) => new _In(expected);
775 616
776 class _In extends Matcher { 617 class _In extends Matcher {
777 618
778 final _expected; 619 final _expected;
779 620
780 const _In(this._expected); 621 const _In(this._expected);
781 622
782 bool matches(item, Map matchState) { 623 bool matches(item, Map matchState) {
783 if (_expected is String) { 624 if (_expected is String) {
784 return _expected.indexOf(item) >= 0; 625 return _expected.indexOf(item) >= 0;
785 } else if (_expected is Iterable) { 626 } else if (_expected is Iterable) {
786 return _expected.any((e) => e == item); 627 return _expected.any((e) => e == item);
787 } else if (_expected is Map) { 628 } else if (_expected is Map) {
788 return _expected.containsKey(item); 629 return _expected.containsKey(item);
789 } 630 }
790 return false; 631 return false;
791 } 632 }
792 633
793 Description describe(Description description) => 634 Description describe(Description description) =>
794 description.add('is in ').addDescriptionOf(_expected); 635 description.add('is in ').addDescriptionOf(_expected);
795 } 636 }
796 637
797 /** 638 /// Returns a matcher that uses an arbitrary function that returns
798 * Returns a matcher that uses an arbitrary function that returns 639 /// true or false for the actual value. For example:
799 * true or false for the actual value. For example: 640 ///
800 * 641 /// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
801 * expect(v, predicate((x) => ((x % 2) == 0), "is even")) 642 Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
802 */
803 Matcher predicate(Function f, [description = 'satisfies function']) =>
804 new _Predicate(f, description); 643 new _Predicate(f, description);
805 644
645 typedef bool _PredicateFunction(value);
646
806 class _Predicate extends Matcher { 647 class _Predicate extends Matcher {
807 648 final _PredicateFunction _matcher;
808 final Function _matcher;
809 final String _description; 649 final String _description;
810 650
811 const _Predicate(this._matcher, this._description); 651 const _Predicate(this._matcher, this._description);
812 652
813 bool matches(item, Map matchState) => _matcher(item); 653 bool matches(item, Map matchState) => _matcher(item);
814 654
815 Description describe(Description description) => 655 Description describe(Description description) =>
816 description.add(_description); 656 description.add(_description);
817 } 657 }
818 658
819 /** 659 /// A useful utility class for implementing other matchers through inheritance.
820 * A useful utility class for implementing other matchers through inheritance. 660 /// Derived classes should call the base constructor with a feature name and
821 * Derived classes should call the base constructor with a feature name and 661 /// description, and an instance matcher, and should implement the
822 * description, and an instance matcher, and should implement the 662 /// [featureValueOf] abstract method.
823 * [featureValueOf] abstract method. 663 ///
824 * 664 /// The feature description will typically describe the item and the feature,
825 * The feature description will typically describe the item and the feature, 665 /// while the feature name will just name the feature. For example, we may
826 * while the feature name will just name the feature. For example, we may 666 /// have a Widget class where each Widget has a price; we could make a
827 * have a Widget class where each Widget has a price; we could make a 667 /// [CustomMatcher] that can make assertions about prices with:
828 * [CustomMatcher] that can make assertions about prices with: 668 ///
829 * 669 /// class HasPrice extends CustomMatcher {
830 * class HasPrice extends CustomMatcher { 670 /// const HasPrice(matcher) :
831 * const HasPrice(matcher) : 671 /// super("Widget with price that is", "price", matcher);
832 * super("Widget with price that is", "price", matcher); 672 /// featureValueOf(actual) => actual.price;
833 * featureValueOf(actual) => actual.price; 673 /// }
834 * } 674 ///
835 * 675 /// and then use this for example like:
836 * and then use this for example like: 676 ///
837 * 677 /// expect(inventoryItem, new HasPrice(greaterThan(0)));
838 * expect(inventoryItem, new HasPrice(greaterThan(0)));
839 */
840 class CustomMatcher extends Matcher { 678 class CustomMatcher extends Matcher {
841 final String _featureDescription; 679 final String _featureDescription;
842 final String _featureName; 680 final String _featureName;
843 final Matcher _matcher; 681 final Matcher _matcher;
844 682
845 CustomMatcher(this._featureDescription, this._featureName, matcher) 683 CustomMatcher(this._featureDescription, this._featureName, matcher)
846 : this._matcher = wrapMatcher(matcher); 684 : this._matcher = wrapMatcher(matcher);
847 685
848 /** Override this to extract the interesting feature.*/ 686 /// Override this to extract the interesting feature.
849 featureValueOf(actual) => actual; 687 featureValueOf(actual) => actual;
850 688
851 bool matches(item, Map matchState) { 689 bool matches(item, Map matchState) {
852 var f = featureValueOf(item); 690 var f = featureValueOf(item);
853 if (_matcher.matches(f, matchState)) return true; 691 if (_matcher.matches(f, matchState)) return true;
854 addStateInfo(matchState, {'feature': f}); 692 addStateInfo(matchState, {'feature': f});
855 return false; 693 return false;
856 } 694 }
857 695
858 Description describe(Description description) => 696 Description describe(Description description) =>
859 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); 697 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher);
860 698
861 Description describeMismatch(item, Description mismatchDescription, 699 Description describeMismatch(item, Description mismatchDescription,
862 Map matchState, bool verbose) { 700 Map matchState, bool verbose) {
863 mismatchDescription.add('has ').add(_featureName).add(' with value '). 701 mismatchDescription.add('has ').add(_featureName).add(' with value ').
864 addDescriptionOf(matchState['feature']); 702 addDescriptionOf(matchState['feature']);
865 var innerDescription = new StringDescription(); 703 var innerDescription = new StringDescription();
866 _matcher.describeMismatch(matchState['feature'], innerDescription, 704 _matcher.describeMismatch(matchState['feature'], innerDescription,
867 matchState['state'], verbose); 705 matchState['state'], verbose);
868 if (innerDescription.length > 0) { 706 if (innerDescription.length > 0) {
869 mismatchDescription.add(' which ').add(innerDescription.toString()); 707 mismatchDescription.add(' which ').add(innerDescription.toString());
870 } 708 }
871 return mismatchDescription; 709 return mismatchDescription;
872 } 710 }
873 } 711 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698