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 | |
6 /** | |
7 * Returns a matcher that matches empty strings, maps or collections. | |
8 */ | |
9 final Matcher isEmpty = const _Empty(); | |
10 | |
11 class _Empty extends BaseMatcher { | |
12 const _Empty(); | |
13 bool matches(item) { | |
14 if (item is Map || item is Collection) { | |
15 return item.isEmpty(); | |
16 } else if (item is String) { | |
17 return item.length == 0; | |
18 } else { | |
19 return false; | |
20 } | |
21 } | |
22 Description describe(Description description) => | |
23 description.add('empty'); | |
24 } | |
25 | |
26 /** A matcher that matches any null value. */ | |
27 final Matcher isNull = const _IsNull(); | |
28 | |
29 /** A matcher that matches any non-null value. */ | |
30 final Matcher isNotNull = const _IsNotNull(); | |
31 | |
32 class _IsNull extends BaseMatcher { | |
33 const _IsNull(); | |
34 bool matches(item) => item == null; | |
35 Description describe(Description description) => | |
36 description.add('null'); | |
37 } | |
38 | |
39 class _IsNotNull extends BaseMatcher { | |
40 const _IsNotNull(); | |
41 bool matches(item) => item != null; | |
42 Description describe(Description description) => | |
43 description.add('not null'); | |
44 } | |
45 | |
46 /** A matcher that matches the Boolean value true. */ | |
47 final Matcher isTrue = const _IsTrue(); | |
48 | |
49 /** A matcher that matches anything except the Boolean value true. */ | |
50 final Matcher isFalse = const _IsFalse(); | |
51 | |
52 class _IsTrue extends BaseMatcher { | |
53 const _IsTrue(); | |
54 bool matches(item) => item == true; | |
55 Description describe(Description description) => | |
56 description.add('true'); | |
57 } | |
58 | |
59 class _IsFalse extends BaseMatcher { | |
60 const _IsFalse(); | |
61 bool matches(item) => item != true; | |
62 Description describe(Description description) => | |
63 description.add('false'); | |
64 } | |
65 | |
66 /** | |
67 * Returns a matches that matches if the value is the same instance | |
68 * as [object] (`===`). | |
69 */ | |
70 Matcher same(expected) => new _IsSameAs(expected); | |
71 | |
72 class _IsSameAs extends BaseMatcher { | |
73 final _expected; | |
74 const _IsSameAs(this._expected); | |
75 bool matches(item) => item === _expected; | |
76 // If all types were hashable we could show a hash here. | |
77 Description describe(Description description) => | |
78 description.add('same instance as ').addDescriptionOf(_expected); | |
79 } | |
80 | |
81 /** Returns a matcher that matches if two objects are equal (==). */ | |
82 Matcher equals(expected) => new _IsEqual(expected); | |
83 | |
84 class _IsEqual extends BaseMatcher { | |
85 final _expected; | |
86 const _IsEqual(this._expected); | |
87 bool matches(item) => item == _expected; | |
88 Description describe(Description description) => | |
89 description.addDescriptionOf(_expected); | |
90 } | |
91 | |
92 /** A matcher that matches any value. */ | |
93 final Matcher anything = const _IsAnything(); | |
94 | |
95 class _IsAnything extends BaseMatcher { | |
96 const _IsAnything(); | |
97 bool matches(item) => true; | |
98 Description describe(Description description) => | |
99 description.add('anything'); | |
100 } | |
101 | |
102 /** | |
103 * A matcher that matches functions that throw exceptions when called. | |
104 * The value passed to expect() should be a reference to the function. | |
105 * Note that the function cannot take arguments; to handle this | |
106 * a wrapper will have to be created. | |
107 * The function will be called once upon success, or twice upon failure | |
108 * (the second time to get the failure description). | |
109 */ | |
110 final Matcher throws = const _Throws(); | |
111 | |
112 /** | |
113 * Returns a matcher that matches a function call against an exception, | |
114 * which is in turn constrained by a [matcher]. | |
115 * The value passed to expect() should be a reference to the function. | |
116 * Note that the function cannot take arguments; to handle this | |
117 * a wrapper will have to be created. | |
118 * The function will be called once upon success, or twice upon failure | |
119 * (the second time to get the failure description). | |
120 */ | |
121 Matcher throwsA(Matcher matcher) => new _Throws(matcher); | |
122 | |
123 /** | |
124 * A matcher that matches a function call against no exception. | |
125 * The function will be called once. Any exceptions will be silently swallowed. | |
126 * The value passed to expect() should be a reference to the function. | |
127 * Note that the function cannot take arguments; to handle this | |
128 * a wrapper will have to be created. | |
129 */ | |
130 final Matcher returnsNormally = const _ReturnsNormally(); | |
131 | |
132 class _Throws extends BaseMatcher { | |
133 final Matcher _matcher; | |
134 | |
135 const _Throws([Matcher this._matcher = null]); | |
136 | |
137 bool matches(item) { | |
138 try { | |
139 item(); | |
140 return false; | |
141 } catch (final e) { | |
142 return _matcher == null || _matcher.matches(e); | |
143 } | |
144 } | |
145 | |
146 Description describe(Description description) { | |
147 if (_matcher == null) { | |
148 return description.add("throws an exception"); | |
149 } else { | |
150 return description.add('throws an exception which matches '). | |
151 addDescriptionOf(_matcher); | |
152 } | |
153 } | |
154 | |
155 Description describeMismatch(item, Description mismatchDescription) { | |
156 try { | |
157 item(); | |
158 return mismatchDescription.add(' no exception'); | |
159 } catch (final e) { | |
160 return mismatchDescription.add(' exception does not match '). | |
161 addDescriptionOf(_matcher); | |
162 } | |
163 } | |
164 } | |
165 | |
166 class _ReturnsNormally extends BaseMatcher { | |
167 | |
168 const _ReturnsNormally(); | |
169 | |
170 bool matches(f) { | |
171 try { | |
172 f(); | |
173 return true; | |
174 } catch (final e) { | |
175 return false; | |
176 } | |
177 } | |
178 | |
179 Description describe(Description description) => | |
180 description.add("return normally"); | |
181 | |
182 Description describeMismatch(item, Description mismatchDescription) { | |
183 return mismatchDescription.add(' threw exception'); | |
184 } | |
185 } | |
186 | |
187 /** | |
188 * Returns a matcher that matches if an object is an instance | |
189 * of [type] (or a subtype). | |
190 * | |
191 * As types are not first class objects in Dart we can only | |
192 * approximate this test by using a generic wrapper class. | |
193 * | |
194 * For example, to test whether 'bar' is an instance of type | |
195 * 'Foo', we would write: | |
196 * | |
197 * expect(bar, new isInstanceOf<Foo>()); | |
198 * | |
199 * To get better error message, supply a name when creating the | |
200 * Type wrapper; e.g.: | |
201 * | |
202 * expect(bar, new isInstanceOf<Foo>('Foo')); | |
203 */ | |
204 class isInstanceOf<T> extends BaseMatcher { | |
205 final String _name; | |
206 const isInstanceOf([this._name = 'specified type']); | |
207 bool matches(obj) => obj is T; | |
Siggi Cherem (dart-lang)
2012/06/06 00:26:08
FYI - I believe frog fails incorrectly here (retur
gram
2012/06/06 16:23:56
I'll test this. If it fails I'll file a bug agains
| |
208 // The description here is lame :-( | |
209 Description describe(Description description) => | |
210 description.add('an instance of ${_name}'); | |
211 } | |
212 | |
213 /** | |
214 * Returns a matcher that matches if an object has a length property | |
215 * that matches [matcher]. | |
216 */ | |
217 Matcher hasLength(Matcher matcher) => | |
218 new _HasLength(wrapMatcher(matcher)); | |
219 | |
220 class _HasLength extends BaseMatcher { | |
221 final Matcher _matcher; | |
222 const _HasLength([Matcher this._matcher = null]); | |
223 | |
224 bool matches(item) { | |
225 return _matcher.matches(item.length); | |
226 } | |
227 | |
228 Description describe(Description description) => | |
229 description.add('an object with length of '). | |
230 addDescriptionOf(_matcher); | |
231 | |
232 Description describeMismatch(item, Description mismatchDescription) { | |
233 super.describeMismatch(item, mismatchDescription); | |
234 try { | |
235 // We want to generate a different description if there is no length | |
236 // property. This is harmless code that will throw if no length property | |
237 // but subtle enough that an optimizer shouldn't strip it out. | |
238 if (item.length * item.length >= 0) { | |
239 return mismatchDescription.add(' with length of '). | |
240 addDescriptionOf(item.length); | |
241 } | |
242 } catch (var e) { | |
243 return mismatchDescription.add(' has no length property'); | |
244 } | |
245 } | |
246 } | |
247 | |
248 /** | |
249 * Returns a matcher that does a deep recursive match. This only works | |
250 * with scalars, Maps and Iterables. To handle cyclic structures an | |
251 * item [limit] can be provided; if after [limit] items have been | |
252 * compared and the process is not complete this will be treated as | |
253 * a mismatch. The default limit is 1000. | |
254 */ | |
255 Matcher recursivelyMatches(expected, [limit=1000]) => | |
256 new _DeepMatcher(expected, limit); | |
257 | |
258 // A utility function for comparing iterators | |
259 | |
260 String _compareIterables(expected, actual, matcher) { | |
261 if (actual is !Iterable) { | |
262 return 'is not Iterable'; | |
263 } | |
264 var expectedIterator = expected.iterator(); | |
265 var actualIterator = actual.iterator(); | |
266 var position = 0; | |
267 String reason = null; | |
268 while (reason == null) { | |
269 if (expectedIterator.hasNext()) { | |
270 if (actualIterator.hasNext()) { | |
271 reason = matcher(expectedIterator.next(), | |
272 actualIterator.next(), | |
273 'mismatch at position ${position}'); | |
274 ++position; | |
275 } else { | |
276 reason = 'shorter than expected'; | |
277 } | |
278 } else if (actualIterator.hasNext()) { | |
279 reason = 'longer than expected'; | |
280 } else { | |
281 return null; | |
282 } | |
283 } | |
284 return reason; | |
285 } | |
286 | |
287 class _DeepMatcher extends BaseMatcher { | |
288 final _expected; | |
289 final int _limit; | |
290 var count; | |
291 | |
292 _DeepMatcher(this._expected, [this._limit = 1000]); | |
293 | |
294 String _recursiveMatch(expected, actual, String location) { | |
295 String reason = null; | |
296 if (++count >= _limit) { | |
297 reason = 'item comparison limit exceeded'; | |
298 } else if (expected is Iterable) { | |
299 reason = _compareIterables(expected, actual, _recursiveMatch); | |
300 } else if (expected is Map) { | |
301 if (actual is !Map) { | |
302 reason = 'expected a map'; | |
303 } else if (expected.length != actual.length) { | |
304 reason = 'different map lengths'; | |
305 } else { | |
306 for (var key in expected.getKeys()) { | |
307 if (!actual.containsKey(key)) { | |
308 reason = 'missing map key ${key}'; | |
309 break; | |
310 } | |
311 reason = _recursiveMatch(expected[key], actual[key], | |
312 'with key ${key} ${location}'); | |
313 if (reason != null) { | |
314 break; | |
315 } | |
316 } | |
317 } | |
318 } else if (expected != actual) { | |
319 reason = 'expected ${expected} but got ${actual}'; | |
320 } | |
321 if (reason == null) { | |
322 return null; | |
323 } else { | |
324 return '${reason} ${location}'; | |
325 } | |
326 } | |
327 | |
328 String _match(expected, actual) { | |
329 count = 0; | |
330 return _recursiveMatch(expected, actual, ''); | |
331 } | |
332 | |
333 bool matches(item) => _match(_expected, item) == null; | |
334 | |
335 Description describe(Description description) => | |
336 description.add('recursively matches ').addDescriptionOf(_expected); | |
337 | |
338 Description describeMismatch(item, Description mismatchDescription) => | |
339 mismatchDescription.add(_match(_expected, item)); | |
340 } | |
341 | |
342 /** | |
343 * Returns a matcher that matches if the match argument contains | |
344 * the expected value. For [String]s this means substring matching; | |
345 * for [Map]s is means the map has the key, and for [Collection]s it | |
346 * means the collection has a matching element. In the case of collections, | |
347 * [expected] can itself be a matcher. | |
348 */ | |
349 Matcher contains(expected) => new _Contains(expected); | |
350 | |
351 class _Contains extends BaseMatcher { | |
352 | |
353 final _expected; | |
354 | |
355 const _Contains(this._expected); | |
356 | |
357 bool matches(item) { | |
358 if (item is String) { | |
359 return item.indexOf(_expected) >= 0; | |
360 } else if (item is Collection) { | |
361 if (_expected is Matcher) { | |
362 return item.some((e) => _expected.matches(e)); | |
363 } else { | |
364 return item.some((e) => e == _expected); | |
365 } | |
366 } else if (item is Map) { | |
367 return item.containsKey(_expected); | |
368 } | |
369 return false; | |
370 } | |
371 | |
372 Description describe(Description description) => | |
373 description.add('contains ').addDescriptionOf(_expected); | |
374 } | |
OLD | NEW |