diff --git a/dev/bots/check_code_samples.dart b/dev/bots/check_code_samples.dart index b9ef328974..3be363b62b 100644 --- a/dev/bots/check_code_samples.dart +++ b/dev/bots/check_code_samples.dart @@ -261,7 +261,7 @@ class SampleChecker { } } -// These tests are known to be missing. They should all eventually be +// These tests are known to be missing. They should all eventually be // implemented, but until they are we allow them, so that we can catch any new // examples that are added without tests. // @@ -282,7 +282,6 @@ final Set _knownMissingTests = { 'examples/api/test/material/text_field/text_field.1_test.dart', 'examples/api/test/material/button_style/button_style.0_test.dart', 'examples/api/test/material/range_slider/range_slider.0_test.dart', - 'examples/api/test/material/card/card.2_test.dart', 'examples/api/test/material/card/card.0_test.dart', 'examples/api/test/material/selection_container/selection_container_disabled.0_test.dart', 'examples/api/test/material/selection_container/selection_container.0_test.dart', diff --git a/dev/tools/gen_defaults/bin/gen_defaults.dart b/dev/tools/gen_defaults/bin/gen_defaults.dart index 6dc16b7d04..f56afcfe94 100644 --- a/dev/tools/gen_defaults/bin/gen_defaults.dart +++ b/dev/tools/gen_defaults/bin/gen_defaults.dart @@ -112,7 +112,9 @@ Future main(List args) async { ButtonTemplate('md.comp.filled-tonal-button', 'FilledTonalButton', '$materialLib/filled_button.dart', tokens).updateFile(); ButtonTemplate('md.comp.outlined-button', 'OutlinedButton', '$materialLib/outlined_button.dart', tokens).updateFile(); ButtonTemplate('md.comp.text-button', 'TextButton', '$materialLib/text_button.dart', tokens).updateFile(); - CardTemplate('Card', '$materialLib/card.dart', tokens).updateFile(); + CardTemplate('md.comp.elevated-card', 'Card', '$materialLib/card.dart', tokens).updateFile(); + CardTemplate('md.comp.filled-card', 'FilledCard', '$materialLib/card.dart', tokens).updateFile(); + CardTemplate('md.comp.outlined-card', 'OutlinedCard', '$materialLib/card.dart', tokens).updateFile(); CheckboxTemplate('Checkbox', '$materialLib/checkbox.dart', tokens).updateFile(); ColorSchemeTemplate(colorLightTokens, colorDarkTokens, 'ColorScheme', '$materialLib/theme_data.dart', tokens).updateFile(); DatePickerTemplate('DatePicker', '$materialLib/date_picker_theme.dart', tokens).updateFile(); diff --git a/dev/tools/gen_defaults/generated/used_tokens.csv b/dev/tools/gen_defaults/generated/used_tokens.csv index 12754860bc..39c50fe085 100644 --- a/dev/tools/gen_defaults/generated/used_tokens.csv +++ b/dev/tools/gen_defaults/generated/used_tokens.csv @@ -203,6 +203,10 @@ md.comp.filled-button.label-text.text-style, md.comp.filled-button.pressed.container.elevation, md.comp.filled-button.pressed.state-layer.color, md.comp.filled-button.pressed.state-layer.opacity, +md.comp.filled-card.container.color, +md.comp.filled-card.container.elevation, +md.comp.filled-card.container.shadow-color, +md.comp.filled-card.container.shape, md.comp.filled-icon-button.container.color, md.comp.filled-icon-button.container.shape, md.comp.filled-icon-button.container.size, @@ -459,6 +463,13 @@ md.comp.outlined-button.outline.color, md.comp.outlined-button.outline.width, md.comp.outlined-button.pressed.state-layer.color, md.comp.outlined-button.pressed.state-layer.opacity, +md.comp.outlined-card.container.color, +md.comp.outlined-card.container.elevation, +md.comp.outlined-card.container.shadow-color, +md.comp.outlined-card.container.shape, +md.comp.outlined-card.container.surface-tint-layer.color, +md.comp.outlined-card.outline.color, +md.comp.outlined-card.outline.width, md.comp.outlined-icon-button.container.shape, md.comp.outlined-icon-button.container.size, md.comp.outlined-icon-button.disabled.icon.color, diff --git a/dev/tools/gen_defaults/lib/card_template.dart b/dev/tools/gen_defaults/lib/card_template.dart index c20f42a6de..202903fff9 100644 --- a/dev/tools/gen_defaults/lib/card_template.dart +++ b/dev/tools/gen_defaults/lib/card_template.dart @@ -5,32 +5,49 @@ import 'template.dart'; class CardTemplate extends TokenTemplate { - const CardTemplate(super.blockName, super.fileName, super.tokens, { + const CardTemplate(this.tokenGroup, super.blockName, super.fileName, super.tokens, { super.colorSchemePrefix = '_colors.', }); + final String tokenGroup; + + String _shape() { + final String cardShape = shape('$tokenGroup.container'); + if (tokenAvailable('$tokenGroup.outline.color')) { + return ''' + + $cardShape.copyWith( + side: ${border('$tokenGroup.outline')} + )'''; + } else { + return cardShape; + } + } + @override String generate() => ''' class _${blockName}DefaultsM3 extends CardTheme { _${blockName}DefaultsM3(this.context) : super( clipBehavior: Clip.none, - elevation: ${elevation("md.comp.elevated-card.container")}, + elevation: ${elevation('$tokenGroup.container')}, margin: const EdgeInsets.all(4.0), - shape: ${shape("md.comp.elevated-card.container")}, ); final BuildContext context; late final ColorScheme _colors = Theme.of(context).colorScheme; @override - Color? get color => ${componentColor("md.comp.elevated-card.container")}; + Color? get color => ${componentColor('$tokenGroup.container')}; @override - Color? get shadowColor => ${colorOrTransparent("md.comp.elevated-card.container.shadow-color")}; + Color? get shadowColor => ${colorOrTransparent('$tokenGroup.container.shadow-color')}; @override - Color? get surfaceTintColor => ${colorOrTransparent("md.comp.elevated-card.container.surface-tint-layer.color")}; + Color? get surfaceTintColor => ${colorOrTransparent('$tokenGroup.container.surface-tint-layer.color')}; + + @override + ShapeBorder? get shape =>${_shape()}; } '''; } diff --git a/dev/tools/gen_defaults/lib/navigation_bar_template.dart b/dev/tools/gen_defaults/lib/navigation_bar_template.dart index 8298eb1baf..55d0392c0c 100644 --- a/dev/tools/gen_defaults/lib/navigation_bar_template.dart +++ b/dev/tools/gen_defaults/lib/navigation_bar_template.dart @@ -34,9 +34,11 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData { return MaterialStateProperty.resolveWith((Set states) { return IconThemeData( size: ${getToken("md.comp.navigation-bar.icon.size")}, - color: states.contains(MaterialState.selected) - ? ${componentColor("md.comp.navigation-bar.active.icon")} - : ${componentColor("md.comp.navigation-bar.inactive.icon")}, + color: states.contains(MaterialState.disabled) + ? _colors.onSurfaceVariant.withOpacity(0.38) + : states.contains(MaterialState.selected) + ? ${componentColor("md.comp.navigation-bar.active.icon")} + : ${componentColor("md.comp.navigation-bar.inactive.icon")}, ); }); } @@ -47,9 +49,12 @@ class _${blockName}DefaultsM3 extends NavigationBarThemeData { @override MaterialStateProperty? get labelTextStyle { return MaterialStateProperty.resolveWith((Set states) { final TextStyle style = ${textStyle("md.comp.navigation-bar.label-text")}!; - return style.apply(color: states.contains(MaterialState.selected) - ? ${componentColor("md.comp.navigation-bar.active.label-text")} - : ${componentColor("md.comp.navigation-bar.inactive.label-text")} + return style.apply( + color: states.contains(MaterialState.disabled) + ? _colors.onSurfaceVariant.withOpacity(0.38) + : states.contains(MaterialState.selected) + ? ${componentColor("md.comp.navigation-bar.active.label-text")} + : ${componentColor("md.comp.navigation-bar.inactive.label-text")} ); }); } diff --git a/dev/tools/gen_defaults/lib/navigation_drawer_template.dart b/dev/tools/gen_defaults/lib/navigation_drawer_template.dart index 53718220e8..1328826265 100644 --- a/dev/tools/gen_defaults/lib/navigation_drawer_template.dart +++ b/dev/tools/gen_defaults/lib/navigation_drawer_template.dart @@ -42,7 +42,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData { return MaterialStateProperty.resolveWith((Set states) { return IconThemeData( size: ${getToken("md.comp.navigation-drawer.icon.size")}, - color: states.contains(MaterialState.selected) + color: states.contains(MaterialState.disabled) + ? _colors.onSurfaceVariant.withOpacity(0.38) + : states.contains(MaterialState.selected) ? ${componentColor("md.comp.navigation-drawer.active.icon")} : ${componentColor("md.comp.navigation-drawer.inactive.icon")}, ); @@ -54,7 +56,9 @@ class _${blockName}DefaultsM3 extends NavigationDrawerThemeData { return MaterialStateProperty.resolveWith((Set states) { final TextStyle style = ${textStyle("md.comp.navigation-drawer.label-text")}!; return style.apply( - color: states.contains(MaterialState.selected) + color: states.contains(MaterialState.disabled) + ? _colors.onSurfaceVariant.withOpacity(0.38) + : states.contains(MaterialState.selected) ? ${componentColor("md.comp.navigation-drawer.active.label-text")} : ${componentColor("md.comp.navigation-drawer.inactive.label-text")}, ); diff --git a/examples/api/lib/material/card/card.2.dart b/examples/api/lib/material/card/card.2.dart index 5334df907f..89958726c8 100644 --- a/examples/api/lib/material/card/card.2.dart +++ b/examples/api/lib/material/card/card.2.dart @@ -16,97 +16,33 @@ class CardExamplesApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( - theme: ThemeData(colorSchemeSeed: const Color(0xff6750a4), useMaterial3: true), home: Scaffold( appBar: AppBar(title: const Text('Card Examples')), - body: const Column( - children: [ - Spacer(), - ElevatedCardExample(), - FilledCardExample(), - OutlinedCardExample(), - Spacer(), - ], - ), - ), - ); - } -} - -/// An example of the elevated card type. -/// -/// The default settings for [Card] will provide an elevated -/// card matching the spec: -/// -/// https://m3.material.io/components/cards/specs#a012d40d-7a5c-4b07-8740-491dec79d58b -class ElevatedCardExample extends StatelessWidget { - const ElevatedCardExample({super.key}); - - @override - Widget build(BuildContext context) { - return const Center( - child: Card( - child: SizedBox( - width: 300, - height: 100, - child: Center(child: Text('Elevated Card')), - ), - ), - ); - } -} - -/// An example of the filled card type. -/// -/// To make a [Card] match the filled type, the default elevation and color -/// need to be changed to the values from the spec: -/// -/// https://m3.material.io/components/cards/specs#0f55bf62-edf2-4619-b00d-b9ed462f2c5a -class FilledCardExample extends StatelessWidget { - const FilledCardExample({super.key}); - - @override - Widget build(BuildContext context) { - return Center( - child: Card( - elevation: 0, - color: Theme.of(context).colorScheme.surfaceVariant, - child: const SizedBox( - width: 300, - height: 100, - child: Center(child: Text('Filled Card')), - ), - ), - ); - } -} - -/// An example of the outlined card type. -/// -/// To make a [Card] match the outlined type, the default elevation and shape -/// need to be changed to the values from the spec: -/// -/// https://m3.material.io/components/cards/specs#0f55bf62-edf2-4619-b00d-b9ed462f2c5a -class OutlinedCardExample extends StatelessWidget { - const OutlinedCardExample({super.key}); - - @override - Widget build(BuildContext context) { - return Center( - child: Card( - elevation: 0, - shape: RoundedRectangleBorder( - side: BorderSide( - color: Theme.of(context).colorScheme.outline, + body: const Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Card(child: _SampleCard(cardName: 'Elevated Card')), + Card.filled(child: _SampleCard(cardName: 'Filled Card')), + Card.outlined(child: _SampleCard(cardName: 'Outlined Card')), + ], ), - borderRadius: const BorderRadius.all(Radius.circular(12)), - ), - child: const SizedBox( - width: 300, - height: 100, - child: Center(child: Text('Outlined Card')), ), ), ); } } + +class _SampleCard extends StatelessWidget { + const _SampleCard({required this.cardName}); + final String cardName; + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 300, + height: 100, + child: Center(child: Text(cardName)), + ); + } +} diff --git a/examples/api/test/material/card/card.2_test.dart b/examples/api/test/material/card/card.2_test.dart new file mode 100644 index 0000000000..1c0d93affb --- /dev/null +++ b/examples/api/test/material/card/card.2_test.dart @@ -0,0 +1,59 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter/material.dart'; +import 'package:flutter_api_samples/material/card/card.2.dart' as example; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + testWidgets('Card variants', (WidgetTester tester) async { + await tester.pumpWidget(const example.CardExamplesApp()); + + expect(find.byType(Card), findsNWidgets(3)); + + expect(find.widgetWithText(Card, 'Elevated Card'), findsOneWidget); + expect(find.widgetWithText(Card, 'Filled Card'), findsOneWidget); + expect(find.widgetWithText(Card, 'Outlined Card'), findsOneWidget); + + Material getCardMaterial(WidgetTester tester, int cardIndex) { + return tester.widget( + find.descendant( + of: find.byType(Card).at(cardIndex), + matching: find.byType(Material), + ), + ); + } + + final Material defaultCard = getCardMaterial(tester, 0); + expect(defaultCard.clipBehavior, Clip.none); + expect(defaultCard.elevation, 1.0); + expect(defaultCard.shape, const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0)), + )); + expect(defaultCard.color, const Color(0xfffffbfe)); + expect(defaultCard.shadowColor, const Color(0xff000000)); + expect(defaultCard.surfaceTintColor, const Color(0xff6750a4)); + + final Material filledCard = getCardMaterial(tester, 1); + expect(filledCard.clipBehavior, Clip.none); + expect(filledCard.elevation, 0.0); + expect(filledCard.shape, const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0)), + )); + expect(filledCard.color, const Color(0xffe7e0ec)); + expect(filledCard.shadowColor, const Color(0xff000000)); + expect(filledCard.surfaceTintColor, const Color(0x00000000)); + + final Material outlinedCard = getCardMaterial(tester, 2); + expect(outlinedCard.clipBehavior, Clip.none); + expect(outlinedCard.elevation, 0.0); + expect(outlinedCard.shape, const RoundedRectangleBorder( + side: BorderSide(color: Color(0xffcac4d0)), + borderRadius: BorderRadius.all(Radius.circular(12.0)), + )); + expect(outlinedCard.color, const Color(0xfffffbfe)); + expect(outlinedCard.shadowColor, const Color(0xff000000)); + expect(outlinedCard.surfaceTintColor, const Color(0xff6750a4)); + }); +} diff --git a/packages/flutter/lib/src/material/card.dart b/packages/flutter/lib/src/material/card.dart index dd9ced05fa..f1032ffc98 100644 --- a/packages/flutter/lib/src/material/card.dart +++ b/packages/flutter/lib/src/material/card.dart @@ -6,9 +6,12 @@ import 'package:flutter/widgets.dart'; import 'card_theme.dart'; import 'color_scheme.dart'; +import 'colors.dart'; import 'material.dart'; import 'theme.dart'; +enum _CardVariant { elevated, filled, outlined } + /// A Material Design card: a panel with slightly rounded corners and an /// elevation shadow. /// @@ -39,9 +42,9 @@ import 'theme.dart'; /// ** See code in examples/api/lib/material/card/card.1.dart ** /// {@end-tool} /// -/// Material Design 3 introduced new types of cards. These can -/// be produced by configuring the [Card] widget's properties. -/// [Card] widget. +/// Material Design 3 introduced new types of cards. The default [Card] is the +/// elevated card. To create a filled card, use [Card.filled]; to create a outlined +/// card, use [Card.outlined]. /// {@tool dartpad} /// This sample shows creation of [Card] widgets for elevated, filled and /// outlined types, as described in: https://m3.material.io/components/cards/overview @@ -71,7 +74,46 @@ class Card extends StatelessWidget { this.clipBehavior, this.child, this.semanticContainer = true, - }) : assert(elevation == null || elevation >= 0.0); + }) : assert(elevation == null || elevation >= 0.0), + _variant = _CardVariant.elevated; + + /// Create a filled variant of Card. + /// + /// Filled cards provide subtle separation from the background. This has less + /// emphasis than elevated(default) or outlined cards. + const Card.filled({ + super.key, + this.color, + this.shadowColor, + this.surfaceTintColor, + this.elevation, + this.shape, + this.borderOnForeground = true, + this.margin, + this.clipBehavior, + this.child, + this.semanticContainer = true, + }) : assert(elevation == null || elevation >= 0.0), + _variant = _CardVariant.filled; + + /// Create an outlined variant of Card. + /// + /// Outlined cards have a visual boundary around the container. This can + /// provide greater emphasis than the other types. + const Card.outlined({ + super.key, + this.color, + this.shadowColor, + this.surfaceTintColor, + this.elevation, + this.shape, + this.borderOnForeground = true, + this.margin, + this.clipBehavior, + this.child, + this.semanticContainer = true, + }) : assert(elevation == null || elevation >= 0.0), + _variant = _CardVariant.outlined; /// The card's background color. /// @@ -164,10 +206,24 @@ class Card extends StatelessWidget { /// {@macro flutter.widgets.ProxyWidget.child} final Widget? child; + final _CardVariant _variant; + @override Widget build(BuildContext context) { final CardTheme cardTheme = CardTheme.of(context); - final CardTheme defaults = Theme.of(context).useMaterial3 ? _CardDefaultsM3(context) : _CardDefaultsM2(context); + final CardTheme defaults; + if (Theme.of(context).useMaterial3) { + switch (_variant) { + case _CardVariant.elevated: + defaults = _CardDefaultsM3(context); + case _CardVariant.filled: + defaults = _FilledCardDefaultsM3(context); + case _CardVariant.outlined: + defaults = _OutlinedCardDefaultsM3(context); + } + } else { + defaults = _CardDefaultsM2(context); + } return Semantics( container: semanticContainer, @@ -226,7 +282,6 @@ class _CardDefaultsM3 extends CardTheme { clipBehavior: Clip.none, elevation: 1.0, margin: const EdgeInsets.all(4.0), - shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))), ); final BuildContext context; @@ -240,6 +295,78 @@ class _CardDefaultsM3 extends CardTheme { @override Color? get surfaceTintColor => _colors.surfaceTint; + + @override + ShapeBorder? get shape =>const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))); } // END GENERATED TOKEN PROPERTIES - Card + +// BEGIN GENERATED TOKEN PROPERTIES - FilledCard + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +class _FilledCardDefaultsM3 extends CardTheme { + _FilledCardDefaultsM3(this.context) + : super( + clipBehavior: Clip.none, + elevation: 0.0, + margin: const EdgeInsets.all(4.0), + ); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + Color? get color => _colors.surfaceVariant; + + @override + Color? get shadowColor => _colors.shadow; + + @override + Color? get surfaceTintColor => Colors.transparent; + + @override + ShapeBorder? get shape =>const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))); +} + +// END GENERATED TOKEN PROPERTIES - FilledCard + +// BEGIN GENERATED TOKEN PROPERTIES - OutlinedCard + +// Do not edit by hand. The code between the "BEGIN GENERATED" and +// "END GENERATED" comments are generated from data in the Material +// Design token database by the script: +// dev/tools/gen_defaults/bin/gen_defaults.dart. + +class _OutlinedCardDefaultsM3 extends CardTheme { + _OutlinedCardDefaultsM3(this.context) + : super( + clipBehavior: Clip.none, + elevation: 0.0, + margin: const EdgeInsets.all(4.0), + ); + + final BuildContext context; + late final ColorScheme _colors = Theme.of(context).colorScheme; + + @override + Color? get color => _colors.surface; + + @override + Color? get shadowColor => _colors.shadow; + + @override + Color? get surfaceTintColor => _colors.surfaceTint; + + @override + ShapeBorder? get shape => + const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12.0))).copyWith( + side: BorderSide(color: _colors.outlineVariant) + ); +} + +// END GENERATED TOKEN PROPERTIES - OutlinedCard diff --git a/packages/flutter/lib/src/material/navigation_bar.dart b/packages/flutter/lib/src/material/navigation_bar.dart index b209ffb21e..039baaf224 100644 --- a/packages/flutter/lib/src/material/navigation_bar.dart +++ b/packages/flutter/lib/src/material/navigation_bar.dart @@ -1364,9 +1364,10 @@ class _NavigationBarDefaultsM3 extends NavigationBarThemeData { @override MaterialStateProperty? get labelTextStyle { return MaterialStateProperty.resolveWith((Set states) { final TextStyle style = _textTheme.labelMedium!; - return style.apply(color: states.contains(MaterialState.disabled) - ? _colors.onSurfaceVariant.withOpacity(0.38) - : states.contains(MaterialState.selected) + return style.apply( + color: states.contains(MaterialState.disabled) + ? _colors.onSurfaceVariant.withOpacity(0.38) + : states.contains(MaterialState.selected) ? _colors.onSurface : _colors.onSurfaceVariant ); diff --git a/packages/flutter/test/material/card_test.dart b/packages/flutter/test/material/card_test.dart index 1f08017a13..19606fc459 100644 --- a/packages/flutter/test/material/card_test.dart +++ b/packages/flutter/test/material/card_test.dart @@ -9,6 +9,79 @@ import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart'; import '../widgets/semantics_tester.dart'; void main() { + testWidgetsWithLeakTracking('Material3 - Card defaults (Elevated card)', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + final ColorScheme colors = theme.colorScheme; + await tester.pumpWidget(MaterialApp( + theme: theme, + home: const Scaffold( + body: Card(), + ), + )); + + final Container container = _getCardContainer(tester); + final Material material = _getCardMaterial(tester); + + expect(material.clipBehavior, Clip.none); + expect(material.elevation, 1.0); + expect(container.margin, const EdgeInsets.all(4.0)); + expect(material.color, colors.surface); + expect(material.shadowColor, colors.shadow); + expect(material.surfaceTintColor, colors.surfaceTint); // Default primary color + expect(material.shape, const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0)), + )); + }); + + testWidgetsWithLeakTracking('Material3 - Card.filled defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + final ColorScheme colors = theme.colorScheme; + await tester.pumpWidget(MaterialApp( + theme: theme, + home: const Scaffold( + body: Card.filled(), + ), + )); + + final Container container = _getCardContainer(tester); + final Material material = _getCardMaterial(tester); + + expect(material.clipBehavior, Clip.none); + expect(material.elevation, 0.0); + expect(container.margin, const EdgeInsets.all(4.0)); + expect(material.color, colors.surfaceVariant); + expect(material.shadowColor, colors.shadow); + expect(material.surfaceTintColor, Colors.transparent); + expect(material.shape, const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12.0)), + )); + }); + + testWidgetsWithLeakTracking('Material3 - Card.outlined defaults', (WidgetTester tester) async { + final ThemeData theme = ThemeData(); + final ColorScheme colors = theme.colorScheme; + await tester.pumpWidget(MaterialApp( + theme: theme, + home: const Scaffold( + body: Card.outlined(), + ), + )); + + final Container container = _getCardContainer(tester); + final Material material = _getCardMaterial(tester); + + expect(material.clipBehavior, Clip.none); + expect(material.elevation, 0.0); + expect(container.margin, const EdgeInsets.all(4.0)); + expect(material.color, colors.surface); + expect(material.shadowColor, colors.shadow); + expect(material.surfaceTintColor, colors.surfaceTint); + expect(material.shape, RoundedRectangleBorder( + side: BorderSide(color: colors.outlineVariant), + borderRadius: const BorderRadius.all(Radius.circular(12.0)), + )); + }); + testWidgetsWithLeakTracking('Card can take semantic text from multiple children', (WidgetTester tester) async { final SemanticsTester semantics = SemanticsTester(tester); await tester.pumpWidget( @@ -219,3 +292,21 @@ void main() { expect(getCardMaterial(tester).shadowColor, Colors.red); }); } + +Material _getCardMaterial(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(Card), + matching: find.byType(Material), + ), + ); +} + +Container _getCardContainer(WidgetTester tester) { + return tester.widget( + find.descendant( + of: find.byType(Card), + matching: find.byType(Container), + ), + ); +}