Unverified Commit 0bb9409f authored by Qun Cheng's avatar Qun Cheng Committed by GitHub

Update `Switch` tests for M2/M3 (#129810)

Updated and reorganized unit tests for `Switch` to have M2 and M3 versions.

More info in https://github.com/flutter/flutter/issues/127064
parent 2fefe72c
...@@ -106,7 +106,7 @@ void main() { ...@@ -106,7 +106,7 @@ void main() {
expect(tester.getSize(find.byType(Switch)), material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0)); expect(tester.getSize(find.byType(Switch)), material3 ? const Size(60.0, 40.0) : const Size(59.0, 40.0));
}); });
testWidgets('Switch does not get distorted upon changing constraints with parent - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch does not get distorted upon changing constraints with parent', (WidgetTester tester) async {
const double maxWidth = 300; const double maxWidth = 300;
const double maxHeight = 100; const double maxHeight = 100;
...@@ -145,7 +145,7 @@ void main() { ...@@ -145,7 +145,7 @@ void main() {
)); ));
await expectLater( await expectLater(
find.byKey(boundaryKey), find.byKey(boundaryKey),
matchesGoldenFile('switch_test.big.on.png'), matchesGoldenFile('m2_switch_test.big.on.png'),
); );
await tester.pumpWidget(buildSwitch( await tester.pumpWidget(buildSwitch(
...@@ -154,7 +154,59 @@ void main() { ...@@ -154,7 +154,59 @@ void main() {
)); ));
await expectLater( await expectLater(
find.byKey(boundaryKey), find.byKey(boundaryKey),
matchesGoldenFile('switch_test.small.on.png'), matchesGoldenFile('m2_switch_test.small.on.png'),
);
});
testWidgets('Material3 - Switch does not get distorted upon changing constraints with parent', (WidgetTester tester) async {
const double maxWidth = 300;
const double maxHeight = 100;
const ValueKey<String> boundaryKey = ValueKey<String>('switch container');
Widget buildSwitch({required double width, required double height}) {
return MaterialApp(
theme: ThemeData(useMaterial3: true),
home: Scaffold(
body: Directionality(
textDirection: TextDirection.ltr,
child: SizedBox(
width: maxWidth,
height: maxHeight,
child: RepaintBoundary(
key: boundaryKey,
child: SizedBox(
width: width,
height: height,
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: true,
onChanged: (_) {},
),
),
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(
width: maxWidth,
height: maxHeight,
));
await expectLater(
find.byKey(boundaryKey),
matchesGoldenFile('m3_switch_test.big.on.png'),
);
await tester.pumpWidget(buildSwitch(
width: 20,
height: 10,
));
await expectLater(
find.byKey(boundaryKey),
matchesGoldenFile('m3_switch_test.small.on.png'),
); );
}); });
...@@ -348,7 +400,7 @@ void main() { ...@@ -348,7 +400,7 @@ void main() {
expect(value, isFalse); expect(value, isFalse);
}); });
testWidgets('Switch has default colors when enabled - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch has default colors when enabled', (WidgetTester tester) async {
bool value = false; bool value = false;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -407,7 +459,72 @@ void main() { ...@@ -407,7 +459,72 @@ void main() {
); );
}); });
testWidgets('Switch has default colors when disabled - M2', (WidgetTester tester) async { testWidgets('Material3 - Switch has default colors when enabled', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colors = theme.colorScheme;
bool value = false;
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: colors.outline), // thumb color
reason: 'Inactive enabled switch should match these colors',
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.onPrimary), // thumb color
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Material2 - Switch has default colors when disabled', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
theme: ThemeData(useMaterial3: false), theme: ThemeData(useMaterial3: false),
...@@ -471,10 +588,125 @@ void main() { ...@@ -471,10 +588,125 @@ void main() {
); );
}); });
testWidgets('Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async { testWidgets('Material3 - Inactive Switch has default colors when disabled', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
value: false,
onChanged: null,
),
),
),
),
));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), // thumb color
reason: 'Inactive disabled switch should match these colors',
);
});
testWidgets('Material3 - Active Switch has default colors when disabled', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
value: true,
onChanged: null,
),
),
),
),
));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.surface), // thumb color
reason: 'Active disabled switch should match these colors',
);
});
testWidgets('Material2 - Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
Finder findSwitch() {
return find.byWidgetPredicate((Widget widget) => widget is Switch);
}
MaterialInkController? getSwitchMaterial(WidgetTester tester) {
return Material.of(tester.element(findSwitch()));
}
await tester.pumpWidget(MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: Switch(
focusNode: focusNode,
value: true,
onChanged: (_) { },
),
),
));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(getSwitchMaterial(tester),
paints
..circle(color: theme.focusColor)
);
// On both hovered and focused, the overlay color should show hovered overlay color.
final Offset center = tester.getCenter(find.byType(Switch));
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer();
await gesture.moveTo(center);
await tester.pumpAndSettle();
expect(getSwitchMaterial(tester),
paints..circle(color: theme.hoverColor)
);
});
testWidgets('Material3 - Switch default overlayColor resolves hovered/focused state', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true);
final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final bool material3 = theme.useMaterial3;
Finder findSwitch() { Finder findSwitch() {
return find.byWidgetPredicate((Widget widget) => widget is Switch); return find.byWidgetPredicate((Widget widget) => widget is Switch);
...@@ -499,8 +731,7 @@ void main() { ...@@ -499,8 +731,7 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(getSwitchMaterial(tester), expect(getSwitchMaterial(tester),
paints paints..circle(color: theme.colorScheme.primary.withOpacity(0.12))
..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.12) : theme.focusColor)
); );
// On both hovered and focused, the overlay color should show hovered overlay color. // On both hovered and focused, the overlay color should show hovered overlay color.
...@@ -513,11 +744,11 @@ void main() { ...@@ -513,11 +744,11 @@ void main() {
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(getSwitchMaterial(tester), expect(getSwitchMaterial(tester),
paints..circle(color: material3 ? theme.colorScheme.primary.withOpacity(0.08) : theme.hoverColor) paints..circle(color: theme.colorScheme.primary.withOpacity(0.08))
); );
}); });
testWidgets('Switch can be set color - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch can be set color', (WidgetTester tester) async {
bool value = false; bool value = false;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
...@@ -578,6 +809,71 @@ void main() { ...@@ -578,6 +809,71 @@ void main() {
); );
}); });
testWidgets('Material3 - Switch can be set color', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
bool value = false;
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
activeColor: Colors.red[500],
activeTrackColor: Colors.green[500],
inactiveThumbColor: Colors.yellow[500],
inactiveTrackColor: Colors.blue[500],
),
),
);
},
),
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: Colors.blue[500],
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
)
..rrect(color: Colors.yellow[500]), // thumb color
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: Colors.green[500],
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: Colors.red[500]), // thumb color
);
});
testWidgets('Drag ends after animation completes', (WidgetTester tester) async { testWidgets('Drag ends after animation completes', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/17773 // Regression test for https://github.com/flutter/flutter/issues/17773
...@@ -862,7 +1158,7 @@ void main() { ...@@ -862,7 +1158,7 @@ void main() {
} }
}); });
testWidgets('Switch is focusable and has correct focus color - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch is focusable and has correct focus color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true; bool value = true;
...@@ -944,17 +1240,104 @@ void main() { ...@@ -944,17 +1240,104 @@ void main() {
); );
}); });
testWidgets('Switch with splash radius set', (WidgetTester tester) async { testWidgets('Material3 - Switch is focusable and has correct focus color', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const double splashRadius = 30; bool value = true;
Widget buildApp() { Widget buildApp({bool enabled = true}) {
return MaterialApp( return MaterialApp(
theme: theme, theme: themeData,
home: Material( home: Material(
child: Center( child: Center(
child: Switch( child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
value: true, return Switch(
onChanged: (bool newValue) {}, value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// active, enabled switch
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500]),
);
// Check the false value: inactive enabled switch
value = false;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500])
);
// Check what happens when disabled: inactive disabled switch.
value = false;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
);
});
testWidgets('Switch with splash radius set', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const double splashRadius = 30;
Widget buildApp() {
return MaterialApp(
theme: theme,
home: Material(
child: Center(
child: Switch(
value: true,
onChanged: (bool newValue) {},
focusColor: Colors.orange[500], focusColor: Colors.orange[500],
autofocus: true, autofocus: true,
splashRadius: splashRadius, splashRadius: splashRadius,
...@@ -971,7 +1354,7 @@ void main() { ...@@ -971,7 +1354,7 @@ void main() {
); );
}); });
testWidgets('Switch can be hovered and has correct hover color - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch can be hovered and has correct hover color', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true; bool value = true;
Widget buildApp({bool enabled = true}) { Widget buildApp({bool enabled = true}) {
...@@ -1047,6 +1430,78 @@ void main() { ...@@ -1047,6 +1430,78 @@ void main() {
); );
}); });
testWidgets('Material3 - Switch can be hovered and has correct hover color', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: themeData,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
hoverColor: Colors.orange[500],
);
}),
),
),
);
}
// active enabled switch
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.onPrimary),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500]),
);
// Check what happens for disabled active switch
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.surface.withOpacity(1.0)),
);
});
testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async { testWidgets('Switch can be toggled by keyboard shortcuts', (WidgetTester tester) async {
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true; bool value = true;
...@@ -1239,7 +1694,7 @@ void main() { ...@@ -1239,7 +1694,7 @@ void main() {
expect(updatedSwitchState.position.isDismissed, false); expect(updatedSwitchState.position.isDismissed, false);
}); });
testWidgets('Switch thumb color resolves in active/enabled states - M2', (WidgetTester tester) async { testWidgets('Material2 - Switch thumb color resolves in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledThumbColor = Color(0xFF000001); const Color activeEnabledThumbColor = Color(0xFF000001);
const Color activeDisabledThumbColor = Color(0xFF000002); const Color activeDisabledThumbColor = Color(0xFF000002);
const Color inactiveEnabledThumbColor = Color(0xFF000003); const Color inactiveEnabledThumbColor = Color(0xFF000003);
...@@ -1347,7 +1802,116 @@ void main() { ...@@ -1347,7 +1802,116 @@ void main() {
); );
}); });
testWidgets('Switch thumb color resolves in hovered/focused states - M2', (WidgetTester tester) async { testWidgets('Material3 - Switch thumb color resolves in active/enabled states', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
const Color activeEnabledThumbColor = Color(0xFF000001);
const Color activeDisabledThumbColor = Color(0xFF000002);
const Color inactiveEnabledThumbColor = Color(0xFF000003);
const Color inactiveDisabledThumbColor = Color(0xFF000004);
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledThumbColor;
}
return inactiveDisabledThumbColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledThumbColor;
}
return inactiveEnabledThumbColor;
}
final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
thumbColor: thumbColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: inactiveDisabledThumbColor),
reason: 'Inactive disabled switch should default track and custom thumb color',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: activeDisabledThumbColor),
reason: 'Active disabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: inactiveEnabledThumbColor),
reason: 'Inactive enabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: activeEnabledThumbColor),
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Material2 - Switch thumb color resolves in hovered/focused states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredThumbColor = Color(0xFF000001); const Color hoveredThumbColor = Color(0xFF000001);
...@@ -1423,34 +1987,110 @@ void main() { ...@@ -1423,34 +1987,110 @@ void main() {
); );
}); });
testWidgets('Track color resolves in active/enabled states - M2', (WidgetTester tester) async { testWidgets('Material3 - Switch thumb color resolves in hovered/focused states', (WidgetTester tester) async {
const Color activeEnabledTrackColor = Color(0xFF000001); final ThemeData themeData = ThemeData(useMaterial3: true);
const Color activeDisabledTrackColor = Color(0xFF000002); final ColorScheme colors = themeData.colorScheme;
const Color inactiveEnabledTrackColor = Color(0xFF000003); final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
const Color inactiveDisabledTrackColor = Color(0xFF000004); tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredThumbColor = Color(0xFF000001);
const Color focusedThumbColor = Color(0xFF000002);
Color getTrackColor(Set<MaterialState> states) { Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) { if (states.contains(MaterialState.hovered)) {
if (states.contains(MaterialState.selected)) { return hoveredThumbColor;
return activeDisabledTrackColor;
}
return inactiveDisabledTrackColor;
} }
if (states.contains(MaterialState.selected)) { if (states.contains(MaterialState.focused)) {
return activeEnabledTrackColor; return focusedThumbColor;
} }
return inactiveEnabledTrackColor; return Colors.transparent;
} }
final MaterialStateProperty<Color> trackColor = final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch({required bool enabled, required bool active}) { Widget buildSwitch() {
return MaterialApp( return MaterialApp(
theme: ThemeData(useMaterial3: false), theme: themeData,
home: Material( home: Directionality(
child: Center( textDirection: TextDirection.rtl,
child: Switch( child: Material(
child: Center(
child: Switch(
focusNode: focusNode,
autofocus: true,
value: true,
thumbColor: thumbColor,
onChanged: (_) { },
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: colors.primary.withOpacity(0.12))
..rrect(color: focusedThumbColor),
reason: 'active enabled switch should default track and custom thumb color',
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: colors.primary.withOpacity(0.08))
..rrect(color: hoveredThumbColor),
reason: 'active enabled switch should default track and custom thumb color',
);
});
testWidgets('Material2 - Track color resolves in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledTrackColor = Color(0xFF000001);
const Color activeDisabledTrackColor = Color(0xFF000002);
const Color inactiveEnabledTrackColor = Color(0xFF000003);
const Color inactiveDisabledTrackColor = Color(0xFF000004);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledTrackColor;
}
return inactiveDisabledTrackColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledTrackColor;
}
return inactiveEnabledTrackColor;
}
final MaterialStateProperty<Color> trackColor =
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: Switch(
trackColor: trackColor, trackColor: trackColor,
value: active, value: active,
onChanged: enabled ? (_) { } : null, onChanged: enabled ? (_) { } : null,
...@@ -1512,7 +2152,100 @@ void main() { ...@@ -1512,7 +2152,100 @@ void main() {
); );
}); });
testWidgets('Switch track color resolves in hovered/focused states - M2', (WidgetTester tester) async { testWidgets('Material3 - Track color resolves in active/enabled states', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
const Color activeEnabledTrackColor = Color(0xFF000001);
const Color activeDisabledTrackColor = Color(0xFF000002);
const Color inactiveEnabledTrackColor = Color(0xFF000003);
const Color inactiveDisabledTrackColor = Color(0xFF000004);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledTrackColor;
}
return inactiveDisabledTrackColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledTrackColor;
}
return inactiveEnabledTrackColor;
}
final MaterialStateProperty<Color> trackColor =
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
trackColor: trackColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: inactiveDisabledTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Inactive disabled switch track should use this value',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: activeDisabledTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Active disabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: inactiveEnabledTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Inactive enabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: activeEnabledTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Material2 - Switch track color resolves in hovered/focused states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredTrackColor = Color(0xFF000001); const Color hoveredTrackColor = Color(0xFF000001);
...@@ -1581,7 +2314,77 @@ void main() { ...@@ -1581,7 +2314,77 @@ void main() {
); );
}); });
testWidgets('Switch thumb color is blended against surface color - M2', (WidgetTester tester) async { testWidgets('Material3 - Switch track color resolves in hovered/focused states', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredTrackColor = Color(0xFF000001);
const Color focusedTrackColor = Color(0xFF000002);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoveredTrackColor;
}
if (states.contains(MaterialState.focused)) {
return focusedTrackColor;
}
return Colors.transparent;
}
final MaterialStateProperty<Color> trackColor =
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch() {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
focusNode: focusNode,
autofocus: true,
value: true,
trackColor: trackColor,
onChanged: (_) { },
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: focusedTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Active enabled switch should match these colors',
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: hoveredTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Material2 - Switch thumb color is blended against surface color', (WidgetTester tester) async {
final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60); final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60);
final ThemeData theme = ThemeData.light(useMaterial3: false); final ThemeData theme = ThemeData.light(useMaterial3: false);
...@@ -1632,48 +2435,98 @@ void main() { ...@@ -1632,48 +2435,98 @@ void main() {
); );
}); });
testWidgets('Switch overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async { testWidgets('Material3 - Switch thumb color is blended against surface color', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch'); final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60);
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; final ThemeData theme = ThemeData(useMaterial3: true);
final ColorScheme colors = theme.colorScheme;
const Color activeThumbColor = Color(0xFF000000);
const Color inactiveThumbColor = Color(0xFF000010);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color focusOverlayColor = Color(0xFF000004);
const Color hoverColor = Color(0xFF000005);
const Color focusColor = Color(0xFF000006);
Color? getOverlayColor(Set<MaterialState> states) { Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) { if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) { return activeDisabledThumbColor;
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
} }
return null; return Colors.black;
} }
const double splashRadius = 24.0;
Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) { final MaterialStateProperty<Color> thumbColor =
return MaterialApp( MaterialStateColor.resolveWith(getThumbColor);
theme: theme,
home: Scaffold( Widget buildSwitch({required bool enabled, required bool active}) {
body: Switch( return Directionality(
focusNode: focusNode, textDirection: TextDirection.rtl,
autofocus: focused, child: Theme(
value: active, data: theme,
onChanged: (_) { }, child: Material(
thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) { child: Center(
if (states.contains(MaterialState.selected)) { child: Switch(
return activeThumbColor; thumbColor: thumbColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
final Color expectedThumbColor = Color.alphaBlend(activeDisabledThumbColor, theme.colorScheme.surface);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: expectedThumbColor),
reason: 'Active disabled thumb color should be blended on top of surface color',
);
});
testWidgets('Switch overlay color resolves in active/pressed/focused/hovered states', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color activeThumbColor = Color(0xFF000000);
const Color inactiveThumbColor = Color(0xFF000010);
const Color activePressedOverlayColor = Color(0xFF000001);
const Color inactivePressedOverlayColor = Color(0xFF000002);
const Color hoverOverlayColor = Color(0xFF000003);
const Color focusOverlayColor = Color(0xFF000004);
const Color hoverColor = Color(0xFF000005);
const Color focusColor = Color(0xFF000006);
Color? getOverlayColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
if (states.contains(MaterialState.selected)) {
return activePressedOverlayColor;
}
return inactivePressedOverlayColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverOverlayColor;
}
if (states.contains(MaterialState.focused)) {
return focusOverlayColor;
}
return null;
}
const double splashRadius = 24.0;
Widget buildSwitch({bool active = false, bool focused = false, bool useOverlay = true}) {
return MaterialApp(
theme: theme,
home: Scaffold(
body: Switch(
focusNode: focusNode,
autofocus: focused,
value: active,
onChanged: (_) { },
thumbColor: MaterialStateProperty.resolveWith<Color>((Set<MaterialState> states) {
if (states.contains(MaterialState.selected)) {
return activeThumbColor;
} }
return inactiveThumbColor; return inactiveThumbColor;
}), }),
...@@ -1983,7 +2836,7 @@ void main() { ...@@ -1983,7 +2836,7 @@ void main() {
}); });
}); });
group('Switch M3 tests', () { group('Switch M3 only tests', () {
testWidgets('M3 Switch has a 300-millisecond animation in total', (WidgetTester tester) async { testWidgets('M3 Switch has a 300-millisecond animation in total', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true); final ThemeData theme = ThemeData(useMaterial3: true);
bool value = false; bool value = false;
...@@ -2089,827 +2942,117 @@ void main() { ...@@ -2089,827 +2942,117 @@ void main() {
}); });
}, },
), ),
), ),
); );
}, },
),
),
),
);
expect(value, isFalse);
final Rect switchRect = tester.getRect(find.byType(Switch));
final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
await tester.pump();
await gesture.up();
await tester.pump();
// The value on y axis is greater than 1 when t > 0.375
// 300 * 0.375 = 112.5
await tester.pump(const Duration(milliseconds: 113));
final ToggleableStateMixin state = tester.state<ToggleableStateMixin>(
find.descendant(
of: find.byType(Switch),
matching: find.byWidgetPredicate(
(Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch',
),
),
);
expect(tester.hasRunningAnimations, true);
expect(state.position.value, greaterThan(1));
});
testWidgets('Switch has default colors when enabled - M3', (WidgetTester tester) async {
final ThemeData theme = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = theme.colorScheme;
bool value = false;
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
),
),
);
},
),
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: colors.outline), // thumb color
reason: 'Inactive enabled switch should match these colors',
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.onPrimary), // thumb color
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Inactive Switch has default colors when disabled - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
value: false,
onChanged: null,
),
),
),
),
));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)), // thumb color
reason: 'Inactive disabled switch should match these colors',
);
});
testWidgets('Active Switch has default colors when disabled - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true,
colorSchemeSeed: const Color(0xff6750a4),
brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
await tester.pumpWidget(MaterialApp(
theme: themeData,
home: const Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
value: true,
onChanged: null,
),
),
),
),
));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..save()
..rrect(
style: PaintingStyle.fill,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.surface), // thumb color
reason: 'Active disabled switch should match these colors',
);
});
testWidgets('Switch can be set color - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
bool value = false;
await tester.pumpWidget(
MaterialApp(
theme: themeData,
home: Directionality(
textDirection: TextDirection.rtl,
child: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Material(
child: Center(
child: Switch(
dragStartBehavior: DragStartBehavior.down,
value: value,
onChanged: (bool newValue) {
setState(() {
value = newValue;
});
},
activeColor: Colors.red[500],
activeTrackColor: Colors.green[500],
inactiveThumbColor: Colors.yellow[500],
inactiveTrackColor: Colors.blue[500],
),
),
);
},
),
),
),
);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: Colors.blue[500],
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
)
..rrect(color: Colors.yellow[500]), // thumb color
);
await tester.drag(find.byType(Switch), const Offset(-30.0, 0.0));
await tester.pump();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: Colors.green[500],
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: Colors.red[500]), // thumb color
);
});
testWidgets('Switch is focusable and has correct focus color - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: themeData,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
focusColor: Colors.orange[500],
autofocus: true,
focusNode: focusNode,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
// active, enabled switch
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500]),
);
// Check the false value: inactive enabled switch
value = false;
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.outline,
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500])
);
// Check what happens when disabled: inactive disabled switch.
value = false;
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isFalse);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
);
});
testWidgets('Switch can be hovered and has correct hover color - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
bool value = true;
Widget buildApp({bool enabled = true}) {
return MaterialApp(
theme: themeData,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
hoverColor: Colors.orange[500],
);
}),
),
),
);
}
// active enabled switch
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.onPrimary),
);
// Start hovering
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
await gesture.addPointer();
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpWidget(buildApp());
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: Colors.orange[500]),
);
// Check what happens for disabled active switch
await tester.pumpWidget(buildApp(enabled: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: colors.surface.withOpacity(1.0)),
);
});
testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true);
final ColorScheme colors = themeData.colorScheme;
Widget buildApp({bool enabled = true, bool value = true}) {
return MaterialApp(
theme: themeData,
home: Material(
child: Center(
child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
return Switch(
value: value,
onChanged: enabled ? (bool newValue) {
setState(() {
value = newValue;
});
} : null,
);
}),
),
),
);
}
await tester.pumpWidget(buildApp());
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(Material.of(tester.element(find.byType(Switch))),
paints..rrect(
color: colors.primary, // track color
style: PaintingStyle.fill,
)..rrect(
color: Colors.transparent, // track outline color
style: PaintingStyle.stroke,
)..rrect(color: colors.primaryContainer, rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))),
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildApp(value: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(Material.of(tester.element(find.byType(Switch))),
paints..rrect(
color: colors.surfaceVariant, // track color
style: PaintingStyle.fill
)..rrect(
color: colors.outline, // track outline color
style: PaintingStyle.stroke,
)..rrect(color: colors.onSurfaceVariant),
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildApp(enabled: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(Material.of(tester.element(find.byType(Switch))),
paints..rrect(
color: colors.onSurface.withOpacity(0.12), // track color
style: PaintingStyle.fill,
)..rrect(
color: Colors.transparent, // track outline color
style: PaintingStyle.stroke,
)..rrect(color: colors.surface.withOpacity(1.0)),
);
await tester.pumpWidget(Container());
await tester.pumpWidget(buildApp(enabled: false, value: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle();
expect(Material.of(tester.element(find.byType(Switch))),
paints..rrect(
color: colors.surfaceVariant.withOpacity(0.12), // track color
style: PaintingStyle.fill,
)..rrect(
color: colors.onSurface.withOpacity(0.12), // track outline color
style: PaintingStyle.stroke,
)..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
);
}, variant: TargetPlatformVariant.mobile());
testWidgets('Switch thumb color resolves in active/enabled states - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
const Color activeEnabledThumbColor = Color(0xFF000001);
const Color activeDisabledThumbColor = Color(0xFF000002);
const Color inactiveEnabledThumbColor = Color(0xFF000003);
const Color inactiveDisabledThumbColor = Color(0xFF000004);
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
if (states.contains(MaterialState.selected)) {
return activeDisabledThumbColor;
}
return inactiveDisabledThumbColor;
}
if (states.contains(MaterialState.selected)) {
return activeEnabledThumbColor;
}
return inactiveEnabledThumbColor;
}
final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
thumbColor: thumbColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: false));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect(
style: PaintingStyle.stroke,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(5.0, 9.0, 55.0, 39.0, const Radius.circular(16.0)),
)
..rrect(color: inactiveDisabledThumbColor),
reason: 'Inactive disabled switch should default track and custom thumb color',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: activeDisabledThumbColor),
reason: 'Active disabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: false));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.surfaceVariant,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: inactiveEnabledThumbColor),
reason: 'Inactive enabled switch should match these colors',
);
await tester.pumpWidget(buildSwitch(enabled: true, active: true));
await tester.pumpAndSettle();
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: activeEnabledThumbColor),
reason: 'Active enabled switch should match these colors',
);
});
testWidgets('Switch thumb color resolves in hovered/focused states - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final ColorScheme colors = themeData.colorScheme;
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredThumbColor = Color(0xFF000001);
const Color focusedThumbColor = Color(0xFF000002);
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoveredThumbColor;
}
if (states.contains(MaterialState.focused)) {
return focusedThumbColor;
}
return Colors.transparent;
}
final MaterialStateProperty<Color> thumbColor = MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitch() {
return MaterialApp(
theme: themeData,
home: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
focusNode: focusNode,
autofocus: true,
value: true,
thumbColor: thumbColor,
onChanged: (_) { },
),
),
), ),
), ),
); ),
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
style: PaintingStyle.fill,
color: colors.primary,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..circle(color: colors.primary.withOpacity(0.12))
..rrect(color: focusedThumbColor),
reason: 'active enabled switch should default track and custom thumb color',
); );
expect(value, isFalse);
// Start hovering final Rect switchRect = tester.getRect(find.byType(Switch));
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); final TestGesture gesture = await tester.startGesture(switchRect.centerLeft);
await gesture.addPointer(); await tester.pump();
await gesture.moveTo(tester.getCenter(find.byType(Switch))); await gesture.up();
await tester.pumpAndSettle(); await tester.pump();
// The value on y axis is greater than 1 when t > 0.375
expect( // 300 * 0.375 = 112.5
Material.of(tester.element(find.byType(Switch))), await tester.pump(const Duration(milliseconds: 113));
paints final ToggleableStateMixin state = tester.state<ToggleableStateMixin>(
..rrect( find.descendant(
style: PaintingStyle.fill, of: find.byType(Switch),
color: colors.primary, matching: find.byWidgetPredicate(
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), (Widget widget) => widget.runtimeType.toString() == '_MaterialSwitch',
) ),
..circle(color: colors.primary.withOpacity(0.08)) ),
..rrect(color: hoveredThumbColor),
reason: 'active enabled switch should default track and custom thumb color',
); );
expect(tester.hasRunningAnimations, true);
expect(state.position.value, greaterThan(1));
}); });
testWidgets('Track color resolves in active/enabled states - M3', (WidgetTester tester) async { testWidgets('Switch thumb shows correct pressed color - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light); final ThemeData themeData = ThemeData(useMaterial3: true);
const Color activeEnabledTrackColor = Color(0xFF000001); final ColorScheme colors = themeData.colorScheme;
const Color activeDisabledTrackColor = Color(0xFF000002); Widget buildApp({bool enabled = true, bool value = true}) {
const Color inactiveEnabledTrackColor = Color(0xFF000003); return MaterialApp(
const Color inactiveDisabledTrackColor = Color(0xFF000004); theme: themeData,
home: Material(
Color getTrackColor(Set<MaterialState> states) { child: Center(
if (states.contains(MaterialState.disabled)) { child: StatefulBuilder(builder: (BuildContext context, StateSetter setState) {
if (states.contains(MaterialState.selected)) { return Switch(
return activeDisabledTrackColor; value: value,
} onChanged: enabled ? (bool newValue) {
return inactiveDisabledTrackColor; setState(() {
} value = newValue;
if (states.contains(MaterialState.selected)) { });
return activeEnabledTrackColor; } : null,
} );
return inactiveEnabledTrackColor; }),
}
final MaterialStateProperty<Color> trackColor =
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
trackColor: trackColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
), ),
), ),
); );
} }
await tester.pumpWidget(buildSwitch(enabled: false, active: false)); await tester.pumpWidget(buildApp());
await tester.press(find.byType(Switch));
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: inactiveDisabledTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Inactive disabled switch track should use this value',
);
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(Material.of(tester.element(find.byType(Switch))),
Material.of(tester.element(find.byType(Switch))), paints..rrect(
paints color: colors.primary, // track color
..rrect( style: PaintingStyle.fill,
color: activeDisabledTrackColor, )..rrect(
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), color: Colors.transparent, // track outline color
), style: PaintingStyle.stroke,
reason: 'Active disabled switch should match these colors', )..rrect(color: colors.primaryContainer, rrect: RRect.fromLTRBR(26.0, 10.0, 54.0, 38.0, const Radius.circular(14.0))),
); );
await tester.pumpWidget(buildSwitch(enabled: true, active: false)); await tester.pumpWidget(Container());
await tester.pumpWidget(buildApp(value: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(Material.of(tester.element(find.byType(Switch))),
Material.of(tester.element(find.byType(Switch))), paints..rrect(
paints color: colors.surfaceVariant, // track color
..rrect( style: PaintingStyle.fill
color: inactiveEnabledTrackColor, )..rrect(
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), color: colors.outline, // track outline color
), style: PaintingStyle.stroke,
reason: 'Inactive enabled switch should match these colors', )..rrect(color: colors.onSurfaceVariant),
); );
await tester.pumpWidget(buildSwitch(enabled: true, active: true)); await tester.pumpWidget(Container());
await tester.pumpWidget(buildApp(enabled: false));
await tester.press(find.byType(Switch));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(Material.of(tester.element(find.byType(Switch))),
Material.of(tester.element(find.byType(Switch))), paints..rrect(
paints color: colors.onSurface.withOpacity(0.12), // track color
..rrect( style: PaintingStyle.fill,
color: activeEnabledTrackColor, )..rrect(
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), color: Colors.transparent, // track outline color
), style: PaintingStyle.stroke,
reason: 'Active enabled switch should match these colors', )..rrect(color: colors.surface.withOpacity(1.0)),
);
});
testWidgets('Switch track color resolves in hovered/focused states - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData(useMaterial3: true, colorSchemeSeed: const Color(0xff6750a4), brightness: Brightness.light);
final FocusNode focusNode = FocusNode(debugLabel: 'Switch');
tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
const Color hoveredTrackColor = Color(0xFF000001);
const Color focusedTrackColor = Color(0xFF000002);
Color getTrackColor(Set<MaterialState> states) {
if (states.contains(MaterialState.hovered)) {
return hoveredTrackColor;
}
if (states.contains(MaterialState.focused)) {
return focusedTrackColor;
}
return Colors.transparent;
}
final MaterialStateProperty<Color> trackColor =
MaterialStateColor.resolveWith(getTrackColor);
Widget buildSwitch() {
return Theme(
data: themeData,
child: Directionality(
textDirection: TextDirection.rtl,
child: Material(
child: Center(
child: Switch(
focusNode: focusNode,
autofocus: true,
value: true,
trackColor: trackColor,
onChanged: (_) { },
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch());
await tester.pumpAndSettle();
expect(focusNode.hasPrimaryFocus, isTrue);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: focusedTrackColor,
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
),
reason: 'Active enabled switch should match these colors',
); );
// Start hovering await tester.pumpWidget(Container());
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await tester.pumpWidget(buildApp(enabled: false, value: false));
await gesture.addPointer(); await tester.press(find.byType(Switch));
await gesture.moveTo(tester.getCenter(find.byType(Switch)));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(Material.of(tester.element(find.byType(Switch))),
Material.of(tester.element(find.byType(Switch))), paints..rrect(
paints color: colors.surfaceVariant.withOpacity(0.12), // track color
..rrect( style: PaintingStyle.fill,
color: hoveredTrackColor, )..rrect(
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)), color: colors.onSurface.withOpacity(0.12), // track outline color
), style: PaintingStyle.stroke,
reason: 'Active enabled switch should match these colors', )..rrect(color: Color.alphaBlend(colors.onSurface.withOpacity(0.38), colors.surface)),
); );
}); }, variant: TargetPlatformVariant.mobile());
testWidgets('Track outline color resolves in active/enabled states', (WidgetTester tester) async { testWidgets('Track outline color resolves in active/enabled states', (WidgetTester tester) async {
const Color activeEnabledTrackOutlineColor = Color(0xFF000001); const Color activeEnabledTrackOutlineColor = Color(0xFF000001);
...@@ -3181,56 +3324,6 @@ void main() { ...@@ -3181,56 +3324,6 @@ void main() {
); );
}); });
testWidgets('Switch thumb color is blended against surface color - M3', (WidgetTester tester) async {
final Color activeDisabledThumbColor = Colors.blue.withOpacity(.60);
final ThemeData theme = ThemeData.light(useMaterial3: true);
final ColorScheme colors = theme.colorScheme;
Color getThumbColor(Set<MaterialState> states) {
if (states.contains(MaterialState.disabled)) {
return activeDisabledThumbColor;
}
return Colors.black;
}
final MaterialStateProperty<Color> thumbColor =
MaterialStateColor.resolveWith(getThumbColor);
Widget buildSwitch({required bool enabled, required bool active}) {
return Directionality(
textDirection: TextDirection.rtl,
child: Theme(
data: theme,
child: Material(
child: Center(
child: Switch(
thumbColor: thumbColor,
value: active,
onChanged: enabled ? (_) { } : null,
),
),
),
),
);
}
await tester.pumpWidget(buildSwitch(enabled: false, active: true));
final Color expectedThumbColor = Color.alphaBlend(activeDisabledThumbColor, theme.colorScheme.surface);
expect(
Material.of(tester.element(find.byType(Switch))),
paints
..rrect(
color: colors.onSurface.withOpacity(0.12),
rrect: RRect.fromLTRBR(4.0, 8.0, 56.0, 40.0, const Radius.circular(16.0)),
)
..rrect()
..rrect(color: expectedThumbColor),
reason: 'Active disabled thumb color should be blended on top of surface color',
);
});
testWidgets('Switch can set icon - M3', (WidgetTester tester) async { testWidgets('Switch can set icon - M3', (WidgetTester tester) async {
final ThemeData themeData = ThemeData( final ThemeData themeData = ThemeData(
useMaterial3: true, useMaterial3: true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment