blob: 96a89d0cabe49b2eb8bec4f78946e694f7ea03f9 [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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.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() {
Material getMaterial(WidgetTester tester) {
return tester.widget<Material>(find.descendant(
of: find.byType(ExpansionTile),
matching: find.byType(Material),
));
}
test('ExpansionTileThemeData copyWith, ==, hashCode basics', () {
expect(const ExpansionTileThemeData(), const ExpansionTileThemeData().copyWith());
expect(const ExpansionTileThemeData().hashCode, const ExpansionTileThemeData().copyWith().hashCode);
});
test('ExpansionTileThemeData lerp special cases', () {
expect(ExpansionTileThemeData.lerp(null, null, 0), null);
const ExpansionTileThemeData data = ExpansionTileThemeData();
expect(identical(ExpansionTileThemeData.lerp(data, data, 0.5), data), true);
});
test('ExpansionTileThemeData defaults', () {
const ExpansionTileThemeData theme = ExpansionTileThemeData();
expect(theme.backgroundColor, null);
expect(theme.collapsedBackgroundColor, null);
expect(theme.tilePadding, null);
expect(theme.expandedAlignment, null);
expect(theme.childrenPadding, null);
expect(theme.iconColor, null);
expect(theme.collapsedIconColor, null);
expect(theme.textColor, null);
expect(theme.collapsedTextColor, null);
expect(theme.shape, null);
expect(theme.collapsedShape, null);
expect(theme.clipBehavior, null);
expect(theme.expansionAnimationStyle, null);
});
testWidgets('Default ExpansionTileThemeData debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
const TooltipThemeData().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('ExpansionTileThemeData implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
ExpansionTileThemeData(
backgroundColor: const Color(0xff000000),
collapsedBackgroundColor: const Color(0xff6f83fc),
tilePadding: const EdgeInsets.all(20.0),
expandedAlignment: Alignment.bottomCenter,
childrenPadding: const EdgeInsets.all(10.0),
iconColor: const Color(0xffa7c61c),
collapsedIconColor: const Color(0xffdd0b1f),
textColor: const Color(0xffffffff),
collapsedTextColor: const Color(0xff522bab),
shape: const Border(),
collapsedShape: const Border(),
clipBehavior: Clip.antiAlias,
expansionAnimationStyle: AnimationStyle(curve: Curves.easeInOut),
).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>[
'backgroundColor: Color(0xff000000)',
'collapsedBackgroundColor: Color(0xff6f83fc)',
'tilePadding: EdgeInsets.all(20.0)',
'expandedAlignment: Alignment.bottomCenter',
'childrenPadding: EdgeInsets.all(10.0)',
'iconColor: Color(0xffa7c61c)',
'collapsedIconColor: Color(0xffdd0b1f)',
'textColor: Color(0xffffffff)',
'collapsedTextColor: Color(0xff522bab)',
'shape: Border.all(BorderSide(width: 0.0, style: none))',
'collapsedShape: Border.all(BorderSide(width: 0.0, style: none))',
'clipBehavior: Clip.antiAlias',
'expansionAnimationStyle: AnimationStyle#983ac(curve: Cubic(0.42, 0.00, 0.58, 1.00))',
]));
});
testWidgets('ExpansionTileTheme - collapsed', (WidgetTester tester) async {
final Key tileKey = UniqueKey();
final Key titleKey = UniqueKey();
final Key iconKey = UniqueKey();
const Color backgroundColor = Colors.orange;
const Color collapsedBackgroundColor = Colors.red;
const Color iconColor = Colors.green;
const Color collapsedIconColor = Colors.blue;
const Color textColor = Colors.black;
const Color collapsedTextColor = Colors.white;
const ShapeBorder shape = Border(
top: BorderSide(color: Colors.red),
bottom: BorderSide(color: Colors.red),
);
const ShapeBorder collapsedShape = Border(
top: BorderSide(color: Colors.green),
bottom: BorderSide(color: Colors.green),
);
const Clip clipBehavior = Clip.antiAlias;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
expansionTileTheme: const ExpansionTileThemeData(
backgroundColor: backgroundColor,
collapsedBackgroundColor: collapsedBackgroundColor,
tilePadding: EdgeInsets.fromLTRB(8, 12, 4, 10),
expandedAlignment: Alignment.centerRight,
childrenPadding: EdgeInsets.all(20.0),
iconColor: iconColor,
collapsedIconColor: collapsedIconColor,
textColor: textColor,
collapsedTextColor: collapsedTextColor,
shape: shape,
collapsedShape: collapsedShape,
clipBehavior: clipBehavior,
),
),
home: Material(
child: Center(
child: ExpansionTile(
key: tileKey,
title: TestText('Collapsed Tile', key: titleKey),
trailing: TestIcon(key: iconKey),
children: const <Widget>[Text('Tile 1')],
),
),
),
),
);
// When a custom shape is provided, ExpansionTile will use the
// Material widget to draw the shape and background color
// instead of a Container.
final Material material = getMaterial(tester);
// ExpansionTile should have Clip.antiAlias as clipBehavior.
expect(material.clipBehavior, clipBehavior);
// Check the tile's collapsed background color when collapsedBackgroundColor is applied.
expect(material.color, collapsedBackgroundColor);
final Rect titleRect = tester.getRect(find.text('Collapsed Tile'));
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);
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
// Check the collapsed icon color when iconColor is applied.
expect(getIconColor(), collapsedIconColor);
// Check the collapsed text color when textColor is applied.
expect(getTextColor(), collapsedTextColor);
// Check the collapsed ShapeBorder when shape is applied.
expect(material.shape, collapsedShape);
});
testWidgets('ExpansionTileTheme - expanded', (WidgetTester tester) async {
final Key tileKey = UniqueKey();
final Key titleKey = UniqueKey();
final Key iconKey = UniqueKey();
const Color backgroundColor = Colors.orange;
const Color collapsedBackgroundColor = Colors.red;
const Color iconColor = Colors.green;
const Color collapsedIconColor = Colors.blue;
const Color textColor = Colors.black;
const Color collapsedTextColor = Colors.white;
const ShapeBorder shape = Border(
top: BorderSide(color: Colors.red),
bottom: BorderSide(color: Colors.red),
);
const ShapeBorder collapsedShape = Border(
top: BorderSide(color: Colors.green),
bottom: BorderSide(color: Colors.green),
);
const Clip clipBehavior = Clip.none;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
expansionTileTheme: const ExpansionTileThemeData(
backgroundColor: backgroundColor,
collapsedBackgroundColor: collapsedBackgroundColor,
tilePadding: EdgeInsets.fromLTRB(8, 12, 4, 10),
expandedAlignment: Alignment.centerRight,
childrenPadding: EdgeInsets.all(20.0),
iconColor: iconColor,
collapsedIconColor: collapsedIconColor,
textColor: textColor,
collapsedTextColor: collapsedTextColor,
shape: shape,
collapsedShape: collapsedShape,
clipBehavior: clipBehavior,
),
),
home: Material(
child: Center(
child: ExpansionTile(
key: tileKey,
initiallyExpanded: true,
title: TestText('Expanded Tile', key: titleKey),
trailing: TestIcon(key: iconKey),
children: const <Widget>[Text('Tile 1')],
),
),
),
),
);
// When a custom shape is provided, ExpansionTile will use the
// Material widget to draw the shape and background color
// instead of a Container.
final Material material = getMaterial(tester);
// Check the tile's background color when backgroundColor is applied.
expect(material.color, backgroundColor);
final Rect titleRect = tester.getRect(find.text('Expanded Tile'));
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);
Color getIconColor() => tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
Color getTextColor() => tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
// Check the expanded icon color when iconColor is applied.
expect(getIconColor(), iconColor);
// Check the expanded text color when textColor is applied.
expect(getTextColor(), textColor);
// Check the expanded ShapeBorder when shape is applied.
expect(material.shape, shape);
// Check the clipBehavior when shape is applied.
expect(material.clipBehavior, clipBehavior);
// Check the child position when expandedAlignment is applied.
final Rect childRect = tester.getRect(find.text('Tile 1'));
expect(childRect.right, 800 - 20);
expect(childRect.left, 800 - childRect.width - 20);
// Check the child padding when childrenPadding is applied.
final Rect paddingRect = tester.getRect(find.byType(Padding).last);
expect(childRect.top, paddingRect.top + 20);
expect(childRect.left, paddingRect.left + 20);
expect(childRect.right, paddingRect.right - 20);
expect(childRect.bottom, paddingRect.bottom - 20);
});
testWidgets('Override ExpansionTile animation using ExpansionTileThemeData.AnimationStyle', (WidgetTester tester) async {
const Key expansionTileKey = Key('expansionTileKey');
Widget buildExpansionTile({ AnimationStyle? animationStyle }) {
return MaterialApp(
theme: ThemeData(
expansionTileTheme: ExpansionTileThemeData(
expansionAnimationStyle: animationStyle,
),
),
home: const Material(
child: Center(
child: ExpansionTile(
key: expansionTileKey,
title: TestText('title'),
children: <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)));
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);
// Tap to collapse the ExpansionTile.
await tester.tap(find.text('title'));
// 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);
});
}