Line data Source code
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
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'core_matchers.dart';
6 : import 'description.dart';
7 : import 'interfaces.dart';
8 : import 'util.dart';
9 :
10 : /// Returns a matcher which matches [Iterable]s in which all elements
11 : /// match the given [matcher].
12 0 : Matcher everyElement(matcher) => new _EveryElement(wrapMatcher(matcher));
13 :
14 : class _EveryElement extends _IterableMatcher {
15 : final Matcher _matcher;
16 :
17 0 : _EveryElement(this._matcher);
18 :
19 : bool matches(item, Map matchState) {
20 0 : if (item is! Iterable) {
21 : return false;
22 : }
23 : var i = 0;
24 0 : for (var element in item) {
25 0 : if (!_matcher.matches(element, matchState)) {
26 0 : addStateInfo(matchState, {'index': i, 'element': element});
27 : return false;
28 : }
29 0 : ++i;
30 : }
31 : return true;
32 : }
33 :
34 : Description describe(Description description) =>
35 0 : description.add('every element(').addDescriptionOf(_matcher).add(')');
36 :
37 : Description describeMismatch(
38 : item, Description mismatchDescription, Map matchState, bool verbose) {
39 0 : if (matchState['index'] != null) {
40 0 : var index = matchState['index'];
41 0 : var element = matchState['element'];
42 : mismatchDescription
43 0 : .add('has value ')
44 0 : .addDescriptionOf(element)
45 0 : .add(' which ');
46 0 : var subDescription = new StringDescription();
47 0 : _matcher.describeMismatch(
48 0 : element, subDescription, matchState['state'], verbose);
49 0 : if (subDescription.length > 0) {
50 0 : mismatchDescription.add(subDescription.toString());
51 : } else {
52 0 : mismatchDescription.add("doesn't match ");
53 0 : _matcher.describe(mismatchDescription);
54 : }
55 0 : mismatchDescription.add(' at index $index');
56 : return mismatchDescription;
57 : }
58 : return super
59 0 : .describeMismatch(item, mismatchDescription, matchState, verbose);
60 : }
61 : }
62 :
63 : /// Returns a matcher which matches [Iterable]s in which at least one
64 : /// element matches the given [matcher].
65 0 : Matcher anyElement(matcher) => new _AnyElement(wrapMatcher(matcher));
66 :
67 : class _AnyElement extends _IterableMatcher {
68 : final Matcher _matcher;
69 :
70 0 : _AnyElement(this._matcher);
71 :
72 : bool matches(item, Map matchState) {
73 0 : return item.any((e) => _matcher.matches(e, matchState));
74 : }
75 :
76 : Description describe(Description description) =>
77 0 : description.add('some element ').addDescriptionOf(_matcher);
78 : }
79 :
80 : /// Returns a matcher which matches [Iterable]s that have the same
81 : /// length and the same elements as [expected], in the same order.
82 : ///
83 : /// This is equivalent to [equals] but does not recurse.
84 0 : Matcher orderedEquals(Iterable expected) => new _OrderedEquals(expected);
85 :
86 : class _OrderedEquals extends Matcher {
87 : final Iterable _expected;
88 : Matcher _matcher;
89 :
90 0 : _OrderedEquals(this._expected) {
91 0 : _matcher = equals(_expected, 1);
92 : }
93 :
94 : bool matches(item, Map matchState) =>
95 0 : (item is Iterable) && _matcher.matches(item, matchState);
96 :
97 : Description describe(Description description) =>
98 0 : description.add('equals ').addDescriptionOf(_expected).add(' ordered');
99 :
100 : Description describeMismatch(
101 : item, Description mismatchDescription, Map matchState, bool verbose) {
102 0 : if (item is! Iterable) {
103 0 : return mismatchDescription.add('is not an Iterable');
104 : } else {
105 0 : return _matcher.describeMismatch(
106 : item, mismatchDescription, matchState, verbose);
107 : }
108 : }
109 : }
110 :
111 : /// Returns a matcher which matches [Iterable]s that have the same length and
112 : /// the same elements as [expected], but not necessarily in the same order.
113 : ///
114 : /// Note that this is O(n^2) so should only be used on small objects.
115 0 : Matcher unorderedEquals(Iterable expected) => new _UnorderedEquals(expected);
116 :
117 : class _UnorderedEquals extends _UnorderedMatches {
118 : final List _expectedValues;
119 :
120 : _UnorderedEquals(Iterable expected)
121 0 : : _expectedValues = expected.toList(),
122 0 : super(expected.map(equals));
123 :
124 : Description describe(Description description) => description
125 0 : .add('equals ')
126 0 : .addDescriptionOf(_expectedValues)
127 0 : .add(' unordered');
128 : }
129 :
130 : /// Iterable matchers match against [Iterable]s. We add this intermediate
131 : /// class to give better mismatch error messages than the base Matcher class.
132 : abstract class _IterableMatcher extends Matcher {
133 0 : const _IterableMatcher();
134 : Description describeMismatch(
135 : item, Description mismatchDescription, Map matchState, bool verbose) {
136 0 : if (item is! Iterable) {
137 0 : return mismatchDescription.addDescriptionOf(item).add(' not an Iterable');
138 : } else {
139 : return super
140 0 : .describeMismatch(item, mismatchDescription, matchState, verbose);
141 : }
142 : }
143 : }
144 :
145 : /// Returns a matcher which matches [Iterable]s whose elements match the
146 : /// matchers in [expected], but not necessarily in the same order.
147 : ///
148 : /// Note that this is `O(n^2)` and so should only be used on small objects.
149 0 : Matcher unorderedMatches(Iterable expected) => new _UnorderedMatches(expected);
150 :
151 : class _UnorderedMatches extends Matcher {
152 : final List<Matcher> _expected;
153 :
154 : _UnorderedMatches(Iterable expected)
155 0 : : _expected = expected.map(wrapMatcher).toList();
156 :
157 : String _test(item) {
158 0 : if (item is Iterable) {
159 0 : var list = item.toList();
160 :
161 : // Check the lengths are the same.
162 0 : if (_expected.length > list.length) {
163 0 : return 'has too few elements (${list.length} < ${_expected.length})';
164 0 : } else if (_expected.length < list.length) {
165 0 : return 'has too many elements (${list.length} > ${_expected.length})';
166 : }
167 :
168 0 : var matched = new List<bool>.filled(list.length, false);
169 : var expectedPosition = 0;
170 0 : for (var expectedMatcher in _expected) {
171 : var actualPosition = 0;
172 : var gotMatch = false;
173 0 : for (var actualElement in list) {
174 0 : if (!matched[actualPosition]) {
175 0 : if (expectedMatcher.matches(actualElement, {})) {
176 0 : matched[actualPosition] = gotMatch = true;
177 : break;
178 : }
179 : }
180 0 : ++actualPosition;
181 : }
182 :
183 : if (!gotMatch) {
184 0 : return new StringDescription()
185 0 : .add('has no match for ')
186 0 : .addDescriptionOf(expectedMatcher)
187 0 : .add(' at index $expectedPosition')
188 0 : .toString();
189 : }
190 :
191 0 : ++expectedPosition;
192 : }
193 : return null;
194 : } else {
195 : return 'not iterable';
196 : }
197 : }
198 :
199 0 : bool matches(item, Map mismatchState) => _test(item) == null;
200 :
201 : Description describe(Description description) => description
202 0 : .add('matches ')
203 0 : .addAll('[', ', ', ']', _expected)
204 0 : .add(' unordered');
205 :
206 : Description describeMismatch(item, Description mismatchDescription,
207 : Map matchState, bool verbose) =>
208 0 : mismatchDescription.add(_test(item));
209 : }
210 :
211 : /// A pairwise matcher for [Iterable]s.
212 : ///
213 : /// The [comparator] function, taking an expected and an actual argument, and
214 : /// returning whether they match, will be applied to each pair in order.
215 : /// [description] should be a meaningful name for the comparator.
216 : Matcher pairwiseCompare<S, T>(
217 : Iterable<S> expected, bool comparator(S a, T b), String description) =>
218 0 : new _PairwiseCompare(expected, comparator, description);
219 :
220 : typedef bool _Comparator<S, T>(S a, T b);
221 :
222 : class _PairwiseCompare<S, T> extends _IterableMatcher {
223 : final Iterable<S> _expected;
224 : final _Comparator<S, T> _comparator;
225 : final String _description;
226 :
227 0 : _PairwiseCompare(this._expected, this._comparator, this._description);
228 :
229 : bool matches(item, Map matchState) {
230 0 : if (item is Iterable) {
231 0 : if (item.length != _expected.length) return false;
232 0 : var iterator = item.iterator;
233 : var i = 0;
234 0 : for (var e in _expected) {
235 0 : iterator.moveNext();
236 0 : if (!_comparator(e, iterator.current)) {
237 0 : addStateInfo(matchState,
238 0 : {'index': i, 'expected': e, 'actual': iterator.current});
239 : return false;
240 : }
241 0 : i++;
242 : }
243 : return true;
244 : } else {
245 : return false;
246 : }
247 : }
248 :
249 : Description describe(Description description) =>
250 0 : description.add('pairwise $_description ').addDescriptionOf(_expected);
251 :
252 : Description describeMismatch(
253 : item, Description mismatchDescription, Map matchState, bool verbose) {
254 0 : if (item is! Iterable) {
255 0 : return mismatchDescription.add('is not an Iterable');
256 0 : } else if (item.length != _expected.length) {
257 : return mismatchDescription
258 0 : .add('has length ${item.length} instead of ${_expected.length}');
259 : } else {
260 : return mismatchDescription
261 0 : .add('has ')
262 0 : .addDescriptionOf(matchState["actual"])
263 0 : .add(' which is not $_description ')
264 0 : .addDescriptionOf(matchState["expected"])
265 0 : .add(' at index ${matchState["index"]}');
266 : }
267 : }
268 : }
269 :
270 : /// Matches [Iterable]s which contain an element matching every value in
271 : /// [expected] in the same order, but may contain additional values interleaved
272 : /// throughout.
273 : ///
274 : /// For example: `[0, 1, 0, 2, 0]` matches `containsAllInOrder([1, 2])` but not
275 : /// `containsAllInOrder([2, 1])` or `containsAllInOrder([1, 2, 3])`.
276 : Matcher containsAllInOrder(Iterable expected) =>
277 0 : new _ContainsAllInOrder(expected);
278 :
279 : class _ContainsAllInOrder implements Matcher {
280 : final Iterable _expected;
281 :
282 0 : _ContainsAllInOrder(this._expected);
283 :
284 : String _test(item, Map matchState) {
285 0 : if (item is! Iterable) return 'not an iterable';
286 0 : var matchers = _expected.map(wrapMatcher).toList();
287 : var matcherIndex = 0;
288 0 : for (var value in item) {
289 0 : if (matchers[matcherIndex].matches(value, matchState)) matcherIndex++;
290 0 : if (matcherIndex == matchers.length) return null;
291 : }
292 0 : return new StringDescription()
293 0 : .add('did not find a value matching ')
294 0 : .addDescriptionOf(matchers[matcherIndex])
295 0 : .add(' following expected prior values')
296 0 : .toString();
297 : }
298 :
299 : @override
300 0 : bool matches(item, Map matchState) => _test(item, matchState) == null;
301 :
302 : @override
303 : Description describe(Description description) => description
304 0 : .add('contains in order(')
305 0 : .addDescriptionOf(_expected)
306 0 : .add(')');
307 :
308 : @override
309 : Description describeMismatch(item, Description mismatchDescription,
310 : Map matchState, bool verbose) =>
311 0 : mismatchDescription.add(_test(item, matchState));
312 : }
|