| 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 part of matcher; | 5 part of matcher; |
| 6 | 6 |
| 7 /** | 7 /** |
| 8 * Returns a matcher that matches empty strings, maps or iterables | 8 * Returns a matcher that matches empty strings, maps or iterables |
| 9 * (including collections). | 9 * (including collections). |
| 10 */ | 10 */ |
| 11 const Matcher isEmpty = const _Empty(); | 11 const Matcher isEmpty = const _Empty(); |
| 12 | 12 |
| 13 class _Empty extends BaseMatcher { | 13 class _Empty extends BaseMatcher { |
| 14 const _Empty(); | 14 const _Empty(); |
| 15 bool matches(item, MatchState matchState) { | 15 bool matches(item, Map matchState) { |
| 16 if (item is Map || item is Iterable) { | 16 if (item is Map || item is Iterable) { |
| 17 return item.isEmpty; | 17 return item.isEmpty; |
| 18 } else if (item is String) { | 18 } else if (item is String) { |
| 19 return item.length == 0; | 19 return item.length == 0; |
| 20 } else { | 20 } else { |
| 21 return false; | 21 return false; |
| 22 } | 22 } |
| 23 } | 23 } |
| 24 Description describe(Description description) => | 24 Description describe(Description description) => |
| 25 description.add('empty'); | 25 description.add('empty'); |
| 26 } | 26 } |
| 27 | 27 |
| 28 /** A matcher that matches any null value. */ | 28 /** A matcher that matches any null value. */ |
| 29 const Matcher isNull = const _IsNull(); | 29 const Matcher isNull = const _IsNull(); |
| 30 | 30 |
| 31 /** A matcher that matches any non-null value. */ | 31 /** A matcher that matches any non-null value. */ |
| 32 const Matcher isNotNull = const _IsNotNull(); | 32 const Matcher isNotNull = const _IsNotNull(); |
| 33 | 33 |
| 34 class _IsNull extends BaseMatcher { | 34 class _IsNull extends BaseMatcher { |
| 35 const _IsNull(); | 35 const _IsNull(); |
| 36 bool matches(item, MatchState matchState) => item == null; | 36 bool matches(item, Map matchState) => item == null; |
| 37 Description describe(Description description) => | 37 Description describe(Description description) => |
| 38 description.add('null'); | 38 description.add('null'); |
| 39 } | 39 } |
| 40 | 40 |
| 41 class _IsNotNull extends BaseMatcher { | 41 class _IsNotNull extends BaseMatcher { |
| 42 const _IsNotNull(); | 42 const _IsNotNull(); |
| 43 bool matches(item, MatchState matchState) => item != null; | 43 bool matches(item, Map matchState) => item != null; |
| 44 Description describe(Description description) => | 44 Description describe(Description description) => |
| 45 description.add('not null'); | 45 description.add('not null'); |
| 46 } | 46 } |
| 47 | 47 |
| 48 /** A matcher that matches the Boolean value true. */ | 48 /** A matcher that matches the Boolean value true. */ |
| 49 const Matcher isTrue = const _IsTrue(); | 49 const Matcher isTrue = const _IsTrue(); |
| 50 | 50 |
| 51 /** A matcher that matches anything except the Boolean value true. */ | 51 /** A matcher that matches anything except the Boolean value true. */ |
| 52 const Matcher isFalse = const _IsFalse(); | 52 const Matcher isFalse = const _IsFalse(); |
| 53 | 53 |
| 54 class _IsTrue extends BaseMatcher { | 54 class _IsTrue extends BaseMatcher { |
| 55 const _IsTrue(); | 55 const _IsTrue(); |
| 56 bool matches(item, MatchState matchState) => item == true; | 56 bool matches(item, Map matchState) => item == true; |
| 57 Description describe(Description description) => | 57 Description describe(Description description) => |
| 58 description.add('true'); | 58 description.add('true'); |
| 59 } | 59 } |
| 60 | 60 |
| 61 class _IsFalse extends BaseMatcher { | 61 class _IsFalse extends BaseMatcher { |
| 62 const _IsFalse(); | 62 const _IsFalse(); |
| 63 bool matches(item, MatchState matchState) => item == false; | 63 bool matches(item, Map matchState) => item == false; |
| 64 Description describe(Description description) => | 64 Description describe(Description description) => |
| 65 description.add('false'); | 65 description.add('false'); |
| 66 } | 66 } |
| 67 | 67 |
| 68 /** | 68 /** |
| 69 * Returns a matches that matches if the value is the same instance | 69 * Returns a matches that matches if the value is the same instance |
| 70 * as [object] (`===`). | 70 * as [object] (`===`). |
| 71 */ | 71 */ |
| 72 Matcher same(expected) => new _IsSameAs(expected); | 72 Matcher same(expected) => new _IsSameAs(expected); |
| 73 | 73 |
| 74 class _IsSameAs extends BaseMatcher { | 74 class _IsSameAs extends BaseMatcher { |
| 75 final _expected; | 75 final _expected; |
| 76 const _IsSameAs(this._expected); | 76 const _IsSameAs(this._expected); |
| 77 bool matches(item, MatchState matchState) => identical(item, _expected); | 77 bool matches(item, Map matchState) => identical(item, _expected); |
| 78 // If all types were hashable we could show a hash here. | 78 // If all types were hashable we could show a hash here. |
| 79 Description describe(Description description) => | 79 Description describe(Description description) => |
| 80 description.add('same instance as ').addDescriptionOf(_expected); | 80 description.add('same instance as ').addDescriptionOf(_expected); |
| 81 } | 81 } |
| 82 | 82 |
| 83 /** | 83 /** |
| 84 * Returns a matcher that does a deep recursive match. This only works | 84 * Returns a matcher that does a deep recursive match. This only works |
| 85 * with scalars, Maps and Iterables. To handle cyclic structures a | 85 * with scalars, Maps and Iterables. To handle cyclic structures a |
| 86 * recursion depth [limit] can be provided. The default limit is 100. | 86 * recursion depth [limit] can be provided. The default limit is 100. |
| 87 */ | 87 */ |
| 88 Matcher equals(expected, [limit=100]) => | 88 Matcher equals(expected, [limit=100]) => |
| 89 expected is String | 89 expected is String |
| 90 ? new _StringEqualsMatcher(expected) | 90 ? new _StringEqualsMatcher(expected) |
| 91 : new _DeepMatcher(expected, limit); | 91 : new _DeepMatcher(expected, limit); |
| 92 | 92 |
| 93 class _DeepMatcher extends BaseMatcher { | 93 class _DeepMatcher extends BaseMatcher { |
| 94 final _expected; | 94 final _expected; |
| 95 final int _limit; | 95 final int _limit; |
| 96 var count; | 96 var count; |
| 97 | 97 |
| 98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; | 98 _DeepMatcher(this._expected, [limit = 1000]) : this._limit = limit; |
| 99 | 99 |
| 100 String _compareIterables(expected, actual, matcher, depth) { | 100 // Returns a pair (reason, location) |
| 101 List _compareIterables(expected, actual, matcher, depth, location) { |
| 101 if (actual is !Iterable) { | 102 if (actual is !Iterable) { |
| 102 return 'is not Iterable'; | 103 return ['is not Iterable', location]; |
| 103 } | 104 } |
| 104 var expectedIterator = expected.iterator; | 105 var expectedIterator = expected.iterator; |
| 105 var actualIterator = actual.iterator; | 106 var actualIterator = actual.iterator; |
| 106 var position = 0; | 107 var index = 0; |
| 107 String reason = null; | 108 while (true) { |
| 108 while (reason == null) { | |
| 109 if (expectedIterator.moveNext()) { | 109 if (expectedIterator.moveNext()) { |
| 110 var newLocation = '${location}[${index}]'; |
| 110 if (actualIterator.moveNext()) { | 111 if (actualIterator.moveNext()) { |
| 111 Description r = matcher(expectedIterator.current, | 112 var rp = matcher(expectedIterator.current, |
| 112 actualIterator.current, | 113 actualIterator.current, newLocation, |
| 113 'mismatch at position ${position}', | |
| 114 depth); | 114 depth); |
| 115 if (r != null) reason = r.toString(); | 115 if (rp != null) return rp; |
| 116 ++position; | 116 ++index; |
| 117 } else { | 117 } else { |
| 118 reason = 'shorter than expected'; | 118 return ['shorter than expected', newLocation]; |
| 119 } | 119 } |
| 120 } else if (actualIterator.moveNext()) { | 120 } else if (actualIterator.moveNext()) { |
| 121 reason = 'longer than expected'; | 121 return ['longer than expected', newLocation]; |
| 122 } else { | 122 } else { |
| 123 return null; | 123 return null; |
| 124 } | 124 } |
| 125 } | 125 } |
| 126 return reason; | 126 return null; |
| 127 } | 127 } |
| 128 | 128 |
| 129 Description _recursiveMatch(expected, actual, String location, int depth) { | 129 List _recursiveMatch(expected, actual, String location, int depth) { |
| 130 Description reason = null; | 130 String reason = null; |
| 131 // If _limit is 1 we can only recurse one level into object. | 131 // If _limit is 1 we can only recurse one level into object. |
| 132 bool canRecurse = depth == 0 || _limit > 1; | 132 bool canRecurse = depth == 0 || _limit > 1; |
| 133 if (expected == actual) { | 133 if (expected == actual) { |
| 134 // Do nothing. | 134 // Do nothing. |
| 135 } else if (depth > _limit) { | 135 } else if (depth > _limit) { |
| 136 reason = new StringDescription('recursion depth limit exceeded'); | 136 reason = 'recursion depth limit exceeded'; |
| 137 } else { | 137 } else { |
| 138 if (expected is Iterable && canRecurse) { | 138 if (expected is Iterable && canRecurse) { |
| 139 String r = _compareIterables(expected, actual, | 139 List result = _compareIterables(expected, actual, |
| 140 _recursiveMatch, depth+1); | 140 _recursiveMatch, depth + 1, location); |
| 141 if (r != null) reason = new StringDescription(r); | 141 if (result != null) { |
| 142 reason = result[0]; |
| 143 location = result[1]; |
| 144 } |
| 142 } else if (expected is Map && canRecurse) { | 145 } else if (expected is Map && canRecurse) { |
| 143 if (actual is !Map) { | 146 if (actual is !Map) { |
| 144 reason = new StringDescription('expected a map'); | 147 reason = 'expected a map'; |
| 145 } else { | 148 } else { |
| 146 var err = (expected.length == actual.length) ? '' : | 149 var err = (expected.length == actual.length) ? '' : |
| 147 'different map lengths; '; | 150 'has different length and '; |
| 148 for (var key in expected.keys) { | 151 for (var key in expected.keys) { |
| 149 if (!actual.containsKey(key)) { | 152 if (!actual.containsKey(key)) { |
| 150 reason = new StringDescription(err); | 153 reason = '${err}is missing map key \'$key\''; |
| 151 reason.add('missing map key '); | |
| 152 reason.addDescriptionOf(key); | |
| 153 break; | 154 break; |
| 154 } | 155 } |
| 155 } | 156 } |
| 156 if (reason == null) { | 157 if (reason == null) { |
| 157 for (var key in actual.keys) { | 158 for (var key in actual.keys) { |
| 158 if (!expected.containsKey(key)) { | 159 if (!expected.containsKey(key)) { |
| 159 reason = new StringDescription(err); | 160 reason = '${err}has extra map key \'$key\''; |
| 160 reason.add('extra map key '); | |
| 161 reason.addDescriptionOf(key); | |
| 162 break; | 161 break; |
| 163 } | 162 } |
| 164 } | 163 } |
| 165 if (reason == null) { | 164 if (reason == null) { |
| 166 for (var key in expected.keys) { | 165 for (var key in expected.keys) { |
| 167 reason = _recursiveMatch(expected[key], actual[key], | 166 var rp = _recursiveMatch(expected[key], actual[key], |
| 168 'with key <${key}> ${location}', depth+1); | 167 "${location}['${key}']", depth+1); |
| 169 if (reason != null) { | 168 if (rp != null) { |
| 169 reason = rp[0]; |
| 170 location = rp[1]; |
| 170 break; | 171 break; |
| 171 } | 172 } |
| 172 } | 173 } |
| 173 } | 174 } |
| 174 } | 175 } |
| 175 } | 176 } |
| 176 } else { | 177 } else { |
| 177 reason = new StringDescription(); | 178 var description = new StringDescription(); |
| 178 // If we have recursed, show the expected value too; if not, | 179 // If we have recursed, show the expected value too; if not, |
| 179 // expect() will show it for us. | 180 // expect() will show it for us. |
| 180 if (depth > 0) { | 181 if (depth > 0) { |
| 181 reason.add('expected '); | 182 description.add('was '). |
| 182 reason.addDescriptionOf(expected).add(' but '); | 183 addDescriptionOf(actual). |
| 184 add(' instead of '). |
| 185 addDescriptionOf(expected); |
| 186 reason = description.toString(); |
| 187 } else { |
| 188 reason = ''; // We're not adding any value to the actual value. |
| 183 } | 189 } |
| 184 reason.add('was '); | |
| 185 reason.addDescriptionOf(actual); | |
| 186 } | 190 } |
| 187 } | 191 } |
| 188 if (reason != null && location.length > 0) { | 192 if (reason == null) return null; |
| 189 reason.add(' ').add(location); | 193 return [reason, location]; |
| 194 } |
| 195 |
| 196 String _match(expected, actual, Map matchState) { |
| 197 var rp = _recursiveMatch(expected, actual, '', 0); |
| 198 if (rp == null) return null; |
| 199 var reason; |
| 200 if (rp[0].length > 0) { |
| 201 if (rp[1].length > 0) { |
| 202 reason = "${rp[0]} at location ${rp[1]}"; |
| 203 } else { |
| 204 reason = rp[0]; |
| 205 } |
| 206 } else { |
| 207 reason = ''; |
| 190 } | 208 } |
| 209 // Cache the failure reason in the matchState. |
| 210 addStateInfo(matchState, {'reason': reason}); |
| 191 return reason; | 211 return reason; |
| 192 } | 212 } |
| 193 | 213 |
| 194 String _match(expected, actual) { | 214 bool matches(item, Map matchState) => |
| 195 Description reason = _recursiveMatch(expected, actual, '', 0); | 215 _match(_expected, item, matchState) == null; |
| 196 return reason == null ? null : reason.toString(); | |
| 197 } | |
| 198 | |
| 199 // TODO(gram) - see if we can make use of matchState here to avoid | |
| 200 // recursing again in describeMismatch. | |
| 201 bool matches(item, MatchState matchState) => _match(_expected, item) == null; | |
| 202 | 216 |
| 203 Description describe(Description description) => | 217 Description describe(Description description) => |
| 204 description.addDescriptionOf(_expected); | 218 description.addDescriptionOf(_expected); |
| 205 | 219 |
| 206 Description describeMismatch(item, Description mismatchDescription, | 220 Description describeMismatch(item, Description mismatchDescription, |
| 207 MatchState matchState, bool verbose) => | 221 Map matchState, bool verbose) { |
| 208 mismatchDescription.add(_match(_expected, item)); | 222 var reason = matchState['reason']; |
| 223 // If we didn't get a good reason, that would normally be a |
| 224 // simple 'is <value>' message. We only add that if the mismatch |
| 225 // description is non empty (so we are supplementing the mismatch |
| 226 // description). |
| 227 if (reason.length == 0 && mismatchDescription.length > 0) { |
| 228 mismatchDescription.add('is ').addDescriptionOf(item); |
| 229 } else { |
| 230 mismatchDescription.add(reason); |
| 231 } |
| 232 return mismatchDescription; |
| 233 } |
| 209 } | 234 } |
| 210 | 235 |
| 211 /** A special equality matcher for strings. */ | 236 /** A special equality matcher for strings. */ |
| 212 class _StringEqualsMatcher extends BaseMatcher { | 237 class _StringEqualsMatcher extends BaseMatcher { |
| 213 final String _value; | 238 final String _value; |
| 214 | 239 |
| 215 _StringEqualsMatcher(this._value); | 240 _StringEqualsMatcher(this._value); |
| 216 | 241 |
| 217 bool get showActualValue => true; | 242 bool get showActualValue => true; |
| 218 | 243 |
| 219 bool matches(item, MatchState mismatchState) => _value == item; | 244 bool matches(item, Map matchState) => _value == item; |
| 220 | 245 |
| 221 Description describe(Description description) => | 246 Description describe(Description description) => |
| 222 description.addDescriptionOf(_value); | 247 description.addDescriptionOf(_value); |
| 223 | 248 |
| 224 Description describeMismatch(item, Description mismatchDescription, | 249 Description describeMismatch(item, Description mismatchDescription, |
| 225 MatchState matchState, bool verbose) { | 250 Map matchState, bool verbose) { |
| 226 if (item is! String) { | 251 if (item is! String) { |
| 227 return mismatchDescription.addDescriptionOf(item).add(' not a string'); | 252 return mismatchDescription.addDescriptionOf(item).add('is not a string'); |
| 228 } else { | 253 } else { |
| 229 var buff = new StringBuffer(); | 254 var buff = new StringBuffer(); |
| 230 buff.write('Strings are not equal.'); | 255 buff.write('is different.'); |
| 231 var escapedItem = _escape(item); | 256 var escapedItem = _escape(item); |
| 232 var escapedValue = _escape(_value); | 257 var escapedValue = _escape(_value); |
| 233 int minLength = escapedItem.length < escapedValue.length ? | 258 int minLength = escapedItem.length < escapedValue.length ? |
| 234 escapedItem.length : escapedValue.length; | 259 escapedItem.length : escapedValue.length; |
| 235 int start; | 260 int start; |
| 236 for (start = 0; start < minLength; start++) { | 261 for (start = 0; start < minLength; start++) { |
| 237 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { | 262 if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) { |
| 238 break; | 263 break; |
| 239 } | 264 } |
| 240 } | 265 } |
| 241 if (start == minLength) { | 266 if (start == minLength) { |
| 242 if (escapedValue.length < escapedItem.length) { | 267 if (escapedValue.length < escapedItem.length) { |
| 243 buff.write(' Both strings start the same, but the given value also' | 268 buff.write(' Both strings start the same, but the given value also' |
| 244 ' has the following trailing characters: '); | 269 ' has the following trailing characters: '); |
| 245 _writeTrailing(buff, escapedItem, escapedValue.length); | 270 _writeTrailing(buff, escapedItem, escapedValue.length); |
| 246 } else { | 271 } else { |
| 247 buff.write(' Both strings start the same, but the given value is' | 272 buff.write(' Both strings start the same, but the given value is' |
| 248 ' missing the following trailing characters: '); | 273 ' missing the following trailing characters: '); |
| 249 _writeTrailing(buff, escapedValue, escapedItem.length); | 274 _writeTrailing(buff, escapedValue, escapedItem.length); |
| 250 } | 275 } |
| 251 } else { | 276 } else { |
| 252 buff.write('\nExpected: '); | 277 buff.write('\nExpected: '); |
| 253 _writeLeading(buff, escapedValue, start); | 278 _writeLeading(buff, escapedValue, start); |
| 254 _writeTrailing(buff, escapedValue, start); | 279 _writeTrailing(buff, escapedValue, start); |
| 255 buff.write('\n Actual: '); | 280 buff.write('\n Actual: '); |
| 256 _writeLeading(buff, escapedItem, start); | 281 _writeLeading(buff, escapedItem, start); |
| 257 _writeTrailing(buff, escapedItem, start); | 282 _writeTrailing(buff, escapedItem, start); |
| 258 buff.write('\n '); | 283 buff.write('\n '); |
| 259 for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' '); | 284 for (int i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' '); |
| 260 buff.write('^\n Differ at position $start'); | 285 buff.write('^\n Differ at offset $start'); |
| 261 } | 286 } |
| 262 | 287 |
| 263 return mismatchDescription.replace(buff.toString()); | 288 return mismatchDescription.replace(buff.toString()); |
| 264 } | 289 } |
| 265 } | 290 } |
| 266 | 291 |
| 267 static String _escape(String s) => | 292 static String _escape(String s) => |
| 268 s.replaceAll('\n', '\\n').replaceAll('\r', '\\r').replaceAll('\t', '\\t'); | 293 s.replaceAll('\n', '\\n').replaceAll('\r', '\\r').replaceAll('\t', '\\t'); |
| 269 | 294 |
| 270 static String _writeLeading(StringBuffer buff, String s, int start) { | 295 static String _writeLeading(StringBuffer buff, String s, int start) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 284 buff.write(' ...'); | 309 buff.write(' ...'); |
| 285 } | 310 } |
| 286 } | 311 } |
| 287 } | 312 } |
| 288 | 313 |
| 289 /** A matcher that matches any value. */ | 314 /** A matcher that matches any value. */ |
| 290 const Matcher anything = const _IsAnything(); | 315 const Matcher anything = const _IsAnything(); |
| 291 | 316 |
| 292 class _IsAnything extends BaseMatcher { | 317 class _IsAnything extends BaseMatcher { |
| 293 const _IsAnything(); | 318 const _IsAnything(); |
| 294 bool matches(item, MatchState matchState) => true; | 319 bool matches(item, Map matchState) => true; |
| 295 Description describe(Description description) => | 320 Description describe(Description description) => |
| 296 description.add('anything'); | 321 description.add('anything'); |
| 297 } | 322 } |
| 298 | 323 |
| 299 /** | 324 /** |
| 300 * Returns a matcher that matches if an object is an instance | 325 * Returns a matcher that matches if an object is an instance |
| 301 * of [type] (or a subtype). | 326 * of [type] (or a subtype). |
| 302 * | 327 * |
| 303 * As types are not first class objects in Dart we can only | 328 * As types are not first class objects in Dart we can only |
| 304 * approximate this test by using a generic wrapper class. | 329 * approximate this test by using a generic wrapper class. |
| 305 * | 330 * |
| 306 * For example, to test whether 'bar' is an instance of type | 331 * For example, to test whether 'bar' is an instance of type |
| 307 * 'Foo', we would write: | 332 * 'Foo', we would write: |
| 308 * | 333 * |
| 309 * expect(bar, new isInstanceOf<Foo>()); | 334 * expect(bar, new isInstanceOf<Foo>()); |
| 310 * | 335 * |
| 311 * To get better error message, supply a name when creating the | 336 * To get better error message, supply a name when creating the |
| 312 * Type wrapper; e.g.: | 337 * Type wrapper; e.g.: |
| 313 * | 338 * |
| 314 * expect(bar, new isInstanceOf<Foo>('Foo')); | 339 * expect(bar, new isInstanceOf<Foo>('Foo')); |
| 315 * | 340 * |
| 316 * Note that this does not currently work in dart2js; it will | 341 * Note that this does not currently work in dart2js; it will |
| 317 * match any type, and isNot(new isInstanceof<T>()) will always | 342 * match any type, and isNot(new isInstanceof<T>()) will always |
| 318 * fail. This is because dart2js currently ignores template type | 343 * fail. This is because dart2js currently ignores template type |
| 319 * parameters. | 344 * parameters. |
| 320 */ | 345 */ |
| 321 class isInstanceOf<T> extends BaseMatcher { | 346 class isInstanceOf<T> extends BaseMatcher { |
| 322 final String _name; | 347 final String _name; |
| 323 const isInstanceOf([name = 'specified type']) : this._name = name; | 348 const isInstanceOf([name = 'specified type']) : this._name = name; |
| 324 bool matches(obj, MatchState matchState) => obj is T; | 349 bool matches(obj, Map matchState) => obj is T; |
| 325 // The description here is lame :-( | 350 // The description here is lame :-( |
| 326 Description describe(Description description) => | 351 Description describe(Description description) => |
| 327 description.add('an instance of ${_name}'); | 352 description.add('an instance of ${_name}'); |
| 328 } | 353 } |
| 329 | 354 |
| 330 /** | 355 /** |
| 331 * This can be used to match two kinds of objects: | 356 * This can be used to match two kinds of objects: |
| 332 * | 357 * |
| 333 * * A [Function] that throws an exception when called. The function cannot | 358 * * A [Function] that throws an exception when called. The function cannot |
| 334 * take any arguments. If you want to test that a function expecting | 359 * take any arguments. If you want to test that a function expecting |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 369 * a wrapper will have to be created. | 394 * a wrapper will have to be created. |
| 370 */ | 395 */ |
| 371 const Matcher returnsNormally = const _ReturnsNormally(); | 396 const Matcher returnsNormally = const _ReturnsNormally(); |
| 372 | 397 |
| 373 class Throws extends BaseMatcher { | 398 class Throws extends BaseMatcher { |
| 374 final Matcher _matcher; | 399 final Matcher _matcher; |
| 375 | 400 |
| 376 const Throws([Matcher matcher]) : | 401 const Throws([Matcher matcher]) : |
| 377 this._matcher = matcher; | 402 this._matcher = matcher; |
| 378 | 403 |
| 379 bool matches(item, MatchState matchState) { | 404 bool matches(item, Map matchState) { |
| 380 if (item is! Function && item is! Future) return false; | 405 if (item is! Function && item is! Future) return false; |
| 381 if (item is Future) { | 406 if (item is Future) { |
| 382 var done = wrapAsync((fn) => fn()); | 407 var done = wrapAsync((fn) => fn()); |
| 383 | 408 |
| 384 // Queue up an asynchronous expectation that validates when the future | 409 // Queue up an asynchronous expectation that validates when the future |
| 385 // completes. | 410 // completes. |
| 386 item.then((value) { | 411 item.then((value) { |
| 387 done(() => fail("Expected future to fail, but succeeded with '$value'.")
); | 412 done(() => fail("Expected future to fail, but succeeded with '$value'.")
); |
| 388 }, onError: (error) { | 413 }, onError: (error) { |
| 389 done(() { | 414 done(() { |
| (...skipping 12 matching lines...) Expand all Loading... |
| 402 return true; | 427 return true; |
| 403 } | 428 } |
| 404 | 429 |
| 405 try { | 430 try { |
| 406 item(); | 431 item(); |
| 407 return false; | 432 return false; |
| 408 } catch (e, s) { | 433 } catch (e, s) { |
| 409 if (_matcher == null ||_matcher.matches(e, matchState)) { | 434 if (_matcher == null ||_matcher.matches(e, matchState)) { |
| 410 return true; | 435 return true; |
| 411 } else { | 436 } else { |
| 412 matchState.state = { | 437 addStateInfo(matchState, {'exception': e, 'stack': s}); |
| 413 'exception' :e, | |
| 414 'stack': s | |
| 415 }; | |
| 416 return false; | 438 return false; |
| 417 } | 439 } |
| 418 } | 440 } |
| 419 } | 441 } |
| 420 | 442 |
| 421 Description describe(Description description) { | 443 Description describe(Description description) { |
| 422 if (_matcher == null) { | 444 if (_matcher == null) { |
| 423 return description.add("throws an exception"); | 445 return description.add("throws"); |
| 424 } else { | 446 } else { |
| 425 return description.add('throws an exception which matches '). | 447 return description.add('throws ').addDescriptionOf(_matcher); |
| 426 addDescriptionOf(_matcher); | |
| 427 } | 448 } |
| 428 } | 449 } |
| 429 | 450 |
| 430 Description describeMismatch(item, Description mismatchDescription, | 451 Description describeMismatch(item, Description mismatchDescription, |
| 431 MatchState matchState, | 452 Map matchState, |
| 432 bool verbose) { | 453 bool verbose) { |
| 433 if (item is! Function && item is! Future) { | 454 if (item is! Function && item is! Future) { |
| 434 return mismatchDescription.add(' not a Function or Future'); | 455 return mismatchDescription.add('is not a Function or Future'); |
| 435 } else if (_matcher == null || matchState.state == null) { | 456 } else if (_matcher == null || matchState['exception'] == null) { |
| 436 return mismatchDescription.add(' no exception'); | 457 return mismatchDescription.add('did not throw'); |
| 437 } else { | 458 } else { |
| 438 mismatchDescription. | 459 mismatchDescription. add('threw '). |
| 439 add(' exception ').addDescriptionOf(matchState.state['exception']); | 460 addDescriptionOf(matchState['exception']); |
| 440 if (verbose) { | 461 if (verbose) { |
| 441 mismatchDescription.add(' at '). | 462 mismatchDescription.add(' at ').add(matchState['stack'].toString()); |
| 442 add(matchState.state['stack'].toString()); | |
| 443 } | 463 } |
| 444 mismatchDescription.add(' does not match ').addDescriptionOf(_matcher); | 464 return mismatchDescription; |
| 445 return mismatchDescription; | |
| 446 } | 465 } |
| 447 } | 466 } |
| 448 } | 467 } |
| 449 | 468 |
| 450 class _ReturnsNormally extends BaseMatcher { | 469 class _ReturnsNormally extends BaseMatcher { |
| 451 const _ReturnsNormally(); | 470 const _ReturnsNormally(); |
| 452 | 471 |
| 453 bool matches(f, MatchState matchState) { | 472 bool matches(f, Map matchState) { |
| 454 try { | 473 try { |
| 455 f(); | 474 f(); |
| 456 return true; | 475 return true; |
| 457 } catch (e, s) { | 476 } catch (e, s) { |
| 458 matchState.state = { | 477 addStateInfo(matchState, {'exception': e, 'stack': s}); |
| 459 'exception' : e, | |
| 460 'stack': s | |
| 461 }; | |
| 462 return false; | 478 return false; |
| 463 } | 479 } |
| 464 } | 480 } |
| 465 | 481 |
| 466 Description describe(Description description) => | 482 Description describe(Description description) => |
| 467 description.add("return normally"); | 483 description.add("return normally"); |
| 468 | 484 |
| 469 Description describeMismatch(item, Description mismatchDescription, | 485 Description describeMismatch(item, Description mismatchDescription, |
| 470 MatchState matchState, | 486 Map matchState, |
| 471 bool verbose) { | 487 bool verbose) { |
| 472 mismatchDescription.add(' threw '). | 488 mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']); |
| 473 addDescriptionOf(matchState.state['exception']); | 489 if (verbose) { |
| 474 if (verbose) { | 490 mismatchDescription.add(' at ').add(matchState['stack'].toString()); |
| 475 mismatchDescription.add(' at '). | 491 } |
| 476 add(matchState.state['stack'].toString()); | 492 return mismatchDescription; |
| 477 } | |
| 478 return mismatchDescription; | |
| 479 } | 493 } |
| 480 } | 494 } |
| 481 | 495 |
| 482 /* | 496 /* |
| 483 * Matchers for different exception types. Ideally we should just be able to | 497 * Matchers for different exception types. Ideally we should just be able to |
| 484 * use something like: | 498 * use something like: |
| 485 * | 499 * |
| 486 * final Matcher throwsException = | 500 * final Matcher throwsException = |
| 487 * const _Throws(const isInstanceOf<Exception>()); | 501 * const _Throws(const isInstanceOf<Exception>()); |
| 488 * | 502 * |
| (...skipping 20 matching lines...) Expand all Loading... |
| 509 | 523 |
| 510 /** A matcher for FormatExceptions. */ | 524 /** A matcher for FormatExceptions. */ |
| 511 const isFormatException = const _FormatException(); | 525 const isFormatException = const _FormatException(); |
| 512 | 526 |
| 513 /** A matcher for functions that throw FormatException. */ | 527 /** A matcher for functions that throw FormatException. */ |
| 514 const Matcher throwsFormatException = | 528 const Matcher throwsFormatException = |
| 515 const Throws(isFormatException); | 529 const Throws(isFormatException); |
| 516 | 530 |
| 517 class _FormatException extends TypeMatcher { | 531 class _FormatException extends TypeMatcher { |
| 518 const _FormatException() : super("FormatException"); | 532 const _FormatException() : super("FormatException"); |
| 519 bool matches(item, MatchState matchState) => item is FormatException; | 533 bool matches(item, Map matchState) => item is FormatException; |
| 520 } | 534 } |
| 521 | 535 |
| 522 /** A matcher for Exceptions. */ | 536 /** A matcher for Exceptions. */ |
| 523 const isException = const _Exception(); | 537 const isException = const _Exception(); |
| 524 | 538 |
| 525 /** A matcher for functions that throw Exception. */ | 539 /** A matcher for functions that throw Exception. */ |
| 526 const Matcher throwsException = const Throws(isException); | 540 const Matcher throwsException = const Throws(isException); |
| 527 | 541 |
| 528 class _Exception extends TypeMatcher { | 542 class _Exception extends TypeMatcher { |
| 529 const _Exception() : super("Exception"); | 543 const _Exception() : super("Exception"); |
| 530 bool matches(item, MatchState matchState) => item is Exception; | 544 bool matches(item, Map matchState) => item is Exception; |
| 531 } | 545 } |
| 532 | 546 |
| 533 /** A matcher for ArgumentErrors. */ | 547 /** A matcher for ArgumentErrors. */ |
| 534 const isArgumentError = const _ArgumentError(); | 548 const isArgumentError = const _ArgumentError(); |
| 535 | 549 |
| 536 /** A matcher for functions that throw ArgumentError. */ | 550 /** A matcher for functions that throw ArgumentError. */ |
| 537 const Matcher throwsArgumentError = | 551 const Matcher throwsArgumentError = |
| 538 const Throws(isArgumentError); | 552 const Throws(isArgumentError); |
| 539 | 553 |
| 540 class _ArgumentError extends TypeMatcher { | 554 class _ArgumentError extends TypeMatcher { |
| 541 const _ArgumentError() : super("ArgumentError"); | 555 const _ArgumentError() : super("ArgumentError"); |
| 542 bool matches(item, MatchState matchState) => item is ArgumentError; | 556 bool matches(item, Map matchState) => item is ArgumentError; |
| 543 } | 557 } |
| 544 | 558 |
| 545 /** A matcher for RangeErrors. */ | 559 /** A matcher for RangeErrors. */ |
| 546 const isRangeError = const _RangeError(); | 560 const isRangeError = const _RangeError(); |
| 547 | 561 |
| 548 /** A matcher for functions that throw RangeError. */ | 562 /** A matcher for functions that throw RangeError. */ |
| 549 const Matcher throwsRangeError = | 563 const Matcher throwsRangeError = |
| 550 const Throws(isRangeError); | 564 const Throws(isRangeError); |
| 551 | 565 |
| 552 class _RangeError extends TypeMatcher { | 566 class _RangeError extends TypeMatcher { |
| 553 const _RangeError() : super("RangeError"); | 567 const _RangeError() : super("RangeError"); |
| 554 bool matches(item, MatchState matchState) => item is RangeError; | 568 bool matches(item, Map matchState) => item is RangeError; |
| 555 } | 569 } |
| 556 | 570 |
| 557 /** A matcher for NoSuchMethodErrors. */ | 571 /** A matcher for NoSuchMethodErrors. */ |
| 558 const isNoSuchMethodError = const _NoSuchMethodError(); | 572 const isNoSuchMethodError = const _NoSuchMethodError(); |
| 559 | 573 |
| 560 /** A matcher for functions that throw NoSuchMethodError. */ | 574 /** A matcher for functions that throw NoSuchMethodError. */ |
| 561 const Matcher throwsNoSuchMethodError = | 575 const Matcher throwsNoSuchMethodError = |
| 562 const Throws(isNoSuchMethodError); | 576 const Throws(isNoSuchMethodError); |
| 563 | 577 |
| 564 class _NoSuchMethodError extends TypeMatcher { | 578 class _NoSuchMethodError extends TypeMatcher { |
| 565 const _NoSuchMethodError() : super("NoSuchMethodError"); | 579 const _NoSuchMethodError() : super("NoSuchMethodError"); |
| 566 bool matches(item, MatchState matchState) => item is NoSuchMethodError; | 580 bool matches(item, Map matchState) => item is NoSuchMethodError; |
| 567 } | 581 } |
| 568 | 582 |
| 569 /** A matcher for UnimplementedErrors. */ | 583 /** A matcher for UnimplementedErrors. */ |
| 570 const isUnimplementedError = const _UnimplementedError(); | 584 const isUnimplementedError = const _UnimplementedError(); |
| 571 | 585 |
| 572 /** A matcher for functions that throw Exception. */ | 586 /** A matcher for functions that throw Exception. */ |
| 573 const Matcher throwsUnimplementedError = | 587 const Matcher throwsUnimplementedError = |
| 574 const Throws(isUnimplementedError); | 588 const Throws(isUnimplementedError); |
| 575 | 589 |
| 576 class _UnimplementedError extends TypeMatcher { | 590 class _UnimplementedError extends TypeMatcher { |
| 577 const _UnimplementedError() : super("UnimplementedError"); | 591 const _UnimplementedError() : super("UnimplementedError"); |
| 578 bool matches(item, MatchState matchState) => item is UnimplementedError; | 592 bool matches(item, Map matchState) => item is UnimplementedError; |
| 579 } | 593 } |
| 580 | 594 |
| 581 /** A matcher for UnsupportedError. */ | 595 /** A matcher for UnsupportedError. */ |
| 582 const isUnsupportedError = const _UnsupportedError(); | 596 const isUnsupportedError = const _UnsupportedError(); |
| 583 | 597 |
| 584 /** A matcher for functions that throw UnsupportedError. */ | 598 /** A matcher for functions that throw UnsupportedError. */ |
| 585 const Matcher throwsUnsupportedError = const Throws(isUnsupportedError); | 599 const Matcher throwsUnsupportedError = const Throws(isUnsupportedError); |
| 586 | 600 |
| 587 class _UnsupportedError extends TypeMatcher { | 601 class _UnsupportedError extends TypeMatcher { |
| 588 const _UnsupportedError() : | 602 const _UnsupportedError() : |
| 589 super("UnsupportedError"); | 603 super("UnsupportedError"); |
| 590 bool matches(item, MatchState matchState) => item is UnsupportedError; | 604 bool matches(item, Map matchState) => item is UnsupportedError; |
| 591 } | 605 } |
| 592 | 606 |
| 593 /** A matcher for StateErrors. */ | 607 /** A matcher for StateErrors. */ |
| 594 const isStateError = const _StateError(); | 608 const isStateError = const _StateError(); |
| 595 | 609 |
| 596 /** A matcher for functions that throw StateError. */ | 610 /** A matcher for functions that throw StateError. */ |
| 597 const Matcher throwsStateError = | 611 const Matcher throwsStateError = |
| 598 const Throws(isStateError); | 612 const Throws(isStateError); |
| 599 | 613 |
| 600 class _StateError extends TypeMatcher { | 614 class _StateError extends TypeMatcher { |
| 601 const _StateError() : super("StateError"); | 615 const _StateError() : super("StateError"); |
| 602 bool matches(item, MatchState matchState) => item is StateError; | 616 bool matches(item, Map matchState) => item is StateError; |
| 603 } | 617 } |
| 604 | 618 |
| 605 | 619 |
| 606 /** A matcher for Map types. */ | 620 /** A matcher for Map types. */ |
| 607 const isMap = const _IsMap(); | 621 const isMap = const _IsMap(); |
| 608 | 622 |
| 609 class _IsMap extends TypeMatcher { | 623 class _IsMap extends TypeMatcher { |
| 610 const _IsMap() : super("Map"); | 624 const _IsMap() : super("Map"); |
| 611 bool matches(item, MatchState matchState) => item is Map; | 625 bool matches(item, Map matchState) => item is Map; |
| 612 } | 626 } |
| 613 | 627 |
| 614 /** A matcher for List types. */ | 628 /** A matcher for List types. */ |
| 615 const isList = const _IsList(); | 629 const isList = const _IsList(); |
| 616 | 630 |
| 617 class _IsList extends TypeMatcher { | 631 class _IsList extends TypeMatcher { |
| 618 const _IsList() : super("List"); | 632 const _IsList() : super("List"); |
| 619 bool matches(item, MatchState matchState) => item is List; | 633 bool matches(item, Map matchState) => item is List; |
| 620 } | 634 } |
| 621 | 635 |
| 622 /** | 636 /** |
| 623 * Returns a matcher that matches if an object has a length property | 637 * Returns a matcher that matches if an object has a length property |
| 624 * that matches [matcher]. | 638 * that matches [matcher]. |
| 625 */ | 639 */ |
| 626 Matcher hasLength(matcher) => | 640 Matcher hasLength(matcher) => |
| 627 new _HasLength(wrapMatcher(matcher)); | 641 new _HasLength(wrapMatcher(matcher)); |
| 628 | 642 |
| 629 class _HasLength extends BaseMatcher { | 643 class _HasLength extends BaseMatcher { |
| 630 final Matcher _matcher; | 644 final Matcher _matcher; |
| 631 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; | 645 const _HasLength([Matcher matcher = null]) : this._matcher = matcher; |
| 632 | 646 |
| 633 bool matches(item, MatchState matchState) { | 647 bool matches(item, Map matchState) { |
| 634 return _matcher.matches(item.length, matchState); | 648 try { |
| 649 // This is harmless code that will throw if no length property |
| 650 // but subtle enough that an optimizer shouldn't strip it out. |
| 651 if (item.length * item.length >= 0) { |
| 652 return _matcher.matches(item.length, matchState); |
| 653 } |
| 654 } catch (e) { |
| 655 return false; |
| 656 } |
| 635 } | 657 } |
| 636 | 658 |
| 637 Description describe(Description description) => | 659 Description describe(Description description) => |
| 638 description.add('an object with length of '). | 660 description.add('an object with length of '). |
| 639 addDescriptionOf(_matcher); | 661 addDescriptionOf(_matcher); |
| 640 | 662 |
| 641 Description describeMismatch(item, Description mismatchDescription, | 663 Description describeMismatch(item, Description mismatchDescription, |
| 642 MatchState matchState, bool verbose) { | 664 Map matchState, bool verbose) { |
| 643 try { | 665 try { |
| 644 // We want to generate a different description if there is no length | 666 // We want to generate a different description if there is no length |
| 645 // property. This is harmless code that will throw if no length property | 667 // property; we use the same trick as in matches(). |
| 646 // but subtle enough that an optimizer shouldn't strip it out. | |
| 647 if (item.length * item.length >= 0) { | 668 if (item.length * item.length >= 0) { |
| 648 return mismatchDescription.add('had length of '). | 669 return mismatchDescription.add('has length of '). |
| 649 addDescriptionOf(item.length); | 670 addDescriptionOf(item.length); |
| 650 } | 671 } |
| 651 } catch (e) { | 672 } catch (e) { |
| 652 return mismatchDescription.add('had no length property'); | 673 return mismatchDescription.add('has no length property'); |
| 653 } | 674 } |
| 654 } | 675 } |
| 655 } | 676 } |
| 656 | 677 |
| 657 /** | 678 /** |
| 658 * Returns a matcher that matches if the match argument contains | 679 * Returns a matcher that matches if the match argument contains |
| 659 * the expected value. For [String]s this means substring matching; | 680 * the expected value. For [String]s this means substring matching; |
| 660 * for [Map]s it means the map has the key, and for [Iterable]s | 681 * for [Map]s it means the map has the key, and for [Iterable]s |
| 661 * (including [Iterable]s) it means the iterable has a matching | 682 * (including [Iterable]s) it means the iterable has a matching |
| 662 * element. In the case of iterables, [expected] can itself be a | 683 * element. In the case of iterables, [expected] can itself be a |
| 663 * matcher. | 684 * matcher. |
| 664 */ | 685 */ |
| 665 Matcher contains(expected) => new _Contains(expected); | 686 Matcher contains(expected) => new _Contains(expected); |
| 666 | 687 |
| 667 class _Contains extends BaseMatcher { | 688 class _Contains extends BaseMatcher { |
| 668 | 689 |
| 669 final _expected; | 690 final _expected; |
| 670 | 691 |
| 671 const _Contains(this._expected); | 692 const _Contains(this._expected); |
| 672 | 693 |
| 673 bool matches(item, MatchState matchState) { | 694 bool matches(item, Map matchState) { |
| 674 if (item is String) { | 695 if (item is String) { |
| 675 return item.indexOf(_expected) >= 0; | 696 return item.indexOf(_expected) >= 0; |
| 676 } else if (item is Iterable) { | 697 } else if (item is Iterable) { |
| 677 if (_expected is Matcher) { | 698 if (_expected is Matcher) { |
| 678 return item.any((e) => _expected.matches(e, matchState)); | 699 return item.any((e) => _expected.matches(e, matchState)); |
| 679 } else { | 700 } else { |
| 680 return item.contains(_expected); | 701 return item.contains(_expected); |
| 681 } | 702 } |
| 682 } else if (item is Map) { | 703 } else if (item is Map) { |
| 683 return item.containsKey(_expected); | 704 return item.containsKey(_expected); |
| 684 } | 705 } |
| 685 return false; | 706 return false; |
| 686 } | 707 } |
| 687 | 708 |
| 688 Description describe(Description description) => | 709 Description describe(Description description) => |
| 689 description.add('contains ').addDescriptionOf(_expected); | 710 description.add('contains ').addDescriptionOf(_expected); |
| 711 |
| 712 Description describeMismatch(item, Description mismatchDescription, |
| 713 Map matchState, bool verbose) { |
| 714 if (item is String || item is Iterable || item is Map) { |
| 715 return super.describeMismatch(item, mismatchDescription, matchState, |
| 716 verbose); |
| 717 } else { |
| 718 return mismatchDescription.add('is not a string, map or iterable'); |
| 719 } |
| 720 } |
| 690 } | 721 } |
| 691 | 722 |
| 692 /** | 723 /** |
| 693 * Returns a matcher that matches if the match argument is in | 724 * Returns a matcher that matches if the match argument is in |
| 694 * the expected value. This is the converse of [contains]. | 725 * the expected value. This is the converse of [contains]. |
| 695 */ | 726 */ |
| 696 Matcher isIn(expected) => new _In(expected); | 727 Matcher isIn(expected) => new _In(expected); |
| 697 | 728 |
| 698 class _In extends BaseMatcher { | 729 class _In extends BaseMatcher { |
| 699 | 730 |
| 700 final _expected; | 731 final _expected; |
| 701 | 732 |
| 702 const _In(this._expected); | 733 const _In(this._expected); |
| 703 | 734 |
| 704 bool matches(item, MatchState matchState) { | 735 bool matches(item, Map matchState) { |
| 705 if (_expected is String) { | 736 if (_expected is String) { |
| 706 return _expected.indexOf(item) >= 0; | 737 return _expected.indexOf(item) >= 0; |
| 707 } else if (_expected is Iterable) { | 738 } else if (_expected is Iterable) { |
| 708 return _expected.any((e) => e == item); | 739 return _expected.any((e) => e == item); |
| 709 } else if (_expected is Map) { | 740 } else if (_expected is Map) { |
| 710 return _expected.containsKey(item); | 741 return _expected.containsKey(item); |
| 711 } | 742 } |
| 712 return false; | 743 return false; |
| 713 } | 744 } |
| 714 | 745 |
| (...skipping 10 matching lines...) Expand all Loading... |
| 725 Matcher predicate(Function f, [description ='satisfies function']) => | 756 Matcher predicate(Function f, [description ='satisfies function']) => |
| 726 new _Predicate(f, description); | 757 new _Predicate(f, description); |
| 727 | 758 |
| 728 class _Predicate extends BaseMatcher { | 759 class _Predicate extends BaseMatcher { |
| 729 | 760 |
| 730 final Function _matcher; | 761 final Function _matcher; |
| 731 final String _description; | 762 final String _description; |
| 732 | 763 |
| 733 const _Predicate(this._matcher, this._description); | 764 const _Predicate(this._matcher, this._description); |
| 734 | 765 |
| 735 bool matches(item, MatchState matchState) => _matcher(item); | 766 bool matches(item, Map matchState) => _matcher(item); |
| 736 | 767 |
| 737 Description describe(Description description) => | 768 Description describe(Description description) => |
| 738 description.add(_description); | 769 description.add(_description); |
| 739 } | 770 } |
| 740 | 771 |
| 741 /** | 772 /** |
| 742 * A useful utility class for implementing other matchers through inheritance. | 773 * A useful utility class for implementing other matchers through inheritance. |
| 743 * Derived classes should call the base constructor with a feature name and | 774 * Derived classes should call the base constructor with a feature name and |
| 744 * description, and an instance matcher, and should implement the | 775 * description, and an instance matcher, and should implement the |
| 745 * [featureValueOf] abstract method. | 776 * [featureValueOf] abstract method. |
| (...skipping 17 matching lines...) Expand all Loading... |
| 763 final String _featureDescription; | 794 final String _featureDescription; |
| 764 final String _featureName; | 795 final String _featureName; |
| 765 final Matcher _matcher; | 796 final Matcher _matcher; |
| 766 | 797 |
| 767 CustomMatcher(this._featureDescription, this._featureName, matcher) | 798 CustomMatcher(this._featureDescription, this._featureName, matcher) |
| 768 : this._matcher = wrapMatcher(matcher); | 799 : this._matcher = wrapMatcher(matcher); |
| 769 | 800 |
| 770 /** Override this to extract the interesting feature.*/ | 801 /** Override this to extract the interesting feature.*/ |
| 771 featureValueOf(actual) => actual; | 802 featureValueOf(actual) => actual; |
| 772 | 803 |
| 773 bool matches(item, MatchState matchState) { | 804 bool matches(item, Map matchState) { |
| 774 var f = featureValueOf(item); | 805 var f = featureValueOf(item); |
| 775 if (_matcher.matches(f, matchState)) return true; | 806 if (_matcher.matches(f, matchState)) return true; |
| 776 matchState.state = { 'innerState': matchState.state, 'feature': f }; | 807 addStateInfo(matchState, {'feature': f}); |
| 777 return false; | 808 return false; |
| 778 } | 809 } |
| 779 | 810 |
| 780 Description describe(Description description) => | 811 Description describe(Description description) => |
| 781 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); | 812 description.add(_featureDescription).add(' ').addDescriptionOf(_matcher); |
| 782 | 813 |
| 783 Description describeMismatch(item, Description mismatchDescription, | 814 Description describeMismatch(item, Description mismatchDescription, |
| 784 MatchState matchState, bool verbose) { | 815 Map matchState, bool verbose) { |
| 785 mismatchDescription.add(_featureName).add(' '); | 816 mismatchDescription.add('has ').add(_featureName).add(' with value '). |
| 786 _matcher.describeMismatch(matchState.state['feature'], mismatchDescription, | 817 addDescriptionOf(matchState['feature']); |
| 787 matchState.state['innerState'], verbose); | 818 var innerDescription = new StringDescription(); |
| 819 _matcher.describeMismatch(matchState['feature'], innerDescription, |
| 820 matchState['state'], verbose); |
| 821 if (innerDescription.length > 0) { |
| 822 mismatchDescription.add(' which ').add(innerDescription.toString()); |
| 823 } |
| 788 return mismatchDescription; | 824 return mismatchDescription; |
| 789 } | 825 } |
| 790 } | 826 } |
| 827 |
| OLD | NEW |