Skip to content
This repository was archived by the owner on Oct 22, 2024. It is now read-only.

Commit a6a37a8

Browse files
committed
fix: Improve the output of many matchers that expect specific types
- Add a new TypedMatcher class to generalize `matches` type checking - Use TypeMatcher across many of the existing Matcher implementations - Update tests validate new, more consistent failure messages - Add a few new tests for `isIn`
1 parent 7138f54 commit a6a37a8

13 files changed

+261
-274
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.12.3
2+
3+
- Improved the output of `Matcher` instances that fail due to type errors.
4+
15
## 0.12.2+1
26

37
- Updated SDK version to 2.0.0-dev.17.0

lib/src/core_matchers.dart

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'interfaces.dart';
6+
import 'typed_matcher.dart';
67
import 'util.dart';
78

89
/// Returns a matcher that matches the isEmpty property.
@@ -69,15 +70,17 @@ const Matcher isNaN = const _IsNaN();
6970
/// A matcher that matches any non-NaN value.
7071
const Matcher isNotNaN = const _IsNotNaN();
7172

72-
class _IsNaN extends Matcher {
73+
class _IsNaN extends TypedMatcher<num> {
7374
const _IsNaN();
74-
bool matches(item, Map matchState) => double.nan.compareTo(item) == 0;
75+
bool typedMatches(num item, Map matchState) =>
76+
double.nan.compareTo(item) == 0;
7577
Description describe(Description description) => description.add('NaN');
7678
}
7779

78-
class _IsNotNaN extends Matcher {
80+
class _IsNotNaN extends TypedMatcher<num> {
7981
const _IsNotNaN();
80-
bool matches(item, Map matchState) => double.nan.compareTo(item) != 0;
82+
bool typedMatches(num item, Map matchState) =>
83+
double.nan.compareTo(item) != 0;
8184
Description describe(Description description) => description.add('not NaN');
8285
}
8386

@@ -131,10 +134,10 @@ class isInstanceOf<T> extends Matcher {
131134
/// a wrapper will have to be created.
132135
const Matcher returnsNormally = const _ReturnsNormally();
133136

134-
class _ReturnsNormally extends Matcher {
137+
class _ReturnsNormally extends TypedMatcher<Function> {
135138
const _ReturnsNormally();
136139

137-
bool matches(f, Map matchState) {
140+
bool typedMatches(Function f, Map matchState) {
138141
try {
139142
f();
140143
return true;
@@ -147,8 +150,8 @@ class _ReturnsNormally extends Matcher {
147150
Description describe(Description description) =>
148151
description.add("return normally");
149152

150-
Description describeMismatch(
151-
item, Description mismatchDescription, Map matchState, bool verbose) {
153+
Description describeTypedMismatch(Function item,
154+
Description mismatchDescription, Map matchState, bool verbose) {
152155
mismatchDescription.add('threw ').addDescriptionOf(matchState['exception']);
153156
if (verbose) {
154157
mismatchDescription.add(' at ').add(matchState['stack'].toString());
@@ -256,11 +259,12 @@ class _Contains extends Matcher {
256259
const _Contains(this._expected);
257260

258261
bool matches(item, Map matchState) {
262+
var expected = _expected;
259263
if (item is String) {
260-
return item.contains((_expected as Pattern));
264+
return expected is Pattern && item.contains(expected);
261265
} else if (item is Iterable) {
262-
if (_expected is Matcher) {
263-
return item.any((e) => (_expected as Matcher).matches(e, matchState));
266+
if (expected is Matcher) {
267+
return item.any((e) => expected.matches(e, matchState));
264268
} else {
265269
return item.contains(_expected);
266270
}
@@ -286,27 +290,29 @@ class _Contains extends Matcher {
286290

287291
/// Returns a matcher that matches if the match argument is in
288292
/// the expected value. This is the converse of [contains].
289-
Matcher isIn(expected) => new _In(expected);
293+
Matcher isIn(expected) {
294+
if (expected is Iterable) {
295+
return new _In(expected, expected.contains);
296+
} else if (expected is String) {
297+
return new _In<Pattern>(expected, expected.contains);
298+
} else if (expected is Map) {
299+
return new _In(expected, expected.containsKey);
300+
}
290301

291-
class _In extends Matcher {
292-
final Object _expected;
302+
throw new ArgumentError.value(
303+
expected, 'expected', 'Only Iterable, Map, and String are supported.');
304+
}
293305

294-
const _In(this._expected);
306+
class _In<T> extends TypedMatcher<T> {
307+
final Object _source;
308+
final bool Function(T) _containsFunction;
295309

296-
bool matches(item, Map matchState) {
297-
var expected = _expected;
298-
if (expected is String) {
299-
return expected.contains(item as Pattern);
300-
} else if (expected is Iterable) {
301-
return expected.contains(item);
302-
} else if (expected is Map) {
303-
return expected.containsKey(item);
304-
}
305-
return false;
306-
}
310+
const _In(this._source, this._containsFunction);
311+
312+
bool typedMatches(T item, Map matchState) => _containsFunction(item);
307313

308314
Description describe(Description description) =>
309-
description.add('is in ').addDescriptionOf(_expected);
315+
description.add('is in ').addDescriptionOf(_source);
310316
}
311317

312318
/// Returns a matcher that uses an arbitrary function that returns
@@ -321,13 +327,13 @@ Matcher predicate<T>(bool f(T value),
321327

322328
typedef bool _PredicateFunction<T>(T value);
323329

324-
class _Predicate<T> extends Matcher {
330+
class _Predicate<T> extends TypedMatcher<T> {
325331
final _PredicateFunction<T> _matcher;
326332
final String _description;
327333

328334
_Predicate(this._matcher, this._description);
329335

330-
bool matches(item, Map matchState) => _matcher(item as T);
336+
bool typedMatches(T item, Map matchState) => _matcher(item);
331337

332338
Description describe(Description description) =>
333339
description.add(_description);

lib/src/equals_matcher.dart

Lines changed: 38 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'description.dart';
66
import 'interfaces.dart';
7+
import 'typed_matcher.dart';
78
import 'util.dart';
89

910
/// Returns a matcher that matches if the value is structurally equal to
@@ -16,67 +17,61 @@ import 'util.dart';
1617
/// handle cyclic structures a recursion depth [limit] can be provided. The
1718
/// default limit is 100. [Set]s will be compared order-independently.
1819
Matcher equals(expected, [int limit = 100]) => expected is String
19-
? new _StringEqualsMatcher(expected) as Matcher
20+
? new _StringEqualsMatcher(expected)
2021
: new _DeepMatcher(expected, limit);
2122

2223
typedef _RecursiveMatcher = List<String> Function(
2324
dynamic, dynamic, String, int);
2425

2526
/// A special equality matcher for strings.
26-
class _StringEqualsMatcher extends Matcher {
27+
class _StringEqualsMatcher extends TypedMatcher<String> {
2728
final String _value;
2829

2930
_StringEqualsMatcher(this._value);
3031

31-
bool get showActualValue => true;
32-
33-
bool matches(item, Map matchState) => _value == item;
32+
bool typedMatches(String item, Map matchState) => _value == item;
3433

3534
Description describe(Description description) =>
3635
description.addDescriptionOf(_value);
3736

38-
Description describeMismatch(
39-
item, Description mismatchDescription, Map matchState, bool verbose) {
40-
if (item is! String) {
41-
return mismatchDescription.addDescriptionOf(item).add('is not a string');
42-
} else {
43-
var buff = new StringBuffer();
44-
buff.write('is different.');
45-
var escapedItem = escape(item);
46-
var escapedValue = escape(_value);
47-
var minLength = escapedItem.length < escapedValue.length
48-
? escapedItem.length
49-
: escapedValue.length;
50-
var start = 0;
51-
for (; start < minLength; start++) {
52-
if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
53-
break;
54-
}
37+
Description describeTypedMismatch(String item,
38+
Description mismatchDescription, Map matchState, bool verbose) {
39+
var buff = new StringBuffer();
40+
buff.write('is different.');
41+
var escapedItem = escape(item);
42+
var escapedValue = escape(_value);
43+
var minLength = escapedItem.length < escapedValue.length
44+
? escapedItem.length
45+
: escapedValue.length;
46+
var start = 0;
47+
for (; start < minLength; start++) {
48+
if (escapedValue.codeUnitAt(start) != escapedItem.codeUnitAt(start)) {
49+
break;
5550
}
56-
if (start == minLength) {
57-
if (escapedValue.length < escapedItem.length) {
58-
buff.write(' Both strings start the same, but the actual value also'
59-
' has the following trailing characters: ');
60-
_writeTrailing(buff, escapedItem, escapedValue.length);
61-
} else {
62-
buff.write(' Both strings start the same, but the actual value is'
63-
' missing the following trailing characters: ');
64-
_writeTrailing(buff, escapedValue, escapedItem.length);
65-
}
51+
}
52+
if (start == minLength) {
53+
if (escapedValue.length < escapedItem.length) {
54+
buff.write(' Both strings start the same, but the actual value also'
55+
' has the following trailing characters: ');
56+
_writeTrailing(buff, escapedItem, escapedValue.length);
6657
} else {
67-
buff.write('\nExpected: ');
68-
_writeLeading(buff, escapedValue, start);
69-
_writeTrailing(buff, escapedValue, start);
70-
buff.write('\n Actual: ');
71-
_writeLeading(buff, escapedItem, start);
72-
_writeTrailing(buff, escapedItem, start);
73-
buff.write('\n ');
74-
for (var i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
75-
buff.write('^\n Differ at offset $start');
58+
buff.write(' Both strings start the same, but the actual value is'
59+
' missing the following trailing characters: ');
60+
_writeTrailing(buff, escapedValue, escapedItem.length);
7661
}
77-
78-
return mismatchDescription.add(buff.toString());
62+
} else {
63+
buff.write('\nExpected: ');
64+
_writeLeading(buff, escapedValue, start);
65+
_writeTrailing(buff, escapedValue, start);
66+
buff.write('\n Actual: ');
67+
_writeLeading(buff, escapedItem, start);
68+
_writeTrailing(buff, escapedItem, start);
69+
buff.write('\n ');
70+
for (var i = (start > 10 ? 14 : start); i > 0; i--) buff.write(' ');
71+
buff.write('^\n Differ at offset $start');
7972
}
73+
74+
return mismatchDescription.add(buff.toString());
8075
}
8176

8277
static void _writeLeading(StringBuffer buff, String s, int start) {

0 commit comments

Comments
 (0)