blob: 53273095f1001bfa76b5cf958902acf06bf9159b [file] [log] [blame]
// Copyright 2014 The Flutter 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 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
class TestIcon extends StatefulWidget {
const TestIcon({ super.key });
@override
TestIconState createState() => TestIconState();
}
class TestIconState extends State<TestIcon> {
late IconThemeData iconTheme;
@override
Widget build(BuildContext context) {
iconTheme = IconTheme.of(context);
return const Icon(Icons.add);
}
}
class TestText extends StatefulWidget {
const TestText(this.text, { super.key });
final String text;
@override
TestTextState createState() => TestTextState();
}
class TestTextState extends State<TestText> {
late TextStyle textStyle;
@override
Widget build(BuildContext context) {
textStyle = DefaultTextStyle.of(context).style;
return Text(widget.text);
}
}
void main() {
test('ListTileThemeData copyWith, ==, hashCode, basics', () {
expect(const ListTileThemeData(), const ListTileThemeData().copyWith());
expect(const ListTileThemeData().hashCode, const ListTileThemeData().copyWith().hashCode);
});
test('ListTileThemeData lerp special cases', () {
expect(ListTileThemeData.lerp(null, null, 0), null);
const ListTileThemeData data = ListTileThemeData();
expect(identical(ListTileThemeData.lerp(data, data, 0.5), data), true);
});
test('ListTileThemeData defaults', () {
const ListTileThemeData themeData = ListTileThemeData();
expect(themeData.dense, null);
expect(themeData.shape, null);
expect(themeData.style, null);
expect(themeData.selectedColor, null);
expect(themeData.iconColor, null);
expect(themeData.textColor, null);
expect(themeData.titleTextStyle, null);
expect(themeData.subtitleTextStyle, null);
expect(themeData.leadingAndTrailingTextStyle, null);
expect(themeData.contentPadding, null);
expect(themeData.tileColor, null);
expect(themeData.selectedTileColor, null);
expect(themeData.horizontalTitleGap, null);
expect(themeData.minVerticalPadding, null);
expect(themeData.minLeadingWidth, null);
expect(themeData.enableFeedback, null);
expect(themeData.mouseCursor, null);
expect(themeData.visualDensity, null);
expect(themeData.titleAlignment, null);
});
testWidgets('Default ListTileThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData().debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(description, <String>[]);
});
testWidgets('ListTileThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const ListTileThemeData(
dense: true,
shape: StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: Color(0x00000001),
iconColor: Color(0x00000002),
textColor: Color(0x00000003),
titleTextStyle: TextStyle(color: Color(0x00000004)),
subtitleTextStyle: TextStyle(color: Color(0x00000005)),
leadingAndTrailingTextStyle: TextStyle(color: Color(0x00000006)),
contentPadding: EdgeInsets.all(100),
tileColor: Color(0x00000007),
selectedTileColor: Color(0x00000008),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.comfortable,
titleAlignment: ListTileTitleAlignment.top,
).debugFillProperties(builder);
final List<String> description = builder.properties
.where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info))
.map((DiagnosticsNode node) => node.toString())
.toList();
expect(
description,
equalsIgnoringHashCodes(<String>[
'dense: true',
'shape: StadiumBorder(BorderSide(width: 0.0, style: none))',
'style: drawer',
'selectedColor: Color(0x00000001)',
'iconColor: Color(0x00000002)',
'textColor: Color(0x00000003)',
'titleTextStyle: TextStyle(inherit: true, color: Color(0x00000004))',
'subtitleTextStyle: TextStyle(inherit: true, color: Color(0x00000005))',
'leadingAndTrailingTextStyle: TextStyle(inherit: true, color: Color(0x00000006))',
'contentPadding: EdgeInsets.all(100.0)',
'tileColor: Color(0x00000007)',
'selectedTileColor: Color(0x00000008)',
'horizontalTitleGap: 200.0',
'minVerticalPadding: 300.0',
'minLeadingWidth: 400.0',
'enableFeedback: true',
'mouseCursor: MaterialStateMouseCursor(clickable)',
'visualDensity: VisualDensity#00000(h: -1.0, v: -1.0)(horizontal: -1.0, vertical: -1.0)',
'titleAlignment: ListTileTitleAlignment.top',
]),
);
});
testWidgets('ListTileTheme backwards compatibility constructor', (WidgetTester tester) async {
late ListTileThemeData theme;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
dense: true,
shape: const StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: const Color(0x00000001),
iconColor: const Color(0x00000002),
textColor: const Color(0x00000003),
contentPadding: const EdgeInsets.all(100),
tileColor: const Color(0x00000004),
selectedTileColor: const Color(0x00000005),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
mouseCursor: MaterialStateMouseCursor.clickable,
child: Center(
child: Builder(
builder: (BuildContext context) {
theme = ListTileTheme.of(context);
return const Placeholder();
},
),
),
),
),
),
);
expect(theme.dense, true);
expect(theme.shape, const StadiumBorder());
expect(theme.style, ListTileStyle.drawer);
expect(theme.selectedColor, const Color(0x00000001));
expect(theme.iconColor, const Color(0x00000002));
expect(theme.textColor, const Color(0x00000003));
expect(theme.contentPadding, const EdgeInsets.all(100));
expect(theme.tileColor, const Color(0x00000004));
expect(theme.selectedTileColor, const Color(0x00000005));
expect(theme.horizontalTitleGap, 200);
expect(theme.minVerticalPadding, 300);
expect(theme.minLeadingWidth, 400);
expect(theme.enableFeedback, true);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
});
testWidgets('ListTileTheme', (WidgetTester tester) async {
final Key listTileKey = UniqueKey();
final Key titleKey = UniqueKey();
final Key subtitleKey = UniqueKey();
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool dense = false,
bool selected = false,
ShapeBorder? shape,
Color? selectedColor,
Color? iconColor,
Color? textColor,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: ListTileThemeData(
dense: dense,
shape: shape,
selectedColor: selectedColor,
iconColor: iconColor,
textColor: textColor,
minVerticalPadding: 25.0,
mouseCursor: MaterialStateProperty.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return SystemMouseCursors.forbidden;
}
return SystemMouseCursors.click;
}),
visualDensity: VisualDensity.compact,
titleAlignment: ListTileTitleAlignment.bottom,
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
key: listTileKey,
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);
const ShapeBorder roundedShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
);
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
ShapeBorder inkWellBorder() => tester.widget<InkWell>(find.descendant(of: find.byType(ListTile), matching: find.byType(InkWell))).customBorder!;
// 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);
// A selected ListTile's InkWell gets the ListTileTheme's shape
await tester.pumpWidget(buildFrame(selected: true, shape: roundedShape));
expect(inkWellBorder(), roundedShape);
// Cursor updates when hovering disabled ListTile
await tester.pumpWidget(buildFrame(enabled: false));
final Offset listTile = tester.getCenter(find.byKey(titleKey));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(listTile);
await tester.pumpAndSettle();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
// VisualDensity is respected
final RenderBox box = tester.renderObject(find.byKey(listTileKey));
expect(box.size, equals(const Size(800, 80.0)));
// titleAlignment is respected.
final Offset titleOffset = tester.getTopLeft(find.text('title'));
final Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
final Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));
expect(leadingOffset.dy - titleOffset.dy, 6);
expect(trailingOffset.dy - titleOffset.dy, 6);
});
testWidgets('ListTileTheme colors are applied to leading and trailing text widgets', (WidgetTester tester) async {
final Key leadingKey = UniqueKey();
final Key trailingKey = UniqueKey();
const Color selectedColor = Colors.orange;
const Color defaultColor = Colors.black;
late ThemeData theme;
Widget buildFrame({
bool enabled = true,
bool selected = false,
}) {
return MaterialApp(
home: Material(
child: Center(
child: ListTileTheme(
data: const ListTileThemeData(
selectedColor: selectedColor,
textColor: defaultColor,
),
child: Builder(
builder: (BuildContext context) {
theme = Theme.of(context);
return ListTile(
enabled: enabled,
selected: selected,
leading: TestText('leading', key: leadingKey),
title: const TestText('title'),
trailing: TestText('trailing', key: trailingKey),
);
},
),
),
),
),
);
}
Color textColor(Key key) => tester.state<TestTextState>(find.byKey(key)).textStyle.color!;
await tester.pumpWidget(buildFrame());
// Enabled color should use ListTileTheme.textColor.
expect(textColor(leadingKey), defaultColor);
expect(textColor(trailingKey), defaultColor);
await tester.pumpWidget(buildFrame(selected: true));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Selected color should use ListTileTheme.selectedColor.
expect(textColor(leadingKey), selectedColor);
expect(textColor(trailingKey), selectedColor);
await tester.pumpWidget(buildFrame(enabled: false));
// Wait for text color to animate.
await tester.pumpAndSettle();
// Disabled color should be ThemeData.disabledColor.
expect(textColor(leadingKey), theme.disabledColor);
expect(textColor(trailingKey), theme.disabledColor);
});
testWidgets(
"ListTile respects ListTileTheme's titleTextStyle, subtitleTextStyle & leadingAndTrailingTextStyle",
(WidgetTester tester) async {
final ThemeData theme = ThemeData(
useMaterial3: true,
listTileTheme: const ListTileThemeData(
titleTextStyle: TextStyle(fontSize: 20.0),
subtitleTextStyle: TextStyle(fontSize: 17.5),
leadingAndTrailingTextStyle: TextStyle(fontSize: 15.0),
),
);
Widget buildFrame() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return const ListTile(
leading: TestText('leading'),
title: TestText('title'),
subtitle: TestText('subtitle'),
trailing: TestText('trailing'),
);
},
),
),
),
);
}
await tester.pumpWidget(buildFrame());
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
expect(leading.text.style!.fontSize, 15.0);
final RenderParagraph title = _getTextRenderObject(tester, 'title');
expect(title.text.style!.fontSize, 20.0);
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
expect(subtitle.text.style!.fontSize, 17.5);
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
expect(trailing.text.style!.fontSize, 15.0);
});
testWidgets(
"ListTile's titleTextStyle, subtitleTextStyle & leadingAndTrailingTextStyle are overridden by ListTile properties",
(WidgetTester tester) async {
final ThemeData theme = ThemeData(
useMaterial3: true,
listTileTheme: const ListTileThemeData(
titleTextStyle: TextStyle(fontSize: 20.0),
subtitleTextStyle: TextStyle(fontSize: 17.5),
leadingAndTrailingTextStyle: TextStyle(fontSize: 15.0),
),
);
const TextStyle titleTextStyle = TextStyle(fontSize: 23.0);
const TextStyle subtitleTextStyle = TextStyle(fontSize: 20.0);
const TextStyle leadingAndTrailingTextStyle = TextStyle(fontSize: 18.0);
Widget buildFrame() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return const ListTile(
titleTextStyle: titleTextStyle,
subtitleTextStyle: subtitleTextStyle,
leadingAndTrailingTextStyle: leadingAndTrailingTextStyle,
leading: TestText('leading'),
title: TestText('title'),
subtitle: TestText('subtitle'),
trailing: TestText('trailing'),
);
},
),
),
),
);
}
await tester.pumpWidget(buildFrame());
final RenderParagraph leading = _getTextRenderObject(tester, 'leading');
expect(leading.text.style!.fontSize, 18.0);
final RenderParagraph title = _getTextRenderObject(tester, 'title');
expect(title.text.style!.fontSize, 23.0);
final RenderParagraph subtitle = _getTextRenderObject(tester, 'subtitle');
expect(subtitle.text.style!.fontSize, 20.0);
final RenderParagraph trailing = _getTextRenderObject(tester, 'trailing');
expect(trailing.text.style!.fontSize, 18.0);
});
testWidgets("ListTile respects ListTileTheme's tileColor & selectedTileColor", (WidgetTester tester) async {
late ListTileThemeData theme;
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: ListTileThemeData(
tileColor: Colors.green.shade500,
selectedTileColor: Colors.red.shade500,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
theme = ListTileTheme.of(context);
return ListTile(
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..rect(color: theme.tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..rect(color: theme.selectedTileColor));
});
testWidgets("ListTileTheme's tileColor & selectedTileColor are overridden by ListTile properties", (WidgetTester tester) async {
bool isSelected = false;
final Color tileColor = Colors.green.shade500;
final Color selectedTileColor = Colors.red.shade500;
await tester.pumpWidget(
MaterialApp(
home: Material(
child: ListTileTheme(
data: const ListTileThemeData(
selectedTileColor: Colors.green,
tileColor: Colors.red,
),
child: Center(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return ListTile(
tileColor: tileColor,
selectedTileColor: selectedTileColor,
selected: isSelected,
onTap: () {
setState(()=> isSelected = !isSelected);
},
title: const Text('Title'),
);
},
),
),
),
),
),
);
expect(find.byType(Material), paints..rect(color: tileColor));
// Tap on tile to change isSelected.
await tester.tap(find.byType(ListTile));
await tester.pumpAndSettle();
expect(find.byType(Material), paints..rect(color: selectedTileColor));
});
testWidgets('ListTile uses ListTileTheme shape in a drawer', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/106303
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
final ShapeBorder shapeBorder = RoundedRectangleBorder(borderRadius: BorderRadius.circular(20.0));
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
listTileTheme: ListTileThemeData(shape: shapeBorder),
),
home: Scaffold(
key: scaffoldKey,
drawer: const Drawer(
child: ListTile(),
),
body: Container(),
),
));
await tester.pumpAndSettle();
scaffoldKey.currentState!.openDrawer();
// Start drawer animation.
await tester.pump();
final ShapeBorder? inkWellBorder = tester.widget<InkWell>(
find.descendant(
of: find.byType(ListTile),
matching: find.byType(InkWell),
)).customBorder;
// Test shape.
expect(inkWellBorder, shapeBorder);
});
testWidgets('ListTile respects MaterialStateColor LisTileTheme.textColor', (WidgetTester tester) async {
bool enabled = false;
bool selected = false;
const Color defaultColor = Colors.blue;
const Color selectedColor = Colors.green;
const Color disabledColor = Colors.red;
final ThemeData theme = ThemeData(
listTileTheme: ListTileThemeData(
textColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return defaultColor;
}),
),
);
Widget buildFrame() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ListTile(
enabled: enabled,
selected: selected,
title: const TestText('title'),
subtitle: const TestText('subtitle') ,
);
},
),
),
),
);
}
// Test disabled state.
await tester.pumpWidget(buildFrame());
RenderParagraph title = _getTextRenderObject(tester, 'title');
expect(title.text.style!.color, disabledColor);
// Test enabled state.
enabled = true;
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
title = _getTextRenderObject(tester, 'title');
expect(title.text.style!.color, defaultColor);
// Test selected state.
selected = true;
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
title = _getTextRenderObject(tester, 'title');
expect(title.text.style!.color, selectedColor);
});
testWidgets('ListTile respects MaterialStateColor LisTileTheme.iconColor', (WidgetTester tester) async {
bool enabled = false;
bool selected = false;
const Color defaultColor = Colors.blue;
const Color selectedColor = Colors.green;
const Color disabledColor = Colors.red;
final Key leadingKey = UniqueKey();
final ThemeData theme = ThemeData(
listTileTheme: ListTileThemeData(
iconColor: MaterialStateColor.resolveWith((Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return disabledColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return defaultColor;
}),
),
);
Widget buildFrame() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ListTile(
enabled: enabled,
selected: selected,
leading: TestIcon(key: leadingKey),
);
},
),
),
),
);
}
Color iconColor(Key key) => tester.state<TestIconState>(find.byKey(key)).iconTheme.color!;
// Test disabled state.
await tester.pumpWidget(buildFrame());
expect(iconColor(leadingKey), disabledColor);
// Test enabled state.
enabled = true;
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
expect(iconColor(leadingKey), defaultColor);
// Test selected state.
selected = true;
await tester.pumpWidget(buildFrame());
await tester.pumpAndSettle();
expect(iconColor(leadingKey), selectedColor);
});
testWidgets('ListTileThemeData copyWith overrides all properties', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/issues/119734
const ListTileThemeData original = ListTileThemeData(
dense: true,
shape: StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: Color(0x00000001),
iconColor: Color(0x00000002),
textColor: Color(0x00000003),
titleTextStyle: TextStyle(color: Color(0x00000004)),
subtitleTextStyle: TextStyle(color: Color(0x00000005)),
leadingAndTrailingTextStyle: TextStyle(color: Color(0x00000006)),
contentPadding: EdgeInsets.all(100),
tileColor: Color(0x00000007),
selectedTileColor: Color(0x00000008),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
);
final ListTileThemeData copy = original.copyWith(
dense: false,
shape: const RoundedRectangleBorder(),
style: ListTileStyle.list,
selectedColor: const Color(0x00000009),
iconColor: const Color(0x0000000A),
textColor: const Color(0x0000000B),
titleTextStyle: const TextStyle(color: Color(0x0000000C)),
subtitleTextStyle: const TextStyle(color: Color(0x0000000D)),
leadingAndTrailingTextStyle: const TextStyle(color: Color(0x0000000E)),
contentPadding: const EdgeInsets.all(500),
tileColor: const Color(0x0000000F),
selectedTileColor: const Color(0x00000010),
horizontalTitleGap: 600,
minVerticalPadding: 700,
minLeadingWidth: 800,
enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
);
expect(copy.dense, false);
expect(copy.shape, const RoundedRectangleBorder());
expect(copy.style, ListTileStyle.list);
expect(copy.selectedColor, const Color(0x00000009));
expect(copy.iconColor, const Color(0x0000000A));
expect(copy.textColor, const Color(0x0000000B));
expect(copy.titleTextStyle, const TextStyle(color: Color(0x0000000C)));
expect(copy.subtitleTextStyle, const TextStyle(color: Color(0x0000000D)));
expect(copy.leadingAndTrailingTextStyle, const TextStyle(color: Color(0x0000000E)));
expect(copy.contentPadding, const EdgeInsets.all(500));
expect(copy.tileColor, const Color(0x0000000F));
expect(copy.selectedTileColor, const Color(0x00000010));
expect(copy.horizontalTitleGap, 600);
expect(copy.minVerticalPadding, 700);
expect(copy.minLeadingWidth, 800);
expect(copy.enableFeedback, false);
expect(copy.titleAlignment, ListTileTitleAlignment.top);
});
testWidgets('ListTileTheme.titleAlignment is overridden by ListTile.titleAlignment', (WidgetTester tester) async {
final Key leadingKey = GlobalKey();
final Key trailingKey = GlobalKey();
const String titleText = '\nHeadline Text\n';
const String subtitleText = '\nSupporting Text\n';
Widget buildFrame({ ListTileTitleAlignment? alignment }) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
listTileTheme: const ListTileThemeData(
titleAlignment: ListTileTitleAlignment.center,
),
),
home: Material(
child: Center(
child: ListTile(
titleAlignment: ListTileTitleAlignment.top,
leading: SizedBox(key: leadingKey, width: 24.0, height: 24.0),
title: const Text(titleText),
subtitle: const Text(subtitleText),
trailing: SizedBox(key: trailingKey, width: 24.0, height: 24.0),
),
),
),
);
}
await tester.pumpWidget(buildFrame());
final Offset tileOffset = tester.getTopLeft(find.byType(ListTile));
final Offset leadingOffset = tester.getTopLeft(find.byKey(leadingKey));
final Offset trailingOffset = tester.getTopRight(find.byKey(trailingKey));
expect(leadingOffset.dy - tileOffset.dy, 8.0);
expect(trailingOffset.dy - tileOffset.dy, 8.0);
});
testWidgets('ListTileTheme.merge supports all properties', (WidgetTester tester) async {
Widget buildFrame() {
return MaterialApp(
theme: ThemeData(
listTileTheme: const ListTileThemeData(
dense: true,
shape: StadiumBorder(),
style: ListTileStyle.drawer,
selectedColor: Color(0x00000001),
iconColor: Color(0x00000002),
textColor: Color(0x00000003),
titleTextStyle: TextStyle(color: Color(0x00000004)),
subtitleTextStyle: TextStyle(color: Color(0x00000005)),
leadingAndTrailingTextStyle: TextStyle(color: Color(0x00000006)),
contentPadding: EdgeInsets.all(100),
tileColor: Color(0x00000007),
selectedTileColor: Color(0x00000008),
horizontalTitleGap: 200,
minVerticalPadding: 300,
minLeadingWidth: 400,
enableFeedback: true,
titleAlignment: ListTileTitleAlignment.bottom,
mouseCursor: MaterialStateMouseCursor.textable,
visualDensity: VisualDensity.comfortable,
),
),
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ListTileTheme.merge(
dense: false,
shape: const RoundedRectangleBorder(),
style: ListTileStyle.list,
selectedColor: const Color(0x00000009),
iconColor: const Color(0x0000000A),
textColor: const Color(0x0000000B),
titleTextStyle: const TextStyle(color: Color(0x0000000C)),
subtitleTextStyle: const TextStyle(color: Color(0x0000000D)),
leadingAndTrailingTextStyle: const TextStyle(color: Color(0x0000000E)),
contentPadding: const EdgeInsets.all(500),
tileColor: const Color(0x0000000F),
selectedTileColor: const Color(0x00000010),
horizontalTitleGap: 600,
minVerticalPadding: 700,
minLeadingWidth: 800,
enableFeedback: false,
titleAlignment: ListTileTitleAlignment.top,
mouseCursor: MaterialStateMouseCursor.clickable,
visualDensity: VisualDensity.compact,
child: const ListTile(),
);
}
),
),
),
);
}
await tester.pumpWidget(buildFrame());
final ListTileThemeData theme = ListTileTheme.of(tester.element(find.byType(ListTile)));
expect(theme.dense, false);
expect(theme.shape, const RoundedRectangleBorder());
expect(theme.style, ListTileStyle.list);
expect(theme.selectedColor, const Color(0x00000009));
expect(theme.iconColor, const Color(0x0000000A));
expect(theme.textColor, const Color(0x0000000B));
expect(theme.titleTextStyle, const TextStyle(color: Color(0x0000000C)));
expect(theme.subtitleTextStyle, const TextStyle(color: Color(0x0000000D)));
expect(theme.leadingAndTrailingTextStyle, const TextStyle(color: Color(0x0000000E)));
expect(theme.contentPadding, const EdgeInsets.all(500));
expect(theme.tileColor, const Color(0x0000000F));
expect(theme.selectedTileColor, const Color(0x00000010));
expect(theme.horizontalTitleGap, 600);
expect(theme.minVerticalPadding, 700);
expect(theme.minLeadingWidth, 800);
expect(theme.enableFeedback, false);
expect(theme.titleAlignment, ListTileTitleAlignment.top);
expect(theme.mouseCursor, MaterialStateMouseCursor.clickable);
expect(theme.visualDensity, VisualDensity.compact);
});
}
RenderParagraph _getTextRenderObject(WidgetTester tester, String text) {
return tester.renderObject(find.descendant(
of: find.byType(ListTile),
matching: find.text(text),
));
}