Unverified Commit 9574d585 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Fix `shape` and `collapsedShape` isn't applied to `ExpansionTile`'s splash ink (#141777)

This updates the previous attempt https://github.com/flutter/flutter/pull/135855 and removes the complications when testing M3 ink sparkle effect. 
Thanks to this [PR](https://github.com/flutter/flutter/pull/138757) by @Piinks 

fixes [ExpansionTile InkSplash doesn't respect Shape's borderRadius](https://github.com/flutter/flutter/issues/125779)
fixes [`ExpansionTile.backgroundColor` &  `ExpansionTile.collapsedBackgroundColor` removes splash effect](https://github.com/flutter/flutter/issues/107113)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Example(),
    );
  }
}

class Example extends StatelessWidget {
  const Example({super.key});

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
          child: Padding(
        padding: EdgeInsets.symmetric(horizontal: 24.0),
        child: ExpansionTile(
          collapsedBackgroundColor: Color(0x25ff0000),
          backgroundColor: Color(0x250000ff),
          collapsedShape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(30.0)),
            side: BorderSide(color: Colors.black, width: 2.0),
          ),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(30.0)),
            side: BorderSide(color: Colors.black, width: 2.0),
          ),
          clipBehavior: Clip.hardEdge,
          title: Text('Expansion Tile'),
          children: <Widget>[
            FlutterLogo(size: 50),
            FlutterLogo(size: 50),
            FlutterLogo(size: 50),
            FlutterLogo(size: 50),

          ],
        ),
      )),
    );
  }
}
```

</details>

### Before

<img width="789" alt="Screenshot 2024-01-18 at 18 16 15" src="https://github.com/flutter/flutter/assets/48603081/8c6a6f1e-6986-4acf-8dec-e223a682c0d7">

<img width="789" alt="Screenshot 2024-01-18 at 18 16 44" src="https://github.com/flutter/flutter/assets/48603081/f55f6a26-2128-48a1-b24d-3c14e4f6ecdc">

### After 
<img width="789" alt="Screenshot 2024-01-18 at 18 20 27" src="https://github.com/flutter/flutter/assets/48603081/7ec8b888-7319-460d-8488-9cd44c9246a6">

<img width="789" alt="Screenshot 2024-01-18 at 18 20 53" src="https://github.com/flutter/flutter/assets/48603081/80d66d5b-7eb2-4f47-ab4d-d7f469a731fa">
parent 634b326e
...@@ -475,8 +475,11 @@ class ExpansionTile extends StatefulWidget { ...@@ -475,8 +475,11 @@ class ExpansionTile extends StatefulWidget {
/// {@macro flutter.material.Material.clipBehavior} /// {@macro flutter.material.Material.clipBehavior}
/// ///
/// If this is not null and a custom collapsed or expanded shape is provided,
/// the value of [clipBehavior] will be used to clip the expansion tile.
///
/// If this property is null, the [ExpansionTileThemeData.clipBehavior] is used. If that /// If this property is null, the [ExpansionTileThemeData.clipBehavior] is used. If that
/// is also null, a [Clip.none] is used /// is also null, defaults to [Clip.antiAlias].
/// ///
/// See also: /// See also:
/// ///
...@@ -656,11 +659,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -656,11 +659,12 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
Widget _buildChildren(BuildContext context, Widget? child) { Widget _buildChildren(BuildContext context, Widget? child) {
final ThemeData theme = Theme.of(context); final ThemeData theme = Theme.of(context);
final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context); final ExpansionTileThemeData expansionTileTheme = ExpansionTileTheme.of(context);
final Color backgroundColor = _backgroundColor.value ?? expansionTileTheme.backgroundColor ?? Colors.transparent;
final ShapeBorder expansionTileBorder = _border.value ?? const Border( final ShapeBorder expansionTileBorder = _border.value ?? const Border(
top: BorderSide(color: Colors.transparent), top: BorderSide(color: Colors.transparent),
bottom: BorderSide(color: Colors.transparent), bottom: BorderSide(color: Colors.transparent),
); );
final Clip clipBehavior = widget.clipBehavior ?? expansionTileTheme.clipBehavior ?? Clip.none; final Clip clipBehavior = widget.clipBehavior ?? expansionTileTheme.clipBehavior ?? Clip.antiAlias;
final MaterialLocalizations localizations = MaterialLocalizations.of(context); final MaterialLocalizations localizations = MaterialLocalizations.of(context);
final String onTapHint = _isExpanded final String onTapHint = _isExpanded
? localizations.expansionTileExpandedTapHint ? localizations.expansionTileExpandedTapHint
...@@ -679,12 +683,13 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -679,12 +683,13 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
break; break;
} }
return Container( final Decoration decoration = ShapeDecoration(
clipBehavior: clipBehavior, color: backgroundColor,
decoration: ShapeDecoration(
color: _backgroundColor.value ?? expansionTileTheme.backgroundColor ?? Colors.transparent,
shape: expansionTileBorder, shape: expansionTileBorder,
), );
final Widget tile = Padding(
padding: decoration.padding,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
...@@ -720,6 +725,23 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider ...@@ -720,6 +725,23 @@ class _ExpansionTileState extends State<ExpansionTile> with SingleTickerProvider
], ],
), ),
); );
final bool isShapeProvided = widget.shape != null || expansionTileTheme.shape != null
|| widget.collapsedShape != null || expansionTileTheme.collapsedShape != null;
if (isShapeProvided) {
return Material(
clipBehavior: clipBehavior,
color: backgroundColor,
shape: expansionTileBorder,
child: tile,
);
}
return DecoratedBox(
decoration: decoration,
child: tile,
);
} }
@override @override
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -48,19 +53,22 @@ void main() { ...@@ -48,19 +53,22 @@ void main() {
const Color unselectedWidgetColor = Colors.black54; const Color unselectedWidgetColor = Colors.black54;
const Color headerColor = Colors.black45; 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 { testWidgets('ExpansionTile initial state', (WidgetTester tester) async {
final Key topKey = UniqueKey(); final Key topKey = UniqueKey();
final Key tileKey = UniqueKey();
const Key expandedKey = PageStorageKey<String>('expanded'); const Key expandedKey = PageStorageKey<String>('expanded');
const Key collapsedKey = PageStorageKey<String>('collapsed'); const Key collapsedKey = PageStorageKey<String>('collapsed');
const Key defaultKey = PageStorageKey<String>('default'); const Key defaultKey = PageStorageKey<String>('default');
final Key tileKey = UniqueKey();
const Clip clipBehavior = Clip.antiAlias;
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
theme: ThemeData( theme: ThemeData(dividerColor: dividerColor),
dividerColor: dividerColor,
),
home: Material( home: Material(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
...@@ -71,7 +79,6 @@ void main() { ...@@ -71,7 +79,6 @@ void main() {
initiallyExpanded: true, initiallyExpanded: true,
title: const Text('Expanded'), title: const Text('Expanded'),
backgroundColor: Colors.red, backgroundColor: Colors.red,
clipBehavior: clipBehavior,
children: <Widget>[ children: <Widget>[
ListTile( ListTile(
key: tileKey, key: tileKey,
...@@ -103,24 +110,21 @@ void main() { ...@@ -103,24 +110,21 @@ void main() {
)); ));
double getHeight(Key key) => tester.getSize(find.byKey(key)).height; double getHeight(Key key) => tester.getSize(find.byKey(key)).height;
Container getContainer(Key key) => tester.firstWidget(find.descendant( DecoratedBox getDecoratedBox(Key key) => tester.firstWidget(find.descendant(
of: find.byKey(key), of: find.byKey(key),
matching: find.byType(Container), matching: find.byType(DecoratedBox),
)); ));
expect(getHeight(topKey), getHeight(expandedKey) - getHeight(tileKey) - 2.0); expect(getHeight(topKey), getHeight(expandedKey) - getHeight(tileKey) - 2.0);
expect(getHeight(topKey), getHeight(collapsedKey) - 2.0); expect(getHeight(topKey), getHeight(collapsedKey) - 2.0);
expect(getHeight(topKey), getHeight(defaultKey) - 2.0); expect(getHeight(topKey), getHeight(defaultKey) - 2.0);
// expansionTile should have Clip.antiAlias as clipBehavior ShapeDecoration expandedContainerDecoration = getDecoratedBox(expandedKey).decoration as ShapeDecoration;
expect(getContainer(expandedKey).clipBehavior, clipBehavior);
ShapeDecoration expandedContainerDecoration = getContainer(expandedKey).decoration! as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.red); expect(expandedContainerDecoration.color, Colors.red);
expect((expandedContainerDecoration.shape as Border).top.color, dividerColor); expect((expandedContainerDecoration.shape as Border).top.color, dividerColor);
expect((expandedContainerDecoration.shape as Border).bottom.color, dividerColor); expect((expandedContainerDecoration.shape as Border).bottom.color, dividerColor);
ShapeDecoration collapsedContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration; ShapeDecoration collapsedContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent); expect(collapsedContainerDecoration.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).top.color, Colors.transparent); expect((collapsedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).bottom.color, Colors.transparent); expect((collapsedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
...@@ -133,7 +137,7 @@ void main() { ...@@ -133,7 +137,7 @@ void main() {
// Pump to the middle of the animation for expansion. // Pump to the middle of the animation for expansion.
await tester.pump(const Duration(milliseconds: 100)); await tester.pump(const Duration(milliseconds: 100));
final ShapeDecoration collapsingContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration; final ShapeDecoration collapsingContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsingContainerDecoration.color, Colors.transparent); expect(collapsingContainerDecoration.color, Colors.transparent);
expect((collapsingContainerDecoration.shape as Border).top.color, const Color(0x15222222)); expect((collapsingContainerDecoration.shape as Border).top.color, const Color(0x15222222));
expect((collapsingContainerDecoration.shape as Border).bottom.color, const Color(0x15222222)); expect((collapsingContainerDecoration.shape as Border).bottom.color, const Color(0x15222222));
...@@ -146,13 +150,13 @@ void main() { ...@@ -146,13 +150,13 @@ void main() {
expect(getHeight(topKey), getHeight(defaultKey) - getHeight(tileKey) - 2.0); expect(getHeight(topKey), getHeight(defaultKey) - getHeight(tileKey) - 2.0);
// Expanded should be collapsed now. // Expanded should be collapsed now.
expandedContainerDecoration = getContainer(expandedKey).decoration! as ShapeDecoration; expandedContainerDecoration = getDecoratedBox(expandedKey).decoration as ShapeDecoration;
expect(expandedContainerDecoration.color, Colors.transparent); expect(expandedContainerDecoration.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).top.color, Colors.transparent); expect((expandedContainerDecoration.shape as Border).top.color, Colors.transparent);
expect((expandedContainerDecoration.shape as Border).bottom.color, Colors.transparent); expect((expandedContainerDecoration.shape as Border).bottom.color, Colors.transparent);
// Collapsed should be expanded now. // Collapsed should be expanded now.
collapsedContainerDecoration = getContainer(collapsedKey).decoration! as ShapeDecoration; collapsedContainerDecoration = getDecoratedBox(collapsedKey).decoration as ShapeDecoration;
expect(collapsedContainerDecoration.color, Colors.transparent); expect(collapsedContainerDecoration.color, Colors.transparent);
expect((collapsedContainerDecoration.shape as Border).top.color, dividerColor); expect((collapsedContainerDecoration.shape as Border).top.color, dividerColor);
expect((collapsedContainerDecoration.shape as Border).bottom.color, dividerColor); expect((collapsedContainerDecoration.shape as Border).bottom.color, dividerColor);
...@@ -506,20 +510,20 @@ void main() { ...@@ -506,20 +510,20 @@ void main() {
), ),
)); ));
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant( ShapeDecoration shapeDecoration = tester.firstWidget<DecoratedBox>(find.descendant(
of: find.byKey(expansionTileKey), of: find.byKey(expansionTileKey),
matching: find.byType(Container), matching: find.byType(DecoratedBox),
)).decoration! as ShapeDecoration; )).decoration as ShapeDecoration;
expect(shapeDecoration.color, collapsedBackgroundColor); expect(shapeDecoration.color, collapsedBackgroundColor);
await tester.tap(find.text('Title')); await tester.tap(find.text('Title'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<Container>(find.descendant( shapeDecoration = tester.firstWidget<DecoratedBox>(find.descendant(
of: find.byKey(expansionTileKey), of: find.byKey(expansionTileKey),
matching: find.byType(Container), matching: find.byType(DecoratedBox),
)).decoration! as ShapeDecoration; )).decoration as ShapeDecoration;
expect(shapeDecoration.color, backgroundColor); expect(shapeDecoration.color, backgroundColor);
}); });
...@@ -601,10 +605,7 @@ void main() { ...@@ -601,10 +605,7 @@ void main() {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Material( home: Material(
child: SingleChildScrollView( child: ExpansionTile(
child: Column(
children: <Widget>[
ExpansionTile(
key: expansionTileKey, key: expansionTileKey,
title: const Text('ExpansionTile'), title: const Text('ExpansionTile'),
collapsedShape: collapsedShape, collapsedShape: collapsedShape,
...@@ -615,27 +616,24 @@ void main() { ...@@ -615,27 +616,24 @@ void main() {
), ),
], ],
), ),
],
),
),
), ),
)); ));
Container getContainer(Key key) => tester.firstWidget(find.descendant( // When a custom shape is provided, ExpansionTile will use the
of: find.byKey(key), // Material widget to draw the shape and background color
matching: find.byType(Container), // instead of a Container.
)); Material material = getMaterial(tester);
// ExpansionTile should be collapsed initially.
// expansionTile should be Collapsed now. expect(material.shape, collapsedShape);
ShapeDecoration expandedContainerDecoration = getContainer(expansionTileKey).decoration! as ShapeDecoration; expect(material.clipBehavior, Clip.antiAlias);
expect(expandedContainerDecoration.shape, collapsedShape);
await tester.tap(find.text('ExpansionTile')); await tester.tap(find.text('ExpansionTile'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
// expansionTile should be Expanded now. // ExpansionTile should be Expanded now.
expandedContainerDecoration = getContainer(expansionTileKey).decoration! as ShapeDecoration; material = getMaterial(tester);
expect(expandedContainerDecoration.shape, shape); expect(material.shape, shape);
expect(material.clipBehavior, Clip.antiAlias);
}); });
testWidgets('ExpansionTile platform controlAffinity test', (WidgetTester tester) async { testWidgets('ExpansionTile platform controlAffinity test', (WidgetTester tester) async {
...@@ -940,14 +938,15 @@ void main() { ...@@ -940,14 +938,15 @@ void main() {
), ),
)); ));
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant( // When a custom shape is provided, ExpansionTile will use the
of: find.byKey(expansionTileKey), // Material widget to draw the shape and background color
matching: find.byType(Container), // instead of a Container.
)).decoration! as ShapeDecoration; Material material = getMaterial(tester);
// Test initial ExpansionTile properties. // Test initial ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4)))); expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))));
expect(shapeDecoration.color, const Color(0xffff0000)); 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<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff)); expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
...@@ -955,14 +954,12 @@ void main() { ...@@ -955,14 +954,12 @@ void main() {
await tester.tap(find.text('Update collapsed properties')); await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<Container>(find.descendant( material = getMaterial(tester);
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
// Test updated ExpansionTile properties. // Test updated ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16)))); expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))));
expect(shapeDecoration.color, const Color(0xffffff00)); 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<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff000000));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff000000)); expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff000000));
}); });
...@@ -1019,14 +1016,15 @@ void main() { ...@@ -1019,14 +1016,15 @@ void main() {
await tester.tap(find.text('title')); await tester.tap(find.text('title'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant( // When a custom shape is provided, ExpansionTile will use the
of: find.byKey(expansionTileKey), // Material widget to draw the shape and background color
matching: find.byType(Container), // instead of a Container.
)).decoration! as ShapeDecoration; Material material = getMaterial(tester);
// Test initial ExpansionTile properties. // Test initial ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12)))); expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))));
expect(shapeDecoration.color, const Color(0xff0000ff)); 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<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xff00ffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff00ffff)); expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xff00ffff));
...@@ -1034,16 +1032,14 @@ void main() { ...@@ -1034,16 +1032,14 @@ void main() {
await tester.tap(find.text('Update collapsed properties')); await tester.tap(find.text('Update collapsed properties'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
shapeDecoration = tester.firstWidget<Container>(find.descendant( material = getMaterial(tester);
of: find.byKey(expansionTileKey),
matching: find.byType(Container),
)).decoration! as ShapeDecoration;
iconColor = tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!; iconColor = tester.state<TestIconState>(find.byType(TestIcon)).iconTheme.color!;
textColor = tester.state<TestTextState>(find.byType(TestText)).textStyle.color!; textColor = tester.state<TestTextState>(find.byType(TestText)).textStyle.color!;
// Test updated ExpansionTile properties. // Test updated ExpansionTile properties.
expect(shapeDecoration.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6)))); expect(material.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(6))));
expect(shapeDecoration.color, const Color(0xff123456)); 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<TestIconState>(find.byType(TestIcon)).iconTheme.color, const Color(0xffffffff));
expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff)); expect(tester.state<TestTextState>(find.byType(TestText)).textStyle.color, const Color(0xffffffff));
}); });
...@@ -1149,6 +1145,93 @@ void main() { ...@@ -1149,6 +1145,93 @@ void main() {
expect(getHeight(expansionTileKey), 158.0); 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', () { group('Material 2', () {
// These tests are only relevant for Material 2. Once Material 2 // These tests are only relevant for Material 2. Once Material 2
// support is deprecated and the APIs are removed, these tests // support is deprecated and the APIs are removed, these tests
...@@ -1182,6 +1265,56 @@ void main() { ...@@ -1182,6 +1265,56 @@ void main() {
expect(getIconColor(), theme.colorScheme.primary); expect(getIconColor(), theme.colorScheme.primary);
expect(getTextColor(), 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 { testWidgets('ExpansionTileController isExpanded, expand() and collapse()', (WidgetTester tester) async {
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
...@@ -42,6 +47,13 @@ class TestTextState extends State<TestText> { ...@@ -42,6 +47,13 @@ class TestTextState extends State<TestText> {
} }
void main() { 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', () { test('ExpansionTileThemeData copyWith, ==, hashCode basics', () {
expect(const ExpansionTileThemeData(), const ExpansionTileThemeData().copyWith()); expect(const ExpansionTileThemeData(), const ExpansionTileThemeData().copyWith());
expect(const ExpansionTileThemeData().hashCode, const ExpansionTileThemeData().copyWith().hashCode); expect(const ExpansionTileThemeData().hashCode, const ExpansionTileThemeData().copyWith().hashCode);
...@@ -173,21 +185,16 @@ void main() { ...@@ -173,21 +185,16 @@ void main() {
), ),
); );
final ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant( // When a custom shape is provided, ExpansionTile will use the
of: find.byKey(tileKey), // Material widget to draw the shape and background color
matching: find.byType(Container), // instead of a Container.
)).decoration! as ShapeDecoration; final Material material = getMaterial(tester);
final Clip tileClipBehavior = tester.firstWidget<Container>(find.descendant( // ExpansionTile should have Clip.antiAlias as clipBehavior.
of: find.byKey(tileKey), expect(material.clipBehavior, clipBehavior);
matching: find.byType(Container),
)).clipBehavior;
// expansionTile should have Clip.antiAlias as clipBehavior
expect(tileClipBehavior, clipBehavior);
// Check the tile's collapsed background color when collapsedBackgroundColor is applied. // Check the tile's collapsed background color when collapsedBackgroundColor is applied.
expect(shapeDecoration.color, collapsedBackgroundColor); expect(material.color, collapsedBackgroundColor);
final Rect titleRect = tester.getRect(find.text('Collapsed Tile')); final Rect titleRect = tester.getRect(find.text('Collapsed Tile'));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more)); final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more));
...@@ -211,7 +218,7 @@ void main() { ...@@ -211,7 +218,7 @@ void main() {
// Check the collapsed text color when textColor is applied. // Check the collapsed text color when textColor is applied.
expect(getTextColor(), collapsedTextColor); expect(getTextColor(), collapsedTextColor);
// Check the collapsed ShapeBorder when shape is applied. // Check the collapsed ShapeBorder when shape is applied.
expect(shapeDecoration.shape, collapsedShape); expect(material.shape, collapsedShape);
}); });
testWidgets('ExpansionTileTheme - expanded', (WidgetTester tester) async { testWidgets('ExpansionTileTheme - expanded', (WidgetTester tester) async {
...@@ -232,6 +239,7 @@ void main() { ...@@ -232,6 +239,7 @@ void main() {
top: BorderSide(color: Colors.green), top: BorderSide(color: Colors.green),
bottom: BorderSide(color: Colors.green), bottom: BorderSide(color: Colors.green),
); );
const Clip clipBehavior = Clip.none;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -248,6 +256,7 @@ void main() { ...@@ -248,6 +256,7 @@ void main() {
collapsedTextColor: collapsedTextColor, collapsedTextColor: collapsedTextColor,
shape: shape, shape: shape,
collapsedShape: collapsedShape, collapsedShape: collapsedShape,
clipBehavior: clipBehavior,
), ),
), ),
home: Material( home: Material(
...@@ -264,12 +273,12 @@ void main() { ...@@ -264,12 +273,12 @@ void main() {
), ),
); );
final ShapeDecoration shapeDecoration = tester.firstWidget<Container>(find.descendant( // When a custom shape is provided, ExpansionTile will use the
of: find.byKey(tileKey), // Material widget to draw the shape and background color
matching: find.byType(Container), // instead of a Container.
)).decoration! as ShapeDecoration; final Material material = getMaterial(tester);
// Check the tile's background color when backgroundColor is applied. // Check the tile's background color when backgroundColor is applied.
expect(shapeDecoration.color, backgroundColor); expect(material.color, backgroundColor);
final Rect titleRect = tester.getRect(find.text('Expanded Tile')); final Rect titleRect = tester.getRect(find.text('Expanded Tile'));
final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more)); final Rect trailingRect = tester.getRect(find.byIcon(Icons.expand_more));
...@@ -293,7 +302,9 @@ void main() { ...@@ -293,7 +302,9 @@ void main() {
// Check the expanded text color when textColor is applied. // Check the expanded text color when textColor is applied.
expect(getTextColor(), textColor); expect(getTextColor(), textColor);
// Check the expanded ShapeBorder when shape is applied. // Check the expanded ShapeBorder when shape is applied.
expect(shapeDecoration.shape, shape); expect(material.shape, shape);
// Check the clipBehavior when shape is applied.
expect(material.clipBehavior, clipBehavior);
// Check the child position when expandedAlignment is applied. // Check the child position when expandedAlignment is applied.
final Rect childRect = tester.getRect(find.text('Tile 1')); final Rect childRect = tester.getRect(find.text('Tile 1'));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment