Add containsAllInOrder (#46)

his matcher is useful for testing that expected values are present
while allowing for additional values, or when used multiple times for
testing the combination of multiple ordered collections while allowing
for arbitrary interleaving.

Bump version in pubspec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ed15206..9527173 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.12.1
+
+* Add containsAllInOrder matcher for Iterables
+
 ## 0.12.0+2
 
 * Fix all strong-mode warnings.
diff --git a/lib/src/iterable_matchers.dart b/lib/src/iterable_matchers.dart
index b394e74..9159104 100644
--- a/lib/src/iterable_matchers.dart
+++ b/lib/src/iterable_matchers.dart
@@ -260,3 +260,47 @@
     }
   }
 }
+
+/// 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])`.
+Matcher containsAllInOrder(Iterable expected) =>
+    new _ContainsAllInOrder(expected);
+
+class _ContainsAllInOrder implements Matcher {
+  final Iterable _expected;
+
+  _ContainsAllInOrder(this._expected);
+
+  String _test(item, Map matchState) {
+    if (item is! Iterable) return 'not an iterable';
+    var matchers = _expected.map(wrapMatcher).toList();
+    var matcherIndex = 0;
+    for (var value in item) {
+      if (matchers[matcherIndex].matches(value, matchState)) matcherIndex++;
+      if (matcherIndex == matchers.length) return null;
+    }
+    return new StringDescription()
+        .add('did not find a value matching ')
+        .addDescriptionOf(matchers[matcherIndex])
+        .add(' following expected prior values')
+        .toString();
+  }
+
+  @override
+  bool matches(item, Map matchState) => _test(item, matchState) == null;
+
+  @override
+  Description describe(Description description) => description
+      .add('contains in order(')
+      .addDescriptionOf(_expected)
+      .add(')');
+
+  @override
+  Description describeMismatch(item, Description mismatchDescription,
+          Map matchState, bool verbose) =>
+      mismatchDescription.add(_test(item, matchState));
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index e5b13cc..4bb9887 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,6 @@
 name: matcher
 
-# This version should really be 0.12.1-dev, but test depends on matcher <0.12.1,
-# so we're using + instead.
-version: 0.12.0+dev
+version: 0.12.1
 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 f76613b..604a1b6 100644
--- a/test/iterable_matchers_test.dart
+++ b/test/iterable_matchers_test.dart
@@ -177,6 +177,40 @@
         "Which: has no match for a value greater than <3> at index 0");
   });
 
+  test('containsAllInOrder', () {
+    var d = [0, 1, 0, 2];
+    shouldPass(d, containsAllInOrder([1, 2]));
+    shouldPass(d, containsAllInOrder([greaterThan(0), greaterThan(1)]));
+    shouldFail(
+        d,
+        containsAllInOrder([2, 1]),
+        "Expected: contains in order([2, 1]) "
+        "Actual: [0, 1, 0, 2] "
+        "Which: did not find a value matching <1> following expected prior "
+        "values");
+    shouldFail(
+        d,
+        containsAllInOrder([greaterThan(1), greaterThan(0)]),
+        "Expected: contains in order([<a value greater than <1>>, "
+        "<a value greater than <0>>]) "
+        "Actual: [0, 1, 0, 2] "
+        "Which: did not find a value matching a value greater than <0> "
+        "following expected prior values");
+    shouldFail(
+        d,
+        containsAllInOrder([1, 2, 3]),
+        "Expected: contains in order([1, 2, 3]) "
+        "Actual: [0, 1, 0, 2] "
+        "Which: did not find a value matching <3> following expected prior "
+        "values");
+    shouldFail(
+        1,
+        containsAllInOrder([1]),
+        "Expected: contains in order([1]) "
+        "Actual: <1> "
+        "Which: not an iterable");
+  });
+
   test('pairwise compare', () {
     var c = [1, 2];
     var d = [1, 2, 3];