Add containsAll (#76)
Closes #72
- Add `allowUnmatchedValues` to the implementation for unordered matches.
- Implement containsAll as an unorderedMatches which does allows extra
values in the checked iterable.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 50e0e32..a5ebcfd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@
element and order of the elements doesn't line up with the order of the
matchers.
+* Add containsAll matcher for Iterables. This Matcher checks that all
+ values/matchers in an expected iterable are satisfied by an element in the
+ value without allowing the same value to satisfy multiple matchers.
+
## 0.12.1+4
* Fixed SDK constraint to allow edge builds.
diff --git a/lib/src/iterable_matchers.dart b/lib/src/iterable_matchers.dart
index 697df0c..a4a122c 100644
--- a/lib/src/iterable_matchers.dart
+++ b/lib/src/iterable_matchers.dart
@@ -152,9 +152,11 @@
class _UnorderedMatches extends Matcher {
final List<Matcher> _expected;
+ final bool _allowUnmatchedValues;
- _UnorderedMatches(Iterable expected)
- : _expected = expected.map(wrapMatcher).toList();
+ _UnorderedMatches(Iterable expected, {bool allowUnmatchedValues})
+ : _expected = expected.map(wrapMatcher).toList(),
+ _allowUnmatchedValues = allowUnmatchedValues ?? false;
String _test(item) {
if (item is! Iterable) return 'not iterable';
@@ -164,7 +166,7 @@
// Check the lengths are the same.
if (_expected.length > values.length) {
return 'has too few elements (${values.length} < ${_expected.length})';
- } else if (_expected.length < values.length) {
+ } else if (!_allowUnmatchedValues && _expected.length < values.length) {
return 'has too many elements (${values.length} > ${_expected.length})';
}
@@ -300,11 +302,43 @@
}
/// Matches [Iterable]s which contain an element matching every value in
+/// [expected] in any order, and may contain additional values.
+///
+/// For example: `[0, 1, 0, 2, 0]` matches `containsAll([1, 2])` and
+/// `containsAll([2, 1])` but not `containsAll([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
+///
+/// Each element in the value will only be considered a match for a single
+/// matcher in [expected] even if it could satisfy more than one. For instance
+/// `containsAll([greaterThan(1), greaterThan(2)])` will not be satisfied by
+/// `[3]`. To check that all matchers are satisfied within an iterable and allow
+/// the same element to satisfy multiple matchers use
+/// `allOf(matchers.map(contains))`.
+///
+/// Note that this is worst case O(n^2) runtime and memory usage so it should
+/// only be used on small iterables.
+Matcher containsAll(Iterable expected) => new _ContainsAll(expected);
+
+class _ContainsAll extends _UnorderedMatches {
+ final Iterable _unwrappedExpected;
+
+ _ContainsAll(Iterable expected)
+ : _unwrappedExpected = expected,
+ super(expected.map(wrapMatcher), allowUnmatchedValues: true);
+ @override
+ Description describe(Description description) =>
+ description.add('contains all of ').addDescriptionOf(_unwrappedExpected);
+}
+
+/// Matches [Iterable]s which contain an element matching every value in
/// [expected] in the same order, but may contain additional values interleaved
/// throughout.
///
/// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
/// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
+///
+/// Will only match values which implement [Iterable].
Matcher containsAllInOrder(Iterable expected) =>
new _ContainsAllInOrder(expected);
diff --git a/pubspec.yaml b/pubspec.yaml
index 015c64f..c898554 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: matcher
-version: 0.12.2-dev
+version: 0.12.2
author: Dart Team <misc@dartlang.org>
description: Support for specifying test expectations
homepage: https://github.com/dart-lang/matcher
diff --git a/test/iterable_matchers_test.dart b/test/iterable_matchers_test.dart
index ffd2e60..e8a8df8 100644
--- a/test/iterable_matchers_test.dart
+++ b/test/iterable_matchers_test.dart
@@ -199,6 +199,33 @@
"Which: has no match for a value greater than <3> at index 0");
});
+ test('containsAll', () {
+ var d = [0, 1, 2];
+ shouldPass(d, containsAll([1, 2]));
+ shouldPass(d, containsAll([2, 1]));
+ shouldPass(d, containsAll([greaterThan(0), greaterThan(1)]));
+ shouldPass([2, 1], containsAll([greaterThan(0), greaterThan(1)]));
+ shouldFail(
+ d,
+ containsAll([1, 2, 3]),
+ "Expected: contains all of [1, 2, 3] "
+ "Actual: [0, 1, 2] "
+ "Which: has no match for <3> at index 2");
+ shouldFail(
+ 1,
+ containsAll([1]),
+ "Expected: contains all of [1] "
+ "Actual: <1> "
+ "Which: not iterable");
+ shouldFail(
+ [-1, 2],
+ containsAll([greaterThan(0), greaterThan(1)]),
+ "Expected: contains all of [<a value greater than <0>>, "
+ "<a value greater than <1>>] "
+ "Actual: [-1, 2] "
+ "Which: has no match for a value greater than <1> at index 1");
+ });
+
test('containsAllInOrder', () {
var d = [0, 1, 0, 2];
shouldPass(d, containsAllInOrder([1, 2]));