
issue: https://github.com/flutter/flutter/issues/127855 This is a forward fix to help https://github.com/flutter/engine/pull/54981 land. It makes the following changes: 1) Removes hardcoded `Color.toString()` assumptions in asserts 1) Switches some lerp tests to use numbers that are more friendly to uint8_t and floating point numbers 1) implements custom matchers for color 1) removes word wrapping for some asserts since it makes them fragile to changes in `Color.toString()` invocations ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
427 lines
16 KiB
Dart
427 lines
16 KiB
Dart
// 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: ${const Color(0xff000000)}',
|
|
'collapsedBackgroundColor: ${const Color(0xff6f83fc)}',
|
|
'tilePadding: EdgeInsets.all(20.0)',
|
|
'expandedAlignment: Alignment.bottomCenter',
|
|
'childrenPadding: EdgeInsets.all(10.0)',
|
|
'iconColor: ${const Color(0xffa7c61c)}',
|
|
'collapsedIconColor: ${const Color(0xffdd0b1f)}',
|
|
'textColor: ${const Color(0xffffffff)}',
|
|
'collapsedTextColor: ${const 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);
|
|
});
|
|
}
|