| OLD | NEW |
| 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 library matcher.iterable_matchers; | |
| 6 | |
| 7 import 'core_matchers.dart'; | 5 import 'core_matchers.dart'; |
| 8 import 'description.dart'; | 6 import 'description.dart'; |
| 9 import 'interfaces.dart'; | 7 import 'interfaces.dart'; |
| 10 import 'util.dart'; | 8 import 'util.dart'; |
| 11 | 9 |
| 12 /// Returns a matcher which matches [Iterable]s in which all elements | 10 /// Returns a matcher which matches [Iterable]s in which all elements |
| 13 /// match the given [matcher]. | 11 /// match the given [matcher]. |
| 14 Matcher everyElement(matcher) => new _EveryElement(wrapMatcher(matcher)); | 12 Matcher everyElement(matcher) => new _EveryElement(wrapMatcher(matcher)); |
| 15 | 13 |
| 16 class _EveryElement extends _IterableMatcher { | 14 class _EveryElement extends _IterableMatcher { |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 50 element, subDescription, matchState['state'], verbose); | 48 element, subDescription, matchState['state'], verbose); |
| 51 if (subDescription.length > 0) { | 49 if (subDescription.length > 0) { |
| 52 mismatchDescription.add(subDescription.toString()); | 50 mismatchDescription.add(subDescription.toString()); |
| 53 } else { | 51 } else { |
| 54 mismatchDescription.add("doesn't match "); | 52 mismatchDescription.add("doesn't match "); |
| 55 _matcher.describe(mismatchDescription); | 53 _matcher.describe(mismatchDescription); |
| 56 } | 54 } |
| 57 mismatchDescription.add(' at index $index'); | 55 mismatchDescription.add(' at index $index'); |
| 58 return mismatchDescription; | 56 return mismatchDescription; |
| 59 } | 57 } |
| 60 return super.describeMismatch( | 58 return super |
| 61 item, mismatchDescription, matchState, verbose); | 59 .describeMismatch(item, mismatchDescription, matchState, verbose); |
| 62 } | 60 } |
| 63 } | 61 } |
| 64 | 62 |
| 65 /// Returns a matcher which matches [Iterable]s in which at least one | 63 /// Returns a matcher which matches [Iterable]s in which at least one |
| 66 /// element matches the given [matcher]. | 64 /// element matches the given [matcher]. |
| 67 Matcher anyElement(matcher) => new _AnyElement(wrapMatcher(matcher)); | 65 Matcher anyElement(matcher) => new _AnyElement(wrapMatcher(matcher)); |
| 68 | 66 |
| 69 class _AnyElement extends _IterableMatcher { | 67 class _AnyElement extends _IterableMatcher { |
| 70 final Matcher _matcher; | 68 final Matcher _matcher; |
| 71 | 69 |
| (...skipping 59 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 131 | 129 |
| 132 /// Iterable matchers match against [Iterable]s. We add this intermediate | 130 /// Iterable matchers match against [Iterable]s. We add this intermediate |
| 133 /// class to give better mismatch error messages than the base Matcher class. | 131 /// class to give better mismatch error messages than the base Matcher class. |
| 134 abstract class _IterableMatcher extends Matcher { | 132 abstract class _IterableMatcher extends Matcher { |
| 135 const _IterableMatcher(); | 133 const _IterableMatcher(); |
| 136 Description describeMismatch( | 134 Description describeMismatch( |
| 137 item, Description mismatchDescription, Map matchState, bool verbose) { | 135 item, Description mismatchDescription, Map matchState, bool verbose) { |
| 138 if (item is! Iterable) { | 136 if (item is! Iterable) { |
| 139 return mismatchDescription.addDescriptionOf(item).add(' not an Iterable'); | 137 return mismatchDescription.addDescriptionOf(item).add(' not an Iterable'); |
| 140 } else { | 138 } else { |
| 141 return super.describeMismatch( | 139 return super |
| 142 item, mismatchDescription, matchState, verbose); | 140 .describeMismatch(item, mismatchDescription, matchState, verbose); |
| 143 } | 141 } |
| 144 } | 142 } |
| 145 } | 143 } |
| 146 | 144 |
| 147 /// Returns a matcher which matches [Iterable]s whose elements match the | 145 /// Returns a matcher which matches [Iterable]s whose elements match the |
| 148 /// matchers in [expected], but not necessarily in the same order. | 146 /// matchers in [expected], but not necessarily in the same order. |
| 149 /// | 147 /// |
| 150 /// Note that this is `O(n^2)` and so should only be used on small objects. | 148 /// Note that this is `O(n^2)` and so should only be used on small objects. |
| 151 Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected); | 149 Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected); |
| 152 | 150 |
| (...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 195 return null; | 193 return null; |
| 196 } | 194 } |
| 197 | 195 |
| 198 bool matches(item, Map mismatchState) => _test(item) == null; | 196 bool matches(item, Map mismatchState) => _test(item) == null; |
| 199 | 197 |
| 200 Description describe(Description description) => description | 198 Description describe(Description description) => description |
| 201 .add('matches ') | 199 .add('matches ') |
| 202 .addAll('[', ', ', ']', _expected) | 200 .addAll('[', ', ', ']', _expected) |
| 203 .add(' unordered'); | 201 .add(' unordered'); |
| 204 | 202 |
| 205 Description describeMismatch( | 203 Description describeMismatch(item, Description mismatchDescription, |
| 206 item, Description mismatchDescription, Map matchState, bool verbose) => | 204 Map matchState, bool verbose) => |
| 207 mismatchDescription.add(_test(item)); | 205 mismatchDescription.add(_test(item)); |
| 208 } | 206 } |
| 209 | 207 |
| 210 /// A pairwise matcher for [Iterable]s. | 208 /// A pairwise matcher for [Iterable]s. |
| 211 /// | 209 /// |
| 212 /// The [comparator] function, taking an expected and an actual argument, and | 210 /// The [comparator] function, taking an expected and an actual argument, and |
| 213 /// returning whether they match, will be applied to each pair in order. | 211 /// returning whether they match, will be applied to each pair in order. |
| 214 /// [description] should be a meaningful name for the comparator. | 212 /// [description] should be a meaningful name for the comparator. |
| 215 Matcher pairwiseCompare( | 213 Matcher pairwiseCompare( |
| 216 Iterable expected, bool comparator(a, b), String description) => | 214 Iterable expected, bool comparator(a, b), String description) => |
| 217 new _PairwiseCompare(expected, comparator, description); | 215 new _PairwiseCompare(expected, comparator, description); |
| 218 | 216 |
| 219 typedef bool _Comparator(a, b); | 217 typedef bool _Comparator(a, b); |
| 220 | 218 |
| 221 class _PairwiseCompare extends _IterableMatcher { | 219 class _PairwiseCompare extends _IterableMatcher { |
| 222 final Iterable _expected; | 220 final Iterable _expected; |
| 223 final _Comparator _comparator; | 221 final _Comparator _comparator; |
| 224 final String _description; | 222 final String _description; |
| 225 | 223 |
| 226 _PairwiseCompare(this._expected, this._comparator, this._description); | 224 _PairwiseCompare(this._expected, this._comparator, this._description); |
| 227 | 225 |
| 228 bool matches(item, Map matchState) { | 226 bool matches(item, Map matchState) { |
| 229 if (item is! Iterable) return false; | 227 if (item is! Iterable) return false; |
| 230 if (item.length != _expected.length) return false; | 228 if (item.length != _expected.length) return false; |
| 231 var iterator = item.iterator; | 229 var iterator = item.iterator; |
| 232 var i = 0; | 230 var i = 0; |
| 233 for (var e in _expected) { | 231 for (var e in _expected) { |
| 234 iterator.moveNext(); | 232 iterator.moveNext(); |
| 235 if (!_comparator(e, iterator.current)) { | 233 if (!_comparator(e, iterator.current)) { |
| 236 addStateInfo(matchState, { | 234 addStateInfo(matchState, |
| 237 'index': i, | 235 {'index': i, 'expected': e, 'actual': iterator.current}); |
| 238 'expected': e, | |
| 239 'actual': iterator.current | |
| 240 }); | |
| 241 return false; | 236 return false; |
| 242 } | 237 } |
| 243 i++; | 238 i++; |
| 244 } | 239 } |
| 245 return true; | 240 return true; |
| 246 } | 241 } |
| 247 | 242 |
| 248 Description describe(Description description) => | 243 Description describe(Description description) => |
| 249 description.add('pairwise $_description ').addDescriptionOf(_expected); | 244 description.add('pairwise $_description ').addDescriptionOf(_expected); |
| 250 | 245 |
| 251 Description describeMismatch( | 246 Description describeMismatch( |
| 252 item, Description mismatchDescription, Map matchState, bool verbose) { | 247 item, Description mismatchDescription, Map matchState, bool verbose) { |
| 253 if (item is! Iterable) { | 248 if (item is! Iterable) { |
| 254 return mismatchDescription.add('is not an Iterable'); | 249 return mismatchDescription.add('is not an Iterable'); |
| 255 } else if (item.length != _expected.length) { | 250 } else if (item.length != _expected.length) { |
| 256 return mismatchDescription | 251 return mismatchDescription |
| 257 .add('has length ${item.length} instead of ${_expected.length}'); | 252 .add('has length ${item.length} instead of ${_expected.length}'); |
| 258 } else { | 253 } else { |
| 259 return mismatchDescription | 254 return mismatchDescription |
| 260 .add('has ') | 255 .add('has ') |
| 261 .addDescriptionOf(matchState["actual"]) | 256 .addDescriptionOf(matchState["actual"]) |
| 262 .add(' which is not $_description ') | 257 .add(' which is not $_description ') |
| 263 .addDescriptionOf(matchState["expected"]) | 258 .addDescriptionOf(matchState["expected"]) |
| 264 .add(' at index ${matchState["index"]}'); | 259 .add(' at index ${matchState["index"]}'); |
| 265 } | 260 } |
| 266 } | 261 } |
| 267 } | 262 } |
| 263 |
| 264 /// Matches [Iterable]s which contain an element matching every value in |
| 265 /// [expected] in the same order, but may contain additional values interleaved |
| 266 /// throughout. |
| 267 /// |
| 268 /// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not |
| 269 /// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`. |
| 270 Matcher containsAllInOrder(Iterable expected) => |
| 271 new _ContainsAllInOrder(expected); |
| 272 |
| 273 class _ContainsAllInOrder implements Matcher { |
| 274 final Iterable _expected; |
| 275 |
| 276 _ContainsAllInOrder(this._expected); |
| 277 |
| 278 String _test(item, Map matchState) { |
| 279 if (item is! Iterable) return 'not an iterable'; |
| 280 var matchers = _expected.map(wrapMatcher).toList(); |
| 281 var matcherIndex = 0; |
| 282 for (var value in item) { |
| 283 if (matchers[matcherIndex].matches(value, matchState)) matcherIndex++; |
| 284 if (matcherIndex == matchers.length) return null; |
| 285 } |
| 286 return new StringDescription() |
| 287 .add('did not find a value matching ') |
| 288 .addDescriptionOf(matchers[matcherIndex]) |
| 289 .add(' following expected prior values') |
| 290 .toString(); |
| 291 } |
| 292 |
| 293 @override |
| 294 bool matches(item, Map matchState) => _test(item, matchState) == null; |
| 295 |
| 296 @override |
| 297 Description describe(Description description) => description |
| 298 .add('contains in order(') |
| 299 .addDescriptionOf(_expected) |
| 300 .add(')'); |
| 301 |
| 302 @override |
| 303 Description describeMismatch(item, Description mismatchDescription, |
| 304 Map matchState, bool verbose) => |
| 305 mismatchDescription.add(_test(item, matchState)); |
| 306 } |
| OLD | NEW |