Commit 0962dd6a authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

More better toStrings and more vigorous smoke testing of gallery (#8237)

* More better toStrings and more vigorous smoke testing of gallery

* Update scroll_controller.dart

* Update sliver.dart
parent 9610ff6b
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:collection' show LinkedHashSet; import 'dart:collection' show LinkedHashSet;
import 'dart:math' as math;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -25,6 +26,36 @@ Finder findGalleryItemByRouteName(WidgetTester tester, String routeName) { ...@@ -25,6 +26,36 @@ Finder findGalleryItemByRouteName(WidgetTester tester, String routeName) {
}); });
} }
int errors = 0;
void reportToStringError(String name, String route, int lineNumber, List<String> lines, String message) {
// If you're on line 12, then it has index 11.
// If you want 1 line before and 1 line after, then you want lines with index 10, 11, and 12.
// That's (lineNumber-1)-margin .. (lineNumber-1)+margin, or lineNumber-(margin+1) .. lineNumber+(margin-1)
int margin = 5;
int firstLine = math.max(0, lineNumber - margin);
int lastLine = math.min(lines.length, lineNumber + margin);
print('$name : $route : line $lineNumber of ${lines.length} : $message; nearby lines were:\n ${lines.sublist(firstLine, lastLine).join("\n ")}');
errors += 1;
}
void verifyToStringOutput(String name, String route, String testString) {
int lineNumber = 0;
List<String> lines = testString.split('\n');
if (!testString.endsWith('\n'))
reportToStringError(name, route, lines.length, lines, 'does not end with a line feed');
for (String line in lines) {
lineNumber += 1;
if (line == '' && lineNumber != lines.length) {
reportToStringError(name, route, lineNumber, lines, 'found empty line');
} else if (line.contains('Instance of ')) {
reportToStringError(name, route, lineNumber, lines, 'found a class that does not have its own toString');
} else if (line.endsWith(' ')) {
reportToStringError(name, route, lineNumber, lines, 'found a line with trailing whitespace');
}
}
}
// Start a gallery demo and then go back. This function assumes that the // Start a gallery demo and then go back. This function assumes that the
// we're starting on the home route and that the submenu that contains // we're starting on the home route and that the submenu that contains
// the item for a demo that pushes route 'routeName' is already open. // the item for a demo that pushes route 'routeName' is already open.
...@@ -33,12 +64,43 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async { ...@@ -33,12 +64,43 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
final Finder menuItem = findGalleryItemByRouteName(tester, routeName); final Finder menuItem = findGalleryItemByRouteName(tester, routeName);
expect(menuItem, findsOneWidget); expect(menuItem, findsOneWidget);
// Don't use pumpUntilNoTransientCallbacks in this function, because some of
// the smoketests have infinitely-running animations (e.g. the progress
// indicators demo).
await tester.tap(menuItem); await tester.tap(menuItem);
await tester.pump(); // Launch the demo. await tester.pump(); // Launch the demo.
await tester.pump(const Duration(seconds: 1)); // Wait until the demo has opened. await tester.pump(const Duration(milliseconds: 400)); // Wait until the demo has opened.
expect(find.text(kCaption), findsNothing); expect(find.text(kCaption), findsNothing);
await tester.pump(const Duration(seconds: 1)); // Leave the demo on the screen briefly for manual testing.
// Leave the demo on the screen briefly for manual testing.
await tester.pump(const Duration(milliseconds: 400));
// Scroll the demo around a bit.
await tester.flingFrom(const Point(400.0, 300.0), const Offset(-100.0, 0.0), 500.0);
await tester.flingFrom(const Point(400.0, 300.0), const Offset(0.0, -100.0), 500.0);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 400));
// Verify that the dumps are pretty.
verifyToStringOutput('debugDumpApp', routeName, WidgetsBinding.instance.renderViewElement.toStringDeep());
verifyToStringOutput('debugDumpRenderTree', routeName, RendererBinding.instance?.renderView?.toStringDeep());
verifyToStringOutput('debugDumpLayerTree', routeName, RendererBinding.instance?.renderView?.debugLayer?.toStringDeep());
// Scroll the demo around a bit more.
await tester.flingFrom(const Point(400.0, 300.0), const Offset(-200.0, 0.0), 500.0);
await tester.pump();
await tester.pump(const Duration(milliseconds: 50));
await tester.pump(const Duration(milliseconds: 200));
await tester.pump(const Duration(milliseconds: 400));
await tester.flingFrom(const Point(400.0, 300.0), const Offset(100.0, 0.0), 500.0);
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
await tester.flingFrom(const Point(400.0, 300.0), const Offset(0.0, 400.0), 1000.0);
await tester.pump();
await tester.pump(const Duration(milliseconds: 400));
// Go back // Go back
Finder backButton = find.byTooltip('Back'); Finder backButton = find.byTooltip('Back');
...@@ -46,7 +108,7 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async { ...@@ -46,7 +108,7 @@ Future<Null> smokeDemo(WidgetTester tester, String routeName) async {
await tester.tap(backButton); await tester.tap(backButton);
await tester.pump(); // Start the pop "back" operation. await tester.pump(); // Start the pop "back" operation.
await tester.pump(); // Complete the willPop() Future. await tester.pump(); // Complete the willPop() Future.
await tester.pump(const Duration(seconds: 1)); // Wait until it has finished. await tester.pump(const Duration(milliseconds: 400)); // Wait until it has finished.
return null; return null;
} }
...@@ -71,6 +133,8 @@ Future<Null> runSmokeTest(WidgetTester tester) async { ...@@ -71,6 +133,8 @@ Future<Null> runSmokeTest(WidgetTester tester) async {
tester.binding.debugAssertNoTransientCallbacks('A transient callback was still active after leaving route $routeName'); tester.binding.debugAssertNoTransientCallbacks('A transient callback was still active after leaving route $routeName');
} }
expect(errors, 0);
Finder navigationMenuButton = find.byTooltip('Open navigation menu'); Finder navigationMenuButton = find.byTooltip('Open navigation menu');
expect(navigationMenuButton, findsOneWidget); expect(navigationMenuButton, findsOneWidget);
await tester.tap(navigationMenuButton); await tester.tap(navigationMenuButton);
......
...@@ -555,6 +555,11 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { ...@@ -555,6 +555,11 @@ class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
|| expandedHeight != oldDelegate.expandedHeight || expandedHeight != oldDelegate.expandedHeight
|| topPadding != oldDelegate.topPadding; || topPadding != oldDelegate.topPadding;
} }
@override
String toString() {
return '$runtimeType#$hashCode(topPadding: ${topPadding.toStringAsFixed(1)}, bottomHeight: ${_bottomHeight.toStringAsFixed(1)}, ...)';
}
} }
class SliverAppBar extends StatelessWidget { class SliverAppBar extends StatelessWidget {
......
...@@ -266,7 +266,7 @@ void debugDumpRenderTree() { ...@@ -266,7 +266,7 @@ void debugDumpRenderTree() {
/// Prints a textual representation of the entire layer tree. /// Prints a textual representation of the entire layer tree.
void debugDumpLayerTree() { void debugDumpLayerTree() {
debugPrint(RendererBinding.instance?.renderView?.layer?.toStringDeep()); debugPrint(RendererBinding.instance?.renderView?.debugLayer?.toStringDeep());
} }
/// Prints a textual representation of the entire semantics tree. /// Prints a textual representation of the entire semantics tree.
......
...@@ -403,6 +403,6 @@ class RenderEditable extends RenderBox { ...@@ -403,6 +403,6 @@ class RenderEditable extends RenderBox {
return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n' return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n'
'${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline '${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline
'$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n' '$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n'
'$prefix\n'; '${prefix.trimRight()}\n';
} }
} }
...@@ -87,9 +87,13 @@ abstract class Layer { ...@@ -87,9 +87,13 @@ abstract class Layer {
List<String> description = <String>[]; List<String> description = <String>[];
debugFillDescription(description); debugFillDescription(description);
result += description.map((String description) => "$descriptionPrefix$description\n").join(); result += description.map((String description) => "$descriptionPrefix$description\n").join();
if (childrenDescription == '') if (childrenDescription == '') {
result += '$prefixOtherLines\n'; String prefix = prefixOtherLines.trimRight();
result += childrenDescription; if (prefix != '')
result += '$prefix\n';
} else {
result += childrenDescription;
}
return result; return result;
} }
......
...@@ -1903,12 +1903,31 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -1903,12 +1903,31 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
OffsetLayer _layer; OffsetLayer _layer;
/// The compositing layer that this render object uses to repaint. /// The compositing layer that this render object uses to repaint.
/// ///
/// Call only when [isRepaintBoundary] is true. /// Call only when [isRepaintBoundary] is true and the render object has
/// already painted.
///
/// To access the layer in debug code, even when it might be inappropriate to
/// access it (e.g. because it is dirty), consider [debugLayer].
OffsetLayer get layer { OffsetLayer get layer {
assert(isRepaintBoundary); assert(isRepaintBoundary);
assert(!_needsPaint); assert(!_needsPaint);
return _layer; return _layer;
} }
/// In debug mode, the compositing layer that this render object uses to repaint.
///
/// This getter is intended for debugging purposes only. In release builds, it
/// always returns null. In debug builds, it returns the layer even if the layer
/// is dirty.
///
/// For production code, consider [layer].
OffsetLayer get debugLayer {
OffsetLayer result;
assert(() {
result = _layer;
return true;
});
return result;
}
bool _needsCompositingBitsUpdate = false; // set to true when a child is added bool _needsCompositingBitsUpdate = false; // set to true when a child is added
/// Mark the compositing state for this render object as dirty. /// Mark the compositing state for this render object as dirty.
...@@ -2422,9 +2441,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget { ...@@ -2422,9 +2441,13 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
.expand((String description) => debugWordWrap(description, 65, wrapIndent: ' ')) .expand((String description) => debugWordWrap(description, 65, wrapIndent: ' '))
.map<String>((String line) => "$descriptionPrefix$line\n") .map<String>((String line) => "$descriptionPrefix$line\n")
.join(); .join();
if (childrenDescription == '') if (childrenDescription == '') {
result += '$prefixOtherLines\n'; String prefix = prefixOtherLines.trimRight();
result += childrenDescription; if (prefix != '')
result += '$prefix\n';
} else {
result += childrenDescription;
}
_debugActiveLayout = debugPreviousActiveLayout; _debugActiveLayout = debugPreviousActiveLayout;
return result; return result;
} }
......
...@@ -342,6 +342,6 @@ class RenderParagraph extends RenderBox { ...@@ -342,6 +342,6 @@ class RenderParagraph extends RenderBox {
return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n' return '$prefix \u2558\u2550\u2566\u2550\u2550 text \u2550\u2550\u2550\n'
'${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline '${text.toString("$prefix \u2551 ")}' // TextSpan includes a newline
'$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n' '$prefix \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n'
'$prefix\n'; '${prefix.trimRight()}\n';
} }
} }
...@@ -178,12 +178,12 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje ...@@ -178,12 +178,12 @@ abstract class RenderSliverPersistentHeader extends RenderSliver with RenderObje
try { try {
description.add('maxExtent: ${maxExtent.toStringAsFixed(1)}'); description.add('maxExtent: ${maxExtent.toStringAsFixed(1)}');
} catch (e) { } catch (e) {
description.add('maxExtent: EXCEPTION (${e.runtimeType}) WHILE COMPUTING MAX EXTENT'); description.add('maxExtent: EXCEPTION (${e.runtimeType})');
} }
try { try {
description.add('child position: ${childMainAxisPosition(child).toStringAsFixed(1)}'); description.add('child position: ${childMainAxisPosition(child).toStringAsFixed(1)}');
} catch (e) { } catch (e) {
description.add('child position: EXCEPTION (${e.runtimeType}) WHILE COMPUTING CHILD POSITION'); description.add('child position: EXCEPTION (${e.runtimeType})');
} }
} }
} }
......
...@@ -118,4 +118,21 @@ class ScrollController { ...@@ -118,4 +118,21 @@ class ScrollController {
oldPosition: oldPosition, oldPosition: oldPosition,
); );
} }
@override
String toString() {
final StringBuffer result = new StringBuffer();
result.write('$runtimeType#$hashCode(');
if (initialScrollOffset != 0.0)
result.write('initialScrollOffset: ${initialScrollOffset.toStringAsFixed(1)}, ');
if (_positions.isEmpty) {
result.write('no clients');
} else if (_positions.length == 1) {
result.write('one client, offset $offset');
} else {
result.write('${_positions.length} clients');
}
result.write(')');
return result.toString();
}
} }
...@@ -39,6 +39,24 @@ abstract class SliverChildDelegate { ...@@ -39,6 +39,24 @@ abstract class SliverChildDelegate {
) => null; ) => null;
bool shouldRebuild(@checked SliverChildDelegate oldDelegate); bool shouldRebuild(@checked SliverChildDelegate oldDelegate);
@override
String toString() {
List<String> description = <String>[];
debugFillDescription(description);
return '$runtimeType#$hashCode(${description.join(", ")})';
}
@protected
void debugFillDescription(List<String> description) {
try {
final int children = estimatedChildCount;
if (children != null)
description.add('estimated child count: $children');
} catch (e) {
description.add('estimated child count: EXCEPTION (${e.runtimeType})');
}
}
} }
class SliverChildBuilderDelegate extends SliverChildDelegate { class SliverChildBuilderDelegate extends SliverChildDelegate {
......
...@@ -130,7 +130,6 @@ void main() { ...@@ -130,7 +130,6 @@ void main() {
r' window size: Size\(800\.0, 600\.0\) \(in physical pixels\)\n' r' window size: Size\(800\.0, 600\.0\) \(in physical pixels\)\n'
r' device pixel ratio: 1\.0 \(physical pixels per logical pixel\)\n' r' device pixel ratio: 1\.0 \(physical pixels per logical pixel\)\n'
r' configuration: Size\(800\.0, 600\.0\) at 1\.0x \(in logical pixels\)\n' r' configuration: Size\(800\.0, 600\.0\) at 1\.0x \(in logical pixels\)\n'
r'\n'
), ),
]); ]);
console.clear(); console.clear();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment