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