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 |