![auto-submit[bot]](/assets/img/avatar_default.png)
Reverts: flutter/flutter#142942 Initiated by: zanderso Reason for reverting: Seems to have affected iOS platform view focus: https://ci.chromium.org/ui/p/flutter/builders/prod/Mac_ios%20native_platform_view_ui_tests_ios/10626/overview Original PR Author: gspencergoog Reviewed By: {yjbanov, goderbauer, chunhtai} This change reverts the following previous change: ## Description This causes the `Focus` widget to request focus on its focus node if the accessibility system (screen reader) focuses a widget via the `SemanticsAction.focus` action. ## Related Issues - https://github.com/flutter/flutter/issues/83809 ## Tests - Added a test to make sure that focus is requested when `SemanticsAction.focus` is sent by the engine.
5828 lines
215 KiB
Dart
5828 lines
215 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/scheduler.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import '../widgets/feedback_tester.dart';
|
|
import '../widgets/semantics_tester.dart';
|
|
|
|
Finder findRenderChipElement() {
|
|
return find.byElementPredicate((Element e) => '${e.renderObject.runtimeType}' == '_RenderChip');
|
|
}
|
|
|
|
RenderBox getMaterialBox(WidgetTester tester) {
|
|
return tester.firstRenderObject<RenderBox>(
|
|
find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byType(CustomPaint),
|
|
),
|
|
);
|
|
}
|
|
|
|
Material getMaterial(WidgetTester tester) {
|
|
return tester.widget<Material>(
|
|
find.descendant(
|
|
of: find.byType(RawChip),
|
|
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,
|
|
);
|
|
}
|
|
|
|
TextStyle? getIconStyle(WidgetTester tester, IconData icon) {
|
|
final RichText iconRichText = tester.widget<RichText>(
|
|
find.descendant(of: find.byIcon(icon).first, matching: find.byType(RichText)),
|
|
);
|
|
return iconRichText.text.style;
|
|
}
|
|
|
|
dynamic getRenderChip(WidgetTester tester) {
|
|
if (!tester.any(findRenderChipElement())) {
|
|
return null;
|
|
}
|
|
final Element element = tester.element(findRenderChipElement().first);
|
|
return element.renderObject;
|
|
}
|
|
|
|
// ignore: avoid_dynamic_calls
|
|
double getSelectProgress(WidgetTester tester) => getRenderChip(tester)?.checkmarkAnimation?.value as double;
|
|
// ignore: avoid_dynamic_calls
|
|
double getAvatarDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.avatarDrawerAnimation?.value as double;
|
|
// ignore: avoid_dynamic_calls
|
|
double getDeleteDrawerProgress(WidgetTester tester) => getRenderChip(tester)?.deleteDrawerAnimation?.value as double;
|
|
|
|
/// 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),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Tests that a [Chip] that has its size constrained by its parent is
|
|
/// further constraining the size of its child, the label widget.
|
|
/// Optionally, adding an avatar or delete icon to the chip should not
|
|
/// cause the chip or label to exceed its constrained height.
|
|
Future<void> testConstrainedLabel(
|
|
WidgetTester tester, {
|
|
CircleAvatar? avatar,
|
|
VoidCallback? onDeleted,
|
|
}) async {
|
|
const double labelWidth = 100.0;
|
|
const double labelHeight = 50.0;
|
|
const double chipParentWidth = 75.0;
|
|
const double chipParentHeight = 25.0;
|
|
final Key labelKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: chipParentWidth,
|
|
height: chipParentHeight,
|
|
child: Chip(
|
|
avatar: avatar,
|
|
label: SizedBox(
|
|
key: labelKey,
|
|
width: labelWidth,
|
|
height: labelHeight,
|
|
),
|
|
onDeleted: onDeleted,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size labelSize = tester.getSize(find.byKey(labelKey));
|
|
expect(labelSize.width, lessThan(chipParentWidth));
|
|
expect(labelSize.height, lessThanOrEqualTo(chipParentHeight));
|
|
|
|
final Size chipSize = tester.getSize(find.byType(Chip));
|
|
expect(chipSize.width, chipParentWidth);
|
|
expect(chipSize.height, chipParentHeight);
|
|
}
|
|
|
|
void doNothing() {}
|
|
|
|
Widget chipWithOptionalDeleteButton({
|
|
Key? deleteButtonKey,
|
|
Key? labelKey,
|
|
required bool deletable,
|
|
TextDirection textDirection = TextDirection.ltr,
|
|
String? chipTooltip,
|
|
String? deleteButtonTooltipMessage,
|
|
double? size,
|
|
VoidCallback? onPressed = doNothing,
|
|
ThemeData? themeData,
|
|
}) {
|
|
return wrapForChip(
|
|
textDirection: textDirection,
|
|
theme: themeData,
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
RawChip(
|
|
tooltip: chipTooltip,
|
|
onPressed: onPressed,
|
|
onDeleted: deletable ? doNothing : null,
|
|
deleteIcon: Icon(
|
|
key: deleteButtonKey,
|
|
size: size,
|
|
Icons.close,
|
|
),
|
|
deleteButtonTooltipMessage: deleteButtonTooltipMessage,
|
|
label: Text(
|
|
deletable
|
|
? 'Chip with Delete Button'
|
|
: 'Chip without Delete Button',
|
|
key: labelKey,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
bool offsetsAreClose(Offset a, Offset b) => (a - b).distance < 1.0;
|
|
bool radiiAreClose(double a, double b) => (a - b).abs() < 1.0;
|
|
|
|
// Ripple pattern matches if there exists at least one ripple
|
|
// with the [expectedCenter] and [expectedRadius].
|
|
// This ensures the existence of a ripple.
|
|
PaintPattern ripplePattern(Offset expectedCenter, double expectedRadius) {
|
|
return paints
|
|
..something((Symbol method, List<dynamic> arguments) {
|
|
if (method != #drawCircle) {
|
|
return false;
|
|
}
|
|
final Offset center = arguments[0] as Offset;
|
|
final double radius = arguments[1] as double;
|
|
return offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius);
|
|
}
|
|
);
|
|
}
|
|
|
|
// Unique ripple pattern matches if there does not exist ripples
|
|
// other than ones with the [expectedCenter] and [expectedRadius].
|
|
// This ensures the nonexistence of two different ripples.
|
|
PaintPattern uniqueRipplePattern(Offset expectedCenter, double expectedRadius) {
|
|
return paints
|
|
..everything((Symbol method, List<dynamic> arguments) {
|
|
if (method != #drawCircle) {
|
|
return true;
|
|
}
|
|
final Offset center = arguments[0] as Offset;
|
|
final double radius = arguments[1] as double;
|
|
if (offsetsAreClose(center, expectedCenter) && radiiAreClose(radius, expectedRadius)) {
|
|
return true;
|
|
}
|
|
throw '''
|
|
Expected: center == $expectedCenter, radius == $expectedRadius
|
|
Found: center == $center radius == $radius''';
|
|
}
|
|
);
|
|
}
|
|
|
|
// Finds any container of a tooltip.
|
|
Finder findTooltipContainer(String tooltipText) {
|
|
return find.ancestor(
|
|
of: find.text(tooltipText),
|
|
matching: find.byType(Container),
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('M2 Chip defaults', (WidgetTester tester) async {
|
|
late TextTheme textTheme;
|
|
|
|
Widget buildFrame(Brightness brightness) {
|
|
return MaterialApp(
|
|
theme: ThemeData(brightness: brightness, useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textTheme = Theme.of(context).textTheme;
|
|
return Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () { },
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(Brightness.light));
|
|
expect(getMaterialBox(tester), paints..rrect()..circle(color: const Color(0xff1976d2)));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0));
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, const StadiumBorder());
|
|
expect(getIconData(tester).color?.value, 0xffffffff);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, null);
|
|
|
|
TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color?.value, 0xde000000);
|
|
expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.bodyLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.bodyLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.bodyLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing);
|
|
|
|
await tester.pumpWidget(buildFrame(Brightness.dark));
|
|
await tester.pumpAndSettle(); // Theme transition animation
|
|
expect(getMaterialBox(tester), paints..rrect(color: const Color(0x1fffffff)));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0));
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, const StadiumBorder());
|
|
expect(getIconData(tester).color?.value, 0xffffffff);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, null);
|
|
|
|
labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color?.value, 0xdeffffff);
|
|
expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.bodyLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.bodyLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.bodyLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing);
|
|
});
|
|
|
|
testWidgets('M3 Chip defaults', (WidgetTester tester) async {
|
|
late TextTheme textTheme;
|
|
final ThemeData lightTheme = ThemeData.light();
|
|
final ThemeData darkTheme = ThemeData.dark();
|
|
|
|
Widget buildFrame(ThemeData theme) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textTheme = Theme.of(context).textTheme;
|
|
return Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () { },
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(lightTheme));
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, RoundedRectangleBorder(
|
|
side: BorderSide(color: lightTheme.colorScheme.outline),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
));
|
|
expect(getIconData(tester).color, lightTheme.colorScheme.primary);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, 18);
|
|
|
|
TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color, lightTheme.colorScheme.onSurfaceVariant);
|
|
expect(labelStyle.fontFamily, textTheme.labelLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.labelLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.labelLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.labelLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.labelLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.labelLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.labelLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.labelLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.labelLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.labelLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.labelLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.labelLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.labelLarge?.wordSpacing);
|
|
|
|
await tester.pumpWidget(buildFrame(darkTheme));
|
|
await tester.pumpAndSettle(); // Theme transition animation
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, RoundedRectangleBorder(
|
|
side: BorderSide(color: darkTheme.colorScheme.outline),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
));
|
|
expect(getIconData(tester).color, darkTheme.colorScheme.primary);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, 18);
|
|
|
|
labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color, darkTheme.colorScheme.onSurfaceVariant);
|
|
expect(labelStyle.fontFamily, textTheme.labelLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.labelLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.labelLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.labelLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.labelLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.labelLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.labelLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.labelLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.labelLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.labelLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.labelLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.labelLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.labelLarge?.wordSpacing);
|
|
});
|
|
|
|
testWidgets('Chip control test', (WidgetTester tester) async {
|
|
final FeedbackTester feedback = FeedbackTester();
|
|
final List<String> deletedChipLabels = <String>[];
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () {
|
|
deletedChipLabels.add('A');
|
|
},
|
|
deleteButtonTooltipMessage: 'Delete chip A',
|
|
),
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('B')),
|
|
label: const Text('Chip B'),
|
|
onDeleted: () {
|
|
deletedChipLabels.add('B');
|
|
},
|
|
deleteButtonTooltipMessage: 'Delete chip B',
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.widget(find.byTooltip('Delete chip A')), isNotNull);
|
|
expect(tester.widget(find.byTooltip('Delete chip B')), isNotNull);
|
|
|
|
expect(feedback.clickSoundCount, 0);
|
|
|
|
expect(deletedChipLabels, isEmpty);
|
|
await tester.tap(find.byTooltip('Delete chip A'));
|
|
expect(deletedChipLabels, equals(<String>['A']));
|
|
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(feedback.clickSoundCount, 1);
|
|
|
|
await tester.tap(find.byTooltip('Delete chip B'));
|
|
expect(deletedChipLabels, equals(<String>['A', 'B']));
|
|
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
expect(feedback.clickSoundCount, 2);
|
|
|
|
feedback.dispose();
|
|
});
|
|
|
|
testWidgets(
|
|
'Chip does not constrain size of label widget if it does not exceed '
|
|
'the available space',
|
|
(WidgetTester tester) async {
|
|
const double labelWidth = 50.0;
|
|
const double labelHeight = 30.0;
|
|
final Key labelKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 500.0,
|
|
height: 500.0,
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: SizedBox(
|
|
key: labelKey,
|
|
width: labelWidth,
|
|
height: labelHeight,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Size labelSize = tester.getSize(find.byKey(labelKey));
|
|
expect(labelSize.width, labelWidth);
|
|
expect(labelSize.height, labelHeight);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Chip constrains the size of the label widget when it exceeds the '
|
|
'available space',
|
|
(WidgetTester tester) async {
|
|
await testConstrainedLabel(tester);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Chip constrains the size of the label widget when it exceeds the '
|
|
'available space and the avatar is present',
|
|
(WidgetTester tester) async {
|
|
await testConstrainedLabel(
|
|
tester,
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Chip constrains the size of the label widget when it exceeds the '
|
|
'available space and the delete icon is present',
|
|
(WidgetTester tester) async {
|
|
await testConstrainedLabel(
|
|
tester,
|
|
onDeleted: () { },
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Chip constrains the size of the label widget when it exceeds the '
|
|
'available space and both avatar and delete icons are present',
|
|
(WidgetTester tester) async {
|
|
await testConstrainedLabel(
|
|
tester,
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
onDeleted: () { },
|
|
);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'Chip constrains the avatar, label, and delete icons to the bounds of '
|
|
'the chip when it exceeds the available space',
|
|
(WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/11523
|
|
Widget chipBuilder (String text, {Widget? avatar, VoidCallback? onDeleted}) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: SizedBox(
|
|
width: 150,
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: avatar,
|
|
label: Text(text),
|
|
onDeleted: onDeleted,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void chipRectContains(Rect chipRect, Rect rect) {
|
|
expect(chipRect.contains(rect.topLeft), true);
|
|
expect(chipRect.contains(rect.topRight), true);
|
|
expect(chipRect.contains(rect.bottomLeft), true);
|
|
expect(chipRect.contains(rect.bottomRight), true);
|
|
}
|
|
|
|
Rect chipRect;
|
|
Rect avatarRect;
|
|
Rect labelRect;
|
|
Rect deleteIconRect;
|
|
const String text = 'Very long text that will be clipped';
|
|
|
|
await tester.pumpWidget(chipBuilder(text));
|
|
|
|
chipRect = tester.getRect(find.byType(Chip));
|
|
labelRect = tester.getRect(find.text(text));
|
|
chipRectContains(chipRect, labelRect);
|
|
|
|
await tester.pumpWidget(chipBuilder(
|
|
text,
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
chipRect = tester.getRect(find.byType(Chip));
|
|
avatarRect = tester.getRect(find.byType(CircleAvatar));
|
|
chipRectContains(chipRect, avatarRect);
|
|
|
|
labelRect = tester.getRect(find.text(text));
|
|
chipRectContains(chipRect, labelRect);
|
|
|
|
await tester.pumpWidget(chipBuilder(
|
|
text,
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
onDeleted: () {},
|
|
));
|
|
await tester.pumpAndSettle();
|
|
|
|
chipRect = tester.getRect(find.byType(Chip));
|
|
avatarRect = tester.getRect(find.byType(CircleAvatar));
|
|
chipRectContains(chipRect, avatarRect);
|
|
|
|
labelRect = tester.getRect(find.text(text));
|
|
chipRectContains(chipRect, labelRect);
|
|
|
|
deleteIconRect = tester.getRect(find.byIcon(Icons.cancel));
|
|
chipRectContains(chipRect, deleteIconRect);
|
|
},
|
|
);
|
|
|
|
testWidgets('Material2 - Chip in row works ok', (WidgetTester tester) async {
|
|
const TextStyle style = TextStyle(fontSize: 10.0);
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Chip(label: Text('Test'), labelStyle: style),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0));
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Flexible(child: Chip(label: Text('Test'), labelStyle: style)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(64.0, 48.0));
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Expanded(child: Chip(label: Text('Test'), labelStyle: style)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)), const Size(40.0, 10.0));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 48.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip in row works ok', (WidgetTester tester) async {
|
|
const TextStyle style = TextStyle(fontSize: 10.0);
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Chip(label: Text('Test'), labelStyle: style),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)).width, closeTo(40.4, 0.01));
|
|
expect(tester.getSize(find.byType(Text)).height, equals(14.0));
|
|
expect(tester.getSize(find.byType(Chip)).width, closeTo(74.4, 0.01));
|
|
expect(tester.getSize(find.byType(Chip)).height, equals(48.0));
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Flexible(child: Chip(label: Text('Test'), labelStyle: style)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)).width, closeTo(40.4, 0.01));
|
|
expect(tester.getSize(find.byType(Text)).height, equals(14.0));
|
|
expect(tester.getSize(find.byType(Chip)).width, closeTo(74.4, 0.01));
|
|
expect(tester.getSize(find.byType(Chip)).height, equals(48.0));
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Row(
|
|
children: <Widget>[
|
|
Expanded(child: Chip(label: Text('Test'), labelStyle: style)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Text)).width, closeTo(40.4, 0.01));
|
|
expect(tester.getSize(find.byType(Text)).height, equals(14.0));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(800.0, 48.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Material2 - Chip responds to materialTapTargetSize', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: Text('X'),
|
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
),
|
|
Chip(
|
|
label: Text('X'),
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(Chip).first), const Size(48.0, 48.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(38.0, 32.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip responds to materialTapTargetSize', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: Text('X'),
|
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
),
|
|
Chip(
|
|
label: Text('X'),
|
|
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byType(Chip).first).width, closeTo(48.1, 0.01));
|
|
expect(tester.getSize(find.byType(Chip).first).height, equals(48.0));
|
|
expect(tester.getSize(find.byType(Chip).last).width, closeTo(48.1, 0.01));
|
|
expect(tester.getSize(find.byType(Chip).last).height, equals(38.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Delete button tap target is the right proportion of the chip', (WidgetTester tester) async {
|
|
final UniqueKey deleteKey = UniqueKey();
|
|
bool calledDelete = false;
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: const Text('Really Long Label'),
|
|
deleteIcon: Icon(Icons.delete, key: deleteKey),
|
|
onDeleted: () {
|
|
calledDelete = true;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
// Test correct tap target size.
|
|
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(18.0, 0.0)); // Half the width of the delete button + right label padding.
|
|
await tester.pump();
|
|
expect(calledDelete, isTrue);
|
|
calledDelete = false;
|
|
|
|
// Test incorrect tap target size.
|
|
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(19.0, 0.0));
|
|
await tester.pump();
|
|
expect(calledDelete, isFalse);
|
|
calledDelete = false;
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: const SizedBox(), // Short label
|
|
deleteIcon: Icon(Icons.cancel, key: deleteKey),
|
|
onDeleted: () {
|
|
calledDelete = true;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
// Chip width is 48 with padding, 40 without padding, so halfway is at 20. Cancel
|
|
// icon is 24x24, so since 24 > 20 the split location should be halfway across the
|
|
// chip, which is at 12 + 8 = 20 from the right side. Since the split is just
|
|
// slightly less than 50%, 8 from the center of the delete button should hit the
|
|
// chip, not the delete button.
|
|
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(7.0, 0.0));
|
|
await tester.pump();
|
|
expect(calledDelete, isTrue);
|
|
calledDelete = false;
|
|
|
|
await tester.tapAt(tester.getCenter(find.byKey(deleteKey)) - const Offset(8.0, 0.0));
|
|
await tester.pump();
|
|
expect(calledDelete, isFalse);
|
|
});
|
|
|
|
testWidgets('Chip elements are ordered horizontally for locale', (WidgetTester tester) async {
|
|
final UniqueKey iconKey = UniqueKey();
|
|
late final OverlayEntry entry;
|
|
addTearDown(() => entry..remove()..dispose());
|
|
final Widget test = Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
entry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Material(
|
|
child: Chip(
|
|
deleteIcon: Icon(Icons.delete, key: iconKey),
|
|
onDeleted: () { },
|
|
label: const Text('ABC'),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: test,
|
|
textDirection: TextDirection.rtl,
|
|
),
|
|
);
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
|
expect(tester.getCenter(find.text('ABC')).dx, greaterThan(tester.getCenter(find.byKey(iconKey)).dx));
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: test,
|
|
),
|
|
);
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
|
expect(tester.getCenter(find.text('ABC')).dx, lessThan(tester.getCenter(find.byKey(iconKey)).dx));
|
|
});
|
|
|
|
testWidgets('Material2 - Chip responds to textScaleFactor', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A'),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSize(find.text('Chip A')),
|
|
const Size(84.0, 14.0),
|
|
);
|
|
expect(
|
|
tester.getSize(find.text('Chip B')),
|
|
const Size(84.0, 14.0),
|
|
);
|
|
expect(tester.getSize(find.byType(Chip).first), const Size(132.0, 48.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(132.0, 48.0));
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
textScaler: const TextScaler.linear(3.0),
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A'),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.text('Chip A')), const Size(252.0, 42.0));
|
|
expect(tester.getSize(find.text('Chip B')), const Size(252.0, 42.0));
|
|
expect(tester.getSize(find.byType(Chip).first), const Size(310.0, 50.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(310.0, 50.0));
|
|
|
|
// Check that individual text scales are taken into account.
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A', textScaleFactor: 3.0),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.text('Chip A')), const Size(252.0, 42.0));
|
|
expect(tester.getSize(find.text('Chip B')), const Size(84.0, 14.0));
|
|
expect(tester.getSize(find.byType(Chip).first), const Size(318.0, 50.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(132.0, 48.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip responds to textScaleFactor', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A'),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.text('Chip A')).width, closeTo(84.5, 0.1));
|
|
expect(tester.getSize(find.text('Chip A')).height, equals(20.0));
|
|
expect(tester.getSize(find.text('Chip B')).width, closeTo(84.5, 0.1));
|
|
expect(tester.getSize(find.text('Chip B')).height, equals(20.0));
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
textScaler: const TextScaler.linear(3.0),
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A'),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.text('Chip A')).width, closeTo(252.6, 0.1));
|
|
expect(tester.getSize(find.text('Chip A')).height, equals(60.0));
|
|
expect(tester.getSize(find.text('Chip B')).width, closeTo(252.6, 0.1));
|
|
expect(tester.getSize(find.text('Chip B')).height, equals(60.0));
|
|
expect(tester.getSize(find.byType(Chip).first).width, closeTo(338.6, 0.1));
|
|
expect(tester.getSize(find.byType(Chip).first).height, equals(78.0));
|
|
expect(tester.getSize(find.byType(Chip).last).width, closeTo(338.6, 0.1));
|
|
expect(tester.getSize(find.byType(Chip).last).height, equals(78.0));
|
|
|
|
// Check that individual text scales are taken into account.
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A', textScaleFactor: 3.0),
|
|
),
|
|
Chip(
|
|
avatar: CircleAvatar(child: Text('B')),
|
|
label: Text('Chip B'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.text('Chip A')).width, closeTo(252.6, 0.01));
|
|
expect(tester.getSize(find.text('Chip A')).height, equals(60.0));
|
|
expect(tester.getSize(find.text('Chip B')).width, closeTo(84.59, 0.01));
|
|
expect(tester.getSize(find.text('Chip B')).height, equals(20.0));
|
|
expect(tester.getSize(find.byType(Chip).first).width, closeTo(346.6, 0.01));
|
|
expect(tester.getSize(find.byType(Chip).first).height, equals(78.0));
|
|
expect(tester.getSize(find.byType(Chip).last).width, closeTo(138.59, 0.01));
|
|
expect(tester.getSize(find.byType(Chip).last).height, equals(48.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Material2 - Labels can be non-text widgets', (WidgetTester tester) async {
|
|
final Key keyA = GlobalKey();
|
|
final Key keyB = GlobalKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A', key: keyA),
|
|
),
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('B')),
|
|
label: SizedBox(key: keyB, width: 10.0, height: 10.0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(keyA)), const Size(84.0, 14.0));
|
|
expect(tester.getSize(find.byKey(keyB)), const Size(10.0, 10.0));
|
|
expect(tester.getSize(find.byType(Chip).first), const Size(132.0, 48.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(58.0, 48.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Labels can be non-text widgets', (WidgetTester tester) async {
|
|
final Key keyA = GlobalKey();
|
|
final Key keyB = GlobalKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: Text('Chip A', key: keyA),
|
|
),
|
|
Chip(
|
|
avatar: const CircleAvatar(child: Text('B')),
|
|
label: SizedBox(key: keyB, width: 10.0, height: 10.0),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(keyA)).width, moreOrLessEquals(84.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(keyA)).height, equals(20.0));
|
|
expect(tester.getSize(find.byKey(keyB)), const Size(10.0, 10.0));
|
|
expect(tester.getSize(find.byType(Chip).first).width, moreOrLessEquals(138.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(Chip).first).height, equals(48.0));
|
|
expect(tester.getSize(find.byType(Chip).last), const Size(60.0, 48.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Avatars can be non-circle avatar widgets', (WidgetTester tester) async {
|
|
final Key keyA = GlobalKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
avatar: SizedBox(key: keyA, width: 20.0, height: 20.0),
|
|
label: const Text('Chip A'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0)));
|
|
});
|
|
|
|
testWidgets('Delete icons can be non-icon widgets', (WidgetTester tester) async {
|
|
final Key keyA = GlobalKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
deleteIcon: SizedBox(key: keyA, width: 20.0, height: 20.0),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () { },
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(keyA)), equals(const Size(20.0, 20.0)));
|
|
});
|
|
|
|
testWidgets('Chip padding - LTR', (WidgetTester tester) async {
|
|
final GlobalKey keyA = GlobalKey();
|
|
final GlobalKey keyB = GlobalKey();
|
|
|
|
late final OverlayEntry entry;
|
|
addTearDown(() => entry..remove()..dispose());
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
entry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Material(
|
|
child: Center(
|
|
child: Chip(
|
|
avatar: Placeholder(key: keyA),
|
|
label: SizedBox(
|
|
key: keyB,
|
|
width: 40.0,
|
|
height: 40.0,
|
|
),
|
|
onDeleted: () { },
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(332.0, 280.0));
|
|
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(372.0, 320.0));
|
|
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(380.0, 280.0));
|
|
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(420.0, 320.0));
|
|
expect(tester.getTopLeft(find.byType(Icon)), const Offset(439.0, 291.0));
|
|
expect(tester.getBottomRight(find.byType(Icon)), const Offset(457.0, 309.0));
|
|
});
|
|
|
|
testWidgets('Chip padding - RTL', (WidgetTester tester) async {
|
|
final GlobalKey keyA = GlobalKey();
|
|
final GlobalKey keyB = GlobalKey();
|
|
|
|
late final OverlayEntry entry;
|
|
addTearDown(() => entry..remove()..dispose());
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
textDirection: TextDirection.rtl,
|
|
child: Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
entry = OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return Material(
|
|
child: Center(
|
|
child: Chip(
|
|
avatar: Placeholder(key: keyA),
|
|
label: SizedBox(
|
|
key: keyB,
|
|
width: 40.0,
|
|
height: 40.0,
|
|
),
|
|
onDeleted: () { },
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getTopLeft(find.byKey(keyA)), const Offset(428.0, 280.0));
|
|
expect(tester.getBottomRight(find.byKey(keyA)), const Offset(468.0, 320.0));
|
|
expect(tester.getTopLeft(find.byKey(keyB)), const Offset(380.0, 280.0));
|
|
expect(tester.getBottomRight(find.byKey(keyB)), const Offset(420.0, 320.0));
|
|
expect(tester.getTopLeft(find.byType(Icon)), const Offset(343.0, 291.0));
|
|
expect(tester.getBottomRight(find.byType(Icon)), const Offset(361.0, 309.0));
|
|
});
|
|
|
|
testWidgets('Material2 - Avatar drawer works as expected on RawChip', (WidgetTester tester) async {
|
|
final GlobalKey labelKey = GlobalKey();
|
|
Future<void> pushChip({ Widget? avatar }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
RawChip(
|
|
avatar: avatar,
|
|
label: Text('Chip', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// No avatar
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
final GlobalKey avatarKey = GlobalKey();
|
|
|
|
// Add an avatar
|
|
await pushChip(
|
|
avatar: Container(
|
|
key: avatarKey,
|
|
color: const Color(0xff000000),
|
|
width: 40.0,
|
|
height: 40.0,
|
|
),
|
|
);
|
|
// Avatar drawer should start out closed.
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(-20.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Avatar drawer should start expanding.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(81.2, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-18.8, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(13.2, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(86.7, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-13.3, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(18.6, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(94.7, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-5.3, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(26.7, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(99.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-0.5, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(31.5, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, and make sure it didn't change
|
|
// height.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0)));
|
|
|
|
// Remove the avatar again
|
|
await pushChip();
|
|
// Avatar drawer should start out open.
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(4.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(36.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Avatar drawer should start contracting.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(2.9, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(34.9, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(98.0, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-2.0, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(30.0, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(84.1, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-15.9, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(16.1, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(80.0, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-20.0, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(12.0, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, make sure it didn't change
|
|
// height, and make sure that the avatar is no longer drawn.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
expect(find.byKey(avatarKey), findsNothing);
|
|
});
|
|
|
|
testWidgets('Material3 - Avatar drawer works as expected on RawChip', (WidgetTester tester) async {
|
|
final GlobalKey labelKey = GlobalKey();
|
|
Future<void> pushChip({ Widget? avatar }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
RawChip(
|
|
avatar: avatar,
|
|
label: Text('Chip', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// No avatar
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
final GlobalKey avatarKey = GlobalKey();
|
|
|
|
// Add an avatar
|
|
await pushChip(
|
|
avatar: Container(
|
|
key: avatarKey,
|
|
color: const Color(0xff000000),
|
|
width: 40.0,
|
|
height: 40.0,
|
|
),
|
|
);
|
|
// Avatar drawer should start out closed.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(-11.0, 14.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Avatar drawer should start expanding.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(91.3, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-10, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(17.9, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(95.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-5.4, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(22.5, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.6, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(1.2, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(29.2, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(106.6, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(5.2, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(33.2, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, and make sure it didn't change
|
|
// height.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(110.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(9.0, 14.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(37.0, 14.0)));
|
|
|
|
// Remove the avatar again
|
|
await pushChip();
|
|
// Avatar drawer should start out open.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(110.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)), equals(const Offset(9.0, 14.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(37.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Avatar drawer should start contracting.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(109.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(8.1, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(36.1, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(105.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(4.0, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(32.0, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(93.7, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-7.6, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(20.3, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(avatarKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(avatarKey)).dx, moreOrLessEquals(-11.0, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)).dx, moreOrLessEquals(17.0, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, make sure it didn't change
|
|
// height, and make sure that the avatar is no longer drawn.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
expect(find.byKey(avatarKey), findsNothing);
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Material2 - Delete button drawer works as expected on RawChip', (WidgetTester tester) async {
|
|
const Key labelKey = Key('label');
|
|
const Key deleteButtonKey = Key('delete');
|
|
bool wasDeleted = false;
|
|
Future<void> pushChip({ bool deletable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
onDeleted: deletable
|
|
? () {
|
|
setState(() {
|
|
wasDeleted = true;
|
|
});
|
|
}
|
|
: null,
|
|
deleteIcon: Container(width: 40.0, height: 40.0, color: Colors.blue, key: deleteButtonKey),
|
|
label: const Text('Chip', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// No delete button
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
|
|
// Add a delete button
|
|
await pushChip(deletable: true);
|
|
// Delete button drawer should start out closed.
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(52.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Delete button drawer should start expanding.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(81.2, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(53.2, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(86.7, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(58.7, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(94.7, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(66.7, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(99.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(71.5, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, and make sure it didn't change
|
|
// height.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
// Test the tap work for the delete button, but not the rest of the chip.
|
|
expect(wasDeleted, isFalse);
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(wasDeleted, isFalse);
|
|
await tester.tap(find.byKey(deleteButtonKey));
|
|
expect(wasDeleted, isTrue);
|
|
|
|
// Remove the delete button again
|
|
await pushChip();
|
|
// Delete button drawer should start out open.
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(104.0, 48.0)));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)), equals(const Offset(76.0, 12.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Delete button drawer should start contracting.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(103.8, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(75.8, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(74.9, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(101.0, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(73.0, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(97.5, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(24.0, 24.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(69.5, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, make sure it didn't change
|
|
// height, and make sure that the delete button is no longer drawn.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(80.0, 48.0)));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(12.0, 17.0)));
|
|
expect(find.byKey(deleteButtonKey), findsNothing);
|
|
});
|
|
|
|
testWidgets('Material3 - Delete button drawer works as expected on RawChip', (WidgetTester tester) async {
|
|
const Key labelKey = Key('label');
|
|
const Key deleteButtonKey = Key('delete');
|
|
bool wasDeleted = false;
|
|
Future<void> pushChip({ bool deletable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
onDeleted: deletable
|
|
? () {
|
|
setState(() {
|
|
wasDeleted = true;
|
|
});
|
|
}
|
|
: null,
|
|
deleteIcon: Container(width: 40.0, height: 40.0, color: Colors.blue, key: deleteButtonKey),
|
|
label: const Text('Chip', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// No delete button
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.01));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
|
|
// Add a delete button
|
|
await pushChip(deletable: true);
|
|
// Delete button drawer should start out closed.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.01));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(
|
|
find.byKey(deleteButtonKey)),
|
|
offsetMoreOrLessEquals(const Offset(61.4, 14.0), epsilon: 0.01),
|
|
);
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Delete button drawer should start expanding.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(91.3, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(62.3, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(95.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(66.9, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(102.6, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(73.6, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(106.6, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(77.6, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, and make sure it didn't change
|
|
// height.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(110.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(
|
|
tester.getTopLeft(find.byKey(deleteButtonKey)),
|
|
offsetMoreOrLessEquals(const Offset(81.4, 14.0), epsilon: 0.01),
|
|
);
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
// Test the tap work for the delete button, but not the rest of the chip.
|
|
expect(wasDeleted, isFalse);
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(wasDeleted, isFalse);
|
|
await tester.tap(find.byKey(deleteButtonKey));
|
|
expect(wasDeleted, isTrue);
|
|
|
|
// Remove the delete button again
|
|
await pushChip();
|
|
// Delete button drawer should start out open.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(110.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(
|
|
tester.getTopLeft(find.byKey(deleteButtonKey)),
|
|
offsetMoreOrLessEquals(const Offset(81.4, 14.0), epsilon: 0.01),
|
|
);
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
// Delete button drawer should start contracting.
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(110.1, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(81.1, epsilon: 0.1));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(109.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(80.4, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(107.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(78.9, epsilon: 0.1));
|
|
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(104.9, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(deleteButtonKey)), equals(const Size(20.0, 20.0)));
|
|
expect(tester.getTopLeft(find.byKey(deleteButtonKey)).dx, moreOrLessEquals(75.9, epsilon: 0.1));
|
|
|
|
// Wait for being done with animation, make sure it didn't change
|
|
// height, and make sure that the delete button is no longer drawn.
|
|
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
|
expect(tester.getSize(find.byType(RawChip)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byType(RawChip)).height, equals(48.0));
|
|
expect(tester.getTopLeft(find.byKey(labelKey)), equals(const Offset(17.0, 14.0)));
|
|
expect(find.byKey(deleteButtonKey), findsNothing);
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Delete button takes up at most half of the chip', (WidgetTester tester) async {
|
|
final UniqueKey chipKey = UniqueKey();
|
|
bool chipPressed = false;
|
|
bool deletePressed = false;
|
|
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
RawChip(
|
|
key: chipKey,
|
|
onPressed: () {
|
|
chipPressed = true;
|
|
},
|
|
onDeleted: () {
|
|
deletePressed = true;
|
|
},
|
|
label: const Text(''),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tapAt(tester.getCenter(find.byKey(chipKey)));
|
|
await tester.pump();
|
|
expect(chipPressed, isTrue);
|
|
expect(deletePressed, isFalse);
|
|
chipPressed = false;
|
|
|
|
await tester.tapAt(tester.getCenter(find.byKey(chipKey)) + const Offset(1.0, 0.0));
|
|
await tester.pump();
|
|
expect(chipPressed, isFalse);
|
|
expect(deletePressed, isTrue);
|
|
});
|
|
|
|
testWidgets('Material2 - Chip creates centered, unique ripple when label is tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
themeData: ThemeData(useMaterial3: false),
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
),
|
|
);
|
|
|
|
final RenderBox box = getMaterialBox(tester);
|
|
|
|
// Taps at a location close to the center of the label.
|
|
final Offset centerOfLabel = tester.getCenter(find.byKey(labelKey));
|
|
final Offset tapLocationOfLabel = centerOfLabel + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfLabel);
|
|
await tester.pump();
|
|
|
|
// Waits for 100 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique, centered ink ripple.
|
|
expect(box, ripplePattern(const Offset(163.0, 6.0), 20.9));
|
|
expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 20.9));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 100 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The ripple should grow, with the same center.
|
|
expect(box, ripplePattern(const Offset(163.0, 6.0), 41.8));
|
|
expect(box, uniqueRipplePattern(const Offset(163.0, 6.0), 41.8));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should still be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material3 - Chip creates centered, unique sparkle when label is tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
),
|
|
);
|
|
|
|
// Taps at a location close to the center of the label.
|
|
final Offset centerOfLabel = tester.getCenter(find.byKey(labelKey));
|
|
final Offset tapLocationOfLabel = centerOfLabel + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfLabel);
|
|
await tester.pump();
|
|
|
|
// Waits for 100 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique, centered ink sparkle.
|
|
await expectLater(find.byType(RawChip), matchesGoldenFile('chip.label_tapped.ink_sparkle.0.png'));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 100 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The sparkle should grow, with the same center.
|
|
await expectLater(find.byType(RawChip), matchesGoldenFile('chip.label_tapped.ink_sparkle.1.png'));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should still be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Delete button is focusable', (WidgetTester tester) async {
|
|
final GlobalKey labelKey = GlobalKey();
|
|
final GlobalKey deleteButtonKey = GlobalKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
),
|
|
);
|
|
|
|
Focus.of(deleteButtonKey.currentContext!).requestFocus();
|
|
await tester.pump();
|
|
|
|
// They shouldn't have the same focus node.
|
|
expect(Focus.of(deleteButtonKey.currentContext!), isNot(equals(Focus.of(labelKey.currentContext!))));
|
|
expect(Focus.of(deleteButtonKey.currentContext!).hasFocus, isTrue);
|
|
expect(Focus.of(deleteButtonKey.currentContext!).hasPrimaryFocus, isTrue);
|
|
// Delete button is a child widget of the Chip, so the Chip should have focus if
|
|
// the delete button does.
|
|
expect(Focus.of(labelKey.currentContext!).hasFocus, isTrue);
|
|
expect(Focus.of(labelKey.currentContext!).hasPrimaryFocus, isFalse);
|
|
|
|
Focus.of(labelKey.currentContext!).requestFocus();
|
|
await tester.pump();
|
|
|
|
expect(Focus.of(deleteButtonKey.currentContext!).hasFocus, isFalse);
|
|
expect(Focus.of(deleteButtonKey.currentContext!).hasPrimaryFocus, isFalse);
|
|
expect(Focus.of(labelKey.currentContext!).hasFocus, isTrue);
|
|
expect(Focus.of(labelKey.currentContext!).hasPrimaryFocus, isTrue);
|
|
});
|
|
|
|
testWidgets('Material2 - Delete button creates centered, unique ripple when tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
themeData: ThemeData(useMaterial3: false),
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
),
|
|
);
|
|
|
|
final RenderBox box = getMaterialBox(tester);
|
|
|
|
// Taps at a location close to the center of the delete icon.
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton);
|
|
await tester.pump();
|
|
|
|
// Waits for 200 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique ink ripple.
|
|
expect(box, ripplePattern(Offset.zero, 1.44));
|
|
expect(box, uniqueRipplePattern(Offset.zero, 1.44));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 200 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The ripple should grow, but the center should move,
|
|
// Towards the center of the delete icon.
|
|
expect(box, ripplePattern(const Offset(2.0, 2.0), 4.32));
|
|
expect(box, uniqueRipplePattern(const Offset(2.0, 2.0), 4.32));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
// This is pressing and holding the delete button.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be a tooltip.
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material3 - Delete button creates non-centered, unique sparkle when tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
size: 18.0,
|
|
),
|
|
);
|
|
|
|
// Taps at a location close to the center of the delete icon.
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton);
|
|
await tester.pump();
|
|
|
|
// Waits for 200 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique ink sparkle.
|
|
await expectLater(find.byType(RawChip), matchesGoldenFile('chip.delete_button_tapped.ink_sparkle.0.png'));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 200 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The sparkle should grow, but the center should move,
|
|
// towards the center of the delete icon.
|
|
await expectLater(find.byType(RawChip), matchesGoldenFile('chip.delete_button_tapped.ink_sparkle.1.png'));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
// This is pressing and holding the delete button.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be a tooltip.
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material2 - Delete button in a chip with null onPressed creates ripple when tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
themeData: ThemeData(useMaterial3: false),
|
|
labelKey: labelKey,
|
|
onPressed: null,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
),
|
|
);
|
|
|
|
final RenderBox box = getMaterialBox(tester);
|
|
|
|
// Taps at a location close to the center of the delete icon.
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton);
|
|
await tester.pump();
|
|
|
|
// Waits for 200 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique ink ripple.
|
|
expect(box, ripplePattern(Offset.zero, 1.44));
|
|
expect(box, uniqueRipplePattern(Offset.zero, 1.44));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 200 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The ripple should grow, but the center should move,
|
|
// Towards the center of the delete icon.
|
|
expect(box, ripplePattern(const Offset(2.0, 2.0), 4.32));
|
|
expect(box, uniqueRipplePattern(const Offset(2.0, 2.0), 4.32));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
// This is pressing and holding the delete button.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be a tooltip.
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material3 - Delete button in a chip with null onPressed creates sparkle when tapped', (WidgetTester tester) async {
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
onPressed: null,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
size: 18.0,
|
|
),
|
|
);
|
|
|
|
// Taps at a location close to the center of the delete icon.
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final Offset tapLocationOfDeleteButton = centerOfDeleteButton + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocationOfDeleteButton);
|
|
await tester.pump();
|
|
|
|
// Waits for 200 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be one unique ink sparkle.
|
|
await expectLater(
|
|
find.byType(RawChip),
|
|
matchesGoldenFile('chip.delete_button_tapped.disabled.ink_sparkle.0.png'),
|
|
);
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 200 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The sparkle should grow, but the center should move,
|
|
// towards the center of the delete icon.
|
|
await expectLater(
|
|
find.byType(RawChip),
|
|
matchesGoldenFile('chip.delete_button_tapped.disabled.ink_sparkle.1.png'),
|
|
);
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
// This is pressing and holding the delete button.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be a tooltip.
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('RTL delete button responds to tap on the left of the chip', (WidgetTester tester) async {
|
|
// Creates an RTL chip with a delete button.
|
|
final UniqueKey labelKey = UniqueKey();
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
textDirection: TextDirection.rtl,
|
|
),
|
|
);
|
|
|
|
// Taps at a location close to the center of the delete icon,
|
|
// Which is on the left side of the chip.
|
|
final Offset topLeftOfInkWell = tester.getTopLeft(find.byType(InkWell).first);
|
|
final Offset tapLocation = topLeftOfInkWell + const Offset(8, 8);
|
|
final TestGesture gesture = await tester.startGesture(tapLocation);
|
|
await tester.pump();
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// The existence of a 'Delete' tooltip indicates the delete icon is tapped,
|
|
// Instead of the label.
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material2 - Chip without delete button creates correct ripple', (WidgetTester tester) async {
|
|
// Creates a chip with a delete button.
|
|
final UniqueKey labelKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
themeData: ThemeData(useMaterial3: false),
|
|
labelKey: labelKey,
|
|
deletable: false,
|
|
),
|
|
);
|
|
|
|
final RenderBox box = getMaterialBox(tester);
|
|
|
|
// Taps at a location close to the bottom-right corner of the chip.
|
|
final Offset bottomRightOfInkWell = tester.getBottomRight(find.byType(InkWell));
|
|
final Offset tapLocation = bottomRightOfInkWell + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocation);
|
|
await tester.pump();
|
|
|
|
// Waits for 100 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be exactly one ink-creating widget.
|
|
expect(find.byType(InkWell), findsOneWidget);
|
|
expect(find.byType(InkResponse), findsNothing);
|
|
|
|
// There should be one unique, centered ink ripple.
|
|
expect(box, ripplePattern(const Offset(378.0, 22.0), 37.9));
|
|
expect(box, uniqueRipplePattern(const Offset(378.0, 22.0), 37.9));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 100 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The ripple should grow, with the same center.
|
|
// This indicates that the tap is not on a delete icon.
|
|
expect(box, ripplePattern(const Offset(378.0, 22.0), 75.8));
|
|
expect(box, uniqueRipplePattern(const Offset(378.0, 22.0), 75.8));
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should still be no tooltip.
|
|
// This indicates that the tap is not on a delete icon.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material3 - Chip without delete button creates correct sparkle', (WidgetTester tester) async {
|
|
// Creates a chip with a delete button.
|
|
final UniqueKey labelKey = UniqueKey();
|
|
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
labelKey: labelKey,
|
|
deletable: false,
|
|
),
|
|
);
|
|
|
|
// Taps at a location close to the bottom-right corner of the chip.
|
|
final Offset bottomRightOfInkWell = tester.getBottomRight(find.byType(InkWell));
|
|
final Offset tapLocation = bottomRightOfInkWell + const Offset(-10, -10);
|
|
final TestGesture gesture = await tester.startGesture(tapLocation);
|
|
await tester.pump();
|
|
|
|
// Waits for 100 ms.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// There should be exactly one ink-creating widget.
|
|
expect(find.byType(InkWell), findsOneWidget);
|
|
expect(find.byType(InkResponse), findsNothing);
|
|
|
|
// There should be one unique, centered ink sparkle.
|
|
await expectLater(
|
|
find.byType(RawChip),
|
|
matchesGoldenFile('chip.without_delete_button.ink_sparkle.0.png'),
|
|
);
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for 100 ms again.
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
|
|
// The sparkle should grow, with the same center.
|
|
// This indicates that the tap is not on a delete icon.
|
|
await expectLater(
|
|
find.byType(RawChip),
|
|
matchesGoldenFile('chip.without_delete_button.ink_sparkle.1.png'),
|
|
);
|
|
|
|
// There should be no tooltip.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
// Waits for a very long time.
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should still be no tooltip.
|
|
// This indicates that the tap is not on a delete icon.
|
|
expect(findTooltipContainer('Delete'), findsNothing);
|
|
|
|
await gesture.up();
|
|
});
|
|
|
|
testWidgets('Material2 - Selection with avatar works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
avatar: avatar,
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// With avatar, but not selectable.
|
|
final UniqueKey avatarKey = UniqueKey();
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
);
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(258.0, 48.0)));
|
|
|
|
// Turn on selection.
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
selectable: true,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
|
|
// Simulate a tap on the label to select the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Simulate another tap on the label to deselect the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(false));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(0.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Selection with avatar works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
avatar: avatar,
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// With avatar, but not selectable.
|
|
final UniqueKey avatarKey = UniqueKey();
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
);
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(265.5, 48.0)));
|
|
|
|
// Turn on selection.
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
selectable: true,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
|
|
// Simulate a tap on the label to select the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(kIsWeb && isSkiaWeb ? 3 : 1));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Simulate another tap on the label to deselect the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(false));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(kIsWeb && isSkiaWeb ? 3 : 1));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(0.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Material2 - Selection without avatar works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Without avatar, but not selectable.
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(234.0, 48.0)));
|
|
|
|
// Turn on selection.
|
|
await pushChip(selectable: true);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Simulate a tap on the label to select the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.459, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.92, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Simulate another tap on the label to deselect the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(false));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.96, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.75, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(0.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(0.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Selection without avatar works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Without avatar, but not selectable.
|
|
await pushChip();
|
|
expect(tester.getSize(find.byType(RawChip)), equals(const Size(245.5, 48.0)));
|
|
|
|
// Turn on selection.
|
|
await pushChip(selectable: true);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
|
|
// Simulate a tap on the label to select the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(kIsWeb && isSkiaWeb ? 3 : 1));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.459, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.92, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
// Simulate another tap on the label to deselect the chip.
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(false));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(kIsWeb && isSkiaWeb ? 3 : 1));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.875, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.96, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 20));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.13, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), moreOrLessEquals(0.75, epsilon: 0.01));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(0.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(0.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Material2 - Activation works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
avatar: avatar,
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
showCheckmark: false,
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final UniqueKey avatarKey = UniqueKey();
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
selectable: true,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(2));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pumpAndSettle();
|
|
});
|
|
|
|
testWidgets('Material3 - Activation works as expected on RawChip', (WidgetTester tester) async {
|
|
bool selected = false;
|
|
final UniqueKey labelKey = UniqueKey();
|
|
Future<void> pushChip({ Widget? avatar, bool selectable = false }) async {
|
|
return tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Wrap(
|
|
children: <Widget>[
|
|
StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
avatar: avatar,
|
|
onSelected: selectable
|
|
? (bool value) {
|
|
setState(() {
|
|
selected = value;
|
|
});
|
|
}
|
|
: null,
|
|
selected: selected,
|
|
label: Text('Long Chip Label', key: labelKey),
|
|
shape: const StadiumBorder(),
|
|
showCheckmark: false,
|
|
);
|
|
}),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final UniqueKey avatarKey = UniqueKey();
|
|
await pushChip(
|
|
avatar: SizedBox(width: 40.0, height: 40.0, key: avatarKey),
|
|
selectable: true,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(0));
|
|
|
|
await tester.tap(find.byKey(labelKey));
|
|
expect(selected, equals(true));
|
|
expect(SchedulerBinding.instance.transientCallbackCount, equals(kIsWeb && isSkiaWeb ? 3 : 1));
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.002, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
expect(getSelectProgress(tester), moreOrLessEquals(0.54, epsilon: 0.01));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pump(const Duration(milliseconds: 100));
|
|
expect(getSelectProgress(tester), equals(1.0));
|
|
expect(getAvatarDrawerProgress(tester), equals(1.0));
|
|
expect(getDeleteDrawerProgress(tester), equals(0.0));
|
|
await tester.pumpAndSettle();
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Chip uses ThemeData chip theme if present', (WidgetTester tester) async {
|
|
final ThemeData theme = ThemeData(chipTheme: const ChipThemeData(backgroundColor: Color(0xffff0000)));
|
|
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: theme,
|
|
child: InputChip(
|
|
label: const Text('Label'),
|
|
onPressed: () {},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
final RenderBox materialBox = tester.firstRenderObject<RenderBox>(
|
|
find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byType(CustomPaint),
|
|
),
|
|
);
|
|
|
|
expect(materialBox, paints..rrect(color: theme.chipTheme.backgroundColor));
|
|
});
|
|
|
|
testWidgets('Chip merges ChipThemeData label style with the provided label style', (WidgetTester tester) async {
|
|
// The font family should be preserved even if the chip overrides some label style properties
|
|
final ThemeData theme = ThemeData(
|
|
fontFamily: 'MyFont',
|
|
);
|
|
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: theme,
|
|
child: const Chip(
|
|
label: Text('Label'),
|
|
labelStyle: TextStyle(fontWeight: FontWeight.w200),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
final TextStyle labelStyle = getLabelStyle(tester, 'Label').style;
|
|
expect(labelStyle.inherit, false);
|
|
expect(labelStyle.fontFamily, 'MyFont');
|
|
expect(labelStyle.fontWeight, FontWeight.w200);
|
|
});
|
|
|
|
testWidgets('ChipTheme labelStyle with inherit:true', (WidgetTester tester) async {
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData.light().copyWith(
|
|
chipTheme: const ChipThemeData(
|
|
labelStyle: TextStyle(height: 4), // inherit: true
|
|
),
|
|
),
|
|
child: const Chip(label: Text('Label')), // labelStyle: null
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
final TextStyle labelStyle = getLabelStyle(tester, 'Label').style;
|
|
expect(labelStyle.inherit, true); // because chipTheme.labelStyle.merge(null)
|
|
expect(labelStyle.height, 4);
|
|
});
|
|
|
|
testWidgets('Chip does not merge inherit:false label style with the theme label style', (WidgetTester tester) async {
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData(fontFamily: 'MyFont'),
|
|
child: const DefaultTextStyle(
|
|
style: TextStyle(height: 8),
|
|
child: Chip(
|
|
label: Text('Label'),
|
|
labelStyle: TextStyle(fontWeight: FontWeight.w200, inherit: false),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
final TextStyle labelStyle = getLabelStyle(tester, 'Label').style;
|
|
expect(labelStyle.inherit, false);
|
|
expect(labelStyle.fontFamily, null);
|
|
expect(labelStyle.height, null);
|
|
expect(labelStyle.fontWeight, FontWeight.w200);
|
|
});
|
|
|
|
testWidgets('Material2 - Chip size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
|
final Key key1 = UniqueKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData(useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Center(
|
|
child: RawChip(
|
|
key: key1,
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(key1)), const Size(80.0, 48.0));
|
|
|
|
final Key key2 = UniqueKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData(useMaterial3: false, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
child: Center(
|
|
child: RawChip(
|
|
key: key2,
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(key2)), const Size(80.0, 32.0));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip size is configurable by ThemeData.materialTapTargetSize', (WidgetTester tester) async {
|
|
final Key key1 = UniqueKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.padded),
|
|
child: Center(
|
|
child: RawChip(
|
|
key: key1,
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(key1)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(key1)).height, equals(48.0));
|
|
|
|
final Key key2 = UniqueKey();
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Theme(
|
|
data: ThemeData(materialTapTargetSize: MaterialTapTargetSize.shrinkWrap),
|
|
child: Center(
|
|
child: RawChip(
|
|
key: key2,
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(tester.getSize(find.byKey(key2)).width, moreOrLessEquals(90.4, epsilon: 0.1));
|
|
expect(tester.getSize(find.byKey(key2)).height, equals(38.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async {
|
|
final ThemeData themeData = ThemeData(
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.blue,
|
|
);
|
|
final ChipThemeData defaultChipTheme = ChipThemeData.fromDefaults(
|
|
brightness: themeData.brightness,
|
|
secondaryColor: Colors.blue,
|
|
labelStyle: themeData.textTheme.bodyLarge!,
|
|
);
|
|
bool value = false;
|
|
Widget buildApp({
|
|
ChipThemeData? chipTheme,
|
|
Widget? avatar,
|
|
Widget? deleteIcon,
|
|
bool isSelectable = true,
|
|
bool isPressable = false,
|
|
bool isDeletable = true,
|
|
bool showCheckmark = true,
|
|
}) {
|
|
chipTheme ??= defaultChipTheme;
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: themeData,
|
|
child: ChipTheme(
|
|
data: chipTheme,
|
|
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
showCheckmark: showCheckmark,
|
|
onDeleted: isDeletable ? () { } : null,
|
|
avatar: avatar,
|
|
deleteIcon: deleteIcon,
|
|
isEnabled: isSelectable || isPressable,
|
|
shape: chipTheme?.shape,
|
|
selected: isSelectable && value,
|
|
label: Text('$value'),
|
|
onSelected: isSelectable
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
onPressed: isPressable
|
|
? () {
|
|
setState(() {
|
|
value = true;
|
|
});
|
|
}
|
|
: null,
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
RenderBox materialBox = getMaterialBox(tester);
|
|
IconThemeData iconData = getIconData(tester);
|
|
DefaultTextStyle labelStyle = getLabelStyle(tester, 'false');
|
|
|
|
// Check default theme for enabled widget.
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.backgroundColor));
|
|
expect(iconData.color, equals(const Color(0xde000000)));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.selectedColor));
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Check default theme with disabled widget.
|
|
await tester.pumpWidget(buildApp(isSelectable: false));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Apply a custom theme.
|
|
const Color customColor1 = Color(0xcafefeed);
|
|
const Color customColor2 = Color(0xdeadbeef);
|
|
const Color customColor3 = Color(0xbeefcafe);
|
|
const Color customColor4 = Color(0xaddedabe);
|
|
final ChipThemeData customTheme = defaultChipTheme.copyWith(
|
|
brightness: Brightness.dark,
|
|
backgroundColor: customColor1,
|
|
disabledColor: customColor2,
|
|
selectedColor: customColor3,
|
|
deleteIconColor: customColor4,
|
|
);
|
|
await tester.pumpWidget(buildApp(chipTheme: customTheme));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
iconData = getIconData(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
|
|
// Check custom theme for enabled widget.
|
|
expect(materialBox, paints..rrect(color: customTheme.backgroundColor));
|
|
expect(iconData.color, equals(customTheme.deleteIconColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
expect(materialBox, paints..rrect(color: customTheme.selectedColor));
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Check custom theme with disabled widget.
|
|
await tester.pumpWidget(buildApp(
|
|
chipTheme: customTheme,
|
|
isSelectable: false,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
expect(materialBox, paints..rrect(color: customTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
});
|
|
|
|
group('Chip semantics', () {
|
|
testWidgets('label only', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: Text('test'),
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('delete', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: const Text('test'),
|
|
onDeleted: () { },
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
tooltip: 'Delete',
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('with onPressed', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: const Text('test'),
|
|
onPressed: () { },
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics> [
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
|
|
testWidgets('with onSelected', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
bool selected = false;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: const Text('test'),
|
|
selected: selected,
|
|
onSelected: (bool value) {
|
|
selected = value;
|
|
},
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: const Text('test'),
|
|
selected: selected,
|
|
onSelected: (bool value) {
|
|
selected = value;
|
|
},
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(selected, true);
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
SemanticsFlag.isSelected,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('disabled', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
isEnabled: false,
|
|
onPressed: () { },
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
],
|
|
actions: <SemanticsAction>[],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('tapEnabled explicitly false', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
tapEnabled: false,
|
|
label: Text('test'),
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[], // Must not be a button when tapping is disabled.
|
|
actions: <SemanticsAction>[],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('enabled when tapEnabled and canTap', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
|
|
// These settings make a Chip which can be tapped, both in general and at this moment.
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
onPressed: () {},
|
|
label: const Text('test'),
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
SemanticsFlag.isEnabled,
|
|
SemanticsFlag.isFocusable,
|
|
],
|
|
actions: <SemanticsAction>[SemanticsAction.tap],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
|
|
testWidgets('disabled when tapEnabled but not canTap', (WidgetTester tester) async {
|
|
final SemanticsTester semanticsTester = SemanticsTester(tester);
|
|
// These settings make a Chip which _could_ be tapped, but not currently (ensures `canTap == false`).
|
|
await tester.pumpWidget(const MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: Text('test'),
|
|
),
|
|
),
|
|
));
|
|
|
|
expect(
|
|
semanticsTester,
|
|
hasSemantics(
|
|
TestSemantics.root(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
textDirection: TextDirection.ltr,
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
|
|
children: <TestSemantics>[
|
|
TestSemantics(
|
|
label: 'test',
|
|
textDirection: TextDirection.ltr,
|
|
flags: <SemanticsFlag>[
|
|
SemanticsFlag.hasEnabledState,
|
|
SemanticsFlag.isButton,
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
ignoreTransform: true,
|
|
ignoreId: true,
|
|
ignoreRect: true,
|
|
),
|
|
);
|
|
|
|
semanticsTester.dispose();
|
|
});
|
|
});
|
|
|
|
testWidgets('can be tapped outside of chip delete icon', (WidgetTester tester) async {
|
|
bool deleted = false;
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Row(
|
|
children: <Widget>[
|
|
Chip(
|
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
shape: const RoundedRectangleBorder(),
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () {
|
|
deleted = true;
|
|
},
|
|
deleteIcon: const Icon(Icons.delete),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tapAt(tester.getTopRight(find.byType(Chip)) - const Offset(2.0, -2.0));
|
|
await tester.pumpAndSettle();
|
|
expect(deleted, true);
|
|
});
|
|
|
|
testWidgets('Chips can be tapped', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
const MaterialApp(
|
|
home: Material(
|
|
child: RawChip(
|
|
label: Text('raw chip'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(RawChip));
|
|
expect(tester.takeException(), null);
|
|
});
|
|
|
|
testWidgets('Material2 - Chip elevation and shadow color work correctly', (WidgetTester tester) async {
|
|
final ThemeData theme = ThemeData(
|
|
useMaterial3: false,
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.red,
|
|
);
|
|
|
|
InputChip inputChip = const InputChip(label: Text('Label'));
|
|
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: theme,
|
|
child: inputChip,
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
Material material = getMaterial(tester);
|
|
expect(material.elevation, 0.0);
|
|
expect(material.shadowColor, Colors.black);
|
|
|
|
inputChip = const InputChip(
|
|
label: Text('Label'),
|
|
elevation: 4.0,
|
|
shadowColor: Colors.green,
|
|
selectedShadowColor: Colors.blue,
|
|
);
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
await tester.pumpAndSettle();
|
|
material = getMaterial(tester);
|
|
expect(material.elevation, 4.0);
|
|
expect(material.shadowColor, Colors.green);
|
|
|
|
inputChip = const InputChip(
|
|
label: Text('Label'),
|
|
selected: true,
|
|
shadowColor: Colors.green,
|
|
selectedShadowColor: Colors.blue,
|
|
);
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
await tester.pumpAndSettle();
|
|
material = getMaterial(tester);
|
|
expect(material.shadowColor, Colors.blue);
|
|
});
|
|
|
|
testWidgets('Material3 - Chip elevation and shadow color work correctly', (WidgetTester tester) async {
|
|
final ThemeData theme = ThemeData();
|
|
|
|
InputChip inputChip = const InputChip(label: Text('Label'));
|
|
|
|
Widget buildChip() {
|
|
return wrapForChip(
|
|
theme: theme,
|
|
child: inputChip,
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
Material material = getMaterial(tester);
|
|
expect(material.elevation, 0.0);
|
|
expect(material.shadowColor, Colors.transparent);
|
|
|
|
inputChip = const InputChip(
|
|
label: Text('Label'),
|
|
elevation: 4.0,
|
|
shadowColor: Colors.green,
|
|
selectedShadowColor: Colors.blue,
|
|
);
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
await tester.pumpAndSettle();
|
|
material = getMaterial(tester);
|
|
expect(material.elevation, 4.0);
|
|
expect(material.shadowColor, Colors.green);
|
|
|
|
inputChip = const InputChip(
|
|
label: Text('Label'),
|
|
selected: true,
|
|
shadowColor: Colors.green,
|
|
selectedShadowColor: Colors.blue,
|
|
);
|
|
|
|
await tester.pumpWidget(buildChip());
|
|
await tester.pumpAndSettle();
|
|
material = getMaterial(tester);
|
|
expect(material.shadowColor, Colors.blue);
|
|
});
|
|
|
|
testWidgets('can be tapped outside of chip body', (WidgetTester tester) async {
|
|
bool pressed = false;
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Row(
|
|
children: <Widget>[
|
|
InputChip(
|
|
materialTapTargetSize: MaterialTapTargetSize.padded,
|
|
shape: const RoundedRectangleBorder(),
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onPressed: () {
|
|
pressed = true;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tapAt(tester.getRect(find.byType(InputChip)).topCenter);
|
|
await tester.pumpAndSettle();
|
|
expect(pressed, true);
|
|
});
|
|
|
|
testWidgets('is hitTestable', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: InputChip(
|
|
shape: const RoundedRectangleBorder(),
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onPressed: () { },
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(InputChip).hitTestable(), findsOneWidget);
|
|
});
|
|
|
|
void checkChipMaterialClipBehavior(WidgetTester tester, Clip clipBehavior) {
|
|
final Iterable<Material> materials = tester.widgetList<Material>(find.byType(Material));
|
|
expect(materials.length, 2);
|
|
expect(materials.last.clipBehavior, clipBehavior);
|
|
}
|
|
|
|
testWidgets('Chip clipBehavior properly passes through to the Material', (WidgetTester tester) async {
|
|
const Text label = Text('label');
|
|
await tester.pumpWidget(wrapForChip(child: const Chip(label: label)));
|
|
checkChipMaterialClipBehavior(tester, Clip.none);
|
|
|
|
await tester.pumpWidget(wrapForChip(child: const Chip(label: label, clipBehavior: Clip.antiAlias)));
|
|
checkChipMaterialClipBehavior(tester, Clip.antiAlias);
|
|
});
|
|
|
|
testWidgets('Material2 - selected chip and avatar draw darkened layer within avatar circle', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: const FilterChip(
|
|
avatar: CircleAvatar(child: Text('t')),
|
|
label: Text('test'),
|
|
selected: true,
|
|
onSelected: null,
|
|
),
|
|
),
|
|
);
|
|
final RenderBox rawChip = tester.firstRenderObject<RenderBox>(
|
|
find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byWidgetPredicate((Widget widget) {
|
|
return widget.runtimeType.toString() == '_ChipRenderWidget';
|
|
}),
|
|
),
|
|
);
|
|
const Color selectScrimColor = Color(0x60191919);
|
|
expect(rawChip, paints..path(color: selectScrimColor, includes: <Offset>[
|
|
const Offset(10, 10),
|
|
], excludes: <Offset>[
|
|
const Offset(4, 4),
|
|
]));
|
|
});
|
|
|
|
testWidgets('Material3 - selected chip and avatar draw darkened layer within avatar circle', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: const FilterChip(
|
|
avatar: CircleAvatar(child: Text('t')),
|
|
label: Text('test'),
|
|
selected: true,
|
|
onSelected: null,
|
|
),
|
|
),
|
|
);
|
|
final RenderBox rawChip = tester.firstRenderObject<RenderBox>(
|
|
find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byWidgetPredicate((Widget widget) {
|
|
return widget.runtimeType.toString() == '_ChipRenderWidget';
|
|
}),
|
|
),
|
|
);
|
|
const Color selectScrimColor = Color(0x60191919);
|
|
expect(rawChip, paints..path(color: selectScrimColor, includes: <Offset>[
|
|
const Offset(11, 11),
|
|
], excludes: <Offset>[
|
|
const Offset(4, 4),
|
|
]));
|
|
});
|
|
|
|
testWidgets('Chips should use InkWell instead of InkResponse.', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/28646
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: ActionChip(
|
|
onPressed: () { },
|
|
label: const Text('action chip'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
expect(find.byType(InkWell), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Chip uses stateful color for text color in different states', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color selectedColor = Color(0x00000005);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
Color getTextColor(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return disabledColor;
|
|
}
|
|
|
|
if (states.contains(MaterialState.pressed)) {
|
|
return pressedColor;
|
|
}
|
|
|
|
if (states.contains(MaterialState.hovered)) {
|
|
return hoverColor;
|
|
}
|
|
|
|
if (states.contains(MaterialState.focused)) {
|
|
return focusedColor;
|
|
}
|
|
|
|
if (states.contains(MaterialState.selected)) {
|
|
return selectedColor;
|
|
}
|
|
|
|
return defaultColor;
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
labelStyle: TextStyle(color: MaterialStateColor.resolveWith(getTextColor)),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
Color textColor() {
|
|
return tester.renderObject<RenderParagraph>(find.text('Chip')).text.style!.color!;
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(textColor(), equals(defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(textColor(), selectedColor);
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(textColor(), focusedColor);
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(textColor(), hoverColor);
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(textColor(), pressedColor);
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(textColor(), disabledColor);
|
|
});
|
|
|
|
testWidgets('Material2 - Chip uses stateful border side color in different states', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color selectedColor = Color(0x00000005);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
BorderSide getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
sideColor = selectedColor;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: _MaterialStateBorderSide(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip uses stateful border side color in different states', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color selectedColor = Color(0x00000005);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
BorderSide getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
sideColor = selectedColor;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: _MaterialStateBorderSide(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..drrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(find.byType(RawChip), paints..drrect(color: selectedColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material2 - Chip uses stateful border side color from resolveWith', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color selectedColor = Color(0x00000005);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
BorderSide getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
sideColor = selectedColor;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: selectedColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip uses stateful border side color from resolveWith', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color selectedColor = Color(0x00000005);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
BorderSide getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
sideColor = selectedColor;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..drrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(find.byType(RawChip), paints..drrect(color: selectedColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material2 - Chip uses stateful nullable border side color from resolveWith', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
const Color fallbackThemeColor = Color(0x00000007);
|
|
const BorderSide defaultBorderSide = BorderSide(color: fallbackThemeColor, width: 10.0);
|
|
|
|
BorderSide? getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
return null;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChipTheme(
|
|
data: ThemeData.light().chipTheme.copyWith(
|
|
side: defaultBorderSide,
|
|
),
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
// Because the resolver returns `null` for this value, we should fall back
|
|
// to the theme.
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: fallbackThemeColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip uses stateful nullable border side color from resolveWith', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
const Color pressedColor = Color(0x00000001);
|
|
const Color hoverColor = Color(0x00000002);
|
|
const Color focusedColor = Color(0x00000003);
|
|
const Color defaultColor = Color(0x00000004);
|
|
const Color disabledColor = Color(0x00000006);
|
|
|
|
const Color fallbackThemeColor = Color(0x00000007);
|
|
const BorderSide defaultBorderSide = BorderSide(color: fallbackThemeColor, width: 10.0);
|
|
|
|
BorderSide? getBorderSide(Set<MaterialState> states) {
|
|
Color sideColor = defaultColor;
|
|
if (states.contains(MaterialState.disabled)) {
|
|
sideColor = disabledColor;
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
sideColor = pressedColor;
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
sideColor = hoverColor;
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
sideColor = focusedColor;
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
return null;
|
|
}
|
|
return BorderSide(color: sideColor);
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChipTheme(
|
|
data: ThemeData.light().chipTheme.copyWith(
|
|
side: defaultBorderSide,
|
|
),
|
|
child: ChoiceChip(
|
|
label: const Text('Chip'),
|
|
selected: selected,
|
|
onSelected: enabled ? (_) {} : null,
|
|
side: MaterialStateBorderSide.resolveWith(getBorderSide),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(find.byType(RawChip), paints..drrect(color: defaultColor));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
// Because the resolver returns `null` for this value, we should fall back
|
|
// to the theme
|
|
expect(find.byType(RawChip), paints..drrect(color: fallbackThemeColor));
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: focusedColor));
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: hoverColor));
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: pressedColor));
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(RawChip), paints..drrect(color: disabledColor));
|
|
});
|
|
|
|
testWidgets('Material2 - Chip uses stateful shape in different states', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
OutlinedBorder? getShape(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return const BeveledRectangleBorder();
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
return const CircleBorder();
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
return const ContinuousRectangleBorder();
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
return const RoundedRectangleBorder();
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
return const BeveledRectangleBorder();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
selected: selected,
|
|
label: const Text('Chip'),
|
|
shape: _MaterialStateOutlinedBorder(getShape),
|
|
onSelected: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled. Defers to default shape.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(getMaterial(tester).shape, isA<StadiumBorder>());
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(getMaterial(tester).shape, isA<BeveledRectangleBorder>());
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<ContinuousRectangleBorder>());
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<CircleBorder>());
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<BeveledRectangleBorder>());
|
|
});
|
|
|
|
testWidgets('Material3 - Chip uses stateful shape in different states', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode();
|
|
addTearDown(focusNode.dispose);
|
|
|
|
OutlinedBorder? getShape(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.disabled)) {
|
|
return const BeveledRectangleBorder();
|
|
} else if (states.contains(MaterialState.pressed)) {
|
|
return const CircleBorder();
|
|
} else if (states.contains(MaterialState.hovered)) {
|
|
return const ContinuousRectangleBorder();
|
|
} else if (states.contains(MaterialState.focused)) {
|
|
return const RoundedRectangleBorder();
|
|
} else if (states.contains(MaterialState.selected)) {
|
|
return const BeveledRectangleBorder();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Focus(
|
|
focusNode: focusNode,
|
|
child: ChoiceChip(
|
|
selected: selected,
|
|
label: const Text('Chip'),
|
|
shape: _MaterialStateOutlinedBorder(getShape),
|
|
onSelected: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled. Defers to default shape.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(getMaterial(tester).shape, isA<BeveledRectangleBorder>());
|
|
|
|
// Focused.
|
|
final FocusNode chipFocusNode = focusNode.children.first;
|
|
chipFocusNode.requestFocus();
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(ChoiceChip));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<ContinuousRectangleBorder>());
|
|
|
|
// Pressed.
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<CircleBorder>());
|
|
|
|
// Disabled.
|
|
await tester.pumpWidget(chipWidget(enabled: false));
|
|
await tester.pumpAndSettle();
|
|
expect(getMaterial(tester).shape, isA<BeveledRectangleBorder>());
|
|
});
|
|
|
|
testWidgets('Material2 - Chip defers to theme, if shape and side resolves to null', (WidgetTester tester) async {
|
|
const OutlinedBorder themeShape = StadiumBorder();
|
|
const OutlinedBorder selectedShape = RoundedRectangleBorder();
|
|
const BorderSide themeBorderSide = BorderSide(color: Color(0x00000001));
|
|
const BorderSide selectedBorderSide = BorderSide(color: Color(0x00000002));
|
|
|
|
OutlinedBorder? getShape(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return selectedShape;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
BorderSide? getBorderSide(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return selectedBorderSide;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(
|
|
useMaterial3: false,
|
|
chipTheme: ThemeData.light().chipTheme.copyWith(
|
|
shape: themeShape,
|
|
side: themeBorderSide,
|
|
),
|
|
),
|
|
home: Scaffold(
|
|
body: ChoiceChip(
|
|
selected: selected,
|
|
label: const Text('Chip'),
|
|
shape: _MaterialStateOutlinedBorder(getShape),
|
|
side: _MaterialStateBorderSide(getBorderSide),
|
|
onSelected: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled. Defer to theme.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(getMaterial(tester).shape, isA<StadiumBorder>());
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: themeBorderSide.color));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
|
|
expect(find.byType(RawChip), paints..rect()..drrect(color: selectedBorderSide.color));
|
|
});
|
|
|
|
testWidgets('Chip defers to theme, if shape and side resolves to null', (WidgetTester tester) async {
|
|
const OutlinedBorder themeShape = StadiumBorder();
|
|
const OutlinedBorder selectedShape = RoundedRectangleBorder();
|
|
const BorderSide themeBorderSide = BorderSide(color: Color(0x00000001));
|
|
const BorderSide selectedBorderSide = BorderSide(color: Color(0x00000002));
|
|
|
|
OutlinedBorder? getShape(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return selectedShape;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
BorderSide? getBorderSide(Set<MaterialState> states) {
|
|
if (states.contains(MaterialState.selected)) {
|
|
return selectedBorderSide;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
Widget chipWidget({ bool enabled = true, bool selected = false }) {
|
|
return MaterialApp(
|
|
theme: ThemeData(
|
|
chipTheme: ThemeData.light().chipTheme.copyWith(
|
|
shape: themeShape,
|
|
side: themeBorderSide,
|
|
),
|
|
),
|
|
home: Scaffold(
|
|
body: ChoiceChip(
|
|
selected: selected,
|
|
label: const Text('Chip'),
|
|
shape: _MaterialStateOutlinedBorder(getShape),
|
|
side: _MaterialStateBorderSide(getBorderSide),
|
|
onSelected: enabled ? (_) {} : null,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Default, not disabled. Defer to theme.
|
|
await tester.pumpWidget(chipWidget());
|
|
expect(getMaterial(tester).shape, isA<StadiumBorder>());
|
|
expect(find.byType(RawChip), paints..rrect()..rrect(color: themeBorderSide.color));
|
|
|
|
// Selected.
|
|
await tester.pumpWidget(chipWidget(selected: true));
|
|
expect(getMaterial(tester).shape, isA<RoundedRectangleBorder>());
|
|
expect(find.byType(RawChip), paints..rect()..drrect(color: selectedBorderSide.color));
|
|
});
|
|
|
|
testWidgets('Material2 - Chip responds to density changes', (WidgetTester tester) async {
|
|
const Key key = Key('test');
|
|
const Key textKey = Key('test text');
|
|
const Key iconKey = Key('test icon');
|
|
const Key avatarKey = Key('test avatar');
|
|
Future<void> buildTest(VisualDensity visualDensity) async {
|
|
return tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData(useMaterial3: false),
|
|
home: Material(
|
|
child: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
InputChip(
|
|
visualDensity: visualDensity,
|
|
key: key,
|
|
onPressed: () {},
|
|
onDeleted: () {},
|
|
label: const Text('Test', key: textKey),
|
|
deleteIcon: const Icon(Icons.delete, key: iconKey),
|
|
avatar: const Icon(Icons.play_arrow, key: avatarKey),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// The Chips only change in size vertically in response to density, so
|
|
// horizontal changes aren't expected.
|
|
await buildTest(VisualDensity.standard);
|
|
Rect box = tester.getRect(find.byKey(key));
|
|
Rect textBox = tester.getRect(find.byKey(textKey));
|
|
Rect iconBox = tester.getRect(find.byKey(iconKey));
|
|
Rect avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size, equals(const Size(128, 32.0 + 16.0)));
|
|
expect(textBox.size, equals(const Size(56, 14)));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(24, 24)));
|
|
expect(textBox.top, equals(17));
|
|
expect(box.bottom - textBox.bottom, equals(17));
|
|
expect(textBox.left, equals(372));
|
|
expect(box.right - textBox.right, equals(36));
|
|
|
|
// Try decreasing density (with higher density numbers).
|
|
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size, equals(const Size(128, 60)));
|
|
expect(textBox.size, equals(const Size(56, 14)));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(24, 24)));
|
|
expect(textBox.top, equals(23));
|
|
expect(box.bottom - textBox.bottom, equals(23));
|
|
expect(textBox.left, equals(372));
|
|
expect(box.right - textBox.right, equals(36));
|
|
|
|
// Try increasing density (with lower density numbers).
|
|
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size, equals(const Size(128, 36)));
|
|
expect(textBox.size, equals(const Size(56, 14)));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(24, 24)));
|
|
expect(textBox.top, equals(11));
|
|
expect(box.bottom - textBox.bottom, equals(11));
|
|
expect(textBox.left, equals(372));
|
|
expect(box.right - textBox.right, equals(36));
|
|
|
|
// Now test that horizontal and vertical are wired correctly. Negating the
|
|
// horizontal should have no change over what's above.
|
|
await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size, equals(const Size(128, 36)));
|
|
expect(textBox.size, equals(const Size(56, 14)));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(24, 24)));
|
|
expect(textBox.top, equals(11));
|
|
expect(box.bottom - textBox.bottom, equals(11));
|
|
expect(textBox.left, equals(372));
|
|
expect(box.right - textBox.right, equals(36));
|
|
|
|
// Make sure the "Comfortable" setting is the spec'd size
|
|
await buildTest(VisualDensity.comfortable);
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
expect(box.size, equals(const Size(128, 28.0 + 16.0)));
|
|
|
|
// Make sure the "Compact" setting is the spec'd size
|
|
await buildTest(VisualDensity.compact);
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
expect(box.size, equals(const Size(128, 24.0 + 16.0)));
|
|
});
|
|
|
|
testWidgets('Material3 - Chip responds to density changes', (WidgetTester tester) async {
|
|
const Key key = Key('test');
|
|
const Key textKey = Key('test text');
|
|
const Key iconKey = Key('test icon');
|
|
const Key avatarKey = Key('test avatar');
|
|
Future<void> buildTest(VisualDensity visualDensity) async {
|
|
return tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
InputChip(
|
|
visualDensity: visualDensity,
|
|
key: key,
|
|
onPressed: () {},
|
|
onDeleted: () {},
|
|
label: const Text('Test', key: textKey),
|
|
deleteIcon: const Icon(Icons.delete, key: iconKey),
|
|
avatar: const Icon(Icons.play_arrow, key: avatarKey),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// The Chips only change in size vertically in response to density, so
|
|
// horizontal changes aren't expected.
|
|
await buildTest(VisualDensity.standard);
|
|
Rect box = tester.getRect(find.byKey(key));
|
|
Rect textBox = tester.getRect(find.byKey(textKey));
|
|
Rect iconBox = tester.getRect(find.byKey(iconKey));
|
|
Rect avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(32.0 + 16.0));
|
|
expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1));
|
|
expect(textBox.size.height, equals(20.0));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(18, 18)));
|
|
expect(textBox.top, equals(14));
|
|
expect(box.bottom - textBox.bottom, equals(14));
|
|
expect(textBox.left, moreOrLessEquals(371.79, epsilon: 0.1));
|
|
expect(box.right - textBox.right, equals(37));
|
|
|
|
// Try decreasing density (with higher density numbers).
|
|
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(60));
|
|
expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1));
|
|
expect(textBox.size.height, equals(20.0));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(18, 18)));
|
|
expect(textBox.top, equals(20));
|
|
expect(box.bottom - textBox.bottom, equals(20));
|
|
expect(textBox.left, moreOrLessEquals(371.79, epsilon: 0.1));
|
|
expect(box.right - textBox.right, equals(37));
|
|
|
|
// Try increasing density (with lower density numbers).
|
|
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(36));
|
|
expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1));
|
|
expect(textBox.size.height, equals(20.0));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(18, 18)));
|
|
expect(textBox.top, equals(8));
|
|
expect(box.bottom - textBox.bottom, equals(8));
|
|
expect(textBox.left, moreOrLessEquals(371.79, epsilon: 0.1));
|
|
expect(box.right - textBox.right, equals(37));
|
|
|
|
// Now test that horizontal and vertical are wired correctly. Negating the
|
|
// horizontal should have no change over what's above.
|
|
await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
textBox = tester.getRect(find.byKey(textKey));
|
|
iconBox = tester.getRect(find.byKey(iconKey));
|
|
avatarBox = tester.getRect(find.byKey(avatarKey));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(36));
|
|
expect(textBox.size.width, moreOrLessEquals(56.4, epsilon: 0.1));
|
|
expect(textBox.size.height, equals(20.0));
|
|
expect(iconBox.size, equals(const Size(18, 18)));
|
|
expect(avatarBox.size, equals(const Size(18, 18)));
|
|
expect(textBox.top, equals(8));
|
|
expect(box.bottom - textBox.bottom, equals(8));
|
|
expect(textBox.left, moreOrLessEquals(371.79, epsilon: 0.1));
|
|
expect(box.right - textBox.right, equals(37));
|
|
|
|
// Make sure the "Comfortable" setting is the spec'd size
|
|
await buildTest(VisualDensity.comfortable);
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(28.0 + 16.0));
|
|
|
|
// Make sure the "Compact" setting is the spec'd size
|
|
await buildTest(VisualDensity.compact);
|
|
await tester.pumpAndSettle();
|
|
box = tester.getRect(find.byKey(key));
|
|
expect(box.size.width, moreOrLessEquals(130.4, epsilon: 0.1));
|
|
expect(box.size.height, equals(24.0 + 16.0));
|
|
}, skip: kIsWeb && !isSkiaWeb); // https://github.com/flutter/flutter/issues/99933
|
|
|
|
testWidgets('Chip delete button tooltip is disabled if deleteButtonTooltipMessage is empty', (WidgetTester tester) async {
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
deleteButtonTooltipMessage: '',
|
|
),
|
|
);
|
|
|
|
// Hover over the delete icon of the chip
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final TestGesture hoverGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await hoverGesture.moveTo(centerOfDeleteButton);
|
|
addTearDown(hoverGesture.removePointer);
|
|
|
|
await tester.pump();
|
|
|
|
// Wait for some more time while hovering over the delete button
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be no delete button tooltip
|
|
expect(findTooltipContainer(''), findsNothing);
|
|
});
|
|
|
|
testWidgets('Disabling delete button tooltip does not disable chip tooltip', (WidgetTester tester) async {
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
deleteButtonTooltipMessage: '',
|
|
chipTooltip: 'Chip Tooltip',
|
|
),
|
|
);
|
|
|
|
// Hover over the delete icon of the chip
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final TestGesture hoverGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await hoverGesture.moveTo(centerOfDeleteButton);
|
|
addTearDown(hoverGesture.removePointer);
|
|
|
|
await tester.pump();
|
|
|
|
// Wait for some more time while hovering over the delete button
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should be no delete button tooltip
|
|
expect(findTooltipContainer(''), findsNothing);
|
|
// There should be a chip tooltip, however.
|
|
expect(findTooltipContainer('Chip Tooltip'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Triggering delete button tooltip does not trigger Chip tooltip', (WidgetTester tester) async {
|
|
final UniqueKey deleteButtonKey = UniqueKey();
|
|
await tester.pumpWidget(
|
|
chipWithOptionalDeleteButton(
|
|
deleteButtonKey: deleteButtonKey,
|
|
deletable: true,
|
|
chipTooltip: 'Chip Tooltip',
|
|
),
|
|
);
|
|
|
|
// Hover over the delete icon of the chip
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byKey(deleteButtonKey));
|
|
final TestGesture hoverGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await hoverGesture.moveTo(centerOfDeleteButton);
|
|
addTearDown(hoverGesture.removePointer);
|
|
|
|
await tester.pump();
|
|
|
|
// Wait for some more time while hovering over the delete button
|
|
await tester.pumpAndSettle();
|
|
|
|
// There should not be a chip tooltip
|
|
expect(findTooltipContainer('Chip Tooltip'), findsNothing);
|
|
// There should be a delete button tooltip
|
|
expect(findTooltipContainer('Delete'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('intrinsicHeight implementation meets constraints', (WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/49478.
|
|
await tester.pumpWidget(wrapForChip(
|
|
child: const Chip(
|
|
label: Text('text'),
|
|
padding: EdgeInsets.symmetric(horizontal: 20),
|
|
),
|
|
));
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('Material2 - Chip background color and shape are drawn on Ink', (WidgetTester tester) async {
|
|
const Color backgroundColor = Color(0xff00ff00);
|
|
const OutlinedBorder shape = ContinuousRectangleBorder();
|
|
|
|
await tester.pumpWidget(wrapForChip(
|
|
theme: ThemeData(useMaterial3: false),
|
|
child: const RawChip(
|
|
label: Text('text'),
|
|
backgroundColor: backgroundColor,
|
|
shape: shape,
|
|
),
|
|
));
|
|
|
|
final Ink ink = tester.widget(find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byType(Ink),
|
|
));
|
|
final ShapeDecoration decoration = ink.decoration! as ShapeDecoration;
|
|
expect(decoration.color, backgroundColor);
|
|
expect(decoration.shape, shape);
|
|
});
|
|
|
|
testWidgets('Material3 - Chip background color and shape are drawn on Ink', (WidgetTester tester) async {
|
|
const Color backgroundColor = Color(0xff00ff00);
|
|
const OutlinedBorder shape = ContinuousRectangleBorder();
|
|
final ThemeData theme = ThemeData();
|
|
|
|
await tester.pumpWidget(wrapForChip(
|
|
theme: theme,
|
|
child: const RawChip(
|
|
label: Text('text'),
|
|
backgroundColor: backgroundColor,
|
|
shape: shape,
|
|
),
|
|
));
|
|
|
|
final Ink ink = tester.widget(find.descendant(
|
|
of: find.byType(RawChip),
|
|
matching: find.byType(Ink),
|
|
));
|
|
final ShapeDecoration decoration = ink.decoration! as ShapeDecoration;
|
|
expect(decoration.color, backgroundColor);
|
|
expect(decoration.shape, shape.copyWith(side: BorderSide(color: theme.colorScheme.outline)));
|
|
});
|
|
|
|
testWidgets('Chip highlight color is drawn on top of the backgroundColor', (WidgetTester tester) async {
|
|
final FocusNode focusNode = FocusNode(debugLabel: 'RawChip');
|
|
addTearDown(focusNode.dispose);
|
|
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
|
|
const Color backgroundColor = Color(0xff00ff00);
|
|
|
|
await tester.pumpWidget(wrapForChip(
|
|
child: RawChip(
|
|
label: const Text('text'),
|
|
backgroundColor: backgroundColor,
|
|
autofocus: true,
|
|
focusNode: focusNode,
|
|
onPressed: () {},
|
|
),
|
|
));
|
|
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(focusNode.hasPrimaryFocus, isTrue);
|
|
expect(
|
|
find.byType(Material).last,
|
|
paints
|
|
// Background color is drawn first.
|
|
..rrect(color: backgroundColor)
|
|
// Highlight color is drawn on top of the background color.
|
|
..rect(color: const Color(0x1f000000)),
|
|
);
|
|
});
|
|
|
|
testWidgets('RawChip.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);
|
|
Widget buildApp({ required bool enabled, required bool selected }) {
|
|
return wrapForChip(
|
|
child: RawChip(
|
|
isEnabled: enabled,
|
|
selected: selected,
|
|
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;
|
|
}),
|
|
label: const Text('RawChip'),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test enabled chip.
|
|
await tester.pumpWidget(buildApp(enabled: true, selected: false));
|
|
|
|
// Enabled chip should have the provided backgroundColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: backgroundColor));
|
|
|
|
// Test disabled chip.
|
|
await tester.pumpWidget(buildApp(enabled: false, selected: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Disabled chip should have the provided disabledColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: disabledColor));
|
|
|
|
// Test enabled & selected chip.
|
|
await tester.pumpWidget(buildApp(enabled: true, selected: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Enabled & selected chip should have the provided selectedColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: selectedColor));
|
|
|
|
// Test disabled & selected chip.
|
|
await tester.pumpWidget(buildApp(enabled: false, selected: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Disabled & selected chip should have the provided disabledSelectedColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: disabledSelectedColor));
|
|
});
|
|
|
|
testWidgets('RawChip 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: RawChip(
|
|
isEnabled: enabled,
|
|
selected: selected,
|
|
disabledColor: disabledColor,
|
|
backgroundColor: backgroundColor,
|
|
selectedColor: selectedColor,
|
|
label: const Text('RawChip'),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test enabled chip.
|
|
await tester.pumpWidget(buildApp(enabled: true, selected: false));
|
|
|
|
// Enabled chip should have the provided backgroundColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: backgroundColor));
|
|
|
|
// Test disabled chip.
|
|
await tester.pumpWidget(buildApp(enabled: false, selected: false));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Disabled chip should have the provided disabledColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: disabledColor));
|
|
|
|
// Test enabled & selected chip.
|
|
await tester.pumpWidget(buildApp(enabled: true, selected: true));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Enabled & selected chip should have the provided selectedColor.
|
|
expect(getMaterialBox(tester), paints..rrect(color: selectedColor));
|
|
});
|
|
|
|
testWidgets('Delete button tap target area does not include label', (WidgetTester tester) async {
|
|
bool calledDelete = false;
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: Column(
|
|
children: <Widget>[
|
|
Chip(
|
|
label: const Text('Chip'),
|
|
onDeleted: () {
|
|
calledDelete = true;
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
|
|
// Tap on the delete button.
|
|
await tester.tapAt(tester.getCenter(find.byType(Icon)));
|
|
await tester.pump();
|
|
expect(calledDelete, isTrue);
|
|
calledDelete = false;
|
|
|
|
final Offset labelCenter = tester.getCenter(find.text('Chip'));
|
|
|
|
// Tap on the label.
|
|
await tester.tapAt(labelCenter);
|
|
await tester.pump();
|
|
expect(calledDelete, isFalse);
|
|
|
|
// Tap before end of the label.
|
|
final Size labelSize = tester.getSize(find.text('Chip'));
|
|
await tester.tapAt(Offset(labelCenter.dx + (labelSize.width / 2) - 1, labelCenter.dy));
|
|
await tester.pump();
|
|
expect(calledDelete, isFalse);
|
|
|
|
// Tap after end of the label.
|
|
await tester.tapAt(Offset(labelCenter.dx + (labelSize.width / 2) + 0.01, labelCenter.dy));
|
|
await tester.pump();
|
|
expect(calledDelete, isTrue);
|
|
});
|
|
|
|
// This is a regression test for https://github.com/flutter/flutter/pull/133615.
|
|
testWidgets('Material3 - Custom shape without provided side uses default side', (WidgetTester tester) async {
|
|
final ThemeData theme = ThemeData();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: theme,
|
|
home: const Material(
|
|
child: Center(
|
|
child: RawChip(
|
|
// No side provided.
|
|
shape: StadiumBorder(),
|
|
label: Text('RawChip'),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Chip should have the default side.
|
|
expect(
|
|
getMaterial(tester).shape,
|
|
StadiumBorder(side: BorderSide(color: theme.colorScheme.outline)),
|
|
);
|
|
});
|
|
|
|
testWidgets("Material3 - RawChip.shape's side is used when provided", (WidgetTester tester) async {
|
|
Widget buildChip({ OutlinedBorder? shape, BorderSide? side }) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: RawChip(
|
|
shape: shape,
|
|
side: side,
|
|
label: const Text('RawChip'),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test [RawChip.shape] with a side.
|
|
await tester.pumpWidget(buildChip(
|
|
shape: const RoundedRectangleBorder(
|
|
side: BorderSide(color: Color(0xffff00ff)),
|
|
borderRadius: BorderRadius.all(Radius.circular(7.0)),
|
|
)),
|
|
);
|
|
|
|
// Chip should have the provided shape and the side from [RawChip.shape].
|
|
expect(
|
|
getMaterial(tester).shape,
|
|
const RoundedRectangleBorder(
|
|
side: BorderSide(color: Color(0xffff00ff)),
|
|
borderRadius: BorderRadius.all(Radius.circular(7.0)),
|
|
),
|
|
);
|
|
|
|
// Test [RawChip.shape] with a side and [RawChip.side].
|
|
await tester.pumpWidget(buildChip(
|
|
shape: const RoundedRectangleBorder(
|
|
side: BorderSide(color: Color(0xffff00ff)),
|
|
borderRadius: BorderRadius.all(Radius.circular(7.0)),
|
|
),
|
|
side: const BorderSide(color: Color(0xfffff000))),
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Chip use shape from [RawChip.shape] and the side from [RawChip.side].
|
|
// [RawChip.shape]'s side should be ignored.
|
|
expect(
|
|
getMaterial(tester).shape,
|
|
const RoundedRectangleBorder(
|
|
side: BorderSide(color: Color(0xfffff000)),
|
|
borderRadius: BorderRadius.all(Radius.circular(7.0)),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Material3 - Chip.iconTheme respects default iconTheme.size', (WidgetTester tester) async {
|
|
Widget buildChip({ IconThemeData? iconTheme }) {
|
|
return MaterialApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Material(
|
|
child: Center(
|
|
child: RawChip(
|
|
iconTheme: iconTheme,
|
|
avatar: const Icon(Icons.add),
|
|
label: const SizedBox(width: 100, height: 100),
|
|
onSelected: (bool newValue) { },
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff332211))));
|
|
|
|
// Icon should have the default chip iconSize.
|
|
expect(getIconData(tester).size, 18.0);
|
|
expect(getIconData(tester).color, const Color(0xff332211));
|
|
|
|
// Icon should have the provided iconSize.
|
|
await tester.pumpWidget(buildChip(iconTheme: const IconThemeData(color: Color(0xff112233), size: 23.0)));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(getIconData(tester).size, 23.0);
|
|
expect(getIconData(tester).color, const Color(0xff112233));
|
|
});
|
|
|
|
// This is a regression test for https://github.com/flutter/flutter/issues/138287.
|
|
testWidgets("Enabling and disabling Chip with Tooltip doesn't throw an exception", (WidgetTester tester) async {
|
|
bool isEnabled = true;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setState) {
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
RawChip(
|
|
tooltip: 'tooltip',
|
|
isEnabled: isEnabled,
|
|
onPressed: isEnabled ? () {} : null,
|
|
label: const Text('RawChip'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
setState(() {
|
|
isEnabled = !isEnabled;
|
|
});
|
|
},
|
|
child: Text('${isEnabled ? 'Disable' : 'Enable'} Chip'),
|
|
),
|
|
],
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
));
|
|
|
|
// Tap the elevated button to disable the chip with a tooltip.
|
|
await tester.tap(find.widgetWithText(ElevatedButton, 'Disable Chip'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// No exception should be thrown.
|
|
expect(tester.takeException(), isNull);
|
|
|
|
// Tap the elevated button to enable the chip with a tooltip.
|
|
await tester.tap(find.widgetWithText(ElevatedButton, 'Enable Chip'));
|
|
await tester.pumpAndSettle();
|
|
|
|
// No exception should be thrown.
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('Delete button is visible on disabled RawChip', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
wrapForChip(
|
|
child: RawChip(
|
|
isEnabled: false,
|
|
label: const Text('Label'),
|
|
onDeleted: () { },
|
|
),
|
|
),
|
|
);
|
|
|
|
// Delete button should be visible.
|
|
await expectLater(find.byType(RawChip), matchesGoldenFile('raw_chip.disabled.delete_button.png'));
|
|
});
|
|
|
|
testWidgets('Delete button tooltip is not shown on disabled RawChip', (WidgetTester tester) async {
|
|
Widget buildChip({ bool enabled = true }) {
|
|
return wrapForChip(
|
|
child: RawChip(
|
|
isEnabled: enabled,
|
|
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('Chip 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: Chip(
|
|
avatarBoxConstraints: avatarBoxConstraints,
|
|
avatar: const Icon(Icons.favorite),
|
|
label: Container(
|
|
width: labelSize.width,
|
|
height: labelSize.width,
|
|
color: const Color(0xFFFF0000),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test default avatar layout constraints.
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
expect(tester.getSize(find.byType(Chip)).width, equals(234.0));
|
|
expect(tester.getSize(find.byType(Chip)).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 distance 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(Chip)).width, equals(152.0));
|
|
expect(tester.getSize(find.byType(Chip)).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 distance between avatar and label.
|
|
labelTopLeft = tester.getTopLeft(find.byType(Container));
|
|
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
|
|
});
|
|
|
|
testWidgets('RawChip 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: RawChip(
|
|
avatarBoxConstraints: avatarBoxConstraints,
|
|
avatar: const Icon(Icons.favorite),
|
|
label: Container(
|
|
width: labelSize.width,
|
|
height: labelSize.width,
|
|
color: const Color(0xFFFF0000),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test default avatar layout constraints.
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
expect(tester.getSize(find.byType(RawChip)).width, equals(234.0));
|
|
expect(tester.getSize(find.byType(RawChip)).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 distance 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(RawChip)).width, equals(152.0));
|
|
expect(tester.getSize(find.byType(RawChip)).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 distance between avatar and label.
|
|
labelTopLeft = tester.getTopLeft(find.byType(Container));
|
|
expect(labelTopLeft.dx, avatarCenter.dx + (iconSize / 2) + labelPadding);
|
|
});
|
|
|
|
testWidgets('Chip 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: Chip(
|
|
deleteIconBoxConstraints: deleteIconBoxConstraints,
|
|
onDeleted: () { },
|
|
label: Container(
|
|
width: labelSize.width,
|
|
height: labelSize.width,
|
|
color: const Color(0xFFFF0000),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test default delete icon layout constraints.
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
expect(tester.getSize(find.byType(Chip)).width, equals(234.0));
|
|
expect(tester.getSize(find.byType(Chip)).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.cancel));
|
|
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(Chip)).width, equals(152.0));
|
|
expect(tester.getSize(find.byType(Chip)).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);
|
|
});
|
|
|
|
testWidgets('RawChip 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: RawChip(
|
|
deleteIconBoxConstraints: deleteIconBoxConstraints,
|
|
onDeleted: () { },
|
|
label: Container(
|
|
width: labelSize.width,
|
|
height: labelSize.width,
|
|
color: const Color(0xFFFF0000),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// Test default delete icon layout constraints.
|
|
await tester.pumpWidget(buildChip());
|
|
|
|
expect(tester.getSize(find.byType(RawChip)).width, equals(234.0));
|
|
expect(tester.getSize(find.byType(RawChip)).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.cancel));
|
|
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(RawChip)).width, equals(152.0));
|
|
expect(tester.getSize(find.byType(RawChip )).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);
|
|
});
|
|
|
|
testWidgets('Default delete button InkWell shape', (WidgetTester tester) async {
|
|
await tester.pumpWidget(wrapForChip(
|
|
child: Center(
|
|
child: RawChip(
|
|
onDeleted: () { },
|
|
label: const Text('RawChip'),
|
|
),
|
|
),
|
|
));
|
|
|
|
final InkWell deleteButtonInkWell = tester.widget<InkWell>(find.ancestor(
|
|
of: find.byIcon(Icons.cancel),
|
|
matching: find.byType(InkWell).last,
|
|
));
|
|
expect(deleteButtonInkWell.customBorder, const CircleBorder());
|
|
});
|
|
|
|
testWidgets('Default delete button overlay', (WidgetTester tester) async {
|
|
final ThemeData theme = ThemeData();
|
|
await tester.pumpWidget(wrapForChip(
|
|
child: Center(
|
|
child: RawChip(
|
|
onDeleted: () { },
|
|
label: const Text('RawChip'),
|
|
),
|
|
),
|
|
theme: theme,
|
|
));
|
|
|
|
RenderObject inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
|
expect(inkFeatures, isNot(paints..rect(color: theme.hoverColor)));
|
|
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 0));
|
|
|
|
// Hover over the delete icon.
|
|
final Offset centerOfDeleteButton = tester.getCenter(find.byType(Icon));
|
|
final TestGesture hoverGesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
|
await hoverGesture.moveTo(centerOfDeleteButton);
|
|
addTearDown(hoverGesture.removePointer);
|
|
await tester.pumpAndSettle();
|
|
|
|
inkFeatures = tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
|
expect(inkFeatures, paints..rect(color: theme.hoverColor));
|
|
expect(inkFeatures, paintsExactlyCountTimes(#clipPath, 1));
|
|
|
|
const Rect expectedClipRect = Rect.fromLTRB(124.7, 10.0, 142.7, 28.0);
|
|
final Path expectedClipPath = Path()..addRect(expectedClipRect);
|
|
expect(
|
|
inkFeatures,
|
|
paints..clipPath(pathMatcher: coversSameAreaAs(
|
|
expectedClipPath,
|
|
areaToCompare: expectedClipRect.inflate(48.0),
|
|
sampleSize: 100,
|
|
)),
|
|
);
|
|
});
|
|
|
|
group('Material 2', () {
|
|
// These tests are only relevant for Material 2. Once Material 2
|
|
// support is deprecated and the APIs are removed, these tests
|
|
// can be deleted.
|
|
|
|
testWidgets('M2 Chip defaults', (WidgetTester tester) async {
|
|
late TextTheme textTheme;
|
|
|
|
Widget buildFrame(Brightness brightness) {
|
|
return MaterialApp(
|
|
theme: ThemeData(brightness: brightness, useMaterial3: false),
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
textTheme = Theme.of(context).textTheme;
|
|
return Chip(
|
|
avatar: const CircleAvatar(child: Text('A')),
|
|
label: const Text('Chip A'),
|
|
onDeleted: () { },
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildFrame(Brightness.light));
|
|
expect(getMaterialBox(tester), paints..rrect()..circle(color: const Color(0xff1976d2)));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0));
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, const StadiumBorder());
|
|
expect(getIconData(tester).color?.value, 0xffffffff);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, null);
|
|
|
|
TextStyle labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color?.value, 0xde000000);
|
|
expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.bodyLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.bodyLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.bodyLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing);
|
|
|
|
await tester.pumpWidget(buildFrame(Brightness.dark));
|
|
await tester.pumpAndSettle(); // Theme transition animation
|
|
expect(getMaterialBox(tester), paints..rrect(color: const Color(0x1fffffff)));
|
|
expect(tester.getSize(find.byType(Chip)), const Size(156.0, 48.0));
|
|
expect(getMaterial(tester).color, null);
|
|
expect(getMaterial(tester).elevation, 0);
|
|
expect(getMaterial(tester).shape, const StadiumBorder());
|
|
expect(getIconData(tester).color?.value, 0xffffffff);
|
|
expect(getIconData(tester).opacity, null);
|
|
expect(getIconData(tester).size, null);
|
|
|
|
labelStyle = getLabelStyle(tester, 'Chip A').style;
|
|
expect(labelStyle.color?.value, 0xdeffffff);
|
|
expect(labelStyle.fontFamily, textTheme.bodyLarge?.fontFamily);
|
|
expect(labelStyle.fontFamilyFallback, textTheme.bodyLarge?.fontFamilyFallback);
|
|
expect(labelStyle.fontFeatures, textTheme.bodyLarge?.fontFeatures);
|
|
expect(labelStyle.fontSize, textTheme.bodyLarge?.fontSize);
|
|
expect(labelStyle.fontStyle, textTheme.bodyLarge?.fontStyle);
|
|
expect(labelStyle.fontWeight, textTheme.bodyLarge?.fontWeight);
|
|
expect(labelStyle.height, textTheme.bodyLarge?.height);
|
|
expect(labelStyle.inherit, textTheme.bodyLarge?.inherit);
|
|
expect(labelStyle.leadingDistribution, textTheme.bodyLarge?.leadingDistribution);
|
|
expect(labelStyle.letterSpacing, textTheme.bodyLarge?.letterSpacing);
|
|
expect(labelStyle.overflow, textTheme.bodyLarge?.overflow);
|
|
expect(labelStyle.textBaseline, textTheme.bodyLarge?.textBaseline);
|
|
expect(labelStyle.wordSpacing, textTheme.bodyLarge?.wordSpacing);
|
|
});
|
|
|
|
testWidgets('Chip uses the right theme colors for the right components', (WidgetTester tester) async {
|
|
final ThemeData themeData = ThemeData(
|
|
platform: TargetPlatform.android,
|
|
primarySwatch: Colors.blue,
|
|
useMaterial3: false,
|
|
);
|
|
final ChipThemeData defaultChipTheme = ChipThemeData.fromDefaults(
|
|
brightness: themeData.brightness,
|
|
secondaryColor: Colors.blue,
|
|
labelStyle: themeData.textTheme.bodyLarge!,
|
|
);
|
|
bool value = false;
|
|
Widget buildApp({
|
|
ChipThemeData? chipTheme,
|
|
Widget? avatar,
|
|
Widget? deleteIcon,
|
|
bool isSelectable = true,
|
|
bool isPressable = false,
|
|
bool isDeletable = true,
|
|
bool showCheckmark = true,
|
|
}) {
|
|
chipTheme ??= defaultChipTheme;
|
|
return wrapForChip(
|
|
child: Theme(
|
|
data: themeData,
|
|
child: ChipTheme(
|
|
data: chipTheme,
|
|
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
|
|
return RawChip(
|
|
showCheckmark: showCheckmark,
|
|
onDeleted: isDeletable ? () { } : null,
|
|
avatar: avatar,
|
|
deleteIcon: deleteIcon,
|
|
isEnabled: isSelectable || isPressable,
|
|
shape: chipTheme?.shape,
|
|
selected: isSelectable && value,
|
|
label: Text('$value'),
|
|
onSelected: isSelectable
|
|
? (bool newValue) {
|
|
setState(() {
|
|
value = newValue;
|
|
});
|
|
}
|
|
: null,
|
|
onPressed: isPressable
|
|
? () {
|
|
setState(() {
|
|
value = true;
|
|
});
|
|
}
|
|
: null,
|
|
);
|
|
}),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp());
|
|
|
|
RenderBox materialBox = getMaterialBox(tester);
|
|
IconThemeData iconData = getIconData(tester);
|
|
DefaultTextStyle labelStyle = getLabelStyle(tester, 'false');
|
|
|
|
// Check default theme for enabled chip.
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.backgroundColor));
|
|
expect(iconData.color, equals(const Color(0xde000000)));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Check default theme for disabled chip.
|
|
await tester.pumpWidget(buildApp(isSelectable: false));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Check default theme for enabled and selected chip.
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.selectedColor));
|
|
|
|
// Check default theme for disabled and selected chip.
|
|
await tester.pumpWidget(buildApp(isSelectable: false));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'true');
|
|
expect(materialBox, paints..rrect(color: defaultChipTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Enable the chip again.
|
|
await tester.pumpWidget(buildApp());
|
|
await tester.pumpAndSettle();
|
|
// Tap to unselect the chip.
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Apply a custom theme.
|
|
const Color customColor1 = Color(0xcafefeed);
|
|
const Color customColor2 = Color(0xdeadbeef);
|
|
const Color customColor3 = Color(0xbeefcafe);
|
|
const Color customColor4 = Color(0xaddedabe);
|
|
final ChipThemeData customTheme = defaultChipTheme.copyWith(
|
|
brightness: Brightness.dark,
|
|
backgroundColor: customColor1,
|
|
disabledColor: customColor2,
|
|
selectedColor: customColor3,
|
|
deleteIconColor: customColor4,
|
|
);
|
|
await tester.pumpWidget(buildApp(chipTheme: customTheme));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
iconData = getIconData(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
|
|
// Check custom theme for enabled chip.
|
|
expect(materialBox, paints..rrect(color: customTheme.backgroundColor));
|
|
expect(iconData.color, equals(customTheme.deleteIconColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Check custom theme with disabled widget.
|
|
await tester.pumpWidget(buildApp(
|
|
chipTheme: customTheme,
|
|
isSelectable: false,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'false');
|
|
expect(materialBox, paints..rrect(color: customTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
|
|
// Check custom theme for enabled and selected chip.
|
|
await tester.pumpWidget(buildApp(chipTheme: customTheme));
|
|
await tester.pumpAndSettle();
|
|
await tester.tap(find.byType(RawChip));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
expect(materialBox, paints..rrect(color: customTheme.selectedColor));
|
|
|
|
// Check custom theme for disabled and selected chip.
|
|
await tester.pumpWidget(buildApp(
|
|
chipTheme: customTheme,
|
|
isSelectable: false,
|
|
));
|
|
await tester.pumpAndSettle();
|
|
materialBox = getMaterialBox(tester);
|
|
labelStyle = getLabelStyle(tester, 'true');
|
|
expect(materialBox, paints..rrect(color: customTheme.disabledColor));
|
|
expect(labelStyle.style.color, equals(Colors.black.withAlpha(0xde)));
|
|
});
|
|
});
|
|
|
|
testWidgets('Chip Baseline location', (WidgetTester tester) async {
|
|
const Text text = Text('A', style: TextStyle(fontSize: 10.0, height: 1.0));
|
|
await tester.pumpWidget(wrapForChip(child: const Align(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.baseline,
|
|
textBaseline: TextBaseline.alphabetic,
|
|
children: <Widget>[
|
|
text,
|
|
RawChip(label: text)
|
|
],
|
|
),
|
|
)));
|
|
|
|
expect(find.text('A'), findsNWidgets(2));
|
|
// Baseline aligning text.
|
|
expect(
|
|
tester.getTopLeft(find.text('A').first).dy,
|
|
tester.getTopLeft(find.text('A').last).dy,
|
|
);
|
|
});
|
|
|
|
testWidgets('ChipThemeData.iconTheme updates avatar and delete icons', (WidgetTester tester) async {
|
|
const Color iconColor = Color(0xffff00ff);
|
|
const double iconSize = 28.0;
|
|
const IconData avatarIcon = Icons.favorite;
|
|
const IconData deleteIcon = Icons.delete;
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: RawChip(
|
|
iconTheme: const IconThemeData(
|
|
color: iconColor,
|
|
size: iconSize,
|
|
),
|
|
avatar: const Icon(Icons.favorite),
|
|
deleteIcon: const Icon(Icons.delete),
|
|
onDeleted: () { },
|
|
label: const SizedBox(height: 100),
|
|
),
|
|
),
|
|
),
|
|
));
|
|
|
|
// Test rendered icon size.
|
|
final RenderBox avatarIconBox = tester.renderObject(find.byIcon(avatarIcon));
|
|
final RenderBox deleteIconBox = tester.renderObject(find.byIcon(deleteIcon));
|
|
expect(avatarIconBox.size.width, equals(iconSize));
|
|
expect(deleteIconBox.size.width, equals(iconSize));
|
|
|
|
// Test rendered icon color.
|
|
expect(getIconStyle(tester, avatarIcon)?.color, iconColor);
|
|
expect(getIconStyle(tester, deleteIcon)?.color, iconColor);
|
|
});
|
|
|
|
testWidgets('RawChip.deleteIconColor overrides iconTheme color', (WidgetTester tester) async {
|
|
const Color iconColor = Color(0xffff00ff);
|
|
const Color deleteIconColor = Color(0xffff00ff);
|
|
const IconData deleteIcon = Icons.delete;
|
|
|
|
Widget buildChip({ Color? deleteIconColor, Color? iconColor }) {
|
|
return MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: RawChip(
|
|
deleteIconColor: deleteIconColor,
|
|
iconTheme: IconThemeData(color: iconColor),
|
|
deleteIcon: const Icon(Icons.delete),
|
|
onDeleted: () { },
|
|
label: const SizedBox(height: 100),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildChip(iconColor: iconColor));
|
|
|
|
// Test rendered icon color.
|
|
expect(getIconStyle(tester, deleteIcon)?.color, iconColor);
|
|
|
|
await tester.pumpWidget(buildChip(
|
|
deleteIconColor: deleteIconColor,
|
|
iconColor: iconColor,
|
|
));
|
|
|
|
// Test rendered icon color.
|
|
expect(getIconStyle(tester, deleteIcon)?.color, deleteIconColor);
|
|
});
|
|
|
|
testWidgets('Chip label only does layout once', (WidgetTester tester) async {
|
|
final RenderLayoutCount renderLayoutCount = RenderLayoutCount();
|
|
final Widget layoutCounter = Center(
|
|
key: GlobalKey(),
|
|
child: WidgetToRenderBoxAdapter(renderBox: renderLayoutCount),
|
|
);
|
|
|
|
await tester.pumpWidget(wrapForChip(child: RawChip(label: layoutCounter)));
|
|
|
|
expect(renderLayoutCount.layoutCount, 1);
|
|
});
|
|
}
|
|
|
|
class _MaterialStateOutlinedBorder extends StadiumBorder implements MaterialStateOutlinedBorder {
|
|
const _MaterialStateOutlinedBorder(this.resolver);
|
|
|
|
final MaterialPropertyResolver<OutlinedBorder?> resolver;
|
|
|
|
@override
|
|
OutlinedBorder? resolve(Set<MaterialState> states) => resolver(states);
|
|
}
|
|
|
|
class _MaterialStateBorderSide extends MaterialStateBorderSide {
|
|
const _MaterialStateBorderSide(this.resolver);
|
|
|
|
final MaterialPropertyResolver<BorderSide?> resolver;
|
|
|
|
@override
|
|
BorderSide? resolve(Set<MaterialState> states) => resolver(states);
|
|
}
|
|
|
|
class RenderLayoutCount extends RenderBox {
|
|
int layoutCount = 0;
|
|
|
|
@override
|
|
Size computeDryLayout(covariant BoxConstraints constraints) => constraints.biggest;
|
|
|
|
@override
|
|
void performLayout() {
|
|
layoutCount += 1;
|
|
size = constraints.biggest;
|
|
}
|
|
}
|