| Index: packages/matcher/lib/src/core_matchers.dart
|
| diff --git a/packages/matcher/lib/src/core_matchers.dart b/packages/matcher/lib/src/core_matchers.dart
|
| index 94f9d7d4f3eaf09a47637da01b9bac92b24a2d30..e8fdb11dd14b485d1c7a3e1695cd94b74224730b 100644
|
| --- a/packages/matcher/lib/src/core_matchers.dart
|
| +++ b/packages/matcher/lib/src/core_matchers.dart
|
| @@ -110,60 +110,70 @@ Matcher equals(expected, [int limit = 100]) => expected is String
|
| ? new _StringEqualsMatcher(expected)
|
| : new _DeepMatcher(expected, limit);
|
|
|
| +typedef _RecursiveMatcher = List<String> Function(
|
| + dynamic, dynamic, String, int);
|
| +
|
| class _DeepMatcher extends Matcher {
|
| final _expected;
|
| final int _limit;
|
| - var count;
|
|
|
| _DeepMatcher(this._expected, [int limit = 1000]) : this._limit = limit;
|
|
|
| // Returns a pair (reason, location)
|
| - List _compareIterables(expected, actual, matcher, depth, location) {
|
| - if (actual is! Iterable) return ['is not Iterable', location];
|
| -
|
| - var expectedIterator = expected.iterator;
|
| - var actualIterator = actual.iterator;
|
| - for (var index = 0;; index++) {
|
| - // Advance in lockstep.
|
| - var expectedNext = expectedIterator.moveNext();
|
| - var actualNext = actualIterator.moveNext();
|
| -
|
| - // If we reached the end of both, we succeeded.
|
| - if (!expectedNext && !actualNext) return null;
|
| -
|
| - // Fail if their lengths are different.
|
| - var newLocation = '${location}[${index}]';
|
| - if (!expectedNext) return ['longer than expected', newLocation];
|
| - if (!actualNext) return ['shorter than expected', newLocation];
|
| -
|
| - // Match the elements.
|
| - var rp = matcher(
|
| - expectedIterator.current, actualIterator.current, newLocation, depth);
|
| - if (rp != null) return rp;
|
| + List<String> _compareIterables(Iterable expected, Object actual,
|
| + _RecursiveMatcher matcher, int depth, String location) {
|
| + if (actual is Iterable) {
|
| + var expectedIterator = expected.iterator;
|
| + var actualIterator = actual.iterator;
|
| + for (var index = 0;; index++) {
|
| + // Advance in lockstep.
|
| + var expectedNext = expectedIterator.moveNext();
|
| + var actualNext = actualIterator.moveNext();
|
| +
|
| + // If we reached the end of both, we succeeded.
|
| + if (!expectedNext && !actualNext) return null;
|
| +
|
| + // Fail if their lengths are different.
|
| + var newLocation = '$location[$index]';
|
| + if (!expectedNext) return ['longer than expected', newLocation];
|
| + if (!actualNext) return ['shorter than expected', newLocation];
|
| +
|
| + // Match the elements.
|
| + var rp = matcher(expectedIterator.current, actualIterator.current,
|
| + newLocation, depth);
|
| + if (rp != null) return rp;
|
| + }
|
| + } else {
|
| + return ['is not Iterable', location];
|
| }
|
| }
|
|
|
| - List _compareSets(Set expected, actual, matcher, depth, location) {
|
| - if (actual is! Iterable) return ['is not Iterable', location];
|
| - actual = actual.toSet();
|
| + List<String> _compareSets(Set expected, Object actual,
|
| + _RecursiveMatcher matcher, int depth, String location) {
|
| + if (actual is Iterable) {
|
| + Set other = actual.toSet();
|
|
|
| - for (var expectedElement in expected) {
|
| - if (actual.every((actualElement) =>
|
| - matcher(expectedElement, actualElement, location, depth) != null)) {
|
| - return ['does not contain $expectedElement', location];
|
| + for (var expectedElement in expected) {
|
| + if (other.every((actualElement) =>
|
| + matcher(expectedElement, actualElement, location, depth) != null)) {
|
| + return ['does not contain $expectedElement', location];
|
| + }
|
| }
|
| - }
|
|
|
| - if (actual.length > expected.length) {
|
| - return ['larger than expected', location];
|
| - } else if (actual.length < expected.length) {
|
| - return ['smaller than expected', location];
|
| + if (other.length > expected.length) {
|
| + return ['larger than expected', location];
|
| + } else if (other.length < expected.length) {
|
| + return ['smaller than expected', location];
|
| + } else {
|
| + return null;
|
| + }
|
| } else {
|
| - return null;
|
| + return ['is not Iterable', location];
|
| }
|
| }
|
|
|
| - List _recursiveMatch(expected, actual, String location, int depth) {
|
| + List<String> _recursiveMatch(
|
| + Object expected, Object actual, String location, int depth) {
|
| // If the expected value is a matcher, try to match it.
|
| if (expected is Matcher) {
|
| var matchState = {};
|
| @@ -194,17 +204,16 @@ class _DeepMatcher extends Matcher {
|
| expected, actual, _recursiveMatch, depth + 1, location);
|
| } else if (expected is Map) {
|
| if (actual is! Map) return ['expected a map', location];
|
| -
|
| - var err = (expected.length == actual.length)
|
| - ? ''
|
| - : 'has different length and ';
|
| + var map = (actual as Map);
|
| + var err =
|
| + (expected.length == map.length) ? '' : 'has different length and ';
|
| for (var key in expected.keys) {
|
| - if (!actual.containsKey(key)) {
|
| + if (!map.containsKey(key)) {
|
| return ["${err}is missing map key '$key'", location];
|
| }
|
| }
|
|
|
| - for (var key in actual.keys) {
|
| + for (var key in map.keys) {
|
| if (!expected.containsKey(key)) {
|
| return ["${err}has extra map key '$key'", location];
|
| }
|
| @@ -212,7 +221,7 @@ class _DeepMatcher extends Matcher {
|
|
|
| for (var key in expected.keys) {
|
| var rp = _recursiveMatch(
|
| - expected[key], actual[key], "${location}['${key}']", depth + 1);
|
| + expected[key], map[key], "$location['$key']", depth + 1);
|
| if (rp != null) return rp;
|
| }
|
|
|
| @@ -240,7 +249,7 @@ class _DeepMatcher extends Matcher {
|
| String _match(expected, actual, Map matchState) {
|
| var rp = _recursiveMatch(expected, actual, '', 0);
|
| if (rp == null) return null;
|
| - var reason;
|
| + String reason;
|
| if (rp[0].length > 0) {
|
| if (rp[1].length > 0) {
|
| reason = "${rp[0]} at location ${rp[1]}";
|
| @@ -330,7 +339,7 @@ class _StringEqualsMatcher extends Matcher {
|
| buff.write('^\n Differ at offset $start');
|
| }
|
|
|
| - return mismatchDescription.replace(buff.toString());
|
| + return mismatchDescription.add(buff.toString());
|
| }
|
| }
|
|
|
| @@ -568,18 +577,19 @@ class _In extends Matcher {
|
| /// For example:
|
| ///
|
| /// expect(v, predicate((x) => ((x % 2) == 0), "is even"))
|
| -Matcher predicate(bool f(value), [String description = 'satisfies function']) =>
|
| +Matcher predicate<T>(bool f(T value),
|
| + [String description = 'satisfies function']) =>
|
| new _Predicate(f, description);
|
|
|
| -typedef bool _PredicateFunction(value);
|
| +typedef bool _PredicateFunction<T>(T value);
|
|
|
| -class _Predicate extends Matcher {
|
| - final _PredicateFunction _matcher;
|
| +class _Predicate<T> extends Matcher {
|
| + final _PredicateFunction<T> _matcher;
|
| final String _description;
|
|
|
| - const _Predicate(this._matcher, this._description);
|
| + _Predicate(this._matcher, this._description);
|
|
|
| - bool matches(item, Map matchState) => _matcher(item);
|
| + bool matches(item, Map matchState) => _matcher(item as T);
|
|
|
| Description describe(Description description) =>
|
| description.add(_description);
|
| @@ -595,15 +605,18 @@ class _Predicate extends Matcher {
|
| /// have a Widget class where each Widget has a price; we could make a
|
| /// [CustomMatcher] that can make assertions about prices with:
|
| ///
|
| -/// class HasPrice extends CustomMatcher {
|
| -/// const HasPrice(matcher) :
|
| -/// super("Widget with price that is", "price", matcher);
|
| -/// featureValueOf(actual) => actual.price;
|
| -/// }
|
| +/// ```dart
|
| +/// class HasPrice extends CustomMatcher {
|
| +/// HasPrice(matcher) : super("Widget with price that is", "price", matcher);
|
| +/// featureValueOf(actual) => actual.price;
|
| +/// }
|
| +/// ```
|
| ///
|
| /// and then use this for example like:
|
| ///
|
| -/// expect(inventoryItem, new HasPrice(greaterThan(0)));
|
| +/// ```dart
|
| +/// expect(inventoryItem, new HasPrice(greaterThan(0)));
|
| +/// ```
|
| class CustomMatcher extends Matcher {
|
| final String _featureDescription;
|
| final String _featureName;
|
|
|