blob: 8a4c93afae4ce1f2aac71450bf0be40f15620b1e [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/semantics_tester.dart';
class TestIcon extends StatefulWidget {
const TestIcon({ Key key }) : super(key: key);
@override
TestIconState createState() => TestIconState();
}
class TestIconState extends State<TestIcon> {
IconThemeData iconTheme;
@override
Widget build(BuildContext context) {
iconTheme = IconTheme.of(context);
return const Icon(Icons.add);
}
}
class TestText extends StatefulWidget {
const TestText(this.text, { Key key }) : super(key: key);
final String text;
@override
TestTextState createState() => TestTextState();
}
class TestTextState extends State<TestText> {
TextStyle textStyle;
@override
Widget build(BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return Text(widget.text);
}
}
void main() {
testWidgets('ListTile geometry (LTR)', (WidgetTester tester) async {
// See https://material.io/go/design-lists
final Key leadingKey = GlobalKey();
final Key trailingKey = GlobalKey();
bool hasSubtitle;
const double leftPadding = 10.0;
const double rightPadding = 20.0;
Widget buildFrame({ bool dense = false, bool isTwoLine = false, bool isThreeLine = false, double textScaleFactor = 1.0, double subtitleScaleFactor }) {
hasSubtitle = isTwoLine || isThreeLine;
subtitleScaleFactor ??= textScaleFactor;
return MaterialApp(
home: MediaQuery(
data: MediaQueryData(
padding: const EdgeInsets.only(left: leftPadding, right: rightPadding),
textScaleFactor: textScaleFactor,
),
child: Material(
child: Center(
child: ListTile(
leading: Container(key: leadingKey, width: 24.0, height: 24.0),
title: const Text('title'),
subtitle: hasSubtitle ? Text('subtitle', textScaleFactor: subtitleScaleFactor) : null,
trailing: Container(key: trailingKey, width: 24.0, height: 24.0),
dense: dense,
isThreeLine: isThreeLine,
),
),
),
),
);
}
void testChildren() {
expect(find.byKey(leadingKey), findsOneWidget);
expect(find.text('title'), findsOneWidget);
if (hasSubtitle)
expect(find.text('subtitle'), findsOneWidget);
expect(find.byKey(trailingKey), findsOneWidget);
}
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double top(String text) => tester.getTopLeft(find.text(text)).dy;
double bottom(String text) => tester.getBottomLeft(find.text(text)).dy;
double height(String text) => tester.getRect(find.text(text)).height;
double leftKey(Key key) => tester.getTopLeft(find.byKey(key)).dx;
double rightKey(Key key) => tester.getTopRight(find.byKey(key)).dx;
double widthKey(Key key) => tester.getSize(find.byKey(key)).width;
double heightKey(Key key) => tester.getSize(find.byKey(key)).height;
// ListTiles are contained by a SafeArea defined like this:
// SafeArea(top: false, bottom: false, minimum: contentPadding)
// The default contentPadding is 16.0 on the left and right.
void testHorizontalGeometry() {
expect(leftKey(leadingKey), math.max(16.0, leftPadding));
expect(left('title'), 56.0 + math.max(16.0, leftPadding));
if (hasSubtitle)
expect(left('subtitle'), 56.0 + math.max(16.0, leftPadding));
expect(left('title'), rightKey(leadingKey) + 32.0);
expect(rightKey(trailingKey), 800.0 - math.max(16.0, rightPadding));
expect(widthKey(trailingKey), 24.0);
}
void testVerticalGeometry(double expectedHeight) {
final Rect tileRect = tester.getRect(find.byType(ListTile));
expect(tileRect.size, Size(800.0, expectedHeight));
expect(top('title'), greaterThanOrEqualTo(tileRect.top));
if (hasSubtitle) {
expect(top('subtitle'), greaterThanOrEqualTo(bottom('title')));
expect(bottom('subtitle'), lessThan(tileRect.bottom));
} else {
expect(top('title'), equals(tileRect.top + (tileRect.height - height('title')) / 2.0));
}
expect(heightKey(trailingKey), 24.0);
}
await tester.pumpWidget(buildFrame());
testChildren();
testHorizontalGeometry();
testVerticalGeometry(56.0);
await tester.pumpWidget(buildFrame(dense: true));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(48.0);
await tester.pumpWidget(buildFrame(isTwoLine: true));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(72.0);
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(64.0);
await tester.pumpWidget(buildFrame(isThreeLine: true));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(88.0);
await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(76.0);
await tester.pumpWidget(buildFrame(textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(72.0);
await tester.pumpWidget(buildFrame(dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(72.0);
await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(128.0);
// Make sure that the height of a large subtitle is taken into account.
await tester.pumpWidget(buildFrame(isTwoLine: true, textScaleFactor: 0.5, subtitleScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(72.0);
await tester.pumpWidget(buildFrame(isTwoLine: true, dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(128.0);
await tester.pumpWidget(buildFrame(isThreeLine: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(128.0);
await tester.pumpWidget(buildFrame(isThreeLine: true, dense: true, textScaleFactor: 4.0));
testChildren();
testHorizontalGeometry();
testVerticalGeometry(128.0);
});
testWidgets('ListTile geometry (RTL)', (WidgetTester tester) async {
const double leftPadding = 10.0;
const double rightPadding = 20.0;
await tester.pumpWidget(const MediaQuery(
data: MediaQueryData(
padding: EdgeInsets.only(left: leftPadding, right: rightPadding),
),
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: ListTile(
leading: Text('L'),
title: Text('title'),
trailing: Text('T'),
),
),
),
),
));
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx;
void testHorizontalGeometry() {
expect(right('L'), 800.0 - math.max(16.0, rightPadding));
expect(right('title'), 800.0 - 56.0 - math.max(16.0, rightPadding));
expect(left('T'), math.max(16.0, leftPadding));
}
testHorizontalGeometry();
});
testWidgets('ListTile.divideTiles', (WidgetTester tester) async {
final List<String> titles = <String>[ 'first', 'second', 'third' ];
await tester.pumpWidget(MaterialApp(
home: Material(
child: Builder(
builder: (BuildContext context) {
return ListView(
children: ListTile.divideTiles(
context: context,
tiles: titles.map<Widget>((String title) => ListTile(title: Text(title))),
).toList(),
);
},
),
),
));
expect(find.text('first'), findsOneWidget);
expect(find.text('second'), findsOneWidget);
expect(find.text('third'), findsOneWidget);
});
testWidgets('ListTileTheme', (WidgetTester tester) async {
final Key titleKey = UniqueKey();
final Key subtitleKey = UniqueKey();
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool dense = false,
bool selected = false,
Color selectedColor,
Color iconColor,
Color textColor,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
dense: dense,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
enabled: enabled,
selected: selected,
leading: TestIcon(key: leadingKey),
trailing: TestIcon(key: trailingKey),
title: TestText('title', key: titleKey),
subtitle: TestText('subtitle', key: subtitleKey),
);
}
),
),
),
),
);
}
const Color green = Color(0xFF00FF00);
const Color red = Color(0xFFFF0000);
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color;
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color;
// A selected ListTile's leading, trailing, and text get the primary color by default
await tester.pumpWidget(buildFrame(selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.primaryColor);
expect(iconColor(trailingKey), theme.primaryColor);
expect(textColor(titleKey), theme.primaryColor);
expect(textColor(subtitleKey), theme.primaryColor);
// A selected ListTile's leading, trailing, and text get the ListTileTheme's selectedColor
await tester.pumpWidget(buildFrame(selected: true, selectedColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), green);
expect(iconColor(trailingKey), green);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// An unselected ListTile's leading and trailing get the ListTileTheme's iconColor
// An unselected ListTile's title texts get the ListTileTheme's textColor
await tester.pumpWidget(buildFrame(iconColor: red, textColor: green));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), red);
expect(iconColor(trailingKey), red);
expect(textColor(titleKey), green);
expect(textColor(subtitleKey), green);
// If the item is disabled it's rendered with the theme's disabled color.
await tester.pumpWidget(buildFrame(enabled: false));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
// If the item is disabled it's rendered with the theme's disabled color.
// Even if it's selected.
await tester.pumpWidget(buildFrame(enabled: false, selected: true));
await tester.pump(const Duration(milliseconds: 300)); // DefaultTextStyle changes animate
expect(iconColor(leadingKey), theme.disabledColor);
expect(iconColor(trailingKey), theme.disabledColor);
expect(textColor(titleKey), theme.disabledColor);
expect(textColor(subtitleKey), theme.disabledColor);
});
testWidgets('ListTile semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: MediaQuery(
data: const MediaQueryData(),
child: Column(
children: const <Widget>[
ListTile(
title: Text('one'),
),
ListTile(
title: Text('two'),
selected: true,
),
ListTile(
title: Text('three'),
enabled: false,
),
],
),
),
),
),
);
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
label: 'one',
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
TestSemantics.rootChild(
label: 'two',
flags: <SemanticsFlag>[
SemanticsFlag.isSelected,
SemanticsFlag.hasEnabledState,
SemanticsFlag.isEnabled,
],
),
TestSemantics.rootChild(
label: 'three',
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
],
),
]
),
ignoreTransform: true, ignoreId: true, ignoreRect: true),
);
semantics.dispose();
});
testWidgets('ListTile contentPadding', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) {
return MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.zero,
textScaleFactor: 1.0,
),
child: Directionality(
textDirection: textDirection,
child: Material(
child: Container(
alignment: Alignment.topLeft,
child: const ListTile(
contentPadding: EdgeInsetsDirectional.only(
start: 10.0,
end: 20.0,
top: 30.0,
bottom: 40.0,
),
leading: Text('L'),
title: Text('title'),
trailing: Text('T'),
),
),
),
),
);
}
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx;
await tester.pumpWidget(buildFrame(TextDirection.ltr));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
expect(left('L'), 10.0); // contentPadding.start = 10
expect(right('T'), 780.0); // 800 - contentPadding.end
await tester.pumpWidget(buildFrame(TextDirection.rtl));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
expect(left('T'), 20.0); // contentPadding.end = 20
expect(right('L'), 790.0); // 800 - contentPadding.start
});
testWidgets('ListTile contentPadding', (WidgetTester tester) async {
Widget buildFrame(TextDirection textDirection) {
return MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.zero,
textScaleFactor: 1.0,
),
child: Directionality(
textDirection: textDirection,
child: Material(
child: Container(
alignment: Alignment.topLeft,
child: const ListTile(
contentPadding: EdgeInsetsDirectional.only(
start: 10.0,
end: 20.0,
top: 30.0,
bottom: 40.0,
),
leading: Text('L'),
title: Text('title'),
trailing: Text('T'),
),
),
),
),
);
}
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx;
await tester.pumpWidget(buildFrame(TextDirection.ltr));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
expect(left('L'), 10.0); // contentPadding.start = 10
expect(right('T'), 780.0); // 800 - contentPadding.end
await tester.pumpWidget(buildFrame(TextDirection.rtl));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 126.0)); // 126 = 56 + 30 + 40
expect(left('T'), 20.0); // contentPadding.end = 20
expect(right('L'), 790.0); // 800 - contentPadding.start
});
testWidgets('ListTileTheme wide leading Widget', (WidgetTester tester) async {
const Key leadingKey = ValueKey<String>('L');
Widget buildFrame(double leadingWidth, TextDirection textDirection) {
return MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.zero,
textScaleFactor: 1.0,
),
child: Directionality(
textDirection: textDirection,
child: Material(
child: Container(
alignment: Alignment.topLeft,
child: ListTile(
contentPadding: EdgeInsets.zero,
leading: SizedBox(key: leadingKey, width: leadingWidth, height: 32.0),
title: const Text('title'),
subtitle: const Text('subtitle'),
),
),
),
),
);
}
double left(String text) => tester.getTopLeft(find.text(text)).dx;
double right(String text) => tester.getTopRight(find.text(text)).dx;
// textDirection = LTR
// Two-line tile's height = 72, leading 24x32 widget is positioned 16.0 pixels from the top.
await tester.pumpWidget(buildFrame(24.0, TextDirection.ltr));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 16.0));
expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(24.0, 16.0 + 32.0));
// Leading widget's width is 20, so default layout: the left edges of the
// title and subtitle are at 56dps (contentPadding is zero).
expect(left('title'), 56.0);
expect(left('subtitle'), 56.0);
// If the leading widget is wider than 40 it is separated from the
// title and subtitle by 16.
await tester.pumpWidget(buildFrame(56.0, TextDirection.ltr));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
expect(tester.getTopLeft(find.byKey(leadingKey)), const Offset(0.0, 16.0));
expect(tester.getBottomRight(find.byKey(leadingKey)), const Offset(56.0, 16.0 + 32.0));
expect(left('title'), 72.0);
expect(left('subtitle'), 72.0);
// Same tests, textDirection = RTL
await tester.pumpWidget(buildFrame(24.0, TextDirection.rtl));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 16.0));
expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 24.0, 16.0 + 32.0));
expect(right('title'), 800.0 - 56.0);
expect(right('subtitle'), 800.0 - 56.0);
await tester.pumpWidget(buildFrame(56.0, TextDirection.rtl));
expect(tester.getSize(find.byType(ListTile)), const Size(800.0, 72.0));
expect(tester.getTopRight(find.byKey(leadingKey)), const Offset(800.0, 16.0));
expect(tester.getBottomLeft(find.byKey(leadingKey)), const Offset(800.0 - 56.0, 16.0 + 32.0));
expect(right('title'), 800.0 - 72.0);
expect(right('subtitle'), 800.0 - 72.0);
});
testWidgets('ListTile leading and trailing positions', (WidgetTester tester) async {
// This test is based on the redlines at
// https://material.io/design/components/lists.html#specs
// DENSE "ONE"-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
dense: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
dense: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
),
],
),
),
),
);
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 177.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 177.0, 800.0, 48.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 177.0 + 4.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 177.0 + 12.0, 24.0, 24.0));
// NON-DENSE "ONE"-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
),
],
),
),
),
);
await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 216.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 216.0 , 800.0, 56.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 216.0 + 8.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0, 24.0, 24.0));
// DENSE "TWO"-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
dense: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
dense: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
);
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 64.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 12.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 20.0, 24.0, 24.0));
// NON-DENSE "TWO"-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
);
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 72.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 24.0, 24.0, 24.0));
// DENSE "THREE"-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
dense: true,
isThreeLine: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
dense: true,
isThreeLine: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
);
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 76.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 16.0, 24.0, 24.0));
// NON-DENSE THREE-LINE
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
isThreeLine: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
isThreeLine: true,
leading: CircleAvatar(),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
subtitle: Text('A'),
),
],
),
),
),
);
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 180.0));
expect(tester.getRect(find.byType(CircleAvatar).at(0)), Rect.fromLTWH( 16.0, 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 180.0, 800.0, 88.0));
expect(tester.getRect(find.byType(CircleAvatar).at(1)), Rect.fromLTWH( 16.0, 180.0 + 16.0, 40.0, 40.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 180.0 + 16.0, 24.0, 24.0));
// "ONE-LINE" with Small Leading Widget
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: SizedBox(height:12.0, width:24.0, child: Placeholder()),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM'),
),
ListTile(
leading: SizedBox(height:12.0, width:24.0, child: Placeholder()),
trailing: SizedBox(height: 24.0, width: 24.0, child: Placeholder()),
title: Text('A'),
),
],
),
),
),
);
await tester.pump(const Duration(seconds: 2)); // the text styles are animated when we change dense
// LEFT TOP WIDTH HEIGHT
expect(tester.getRect(find.byType(ListTile).at(0)), Rect.fromLTWH( 0.0, 0.0, 800.0, 216.0));
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH( 16.0, 16.0, 24.0, 12.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 16.0, 24.0, 24.0));
expect(tester.getRect(find.byType(ListTile).at(1)), Rect.fromLTWH( 0.0, 216.0 , 800.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(2)), Rect.fromLTWH( 16.0, 216.0 + 16.0, 24.0, 12.0));
expect(tester.getRect(find.byType(Placeholder).at(3)), Rect.fromLTWH(800.0 - 24.0 - 16.0, 216.0 + 16.0, 24.0, 24.0));
});
testWidgets('ListTile leading icon height does not exceed ListTile height', (WidgetTester tester) async {
// regression test for https://github.com/flutter/flutter/issues/28765
const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());
// Dense One line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
dense: true,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 0.0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 48.0, 24.0, 48.0));
// Non-dense One line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
dense: false,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 0.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 56.0, 24.0, 56.0));
// Dense Two line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
dense: true,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 8.0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 64.0 + 8.0, 24.0, 48.0));
// Non-dense Two line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
dense: false,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 8.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 72.0 + 8.0, 24.0, 56.0));
// Dense Three line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
isThreeLine: true,
dense: true,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
isThreeLine: true,
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 16.0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 76.0 + 16.0, 24.0, 48.0));
// Non-dense Three line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
leading: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
isThreeLine: true,
dense: false,
),
ListTile(
leading: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
isThreeLine: true,
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(16.0, 16.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(16.0, 88.0 + 16.0, 24.0, 56.0));
});
testWidgets('ListTile trailing icon height does not exceed ListTile height', (WidgetTester tester) async {
// regression test for https://github.com/flutter/flutter/issues/28765
const SizedBox oversizedWidget = SizedBox(height: 80.0, width: 24.0, child: Placeholder());
// Dense One line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
dense: true,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 48.0, 24.0, 48.0));
// Non-dense One line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
dense: false,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 0.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 56.0, 24.0, 56.0));
// Dense Two line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
dense: true,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 8.0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 64.0 + 8.0, 24.0, 48.0));
// Non-dense Two line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
dense: false,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 8.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 72.0 + 8.0, 24.0, 56.0));
// Dense Three line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
isThreeLine: true,
dense: true,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
isThreeLine: true,
dense: true,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 16.0, 24.0, 48.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 76.0 + 16.0, 24.0, 48.0));
// Non-dense Three line
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListView(
children: const <Widget>[
ListTile(
trailing: oversizedWidget,
title: Text('A'),
subtitle: Text('A'),
isThreeLine: true,
dense: false,
),
ListTile(
trailing: oversizedWidget,
title: Text('B'),
subtitle: Text('B'),
isThreeLine: true,
dense: false,
),
],
),
),
),
);
expect(tester.getRect(find.byType(Placeholder).at(0)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 16.0, 24.0, 56.0));
expect(tester.getRect(find.byType(Placeholder).at(1)), Rect.fromLTWH(800.0 - 16.0 - 24.0, 88.0 + 16.0, 24.0, 56.0));
});
}