blob: 2f01474f4097ccc937777b320e08b82684f3f7a0 [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.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.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.expand_more);
}
}
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() {
const Color dividerColor = Color(0x1f333333);
const Color foregroundColor = Colors.blueAccent;
const Color unselectedWidgetColor = Colors.black54;
const Color headerColor = Colors.black45;
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(find.descendant(
of: find.byType(ExpansionTile),
matching: find.byType(Material),
));
}
testWidgets('ExpansionTile initial state', (WidgetTester tester) async {
final Key topKey = UniqueKey();
final Key tileKey = UniqueKey();
const Key expandedKey = PageStorageKey<String>('expanded');
const Key collapsedKey = PageStorageKey<String>('collapsed');
const Key defaultKey = PageStorageKey<String>('default');
await tester.pumpWidget(MaterialApp(
theme: ThemeData(dividerColor: dividerColor),
home: Material(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
ListTile(title: const Text('Top'), key: topKey),
ExpansionTile(
key: expandedKey,
initiallyExpanded: true,
title: const Text('Expanded'),
backgroundColor: Colors.red,
children: <Widget>[
ListTile(
key: tileKey,
title: const Text('0'),
),
],
),
ExpansionTile(
key: collapsedKey,
title: const Text('Collapsed'),
children: <Widget>[
ListTile(
key: tileKey,
title: const Text('0'),
),
],
),
const ExpansionTile(
key: defaultKey,
title: Text('Default'),
children: <Widget>[
ListTile(title: Text('0')),
],
),
],
),
),
),
));
double getHeight(Key key) => tester.getSize(find.byKey(key)).height;
DecoratedBox getDecoratedBox(Key key) => tester.firstWidget(find.descendant(
of: find.byKey(key),
matching: find.byType(DecoratedBox),
));
expect(getHeight(topKey), getHeight(expandedKey) - getHeight(tileKey) - 2.0);
expect(getHeight(topKey), getHeight(collapsedKey) - 2.0);
expect(getHeight(topKey), getHeight(defaultKey) - 2.0);
ShapeDecoration expandedContainerDecoration = getDecoratedBox(expandedKey).decoration as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.red);
expect((expandedContainerDecoration.shape as Border).top.color, dividerColor);
expect((expandedContainerDecoration.shape as Border).bottom.color, dividerColor);
ShapeDecoration collapsedContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
await tester.tap(find.text('Expanded'));
await tester.tap(find.text('Collapsed'));
await tester.tap(find.text('Default'));
await tester.pump();
// Pump to the middle of the animation for expansion.
await tester.pump(const Duration(milliseconds: 100));
final ShapeDecoration collapsingContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsingContainerDecoration.color, Colors.transparent);
expect((collapsingContainerDecoration.shape as Border).top.color, const Color(0x15222222));
expect((collapsingContainerDecoration.shape as Border).bottom.color, const Color(0x15222222));
// Pump all the way to the end now.
await tester.pump(const Duration(seconds: 1));
expect(getHeight(topKey), getHeight(expandedKey) - 2.0);
expect(getHeight(topKey), getHeight(collapsedKey) - getHeight(tileKey) - 2.0);
expect(getHeight(topKey), getHeight(defaultKey) - getHeight(tileKey) - 2.0);
// Expanded should be collapsed now.
expandedContainerDecoration = getDecoratedBox(expandedKey).decoration as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
// Collapsed should be expanded now.
collapsedContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).top.color, dividerColor);
expect((collapsedContainerDecoration.shape as Border).bottom.color, dividerColor);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('ExpansionTile Theme dependencies', (WidgetTester tester) async {
final Key expandedTitleKey = UniqueKey();
final Key collapsedTitleKey = UniqueKey();
final Key expandedIconKey = UniqueKey();
final Key collapsedIconKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
useMaterial3: false,
colorScheme: ColorScheme.fromSwatch().copyWith(primary: foregroundColor),
unselectedWidgetColor: unselectedWidgetColor,
textTheme: const TextTheme(titleMedium: TextStyle(color: headerColor)),
),
home: Material(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
const ListTile(title: Text('Top')),
ExpansionTile(
initiallyExpanded: true,
title: TestText('Expanded', key: expandedTitleKey),
backgroundColor: Colors.red,
trailing: TestIcon(key: expandedIconKey),
children: const <Widget>[ListTile(title: Text('0'))],
),
ExpansionTile(
title: TestText('Collapsed', key: collapsedTitleKey),
trailing: TestIcon(key: collapsedIconKey),
children: const <Widget>[ListTile(title: Text('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!;
expect(textColor(expandedTitleKey), foregroundColor);
expect(textColor(collapsedTitleKey), headerColor);
expect(iconColor(expandedIconKey), foregroundColor);
expect(iconColor(collapsedIconKey), unselectedWidgetColor);
// Tap both tiles to change their state: collapse and extend respectively
await tester.tap(find.text('Expanded'));
await tester.tap(find.text('Collapsed'));
await tester.pump();
await tester.pump(const Duration(seconds: 1));
await tester.pump(const Duration(seconds: 1));
expect(textColor(expandedTitleKey), headerColor);
expect(textColor(collapsedTitleKey), foregroundColor);
expect(iconColor(expandedIconKey), unselectedWidgetColor);
expect(iconColor(collapsedIconKey), foregroundColor);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('ExpansionTile subtitle',
// TODO(polina-c): remove when fixed https://github.com/flutter/flutter/issues/145600 [leak-tracking-opt-in]
experimentalLeakTesting: LeakTesting.settings.withTracked(classes: const <String>['CurvedAnimation']),
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: ExpansionTile(
title: Text('Title'),
subtitle: Text('Subtitle'),
children: <Widget>[ListTile(title: Text('0'))],
),
),
),
);
expect(find.text('Subtitle'), findsOneWidget);
});
testWidgets('ExpansionTile maintainState', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
platform: TargetPlatform.iOS,
dividerColor: dividerColor,
),
home: const Material(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('Tile 1'),
maintainState: true,
children: <Widget>[
Text('Maintaining State'),
],
),
ExpansionTile(
title: Text('Title 2'),
children: <Widget>[
Text('Discarding State'),
],
),
],
),
),
),
),
);
// This text should be offstage while ExpansionTile collapsed
expect(find.text('Maintaining State', skipOffstage: false), findsOneWidget);
expect(find.text('Maintaining State'), findsNothing);
// This text shouldn't be there while ExpansionTile collapsed
expect(find.text('Discarding State'), findsNothing);
});
testWidgets('ExpansionTile padding test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('Hello'),
tilePadding: EdgeInsets.fromLTRB(8, 12, 4, 10),
),
),
),
));
final Rect titleRect = tester.getRect(find.text('Hello'));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more));
final Rect listTileRect = tester.getRect(find.byType(ListTile));
final Rect tallerWidget = titleRect.height > trailingRect.height ? titleRect : trailingRect;
// Check the positions of title and trailing Widgets, after padding is applied.
expect(listTileRect.left, titleRect.left - 8);
expect(listTileRect.right, trailingRect.right + 4);
// Calculate the remaining height of ListTile from the default height.
final double remainingHeight = 56 - tallerWidget.height;
expect(listTileRect.top, tallerWidget.top - remainingHeight / 2 - 12);
expect(listTileRect.bottom, tallerWidget.bottom + remainingHeight / 2 + 10);
});
testWidgets('ExpansionTile expandedAlignment test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('title'),
expandedAlignment: Alignment.centerLeft,
children: <Widget>[
SizedBox(height: 100, width: 100),
SizedBox(height: 100, width: 80),
],
),
),
),
));
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
final Rect columnRect = tester.getRect(find.byType(Column).last);
// The expandedAlignment is used to define the alignment of the Column widget in
// expanded tile, not the alignment of the children inside the Column.
expect(columnRect.left, 0.0);
// The width of the Column is the width of the largest child. The largest width
// being 100.0, the offset of the right edge of Column from X-axis should be 100.0.
expect(columnRect.right, 100.0);
});
testWidgets('ExpansionTile expandedCrossAxisAlignment test', (WidgetTester tester) async {
const Key child0Key = Key('child0');
const Key child1Key = Key('child1');
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('title'),
// Set the column's alignment to Alignment.centerRight to test CrossAxisAlignment
// of children widgets. This helps distinguish the effect of expandedAlignment
// and expandedCrossAxisAlignment later in the test.
expandedAlignment: Alignment.centerRight,
expandedCrossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 100, width: 100, key: child0Key),
SizedBox(height: 100, width: 80, key: child1Key),
],
),
),
),
));
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
final Rect columnRect = tester.getRect(find.byType(Column).last);
final Rect child0Rect = tester.getRect(find.byKey(child0Key));
final Rect child1Rect = tester.getRect(find.byKey(child1Key));
// Since expandedAlignment is set to Alignment.centerRight, the column of children
// should be aligned to the center right of the expanded tile. This provides confirmation
// that the expandedCrossAxisAlignment.start is 700.0, where columnRect.left is.
expect(columnRect.right, 800.0);
// The width of the Column is the width of the largest child. The largest width
// being 100.0, the offset of the left edge of Column from X-axis should be 700.0.
expect(columnRect.left, 700.0);
// Considering the value of expandedCrossAxisAlignment is CrossAxisAlignment.start,
// the offset of the left edge of both the children from X-axis should be 700.0.
expect(child0Rect.left, 700.0);
expect(child1Rect.left, 700.0);
});
testWidgets('CrossAxisAlignment.baseline is not allowed', (WidgetTester tester) async {
expect(
() {
MaterialApp(
home: Material(
child: ExpansionTile(
initiallyExpanded: true,
title: const Text('title'),
expandedCrossAxisAlignment: CrossAxisAlignment.baseline,
),
),
);
},
throwsA(isA<AssertionError>().having((AssertionError error) => error.toString(), '.toString()', contains(
'CrossAxisAlignment.baseline is not supported since the expanded'
' children are aligned in a column, not a row. Try to use another constant.',
))),
);
});
testWidgets('expandedCrossAxisAlignment and expandedAlignment default values', (WidgetTester tester) async {
const Key child1Key = Key('child1');
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('title'),
children: <Widget>[
SizedBox(height: 100, width: 100),
SizedBox(height: 100, width: 80, key: child1Key),
],
),
),
),
));
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
final Rect columnRect = tester.getRect(find.byType(Column).last);
final Rect child1Rect = tester.getRect(find.byKey(child1Key));
// The default viewport size is Size(800, 600).
// By default the value of extendedAlignment is Alignment.center, hence the offset
// of left and right edges from x axis should be equal.
expect(columnRect.left, 800 - columnRect.right);
// By default the value of extendedCrossAxisAlignment is CrossAxisAlignment.center, hence
// the offset of left and right edges from Column should be equal.
expect(child1Rect.left - columnRect.left, columnRect.right - child1Rect.right);
});
testWidgets('childrenPadding default value', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('title'),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
),
);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
final Rect columnRect = tester.getRect(find.byType(Column).last);
final Rect paddingRect = tester.getRect(find.byType(Padding).last);
// By default, the value of childrenPadding is EdgeInsets.zero, hence offset
// of all the edges from x-axis and y-axis should be equal for Padding and Column.
expect(columnRect.top, paddingRect.top);
expect(columnRect.left, paddingRect.left);
expect(columnRect.right, paddingRect.right);
expect(columnRect.bottom, paddingRect.bottom);
});
testWidgets('ExpansionTile childrenPadding test', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
title: Text('title'),
childrenPadding: EdgeInsets.fromLTRB(10, 8, 12, 4),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
),
);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
final Rect columnRect = tester.getRect(find.byType(Column).last);
final Rect paddingRect = tester.getRect(find.byType(Padding).last);
// Check the offset of all the edges from x-axis and y-axis after childrenPadding
// is applied.
expect(columnRect.left, paddingRect.left + 10);
expect(columnRect.top, paddingRect.top + 8);
expect(columnRect.right, paddingRect.right - 12);
expect(columnRect.bottom, paddingRect.bottom - 4);
});
testWidgets('ExpansionTile.collapsedBackgroundColor', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
const Color backgroundColor = Colors.red;
const Color collapsedBackgroundColor = Colors.brown;
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
key: expansionTileKey,
title: Text('Title'),
backgroundColor: backgroundColor,
collapsedBackgroundColor: collapsedBackgroundColor,
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
ShapeDecoration shapeDecoration = tester.firstWidget<DecoratedBox>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(DecoratedBox),
)).decoration as ShapeDecoration;
expect(shapeDecoration.color, collapsedBackgroundColor);
await tester.tap(find.text('Title'));
await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<DecoratedBox>(find.descendant(
of: find.byKey(expansionTileKey),
matching: find.byType(DecoratedBox),
)).decoration as ShapeDecoration;
expect(shapeDecoration.color, backgroundColor);
});
testWidgets('ExpansionTile default iconColor, textColor', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Material(
child: ExpansionTile(
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
expect(getIconColor(), theme.colorScheme.onSurfaceVariant);
expect(getTextColor(), theme.colorScheme.onSurface);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
expect(getIconColor(), theme.colorScheme.primary);
expect(getTextColor(), theme.colorScheme.onSurface);
});
testWidgets('ExpansionTile iconColor, textColor', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/pull/78281
const Color iconColor = Color(0xff00ff00);
const Color collapsedIconColor = Color(0xff0000ff);
const Color textColor = Color(0xff00ffff);
const Color collapsedTextColor = Color(0xffff00ff);
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
iconColor: iconColor,
collapsedIconColor: collapsedIconColor,
textColor: textColor,
collapsedTextColor: collapsedTextColor,
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
expect(getIconColor(), collapsedIconColor);
expect(getTextColor(), collapsedTextColor);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
expect(getIconColor(), iconColor);
expect(getTextColor(), textColor);
});
testWidgets('ExpansionTile Border', (WidgetTester tester) async {
const Key expansionTileKey = PageStorageKey<String>('expansionTile');
const Border collapsedShape = Border(
top: BorderSide(color: Colors.blue),
bottom: BorderSide(color: Colors.green)
);
final Border shape = Border.all(color: Colors.red);
await tester.pumpWidget(MaterialApp(
home: Material(
child: ExpansionTile(
key: expansionTileKey,
title: const Text('ExpansionTile'),
collapsedShape: collapsedShape,
shape: shape,
children: const <Widget>[
ListTile(
title: Text('0'),
),
],
),
),
));
// When a custom shape is provided, ExpansionTile will use the
// Material widget to draw the shape and background color
// instead of a Container.
Material material = getMaterial(tester);
// ExpansionTile should be collapsed initially.
expect(material.shape, collapsedShape);
expect(material.clipBehavior, Clip.antiAlias);
await tester.tap(find.text('ExpansionTile'));
await tester.pumpAndSettle();
// ExpansionTile should be Expanded now.
material = getMaterial(tester);
expect(material.shape, shape);
expect(material.clipBehavior, Clip.antiAlias);
});
testWidgets('ExpansionTile platform controlAffinity test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
),
),
));
final ListTile listTile = tester.widget(find.byType(ListTile));
expect(listTile.leading, isNull);
expect(listTile.trailing.runtimeType, RotationTransition);
});
testWidgets('ExpansionTile trailing controlAffinity test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
controlAffinity: ListTileControlAffinity.trailing,
),
),
));
final ListTile listTile = tester.widget(find.byType(ListTile));
expect(listTile.leading, isNull);
expect(listTile.trailing.runtimeType, RotationTransition);
});
testWidgets('ExpansionTile leading controlAffinity test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
controlAffinity: ListTileControlAffinity.leading,
),
),
));
final ListTile listTile = tester.widget(find.byType(ListTile));
expect(listTile.leading.runtimeType, RotationTransition);
expect(listTile.trailing, isNull);
});
testWidgets('ExpansionTile override rotating icon test', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
leading: Icon(Icons.info),
controlAffinity: ListTileControlAffinity.leading,
),
),
));
final ListTile listTile = tester.widget(find.byType(ListTile));
expect(listTile.leading.runtimeType, Icon);
expect(listTile.trailing, isNull);
});
testWidgets('Nested ListTile Semantics', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final SemanticsHandle handle = tester.ensureSemantics();
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('First Expansion Tile'),
),
ExpansionTile(
initiallyExpanded: true,
title: Text('Second Expansion Tile'),
),
],
),
),
));
await tester.pumpAndSettle();
// Focus the first ExpansionTile.
tester.binding.focusManager.primaryFocus?.nextFocus();
await tester.pumpAndSettle();
// The first list tile is focused.
expect(
tester.getSemantics(find.byType(ListTile).first),
matchesSemantics(
hasTapAction: true,
hasEnabledState: true,
isEnabled: true,
isFocused: true,
isFocusable: true,
label: 'First Expansion Tile',
textDirection: TextDirection.ltr,
),
);
// The first list tile is not focused.
expect(
tester.getSemantics(find.byType(ListTile).last),
matchesSemantics(
hasTapAction: true,
hasEnabledState: true,
isEnabled: true,
isFocusable: true,
label: 'Second Expansion Tile',
textDirection: TextDirection.ltr,
),
);
handle.dispose();
});
testWidgets('ExpansionTile Semantics announcement', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
);
// There is no semantics announcement without tap action.
expect(tester.takeAnnouncements(), isEmpty);
// Tap the title to expand ExpansionTile.
await tester.tap(find.text('Title'));
await tester.pumpAndSettle();
// The announcement should be the opposite of the current state.
// The ExpansionTile is expanded, so the announcement should be
// "Expanded".
expect(tester.takeAnnouncements().first.message, localizations.collapsedHint);
// Tap the title to collapse ExpansionTile.
await tester.tap(find.text('Title'));
await tester.pumpAndSettle();
// The announcement should be the opposite of the current state.
// The ExpansionTile is collapsed, so the announcement should be
// "Collapsed".
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
handle.dispose();
}, skip: defaultTargetPlatform == TargetPlatform.iOS); // [intended] https://github.com/flutter/flutter/issues/122101.
// This is a regression test for https://github.com/flutter/flutter/issues/132264.
testWidgets('ExpansionTile Semantics announcement is delayed on iOS', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
await tester.pumpWidget(
const MaterialApp(
home: Material(
child: ExpansionTile(
title: Text('Title'),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
);
// There is no semantics announcement without tap action.
expect(tester.takeAnnouncements(), isEmpty);
// Tap the title to expand ExpansionTile.
await tester.tap(find.text('Title'));
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
expect(tester.takeAnnouncements().first.message, localizations.collapsedHint);
// Tap the title to collapse ExpansionTile.
await tester.tap(find.text('Title'));
await tester.pump(const Duration(seconds: 1)); // Wait for the announcement to be made.
expect(tester.takeAnnouncements().first.message, localizations.expandedHint);
handle.dispose();
}, variant: TargetPlatformVariant.only(TargetPlatform.iOS));
testWidgets('Semantics with the onTapHint is an ancestor of ListTile', (WidgetTester tester) async {
// This is a regression test for https://github.com/flutter/flutter/pull/121624
final SemanticsHandle handle = tester.ensureSemantics();
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('First Expansion Tile'),
),
ExpansionTile(
initiallyExpanded: true,
title: Text('Second Expansion Tile'),
),
],
),
),
));
SemanticsNode semantics = tester.getSemantics(
find.ancestor(
of: find.byType(ListTile).first,
matching: find.byType(Semantics),
).first,
);
expect(semantics, isNotNull);
// The onTapHint is passed to semantics properties's hintOverrides.
expect(semantics.hintOverrides, isNotNull);
// The hint should be the opposite of the current state.
// The first ExpansionTile is collapsed, so the hint should be
// "double tap to expand".
expect(semantics.hintOverrides!.onTapHint, localizations.expansionTileCollapsedTapHint);
semantics = tester.getSemantics(
find.ancestor(
of: find.byType(ListTile).last,
matching: find.byType(Semantics),
).first,
);
expect(semantics, isNotNull);
// The onTapHint is passed to semantics properties's hintOverrides.
expect(semantics.hintOverrides, isNotNull);
// The hint should be the opposite of the current state.
// The second ExpansionTile is expanded, so the hint should be
// "double tap to collapse".
expect(semantics.hintOverrides!.onTapHint, localizations.expansionTileExpandedTapHint);
handle.dispose();
});
testWidgets('Semantics hint for iOS and macOS', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
const DefaultMaterialLocalizations localizations = DefaultMaterialLocalizations();
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('First Expansion Tile'),
),
ExpansionTile(
initiallyExpanded: true,
title: Text('Second Expansion Tile'),
),
],
),
),
));
SemanticsNode semantics = tester.getSemantics(
find.ancestor(
of: find.byType(ListTile).first,
matching: find.byType(Semantics),
).first,
);
expect(semantics, isNotNull);
expect(
semantics.hint,
'${localizations.expandedHint}\n ${localizations.expansionTileCollapsedHint}',
);
semantics = tester.getSemantics(
find.ancestor(
of: find.byType(ListTile).last,
matching: find.byType(Semantics),
).first,
);
expect(semantics, isNotNull);
expect(
semantics.hint,
'${localizations.collapsedHint}\n ${localizations.expansionTileExpandedHint}',
);
handle.dispose();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgets('Collapsed ExpansionTile properties can be updated with setState', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
ShapeBorder collapsedShape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(4)),
);
Color collapsedTextColor = const Color(0xffffffff);
Color collapsedBackgroundColor = const Color(0xffff0000);
Color collapsedIconColor = const Color(0xffffffff);
await tester.pumpWidget(MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey,
collapsedShape: collapsedShape,
collapsedTextColor: collapsedTextColor,
collapsedBackgroundColor: collapsedBackgroundColor,
collapsedIconColor: collapsedIconColor,
title: const TestText('title'),
trailing: const TestIcon(),
children: const <Widget>[
SizedBox(height: 100, width: 100),
],
),
// This button is used to update the ExpansionTile properties.
FilledButton(
onPressed: () {
setState(() {
collapsedShape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
collapsedTextColor = const Color(0xff000000);
collapsedBackgroundColor = const Color(0xffffff00);
collapsedIconColor = const Color(0xff000000);
});
},
child: const Text('Update collapsed properties'),
),
],
);
}
),
),
));
// When a custom shape is provided, ExpansionTile will use the
// Material widget to draw the shape and background color
// instead of a Container.
Material material = getMaterial(tester);
// Test initial ExpansionTile properties.
expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))));
expect(material.color, const Color(0xffff0000));
expect(material.clipBehavior, Clip.antiAlias);
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
// Tap the button to update the ExpansionTile properties.
await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle();
material = getMaterial(tester);
// Test updated ExpansionTile properties.
expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))));
expect(material.color, const Color(0xffffff00));
expect(material.clipBehavior, Clip.antiAlias);
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff000000));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff000000));
});
testWidgets('Expanded ExpansionTile properties can be updated with setState', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
ShapeBorder shape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12)),
);
Color textColor = const Color(0xff00ffff);
Color backgroundColor = const Color(0xff0000ff);
Color iconColor = const Color(0xff00ffff);
await tester.pumpWidget(MaterialApp(
home: Material(
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey,
shape: shape,
textColor: textColor,
backgroundColor: backgroundColor,
iconColor: iconColor,
title: const TestText('title'),
trailing: const TestIcon(),
children: const <Widget>[
SizedBox(height: 100, width: 100),
],
),
// This button is used to update the ExpansionTile properties.
FilledButton(
onPressed: () {
setState(() {
shape = const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6)),
);
textColor = const Color(0xffffffff);
backgroundColor = const Color(0xff123456);
iconColor = const Color(0xffffffff);
});
},
child: const Text('Update collapsed properties'),
),
],
);
}
),
),
));
// Tap to expand the ExpansionTile.
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
// When a custom shape is provided, ExpansionTile will use the
// Material widget to draw the shape and background color
// instead of a Container.
Material material = getMaterial(tester);
// Test initial ExpansionTile properties.
expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))));
expect(material.color, const Color(0xff0000ff));
expect(material.clipBehavior, Clip.antiAlias);
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff00ffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff00ffff));
// Tap the button to update the ExpansionTile properties.
await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle();
material = getMaterial(tester);
iconColor = tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
textColor = tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
// Test updated ExpansionTile properties.
expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6))));
expect(material.color, const Color(0xff123456));
expect(material.clipBehavior, Clip.antiAlias);
expect(tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
});
testWidgets('Override ExpansionTile animation using AnimationStyle', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
Widget buildExpansionTile({ AnimationStyle? animationStyle }) {
return MaterialApp(
home: Material(
child: Center(
child: ExpansionTile(
key: expansionTileKey,
expansionAnimationStyle: animationStyle,
title: const TestText('title'),
children: const <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
);
}
await tester.pumpWidget(buildExpansionTile());
double getHeight(Key key) => tester.getSize(find.byKey(key)).height;
// Test initial ExpansionTile height.
expect(getHeight(expansionTileKey), 58.0);
// Test the default expansion animation.
await tester.tap(find.text('title'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
await tester.pumpAndSettle(); // Advance the animation to the end.
expect(getHeight(expansionTileKey), 158.0);
// Tap to collapse the ExpansionTile.
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
// Override the animation duration.
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(duration: const Duration(milliseconds: 800))));
await tester.pumpAndSettle();
// Test the overridden animation duration.
await tester.tap(find.text('title'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 1/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(67.4, 0.1));
await tester.pump(const Duration(milliseconds: 200)); // Advance the animation by 2/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(89.6, 0.1));
await tester.pumpAndSettle(); // Advance the animation to the end.
expect(getHeight(expansionTileKey), 158.0);
// Tap to collapse the ExpansionTile.
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
// Override the animation curve.
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle(
curve: Easing.emphasizedDecelerate,
reverseCurve: Easing.emphasizedAccelerate,
)));
await tester.pumpAndSettle();
// Test the overridden animation curve.
await tester.tap(find.text('title'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(141.2, 0.1));
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(153, 0.1));
await tester.pumpAndSettle(); // Advance the animation to the end.
expect(getHeight(expansionTileKey), 158.0);
// Test the overridden reverse (collapse) animation curve.
await tester.tap(find.text('title'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 1/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(98.6, 0.1));
await tester.pump(const Duration(milliseconds: 50)); // Advance the animation by 2/4 of its duration.
expect(getHeight(expansionTileKey), closeTo(73.4, 0.1));
await tester.pumpAndSettle(); // Advance the animation to the end.
expect(getHeight(expansionTileKey), 58.0);
// Test no animation.
await tester.pumpWidget(buildExpansionTile(animationStyle: AnimationStyle.noAnimation));
// Tap to expand the ExpansionTile.
await tester.tap(find.text('title'));
await tester.pump();
expect(getHeight(expansionTileKey), 158.0);
});
testWidgets('Material3 - ExpansionTile draws Inkwell splash on top of background color', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
const ShapeBorder collapsedShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
const Color collapsedBackgroundColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xffff0000);
await tester.pumpWidget(const MaterialApp(
home: Material(
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: ExpansionTile(
key: expansionTileKey,
shape: shape,
collapsedBackgroundColor: collapsedBackgroundColor,
backgroundColor: backgroundColor,
collapsedShape: collapsedShape,
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
),
));
// Tap and hold the ExpansionTile to trigger ink splash.
final Offset center = tester.getCenter(find.byKey(expansionTileKey));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // Start the splash animation.
await tester.pump(const Duration(milliseconds: 100)); // Splash is underway.
// Material 3 uses the InkSparkle which uses a shader, so we can't capture
// the effect with paint methods. Use a golden test instead.
// Check if the ink sparkle is drawn on top of the background color.
await expectLater(
find.byKey(expansionTileKey),
matchesGoldenFile('expansion_tile.ink_splash.drawn_on_top_of_background_color.png'),
);
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
testWidgets('Default clipBehavior when a shape is provided', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: ExpansionTile(
title: Text('Title'),
subtitle: Text('Subtitle'),
shape: StadiumBorder(),
children: <Widget>[ListTile(title: Text('0'))],
),
),
),
);
expect(getMaterial(tester).clipBehavior, Clip.antiAlias);
});
testWidgets('Can override clipBehavior when a shape is provided', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: ExpansionTile(
title: Text('Title'),
subtitle: Text('Subtitle'),
shape: StadiumBorder(),
clipBehavior: Clip.none,
children: <Widget>[ListTile(title: Text('0'))],
),
),
),
);
expect(getMaterial(tester).clipBehavior, Clip.none);
});
group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests
// can be deleted.
testWidgets('ExpansionTile default iconColor, textColor', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: false);
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Material(
child: ExpansionTile(
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
));
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
expect(getIconColor(), theme.unselectedWidgetColor);
expect(getTextColor(), theme.textTheme.titleMedium!.color);
await tester.tap(find.text('title'));
await tester.pumpAndSettle();
expect(getIconColor(), theme.colorScheme.primary);
expect(getTextColor(), theme.colorScheme.primary);
});
testWidgets('Material2 - ExpansionTile draws inkwell splash on top of background color', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
final ThemeData theme = ThemeData(useMaterial3: false);
const ShapeBorder shape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
const ShapeBorder collapsedShape = RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16)),
);
const Color collapsedBackgroundColor = Color(0xff00ff00);
const Color backgroundColor = Color(0xffff0000);
await tester.pumpWidget(MaterialApp(
theme: theme,
home: const Material(
child: Center(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: ExpansionTile(
key: expansionTileKey,
shape: shape,
collapsedBackgroundColor: collapsedBackgroundColor,
backgroundColor: backgroundColor,
collapsedShape: collapsedShape,
title: TestText('title'),
trailing: TestIcon(),
children: <Widget>[
SizedBox(height: 100, width: 100),
],
),
),
),
),
));
// Tap and hold the ExpansionTile to trigger ink splash.
final Offset center = tester.getCenter(find.byKey(expansionTileKey));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // Start the splash animation.
await tester.pump(const Duration(milliseconds: 100)); // Splash is underway.
final RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
// Check if the ink splash is drawn on top of the background color.
expect(inkFeatures, paints..path(color: collapsedBackgroundColor)..circle(color: theme.splashColor));
// Finish gesture to release resources.
await gesture.up();
await tester.pumpAndSettle();
});
});
testWidgets('ExpansionTileController isExpanded, expand() and collapse()', (WidgetTester tester) async {
final ExpansionTileController controller = ExpansionTileController();
await tester.pumpWidget(MaterialApp(
home: Material(
child: ExpansionTile(
controller: controller,
title: const Text('Title'),
children: const <Widget>[
Text('Child 0'),
],
),
),
));
expect(find.text('Child 0'), findsNothing);
expect(controller.isExpanded, isFalse);
controller.expand();
expect(controller.isExpanded, isTrue);
await tester.pumpAndSettle();
expect(find.text('Child 0'), findsOneWidget);
expect(controller.isExpanded, isTrue);
controller.collapse();
expect(controller.isExpanded, isFalse);
await tester.pumpAndSettle();
expect(find.text('Child 0'), findsNothing);
});
testWidgets('Calling ExpansionTileController.expand/collapsed has no effect if it is already expanded/collapsed', (WidgetTester tester) async {
final ExpansionTileController controller = ExpansionTileController();
await tester.pumpWidget(MaterialApp(
home: Material(
child: ExpansionTile(
controller: controller,
title: const Text('Title'),
initiallyExpanded: true,
children: const <Widget>[
Text('Child 0'),
],
),
),
));
expect(find.text('Child 0'), findsOneWidget);
expect(controller.isExpanded, isTrue);
controller.expand();
expect(controller.isExpanded, isTrue);
await tester.pump();
expect(tester.hasRunningAnimations, isFalse);
expect(find.text('Child 0'), findsOneWidget);
controller.collapse();
expect(controller.isExpanded, isFalse);
await tester.pump();
expect(tester.hasRunningAnimations, isTrue);
await tester.pumpAndSettle();
expect(controller.isExpanded, isFalse);
expect(find.text('Child 0'), findsNothing);
controller.collapse();
expect(controller.isExpanded, isFalse);
await tester.pump();
expect(tester.hasRunningAnimations, isFalse);
});
testWidgets('Call to ExpansionTileController.of()', (WidgetTester tester) async {
final GlobalKey titleKey = GlobalKey();
final GlobalKey childKey = GlobalKey();
await tester.pumpWidget(MaterialApp(
home: Material(
child: ExpansionTile(
initiallyExpanded: true,
title: Text('Title', key: titleKey),
children: <Widget>[
Text('Child 0', key: childKey),
],
),
),
));
final ExpansionTileController controller1 = ExpansionTileController.of(childKey.currentContext!);
expect(controller1.isExpanded, isTrue);
final ExpansionTileController controller2 = ExpansionTileController.of(titleKey.currentContext!);
expect(controller2.isExpanded, isTrue);
expect(controller1, controller2);
});
testWidgets('Call to ExpansionTile.maybeOf()', (WidgetTester tester) async {
final GlobalKey titleKey = GlobalKey();
final GlobalKey nonDescendantKey = GlobalKey();
await tester.pumpWidget(MaterialApp(
home: Material(
child: Column(
children: <Widget>[
ExpansionTile(
title: Text('Title', key: titleKey),
children: const <Widget>[
Text('Child 0'),
],
),
Text('Non descendant', key: nonDescendantKey),
],
),
),
));
final ExpansionTileController? controller1 = ExpansionTileController.maybeOf(titleKey.currentContext!);
expect(controller1, isNotNull);
expect(controller1?.isExpanded, isFalse);
final ExpansionTileController? controller2 = ExpansionTileController.maybeOf(nonDescendantKey.currentContext!);
expect(controller2, isNull);
});
testWidgets('Check if dense, enableFeedback, visualDensity parameter is working', (WidgetTester tester) async {
final GlobalKey titleKey = GlobalKey();
final GlobalKey nonDescendantKey = GlobalKey();
const bool dense = true;
const bool enableFeedback = false;
const VisualDensity visualDensity = VisualDensity.compact;
await tester.pumpWidget(MaterialApp(
home: Material(
child: Column(
children: <Widget>[
ExpansionTile(
dense: dense,
enableFeedback: enableFeedback,
visualDensity: visualDensity,
title: Text('Title', key: titleKey),
children: const <Widget>[
Text('Child 0'),
],
),
Text('Non descendant', key: nonDescendantKey),
],
),
),
));
final Finder tileFinder = find.byType(ListTile);
final ListTile tileWidget = tester.widget<ListTile>(tileFinder);
expect(tileWidget.dense, dense);
expect(tileWidget.enableFeedback, enableFeedback);
expect(tileWidget.visualDensity, visualDensity);
});
testWidgets('ExpansionTileController should not toggle if disabled', (WidgetTester tester) async {
final ExpansionTileController controller = ExpansionTileController();
await tester.pumpWidget(MaterialApp(
home: Material(
child: ExpansionTile(
enabled: false,
controller: controller,
title: const Text('Title'),
children: const <Widget>[
Text('Child 0'),
],
),
),
));
expect(find.text('Child 0'), findsNothing);
expect(controller.isExpanded, isFalse);
await tester.tap(find.widgetWithText(ExpansionTile, 'Title'));
await tester.pumpAndSettle();
expect(find.text('Child 0'), findsNothing);
expect(controller.isExpanded, isFalse);
controller.expand();
await tester.pumpAndSettle();
expect(find.text('Child 0'), findsOneWidget);
expect(controller.isExpanded, isTrue);
await tester.tap(find.widgetWithText(ExpansionTile, 'Title'));
await tester.pumpAndSettle();
expect(find.text('Child 0'), findsOneWidget);
expect(controller.isExpanded, isTrue);
});
testWidgets('ExpansionTile does not include the default trailing icon when showTrailingIcon: false (#145268)', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
enabled: false,
tilePadding: EdgeInsets.zero,
title: ColoredBox(color: Colors.red, child: Text('Title')),
showTrailingIcon: false,
),
),
));
final Size materialAppSize = tester.getSize(find.byType(MaterialApp));
final Size titleSize = tester.getSize(find.byType(ColoredBox));
expect(titleSize.width, materialAppSize.width);
});
testWidgets('ExpansionTile with smaller trailing widget allocates at least 32.0 units of space (preserves original behavior) (#145268)', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Material(
child: ExpansionTile(
enabled: false,
tilePadding: EdgeInsets.zero,
title: ColoredBox(color: Colors.red, child: Text('Title')),
trailing: SizedBox.shrink(),
),
),
));
final Size materialAppSize = tester.getSize(find.byType(MaterialApp));
final Size titleSize = tester.getSize(find.byType(ColoredBox));
expect(titleSize.width, materialAppSize.width - 32.0);
});
}