| // 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/gestures.dart'; |
| import 'package:flutter/material.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import 'feedback_tester.dart'; |
| |
| /// Adds the basic requirements for a Chip. |
| Widget wrapForChip({ |
| required Widget child, |
| TextDirection textDirection = TextDirection.ltr, |
| TextScaler textScaler = TextScaler.noScaling, |
| ThemeData? theme, |
| }) { |
| return MaterialApp( |
| theme: theme, |
| home: Directionality( |
| textDirection: textDirection, |
| child: MediaQuery( |
| data: MediaQueryData(textScaler: textScaler), |
| child: Material(child: child), |
| ), |
| ), |
| ); |
| } |
| |
| Future<void> pumpCheckmarkChip( |
| WidgetTester tester, { |
| required Widget chip, |
| Color? themeColor, |
| ThemeData? theme, |
| }) async { |
| await tester.pumpWidget( |
| wrapForChip( |
| theme: theme, |
| child: Builder( |
| builder: (BuildContext context) { |
| final ChipThemeData chipTheme = ChipTheme.of(context); |
| return ChipTheme( |
| data: themeColor == null ? chipTheme : chipTheme.copyWith( |
| checkmarkColor: themeColor, |
| ), |
| child: chip, |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Widget selectedFilterChip({ Color? checkmarkColor }) { |
| return FilterChip( |
| label: const Text('InputChip'), |
| selected: true, |
| showCheckmark: true, |
| checkmarkColor: checkmarkColor, |
| onSelected: (bool _) { }, |
| ); |
| } |
| |
| void expectCheckmarkColor(Finder finder, Color color) { |
| expect( |
| finder, |
| paints |
| // Physical model path |
| ..path() |
| // The first layer that is painted is the selection overlay. We do not care |
| // how it is painted but it has to be added it to this pattern so that the |
| // check mark can be checked next. |
| ..rrect() |
| // The second layer that is painted is the check mark. |
| ..path(color: color), |
| ); |
| } |
| |
| RenderBox getMaterialBox(WidgetTester tester, Finder type) { |
| return tester.firstRenderObject<RenderBox>( |
| find.descendant( |
| of: type, |
| matching: find.byType(CustomPaint), |
| ), |
| ); |
| } |
| |
| void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) { |
| final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material)); |
| // There should be two Material widgets, first Material is from the "_wrapForChip" and |
| // last Material is from the "RawChip". |
| expect(materials.length, 2); |
| // The last Material from `RawChip` should have the clip behavior. |
| expect(materials.last.clipBehavior, clipBehavior); |
| } |
| |
| Material getMaterial(WidgetTester tester) { |
| return tester.widget<Material>( |
| find.descendant( |
| of: find.byType(FilterChip), |
| matching: find.byType(Material), |
| ), |
| ); |
| } |
| |
| IconThemeData getIconData(WidgetTester tester) { |
| final IconTheme iconTheme = tester.firstWidget( |
| find.descendant( |
| of: find.byType(RawChip), |
| matching: find.byType(IconTheme), |
| ), |
| ); |
| return iconTheme.data; |
| } |
| |
| DefaultTextStyle getLabelStyle(WidgetTester tester, String labelText) { |
| return tester.widget( |
| find.ancestor( |
| of: find.text(labelText), |
| matching: find.byType(DefaultTextStyle), |
| ).first, |
| ); |
| } |
| |
| // Finds any container of a tooltip. |
| Finder findTooltipContainer(String tooltipText) { |
| return find.ancestor( |
| of: find.text(tooltipText), |
| matching: find.byType(Container), |
| ); |
| } |
| |
| void main() { |
| testWidgets('Material2 - FilterChip defaults', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(useMaterial3: false); |
| const String label = 'filter chip'; |
| |
| // Test enabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test default chip size. |
| expect(tester.getSize(find.byType(FilterChip)), const Size(178.0, 48.0)); |
| |
| // Test default label style. |
| expect( |
| getLabelStyle(tester, label).style.color, |
| theme.textTheme.bodyLarge!.color!.withAlpha(0xde), |
| ); |
| |
| Material chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.black); |
| expect(chipMaterial.shape, const StadiumBorder()); |
| |
| ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, Colors.black.withAlpha(0x1f)); |
| |
| // Test disabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip( |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.black); |
| expect(chipMaterial.shape, const StadiumBorder()); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, Colors.black38); |
| |
| // Test selected enabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: FilterChip( |
| selected: true, |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.black); |
| expect(chipMaterial.shape, const StadiumBorder()); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, Colors.black.withAlpha(0x3d)); |
| |
| // Test selected disabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip( |
| selected: true, |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.black); |
| expect(chipMaterial.shape, const StadiumBorder()); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, Colors.black.withAlpha(0x3d)); |
| }); |
| |
| testWidgets('Material3 - FilterChip defaults', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| const String label = 'filter chip'; |
| |
| // Test enabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test default chip size. |
| expect( |
| tester.getSize(find.byType(FilterChip)), |
| within(distance: 0.001, from: const Size(189.1, 48.0)), |
| ); |
| // Test default label style. |
| expect( |
| getLabelStyle(tester, label).style.color!.value, |
| theme.colorScheme.onSurfaceVariant.value, |
| ); |
| |
| Material chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.transparent); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| RoundedRectangleBorder( |
| borderRadius: const BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: theme.colorScheme.outline), |
| ), |
| ); |
| |
| ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, null); |
| |
| // Test disabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip( |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, Colors.transparent); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| RoundedRectangleBorder( |
| borderRadius: const BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: theme.colorScheme.onSurface.withOpacity(0.12)), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, null); |
| |
| // Test selected enabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: FilterChip( |
| selected: true, |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, null); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.secondaryContainer); |
| |
| // Test selected disabled FilterChip defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip( |
| selected: true, |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, null); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
| }); |
| |
| testWidgets('Material3 - FilterChip.elevated defaults', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| const String label = 'filter chip'; |
| |
| // Test enabled FilterChip.elevated defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip.elevated( |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test default chip size. |
| expect( |
| tester.getSize(find.byType(FilterChip)), |
| within(distance: 0.001, from: const Size(189.1, 48.0)), |
| ); |
| // Test default label style. |
| expect( |
| getLabelStyle(tester, 'filter chip').style.color!.value, |
| theme.colorScheme.onSurfaceVariant.value, |
| ); |
| |
| Material chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 1); |
| expect(chipMaterial.shadowColor, theme.colorScheme.shadow); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| ShapeDecoration decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.surfaceContainerLow); |
| |
| // Test disabled FilterChip.elevated defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip.elevated( |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, theme.colorScheme.shadow); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
| |
| // Test selected enabled FilterChip.elevated defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: FilterChip.elevated( |
| selected: true, |
| onSelected: (bool valueChanged) { }, |
| label: const Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 1); |
| expect(chipMaterial.shadowColor, null); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.secondaryContainer); |
| |
| // Test selected disabled FilterChip.elevated defaults. |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: const Material( |
| child: FilterChip.elevated( |
| selected: true, |
| onSelected: null, |
| label: Text(label), |
| ), |
| ), |
| ), |
| ); |
| await tester.pumpAndSettle(); |
| |
| chipMaterial = getMaterial(tester); |
| expect(chipMaterial.elevation, 0); |
| expect(chipMaterial.shadowColor, null); |
| expect(chipMaterial.surfaceTintColor, Colors.transparent); |
| expect( |
| chipMaterial.shape, |
| const RoundedRectangleBorder( |
| borderRadius: BorderRadius.all(Radius.circular(8.0)), |
| side: BorderSide(color: Colors.transparent), |
| ), |
| ); |
| |
| decoration = tester.widget<Ink>(find.byType(Ink)).decoration! as ShapeDecoration; |
| expect(decoration.color, theme.colorScheme.onSurface.withOpacity(0.12)); |
| }); |
| |
| testWidgets('FilterChip.color resolves material states', (WidgetTester tester) async { |
| const Color disabledSelectedColor = Color(0xffffff00); |
| const Color disabledColor = Color(0xff00ff00); |
| const Color backgroundColor = Color(0xff0000ff); |
| const Color selectedColor = Color(0xffff0000); |
| final MaterialStateProperty<Color?> color = MaterialStateProperty.resolveWith((Set<MaterialState> states) { |
| if (states.contains(MaterialState.disabled) && states.contains(MaterialState.selected)) { |
| return disabledSelectedColor; |
| } |
| if (states.contains(MaterialState.disabled)) { |
| return disabledColor; |
| } |
| if (states.contains(MaterialState.selected)) { |
| return selectedColor; |
| } |
| return backgroundColor; |
| }); |
| Widget buildApp({ required bool enabled, required bool selected }) { |
| return wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| FilterChip( |
| onSelected: enabled ? (bool value) { } : null, |
| selected: selected, |
| color: color, |
| label: const Text('FilterChip'), |
| ), |
| FilterChip.elevated( |
| onSelected: enabled ? (bool value) { } : null, |
| selected: selected, |
| color: color, |
| label: const Text('FilterChip.elevated'), |
| ), |
| ], |
| ), |
| ); |
| } |
| |
| // Test enabled state. |
| await tester.pumpWidget(buildApp(enabled: true, selected: false)); |
| |
| // Enabled FilterChip should have the provided backgroundColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: backgroundColor), |
| ); |
| // Enabled elevated FilterChip should have the provided backgroundColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: backgroundColor), |
| ); |
| |
| // Test disabled state. |
| await tester.pumpWidget(buildApp(enabled: false, selected: false)); |
| await tester.pumpAndSettle(); |
| |
| // Disabled FilterChip should have the provided disabledColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: disabledColor), |
| ); |
| // Disabled elevated FilterChip should have the provided disabledColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: disabledColor), |
| ); |
| |
| // Test enabled & selected state. |
| await tester.pumpWidget(buildApp(enabled: true, selected: true)); |
| await tester.pumpAndSettle(); |
| |
| // Enabled & selected FilterChip should have the provided selectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: selectedColor), |
| ); |
| // Enabled & selected elevated FilterChip should have the provided selectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: selectedColor), |
| ); |
| |
| // Test disabled & selected state. |
| await tester.pumpWidget(buildApp(enabled: false, selected: true)); |
| await tester.pumpAndSettle(); |
| |
| // Disabled & selected FilterChip should have the provided disabledSelectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: disabledSelectedColor), |
| ); |
| // Disabled & selected elevated FilterChip should have the |
| // provided disabledSelectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: disabledSelectedColor), |
| ); |
| }); |
| |
| testWidgets('FilterChip uses provided state color properties', (WidgetTester tester) async { |
| const Color disabledColor = Color(0xff00ff00); |
| const Color backgroundColor = Color(0xff0000ff); |
| const Color selectedColor = Color(0xffff0000); |
| Widget buildApp({ required bool enabled, required bool selected }) { |
| return wrapForChip( |
| child: Column( |
| children: <Widget>[ |
| FilterChip( |
| onSelected: enabled ? (bool value) { } : null, |
| selected: selected, |
| disabledColor: disabledColor, |
| backgroundColor: backgroundColor, |
| selectedColor: selectedColor, |
| label: const Text('FilterChip'), |
| ), |
| FilterChip.elevated( |
| onSelected: enabled ? (bool value) { } : null, |
| selected: selected, |
| disabledColor: disabledColor, |
| backgroundColor: backgroundColor, |
| selectedColor: selectedColor, |
| label: const Text('FilterChip.elevated'), |
| ), |
| ], |
| ), |
| ); |
| } |
| |
| // Test enabled state. |
| await tester.pumpWidget(buildApp(enabled: true, selected: false)); |
| |
| // Enabled FilterChip should have the provided backgroundColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: backgroundColor), |
| ); |
| // Enabled elevated FilterChip should have the provided backgroundColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: backgroundColor), |
| ); |
| |
| // Test disabled state. |
| await tester.pumpWidget(buildApp(enabled: false, selected: false)); |
| await tester.pumpAndSettle(); |
| |
| // Disabled FilterChip should have the provided disabledColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: disabledColor), |
| ); |
| // Disabled elevated FilterChip should have the provided disabledColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: disabledColor), |
| ); |
| |
| // Test enabled & selected state. |
| await tester.pumpWidget(buildApp(enabled: true, selected: true)); |
| await tester.pumpAndSettle(); |
| |
| // Enabled & selected FilterChip should have the provided selectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).first), |
| paints..rrect(color: selectedColor), |
| ); |
| // Enabled & selected elevated FilterChip should have the provided selectedColor. |
| expect( |
| getMaterialBox(tester, find.byType(RawChip).last), |
| paints..rrect(color: selectedColor), |
| ); |
| }); |
| |
| testWidgets('FilterChip can be tapped', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: FilterChip( |
| onSelected: (bool valueChanged) { }, |
| label: const Text('filter chip'), |
| ), |
| ), |
| ), |
| ); |
| |
| await tester.tap(find.byType(FilterChip)); |
| expect(tester.takeException(), null); |
| }); |
| |
| testWidgets('Material2 - Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async { |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(), |
| theme: ThemeData(useMaterial3: false), |
| ); |
| |
| expectCheckmarkColor(find.byType(FilterChip), Colors.black.withAlpha(0xde)); |
| }); |
| |
| testWidgets('Material3 - Filter chip check mark color is determined by platform brightness when light', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(), |
| theme: theme, |
| ); |
| |
| expectCheckmarkColor( |
| find.byType(FilterChip), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| }); |
| |
| testWidgets('Material2 - Filter chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async { |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(), |
| theme: ThemeData.dark(useMaterial3: false), |
| ); |
| |
| expectCheckmarkColor( |
| find.byType(FilterChip), |
| Colors.white.withAlpha(0xde), |
| ); |
| }); |
| |
| testWidgets('Material3 - Filter chip check mark color is determined by platform brightness when dark', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(brightness: Brightness.dark); |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(), |
| theme: theme, |
| ); |
| |
| expectCheckmarkColor( |
| find.byType(FilterChip), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| }); |
| |
| testWidgets('Filter chip check mark color can be set by the chip theme', (WidgetTester tester) async { |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(), |
| themeColor: const Color(0xff00ff00), |
| ); |
| |
| expectCheckmarkColor(find.byType(FilterChip), const Color(0xff00ff00)); |
| }); |
| |
| testWidgets('Filter chip check mark color can be set by the chip constructor', (WidgetTester tester) async { |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(checkmarkColor: const Color(0xff00ff00)), |
| ); |
| |
| expectCheckmarkColor(find.byType(FilterChip), const Color(0xff00ff00)); |
| }); |
| |
| testWidgets('Filter chip check mark color is set by chip constructor even when a theme color is specified', (WidgetTester tester) async { |
| await pumpCheckmarkChip( |
| tester, |
| chip: selectedFilterChip(checkmarkColor: const Color(0xffff0000)), |
| themeColor: const Color(0xff00ff00), |
| ); |
| |
| expectCheckmarkColor(find.byType(FilterChip), const Color(0xffff0000)); |
| }); |
| |
| testWidgets('FilterChip clipBehavior properly passes through to the Material', (WidgetTester tester) async { |
| const Text label = Text('label'); |
| await tester.pumpWidget(wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { }))); |
| checkChipMaterialClipBehavior(tester, Clip.none); |
| |
| await tester.pumpWidget(wrapForChip(child: FilterChip(label: label, onSelected: (bool b) { }, clipBehavior: Clip.antiAlias))); |
| checkChipMaterialClipBehavior(tester, Clip.antiAlias); |
| }); |
| |
| testWidgets('Material3 - width should not change with selection', (WidgetTester tester) async { |
| // Regression tests for: https://github.com/flutter/flutter/issues/110645 |
| |
| // For the text "FilterChip" the chip should default to 175 regardless of selection. |
| const int expectedWidth = 175; |
| |
| // Unselected |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| label: const Text('FilterChip'), |
| showCheckmark: false, |
| onSelected: (bool _) {}, |
| ) |
| ), |
| ), |
| )); |
| expect(tester.getSize(find.byType(FilterChip)).width, expectedWidth); |
| |
| // Selected |
| await tester.pumpWidget(MaterialApp( |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| label: const Text('FilterChip'), |
| showCheckmark: false, |
| selected: true, |
| onSelected: (bool _) {}, |
| ) |
| ), |
| ), |
| )); |
| await tester.pumpAndSettle(); |
| expect(tester.getSize(find.byType(FilterChip)).width, expectedWidth); |
| }); |
| |
| testWidgets('FilterChip uses provided iconTheme', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| |
| Widget buildChip({ IconThemeData? iconTheme }) { |
| return MaterialApp( |
| theme: theme, |
| home: Material( |
| child: FilterChip( |
| iconTheme: iconTheme, |
| avatar: const Icon(Icons.add), |
| label: const Text('FilterChip'), |
| onSelected: (bool _) {}, |
| ), |
| ), |
| ); |
| } |
| |
| // Test default icon theme. |
| await tester.pumpWidget(buildChip()); |
| |
| expect(getIconData(tester).color, theme.colorScheme.primary); |
| |
| // Test provided icon theme. |
| await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff00ff00)))); |
| |
| expect(getIconData(tester).color, const Color(0xff00ff00)); |
| }); |
| |
| testWidgets('Material3 - FilterChip supports delete button', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| onDeleted: () { }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('FilterChip'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test the chip size with delete button. |
| expect(find.text('FilterChip'), findsOneWidget); |
| expect(tester.getSize(find.byType(FilterChip)), const Size(195.0, 48.0)); |
| |
| // Test the delete button icon. |
| expect(tester.getSize(find.byIcon(Icons.clear)), const Size(18.0, 18.0)); |
| expect(getIconData(tester).color, theme.colorScheme.onSurfaceVariant); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip.elevated( |
| onDeleted: () { }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('Elevated FilterChip'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test the elevated chip size with delete button. |
| expect(find.text('Elevated FilterChip'), findsOneWidget); |
| expect( |
| tester.getSize(find.byType(FilterChip)), |
| within(distance: 0.001, from: const Size(321.9, 48.0)), |
| ); |
| |
| // Test the delete button icon. |
| expect(tester.getSize(find.byIcon(Icons.clear)), const Size(18.0, 18.0)); |
| expect(getIconData(tester).color, theme.colorScheme.onSurfaceVariant); |
| }, skip: kIsWeb && !isCanvasKit); // https://github.com/flutter/flutter/issues/99933 |
| |
| testWidgets('Material2 - FilterChip supports delete button', (WidgetTester tester) async { |
| final ThemeData theme = ThemeData(useMaterial3: false); |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| onDeleted: () { }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('FilterChip'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test the chip size with delete button. |
| expect(find.text('FilterChip'), findsOneWidget); |
| expect(tester.getSize(find.byType(FilterChip)), const Size(188.0, 48.0)); |
| |
| // Test the delete button icon. |
| expect(tester.getSize(find.byIcon(Icons.cancel)), const Size(18.0, 18.0)); |
| expect(getIconData(tester).color, theme.iconTheme.color?.withAlpha(0xde)); |
| |
| await tester.pumpWidget( |
| MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Center( |
| child: FilterChip.elevated( |
| onDeleted: () { }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('Elevated FilterChip'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| // Test the elevated chip size with delete button. |
| expect(find.text('Elevated FilterChip'), findsOneWidget); |
| expect(tester.getSize(find.byType(FilterChip)), const Size(314.0, 48.0)); |
| |
| // Test the delete button icon. |
| expect(tester.getSize(find.byIcon(Icons.cancel)), const Size(18.0, 18.0)); |
| expect(getIconData(tester).color, theme.iconTheme.color?.withAlpha(0xde)); |
| }); |
| |
| testWidgets('Customize FilterChip delete button', (WidgetTester tester) async { |
| Widget buildChip({ |
| Widget? deleteIcon, |
| Color? deleteIconColor, |
| String? deleteButtonTooltipMessage, |
| }) { |
| return MaterialApp( |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| deleteIcon: deleteIcon, |
| deleteIconColor: deleteIconColor, |
| deleteButtonTooltipMessage: deleteButtonTooltipMessage, |
| onDeleted: () { }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('FilterChip'), |
| ), |
| ), |
| ), |
| ); |
| } |
| |
| // Test the custom delete icon. |
| await tester.pumpWidget(buildChip(deleteIcon: const Icon(Icons.delete))); |
| |
| expect(find.byIcon(Icons.clear), findsNothing); |
| expect(find.byIcon(Icons.delete), findsOneWidget); |
| |
| // Test the custom delete icon color. |
| await tester.pumpWidget(buildChip( |
| deleteIcon: const Icon(Icons.delete), |
| deleteIconColor: const Color(0xff00ff00)), |
| ); |
| await tester.pumpAndSettle(); |
| |
| expect(find.byIcon(Icons.clear), findsNothing); |
| expect(find.byIcon(Icons.delete), findsOneWidget); |
| expect(getIconData(tester).color, const Color(0xff00ff00)); |
| |
| // Test the custom delete button tooltip message. |
| await tester.pumpWidget(buildChip(deleteButtonTooltipMessage: 'Delete FilterChip')); |
| await tester.pumpAndSettle(); |
| |
| // Hover over the delete icon of the chip |
| final TestGesture gesture = await tester.startGesture(tester.getCenter(find.byIcon(Icons.clear))); |
| |
| await tester.pumpAndSettle(); |
| |
| // Verify the tooltip message is set. |
| expect(find.widgetWithText(Tooltip, 'Delete FilterChip'), findsOneWidget); |
| |
| await gesture.up(); |
| }); |
| |
| testWidgets('FilterChip delete button control test', (WidgetTester tester) async { |
| final FeedbackTester feedback = FeedbackTester(); |
| final List<String> deletedButtonStrings = <String>[]; |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Material( |
| child: Center( |
| child: FilterChip( |
| onDeleted: () { |
| deletedButtonStrings.add('A'); |
| }, |
| onSelected: (bool valueChanged) { }, |
| label: const Text('FilterChip'), |
| ), |
| ), |
| ), |
| ), |
| ); |
| |
| expect(feedback.clickSoundCount, 0); |
| |
| expect(deletedButtonStrings, isEmpty); |
| await tester.tap(find.byIcon(Icons.clear)); |
| expect(deletedButtonStrings, equals(<String>['A'])); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(feedback.clickSoundCount, 1); |
| |
| await tester.tap(find.byIcon(Icons.clear)); |
| expect(deletedButtonStrings, equals(<String>['A', 'A'])); |
| |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| expect(feedback.clickSoundCount, 2); |
| |
| feedback.dispose(); |
| }); |
| |
| testWidgets('Delete button is visible on disabled FilterChip', (WidgetTester tester) async { |
| await tester.pumpWidget( |
| wrapForChip( |
| child: FilterChip( |
| label: const Text('Label'), |
| onSelected: null, |
| onDeleted: () { }, |
| ) |
| ), |
| ); |
| |
| // Delete button should be visible. |
| expectLater(find.byType(RawChip), matchesGoldenFile('filter_chip.disabled.delete_button.png')); |
| }); |
| |
| testWidgets('Delete button tooltip is not shown on disabled FilterChip', (WidgetTester tester) async { |
| Widget buildChip({ bool enabled = true }) { |
| return wrapForChip( |
| child: FilterChip( |
| onSelected: enabled ? (bool value) { } : null, |
| label: const Text('Label'), |
| onDeleted: () { }, |
| ) |
| ); |
| } |
| |
| // Test enabled chip. |
| await tester.pumpWidget(buildChip()); |
| |
| final Offset deleteButtonLocation = tester.getCenter(find.byType(Icon)); |
| final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); |
| await gesture.moveTo(deleteButtonLocation); |
| await tester.pump(); |
| |
| // Delete button tooltip should be visible. |
| expect(findTooltipContainer('Delete'), findsOneWidget); |
| |
| // Test disabled chip. |
| await tester.pumpWidget(buildChip(enabled: false)); |
| await tester.pump(); |
| |
| // Delete button tooltip should not be visible. |
| expect(findTooltipContainer('Delete'), findsNothing); |
| }); |
| |
| testWidgets('Material3 - Default FilterChip icon colors', (WidgetTester tester) async { |
| const IconData flatAvatar = Icons.favorite; |
| const IconData flatDeleteIcon = Icons.delete; |
| const IconData elevatedAvatar = Icons.house; |
| const IconData elevatedDeleteIcon = Icons.clear_all; |
| final ThemeData theme = ThemeData(); |
| |
| Widget buildChips({ required bool selected }) { |
| return MaterialApp( |
| theme: theme, |
| home: Material( |
| child: Column( |
| children: <Widget>[ |
| FilterChip( |
| avatar: const Icon(flatAvatar), |
| deleteIcon: const Icon(flatDeleteIcon), |
| onSelected: (bool valueChanged) { }, |
| label: const Text('FilterChip'), |
| selected: selected, |
| onDeleted: () { }, |
| ), |
| FilterChip.elevated( |
| avatar: const Icon(elevatedAvatar), |
| deleteIcon: const Icon(elevatedDeleteIcon), |
| onSelected: (bool valueChanged) { }, |
| label: const Text('Elevated FilterChip'), |
| selected: selected, |
| onDeleted: () { }, |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| |
| Color getIconColor(WidgetTester tester, IconData icon) { |
| return tester.firstWidget<IconTheme>(find.ancestor( |
| of: find.byIcon(icon), |
| matching: find.byType(IconTheme), |
| )).data.color!; |
| } |
| |
| // Test unselected state. |
| await tester.pumpWidget(buildChips(selected: false)); |
| // Check the flat chip icon colors. |
| expect(getIconColor(tester, flatAvatar), theme.colorScheme.primary); |
| expect( |
| getIconColor(tester, flatDeleteIcon), |
| theme.colorScheme.onSurfaceVariant, |
| ); |
| // Check the elevated chip icon colors. |
| expect(getIconColor(tester, elevatedAvatar), theme.colorScheme.primary); |
| expect( |
| getIconColor(tester, elevatedDeleteIcon), |
| theme.colorScheme.onSurfaceVariant, |
| ); |
| |
| // Test selected state. |
| await tester.pumpWidget(buildChips(selected: true)); |
| // Check the flat chip icon colors. |
| expect( |
| getIconColor(tester, flatAvatar), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| expect( |
| getIconColor(tester, flatDeleteIcon), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| // Check the elevated chip icon colors. |
| expect( |
| getIconColor(tester, elevatedAvatar), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| expect( |
| getIconColor(tester, elevatedDeleteIcon), |
| theme.colorScheme.onSecondaryContainer, |
| ); |
| }); |
| |
| testWidgets('FilterChip avatar layout constraints can be customized', (WidgetTester tester) async { |
| const double border = 1.0; |
| const double iconSize = 18.0; |
| const double labelPadding = 8.0; |
| const double padding = 8.0; |
| const Size labelSize = Size(100, 100); |
| |
| Widget buildChip({BoxConstraints? avatarBoxConstraints}) { |
| return wrapForChip( |
| child: Center( |
| child: FilterChip( |
| avatarBoxConstraints: avatarBoxConstraints, |
| avatar: const Icon(Icons.favorite), |
| label: Container( |
| width: labelSize.width, |
| height: labelSize.width, |
| color: const Color(0xFFFF0000), |
| ), |
| onSelected: (bool value) { }, |
| ), |
| ), |
| ); |
| } |
| |
| // Test default avatar layout constraints. |
| await tester.pumpWidget(buildChip()); |
| |
| expect(tester.getSize(find.byType(FilterChip)).width, equals(234.0)); |
| expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0)); |
| |
| // Calculate the distance between avatar and chip edges. |
| Offset chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester))); |
| final Offset avatarCenter = tester.getCenter(find.byIcon(Icons.favorite)); |
| expect(chipTopLeft.dx, avatarCenter.dx - (labelSize.width / 2) - padding - border); |
| expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); |
| |
| // Calculate the distnance between avatar and label. |
| Offset labelTopLeft = tester.getTopLeft(find.byType(Container)); |
| expect(labelTopLeft.dx, avatarCenter.dx + (labelSize.width / 2) + labelPadding); |
| |
| // Test custom avatar layout constraints. |
| await tester.pumpWidget(buildChip(avatarBoxConstraints: const BoxConstraints.tightForFinite())); |
| await tester.pump(); |
| |
| expect(tester.getSize(find.byType(FilterChip)).width, equals(152.0)); |
| expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0)); |
| |
| // Calculate the distance between avatar and chip edges. |
| chipTopLeft = tester.getTopLeft(find.byWidget(getMaterial(tester))); |
| expect(chipTopLeft.dx, avatarCenter.dx - (iconSize / 2) - padding - border); |
| expect(chipTopLeft.dy, avatarCenter.dy - (labelSize.width / 2) - padding - border); |
| |
| // Calculate the distnance between avatar and label. |
| labelTopLeft = tester.getTopLeft(find.byType(Container)); |
| expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding); |
| }); |
| |
| testWidgets('FilterChip delete icon layout constraints can be customized', (WidgetTester tester) async { |
| const double border = 1.0; |
| const double iconSize = 18.0; |
| const double labelPadding = 8.0; |
| const double padding = 8.0; |
| const Size labelSize = Size(100, 100); |
| |
| Widget buildChip({BoxConstraints? deleteIconBoxConstraints}) { |
| return wrapForChip( |
| child: Center( |
| child: FilterChip( |
| deleteIconBoxConstraints: deleteIconBoxConstraints, |
| onDeleted: () { }, |
| label: Container( |
| width: labelSize.width, |
| height: labelSize.width, |
| color: const Color(0xFFFF0000), |
| ), |
| onSelected: (bool value) { }, |
| ), |
| ), |
| ); |
| } |
| |
| // Test default delete icon layout constraints. |
| await tester.pumpWidget(buildChip()); |
| |
| expect(tester.getSize(find.byType(FilterChip)).width, equals(234.0)); |
| expect(tester.getSize(find.byType(FilterChip)).height, equals(118.0)); |
| |
| // Calculate the distance between delete icon and chip edges. |
| Offset chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester))); |
| final Offset deleteIconCenter = tester.getCenter(find.byIcon(Icons.clear)); |
| expect(chipTopRight.dx, deleteIconCenter.dx + (labelSize.width / 2) + padding + border); |
| expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border); |
| |
| // Calculate the distance between delete icon and label. |
| Offset labelTopRight = tester.getTopRight(find.byType(Container)); |
| expect(labelTopRight.dx, deleteIconCenter.dx - (labelSize.width / 2) - labelPadding); |
| |
| // Test custom avatar layout constraints. |
| await tester.pumpWidget(buildChip( |
| deleteIconBoxConstraints: const BoxConstraints.tightForFinite(), |
| )); |
| await tester.pump(); |
| |
| expect(tester.getSize(find.byType(FilterChip)).width, equals(152.0)); |
| expect(tester.getSize(find.byType(FilterChip )).height, equals(118.0)); |
| |
| // Calculate the distance between delete icon and chip edges. |
| chipTopRight = tester.getTopRight(find.byWidget(getMaterial(tester))); |
| expect(chipTopRight.dx, deleteIconCenter.dx + (iconSize / 2) + padding + border); |
| expect(chipTopRight.dy, deleteIconCenter.dy - (labelSize.width / 2) - padding - border); |
| |
| // Calculate the distance between delete icon and label. |
| labelTopRight = tester.getTopRight(find.byType(Container)); |
| expect(labelTopRight.dx, deleteIconCenter.dx - (iconSize / 2) - labelPadding); |
| }); |
| } |