Handle objects with empty toString (#2662)
The approach to building text descriptions involves appending and
prepending text to string iterables. If the "base" of the string
description is an empty iterable it propagates through as empty, which
can cause missing lines from the failure output.
Add a fallback of `'empty toString()'` in the case any object returns a
completely empty string representation.
diff --git a/pkgs/checks/CHANGELOG.md b/pkgs/checks/CHANGELOG.md
index 9626e53..dce83f6 100644
--- a/pkgs/checks/CHANGELOG.md
+++ b/pkgs/checks/CHANGELOG.md
@@ -8,6 +8,8 @@
- Fix a bug when using asynchronous conditions with `mayEmit` or
`mayEmitMultiple`. Note that extensions using `nestAsync` should synchronously
forward exceptions from that call.
+- Fix a bug when printing a failure message involving a value that returns an
+ empty output from `toString()`.
## 0.3.1
diff --git a/pkgs/checks/lib/src/describe.dart b/pkgs/checks/lib/src/describe.dart
index f5cf7a5..6ee4560 100644
--- a/pkgs/checks/lib/src/describe.dart
+++ b/pkgs/checks/lib/src/describe.dart
@@ -14,7 +14,11 @@
///
/// [Iterable]s and [Map]s will only print their first [_maxItems] elements or
/// key/value pairs, respectively.
-Iterable<String> literal(Object? object) => _prettyPrint(object, 0, {}, true);
+Iterable<String> literal(Object? object) {
+ final result = _prettyPrint(object, 0, {}, true);
+ assert(result.isNotEmpty);
+ return result;
+}
const _maxLineLength = 80;
const _maxItems = 25;
@@ -76,7 +80,10 @@
} else if (object is Condition<Never>) {
return ['<A value that:', ...postfixLast('>', describe(object))];
} else {
- final value = LineSplitter.split(object.toString());
+ final value = switch (object.toString()) {
+ '' => const ['empty toString()'],
+ final s => LineSplitter.split(s),
+ };
return isTopLevel ? prefixFirst('<', postfixLast('>', value)) : value;
}
}
diff --git a/pkgs/checks/test/failure_message_test.dart b/pkgs/checks/test/failure_message_test.dart
index 1c64eee..fb4787a 100644
--- a/pkgs/checks/test/failure_message_test.dart
+++ b/pkgs/checks/test/failure_message_test.dart
@@ -66,6 +66,16 @@
check<int?>(because: 'Some reason', 1).isNotNull().isGreaterThan(2);
}).throwsFailure().endsWith('Reason: Some reason');
});
+
+ test('handles objects with empty toString', () {
+ check(() {
+ check(EmptyToString()).equals(EmptyToString());
+ }).throwsFailure().equals('''
+Expected: a EmptyToString that:
+ equals <empty toString()>
+Actual: <empty toString()>
+Which: are not equal''');
+ });
});
}
@@ -73,3 +83,8 @@
Subject<String> throwsFailure() =>
throws<TestFailure>().has((f) => f.message, 'message').isNotNull();
}
+
+class EmptyToString {
+ @override
+ String toString() => '';
+}