Index: pkg/unittest/lib/src/iterable_matchers.dart |
diff --git a/pkg/unittest/lib/src/iterable_matchers.dart b/pkg/unittest/lib/src/iterable_matchers.dart |
index ab7827f115cb3001203840abb9c397c608aa4da4..a81bfed347b341512ff204baae05ff080be9e826 100644 |
--- a/pkg/unittest/lib/src/iterable_matchers.dart |
+++ b/pkg/unittest/lib/src/iterable_matchers.dart |
@@ -108,6 +108,7 @@ class _OrderedEquals extends Matcher { |
} |
} |
} |
+ |
/** |
* Returns a matcher which matches [Iterable]s that have the same |
* length and the same elements as [expected], but not necessarily in |
@@ -193,6 +194,73 @@ abstract class _IterableMatcher extends Matcher { |
} |
/** |
+ * Returns a matcher which matches [Iterable]s whose elements match the matchers |
+ * in [expected], but not necessarily in the same order. |
+ * |
+ * Note that this is `O(n^2)` and so should only be used on small objects. |
+ */ |
+Matcher unorderedMatches(Iterable expected) => |
+ new _UnorderedMatches(expected); |
+ |
+class _UnorderedMatches extends Matcher { |
Siggi Cherem (dart-lang)
2013/12/17 00:32:03
seems like there is a lot of code in common here a
nweiz
2013/12/17 03:33:47
I intentionally avoided going too deep on modifyin
Siggi Cherem (dart-lang)
2013/12/17 18:48:45
I think it is worth the effort for the matchers li
|
+ final List<Matcher> _expected; |
+ |
+ _UnorderedMatches(Iterable expected) |
+ : _expected = expected.map(wrapMatcher).toList(); |
+ |
+ String _test(item) { |
+ if (item is !Iterable) return 'not iterable'; |
+ item = item.toList(); |
+ |
+ // Check the lengths are the same. |
+ if (_expected.length > item.length) { |
+ return 'has too few elements (${item.length} < ${_expected.length})'; |
+ } else if (_expected.length < item.length) { |
+ return 'has too many elements (${item.length} > ${_expected.length})'; |
+ } |
+ |
+ var matched = new List<bool>.filled(item.length, false); |
+ var expectedPosition = 0; |
+ for (var expectedMatcher in _expected) { |
+ var actualPosition = 0; |
+ var gotMatch = false; |
+ for (var actualElement in item) { |
+ if (!matched[actualPosition]) { |
+ if (expectedMatcher.matches(actualElement, {})) { |
+ matched[actualPosition] = gotMatch = true; |
+ break; |
+ } |
+ } |
+ ++actualPosition; |
+ } |
+ |
+ if (!gotMatch) { |
+ return new StringDescription() |
+ .add('has no match for ') |
+ .addDescriptionOf(expectedMatcher) |
+ .add(' at index ${expectedPosition}') |
+ .toString(); |
+ } |
+ |
+ ++expectedPosition; |
+ } |
+ return null; |
+ } |
+ |
+ bool matches(item, Map mismatchState) => _test(item) == null; |
+ |
+ Description describe(Description description) => |
+ description |
+ .add('matches ') |
+ .addAll('[', ', ', ']', _expected) |
+ .add(' unordered'); |
+ |
+ Description describeMismatch(item, Description mismatchDescription, |
+ Map matchState, bool verbose) => |
+ mismatchDescription.add(_test(item)); |
+} |
+ |
+/** |
* A pairwise matcher for iterable. You can pass an arbitrary [comparator] |
* function that takes an expected and actual argument which will be applied |
* to each pair in order. [description] should be a meaningful name for |