// Copyright 2014 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { test('SwitchThemeData copyWith, ==, hashCode basics', () { expect(const SwitchThemeData(), const SwitchThemeData().copyWith()); expect(const SwitchThemeData().hashCode, const SwitchThemeData().copyWith().hashCode); }); test('SwitchThemeData lerp special cases', () { const data = SwitchThemeData(); expect(identical(SwitchThemeData.lerp(data, data, 0.5), data), true); }); test('SwitchThemeData defaults', () { const themeData = SwitchThemeData(); expect(themeData.thumbColor, null); expect(themeData.trackColor, null); expect(themeData.trackOutlineColor, null); expect(themeData.trackOutlineWidth, null); expect(themeData.mouseCursor, null); expect(themeData.materialTapTargetSize, null); expect(themeData.overlayColor, null); expect(themeData.splashRadius, null); expect(themeData.thumbIcon, null); expect(themeData.padding, null); const theme = SwitchTheme(data: SwitchThemeData(), child: SizedBox()); expect(theme.data.thumbColor, null); expect(theme.data.trackColor, null); expect(theme.data.trackOutlineColor, null); expect(theme.data.trackOutlineWidth, null); expect(theme.data.mouseCursor, null); expect(theme.data.materialTapTargetSize, null); expect(theme.data.overlayColor, null); expect(theme.data.splashRadius, null); expect(theme.data.thumbIcon, null); expect(theme.data.padding, null); }); testWidgets('Default SwitchThemeData debugFillProperties', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const SwitchThemeData().debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, []); }); testWidgets('SwitchThemeData implements debugFillProperties', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const SwitchThemeData( thumbColor: MaterialStatePropertyAll(Color(0xfffffff0)), trackColor: MaterialStatePropertyAll(Color(0xfffffff1)), trackOutlineColor: MaterialStatePropertyAll(Color(0xfffffff3)), trackOutlineWidth: MaterialStatePropertyAll(6.0), mouseCursor: MaterialStatePropertyAll(SystemMouseCursors.click), materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, overlayColor: MaterialStatePropertyAll(Color(0xfffffff2)), splashRadius: 1.0, thumbIcon: MaterialStatePropertyAll(Icon(IconData(123))), padding: EdgeInsets.all(4.0), ).debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description[0], 'thumbColor: WidgetStatePropertyAll(${const Color(0xfffffff0)})'); expect(description[1], 'trackColor: WidgetStatePropertyAll(${const Color(0xfffffff1)})'); expect(description[2], 'trackOutlineColor: WidgetStatePropertyAll(${const Color(0xfffffff3)})'); expect(description[3], 'trackOutlineWidth: WidgetStatePropertyAll(6.0)'); expect(description[4], 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap'); expect(description[5], 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(click))'); expect(description[6], 'overlayColor: WidgetStatePropertyAll(${const Color(0xfffffff2)})'); expect(description[7], 'splashRadius: 1.0'); expect(description[8], 'thumbIcon: WidgetStatePropertyAll(Icon(IconData(U+0007B)))'); expect(description[9], 'padding: EdgeInsets.all(4.0)'); }); testWidgets('Material2 - Switch is themeable', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const defaultThumbColor = Color(0xfffffff0); const selectedThumbColor = Color(0xfffffff1); const defaultTrackColor = Color(0xfffffff2); const selectedTrackColor = Color(0xfffffff3); const defaultTrackOutlineColor = Color(0xfffffff4); const selectedTrackOutlineColor = Color(0xfffffff5); const defaultTrackOutlineWidth = 3.0; const selectedTrackOutlineWidth = 6.0; const MouseCursor mouseCursor = SystemMouseCursors.text; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const focusOverlayColor = Color(0xfffffff4); const hoverOverlayColor = Color(0xfffffff5); const splashRadius = 1.0; const icon1 = Icon(Icons.check); const icon2 = Icon(Icons.close); final themeData = ThemeData( useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedThumbColor; } return defaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackColor; } return defaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackOutlineColor; } return defaultTrackOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackOutlineWidth; } return defaultTrackOutlineWidth; }), mouseCursor: const MaterialStatePropertyAll(mouseCursor), materialTapTargetSize: materialTapTargetSize, overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return focusOverlayColor; } if (states.contains(WidgetState.hovered)) { return hoverOverlayColor; } return null; }), splashRadius: splashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return icon1; } return icon2; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( dragStartBehavior: DragStartBehavior.down, value: selected, onChanged: (bool value) {}, autofocus: autofocus, ), ), ); } // Switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultTrackOutlineColor, strokeWidth: defaultTrackOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: defaultThumbColor), ); // Size from MaterialTapTargetSize.shrinkWrap. expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedTrackOutlineColor, strokeWidth: selectedTrackOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: selectedThumbColor), ); // Switch with hover. await tester.pumpWidget(buildSwitch()); await _pointGestureToSwitch(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getSwitchMaterial(tester), paints..circle(color: hoverOverlayColor)); // Switch with focus. await tester.pumpWidget(buildSwitch(autofocus: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius), ); }); testWidgets('Material3 - Switch is themeable', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const defaultThumbColor = Color(0xfffffff0); const selectedThumbColor = Color(0xfffffff1); const defaultTrackColor = Color(0xfffffff2); const selectedTrackColor = Color(0xfffffff3); const defaultTrackOutlineColor = Color(0xfffffff4); const selectedTrackOutlineColor = Color(0xfffffff5); const defaultTrackOutlineWidth = 3.0; const selectedTrackOutlineWidth = 6.0; const MouseCursor mouseCursor = SystemMouseCursors.text; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const focusOverlayColor = Color(0xfffffff4); const hoverOverlayColor = Color(0xfffffff5); const splashRadius = 1.0; const icon1 = Icon(Icons.check); const icon2 = Icon(Icons.close); final themeData = ThemeData( switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedThumbColor; } return defaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackColor; } return defaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackOutlineColor; } return defaultTrackOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackOutlineWidth; } return defaultTrackOutlineWidth; }), mouseCursor: const MaterialStatePropertyAll(mouseCursor), materialTapTargetSize: materialTapTargetSize, overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return focusOverlayColor; } if (states.contains(WidgetState.hovered)) { return hoverOverlayColor; } return null; }), splashRadius: splashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return icon1; } return icon2; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( dragStartBehavior: DragStartBehavior.down, value: selected, onChanged: (bool value) {}, autofocus: autofocus, ), ), ); } // Switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultTrackOutlineColor, strokeWidth: defaultTrackOutlineWidth) ..rrect(color: defaultThumbColor) ..paragraph(), ); // Size from MaterialTapTargetSize.shrinkWrap. expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedTrackOutlineColor, strokeWidth: selectedTrackOutlineWidth) ..rrect(color: selectedThumbColor) ..paragraph(), ); // Switch with hover. await tester.pumpWidget(buildSwitch()); await _pointGestureToSwitch(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getSwitchMaterial(tester), paints..circle(color: hoverOverlayColor)); // Switch with focus. await tester.pumpWidget(buildSwitch(autofocus: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius), ); }); testWidgets('Material2 - Switch properties are taken over the theme values', ( WidgetTester tester, ) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const themeDefaultThumbColor = Color(0xfffffff0); const themeSelectedThumbColor = Color(0xfffffff1); const themeDefaultTrackColor = Color(0xfffffff2); const themeSelectedTrackColor = Color(0xfffffff3); const themeDefaultOutlineColor = Color(0xfffffff6); const themeSelectedOutlineColor = Color(0xfffffff7); const themeDefaultOutlineWidth = 5.0; const themeSelectedOutlineWidth = 7.0; const MouseCursor themeMouseCursor = SystemMouseCursors.click; const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; const themeFocusOverlayColor = Color(0xfffffff4); const themeHoverOverlayColor = Color(0xfffffff5); const themeSplashRadius = 1.0; const defaultThumbColor = Color(0xffffff0f); const selectedThumbColor = Color(0xffffff1f); const defaultTrackColor = Color(0xffffff2f); const selectedTrackColor = Color(0xffffff3f); const defaultOutlineColor = Color(0xffffff6f); const selectedOutlineColor = Color(0xffffff7f); const defaultOutlineWidth = 6.0; const selectedOutlineWidth = 8.0; const MouseCursor mouseCursor = SystemMouseCursors.text; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const focusColor = Color(0xffffff4f); const hoverColor = Color(0xffffff5f); const splashRadius = 2.0; final themeData = ThemeData( useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedThumbColor; } return themeDefaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedTrackColor; } return themeDefaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedOutlineColor; } return themeDefaultOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedOutlineWidth; } return themeDefaultOutlineWidth; }), mouseCursor: const MaterialStatePropertyAll(themeMouseCursor), materialTapTargetSize: themeMaterialTapTargetSize, overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return themeFocusOverlayColor; } if (states.contains(WidgetState.hovered)) { return themeHoverOverlayColor; } return null; }), splashRadius: themeSplashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return null; } return null; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( value: selected, onChanged: (bool value) {}, autofocus: autofocus, thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedThumbColor; } return defaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackColor; } return defaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedOutlineColor; } return defaultOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedOutlineWidth; } return defaultOutlineWidth; }), mouseCursor: mouseCursor, materialTapTargetSize: materialTapTargetSize, focusColor: focusColor, hoverColor: hoverColor, splashRadius: splashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return const Icon(Icons.add); } return const Icon(Icons.access_alarm); }), ), ), ); } // Switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultOutlineColor, strokeWidth: defaultOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: defaultThumbColor), ); // Size from MaterialTapTargetSize.shrinkWrap. expect(tester.getSize(find.byType(Switch)), const Size(59.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedOutlineColor, strokeWidth: selectedOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: selectedThumbColor), ); // Switch with hover. await tester.pumpWidget(buildSwitch()); await _pointGestureToSwitch(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getSwitchMaterial(tester), paints..circle(color: hoverColor)); // Switch with focus. await tester.pumpWidget(buildSwitch(autofocus: true)); await tester.pumpAndSettle(); expect(_getSwitchMaterial(tester), paints..circle(color: focusColor, radius: splashRadius)); }); testWidgets('Material3 - Switch properties are taken over the theme values', ( WidgetTester tester, ) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const themeDefaultThumbColor = Color(0xfffffff0); const themeSelectedThumbColor = Color(0xfffffff1); const themeDefaultTrackColor = Color(0xfffffff2); const themeSelectedTrackColor = Color(0xfffffff3); const themeDefaultOutlineColor = Color(0xfffffff6); const themeSelectedOutlineColor = Color(0xfffffff7); const themeDefaultOutlineWidth = 5.0; const themeSelectedOutlineWidth = 7.0; const MouseCursor themeMouseCursor = SystemMouseCursors.click; const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; const themeFocusOverlayColor = Color(0xfffffff4); const themeHoverOverlayColor = Color(0xfffffff5); const themeSplashRadius = 1.0; const defaultThumbColor = Color(0xffffff0f); const selectedThumbColor = Color(0xffffff1f); const defaultTrackColor = Color(0xffffff2f); const selectedTrackColor = Color(0xffffff3f); const defaultOutlineColor = Color(0xffffff6f); const selectedOutlineColor = Color(0xffffff7f); const defaultOutlineWidth = 6.0; const selectedOutlineWidth = 8.0; const MouseCursor mouseCursor = SystemMouseCursors.text; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const focusColor = Color(0xffffff4f); const hoverColor = Color(0xffffff5f); const splashRadius = 2.0; final themeData = ThemeData( switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedThumbColor; } return themeDefaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedTrackColor; } return themeDefaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedOutlineColor; } return themeDefaultOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedOutlineWidth; } return themeDefaultOutlineWidth; }), mouseCursor: const MaterialStatePropertyAll(themeMouseCursor), materialTapTargetSize: themeMaterialTapTargetSize, overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return themeFocusOverlayColor; } if (states.contains(WidgetState.hovered)) { return themeHoverOverlayColor; } return null; }), splashRadius: themeSplashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return null; } return null; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( value: selected, onChanged: (bool value) {}, autofocus: autofocus, thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedThumbColor; } return defaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedTrackColor; } return defaultTrackColor; }), trackOutlineColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedOutlineColor; } return defaultOutlineColor; }), trackOutlineWidth: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedOutlineWidth; } return defaultOutlineWidth; }), mouseCursor: mouseCursor, materialTapTargetSize: materialTapTargetSize, focusColor: focusColor, hoverColor: hoverColor, splashRadius: splashRadius, thumbIcon: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return const Icon(Icons.add); } return const Icon(Icons.access_alarm); }), ), ), ); } // Switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect(color: defaultOutlineColor, strokeWidth: defaultOutlineWidth) ..rrect(color: defaultThumbColor) ..paragraph(offset: const Offset(12, 12)), ); // Size from MaterialTapTargetSize.shrinkWrap. expect(tester.getSize(find.byType(Switch)), const Size(60.0, 40.0)); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect(color: selectedOutlineColor, strokeWidth: selectedOutlineWidth) ..rrect(color: selectedThumbColor), ); // Switch with hover. await tester.pumpWidget(buildSwitch()); await _pointGestureToSwitch(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getSwitchMaterial(tester), paints..circle(color: hoverColor)); // Switch with focus. await tester.pumpWidget(buildSwitch(autofocus: true)); await tester.pumpAndSettle(); expect(_getSwitchMaterial(tester), paints..circle(color: focusColor, radius: splashRadius)); }); testWidgets('Material2 - Switch active and inactive properties are taken over the theme values', ( WidgetTester tester, ) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const themeDefaultThumbColor = Color(0xfffffff0); const themeSelectedThumbColor = Color(0xfffffff1); const themeDefaultTrackColor = Color(0xfffffff2); const themeSelectedTrackColor = Color(0xfffffff3); const defaultThumbColor = Color(0xffffff0f); const selectedThumbColor = Color(0xffffff1f); const defaultTrackColor = Color(0xffffff2f); const selectedTrackColor = Color(0xffffff3f); final themeData = ThemeData( useMaterial3: false, switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedThumbColor; } return themeDefaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedTrackColor; } return themeDefaultTrackColor; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( value: selected, onChanged: (bool value) {}, autofocus: autofocus, activeColor: selectedThumbColor, inactiveThumbColor: defaultThumbColor, activeTrackColor: selectedTrackColor, inactiveTrackColor: defaultTrackColor, ), ), ); } // Unselected switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect() ..rrect() ..rrect() ..rrect() ..rrect(color: defaultThumbColor), ); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect() ..rrect() ..rrect() ..rrect() ..rrect(color: selectedThumbColor), ); }); testWidgets('Material3 - Switch active and inactive properties are taken over the theme values', ( WidgetTester tester, ) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const themeDefaultThumbColor = Color(0xfffffff0); const themeSelectedThumbColor = Color(0xfffffff1); const themeDefaultTrackColor = Color(0xfffffff2); const themeSelectedTrackColor = Color(0xfffffff3); const defaultThumbColor = Color(0xffffff0f); const selectedThumbColor = Color(0xffffff1f); const defaultTrackColor = Color(0xffffff2f); const selectedTrackColor = Color(0xffffff3f); final themeData = ThemeData( switchTheme: SwitchThemeData( thumbColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedThumbColor; } return themeDefaultThumbColor; }), trackColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedTrackColor; } return themeDefaultTrackColor; }), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch( value: selected, onChanged: (bool value) {}, autofocus: autofocus, activeColor: selectedThumbColor, inactiveThumbColor: defaultThumbColor, activeTrackColor: selectedTrackColor, inactiveTrackColor: defaultTrackColor, ), ), ); } // Unselected switch. await tester.pumpWidget(buildSwitch()); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: defaultTrackColor) ..rrect(color: themeData.colorScheme.outline) ..rrect(color: defaultThumbColor), ); // Selected switch. await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: selectedTrackColor) ..rrect() ..rrect(color: selectedThumbColor), ); }); testWidgets('Material2 - Switch theme overlay color resolves in active/pressed states', ( WidgetTester tester, ) async { const activePressedOverlayColor = Color(0xFF000001); const inactivePressedOverlayColor = Color(0xFF000002); Color? getOverlayColor(Set states) { if (states.contains(WidgetState.pressed)) { if (states.contains(WidgetState.selected)) { return activePressedOverlayColor; } return inactivePressedOverlayColor; } return null; } const splashRadius = 24.0; final themeData = ThemeData( useMaterial3: false, switchTheme: SwitchThemeData( overlayColor: WidgetStateProperty.resolveWith(getOverlayColor), splashRadius: splashRadius, ), ); Widget buildSwitch({required bool active}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch(value: active, onChanged: (_) {}), ), ); } await tester.pumpWidget(buildSwitch(active: false)); await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect() ..circle(color: inactivePressedOverlayColor, radius: splashRadius), reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor', ); await tester.pumpWidget(buildSwitch(active: true)); await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect() ..circle(color: activePressedOverlayColor, radius: splashRadius), reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor', ); }); testWidgets('Material3 - Switch theme overlay color resolves in active/pressed states', ( WidgetTester tester, ) async { const activePressedOverlayColor = Color(0xFF000001); const inactivePressedOverlayColor = Color(0xFF000002); Color? getOverlayColor(Set states) { if (states.contains(WidgetState.pressed)) { if (states.contains(WidgetState.selected)) { return activePressedOverlayColor; } return inactivePressedOverlayColor; } return null; } const splashRadius = 24.0; final themeData = ThemeData( switchTheme: SwitchThemeData( overlayColor: WidgetStateProperty.resolveWith(getOverlayColor), splashRadius: splashRadius, ), ); Widget buildSwitch({required bool active}) { return MaterialApp( theme: themeData, home: Scaffold( body: Switch(value: active, onChanged: (_) {}), ), ); } await tester.pumpWidget(buildSwitch(active: false)); await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), (paints ..rrect() ..rrect()) ..circle(color: inactivePressedOverlayColor, radius: splashRadius), reason: 'Inactive pressed Switch should have overlay color: $inactivePressedOverlayColor', ); await tester.pumpWidget(buildSwitch(active: true)); await tester.press(find.byType(Switch)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect() ..circle(color: activePressedOverlayColor, radius: splashRadius), reason: 'Active pressed Switch should have overlay color: $activePressedOverlayColor', ); }); testWidgets('Material2 - Local SwitchTheme can override global SwitchTheme', ( WidgetTester tester, ) async { const globalThemeThumbColor = Color(0xfffffff1); const globalThemeTrackColor = Color(0xfffffff2); const globalThemeOutlineColor = Color(0xfffffff3); const globalThemeOutlineWidth = 6.0; const localThemeThumbColor = Color(0xffff0000); const localThemeTrackColor = Color(0xffff0000); const localThemeOutlineColor = Color(0xffff0000); const localThemeOutlineWidth = 4.0; final themeData = ThemeData( useMaterial3: false, switchTheme: const SwitchThemeData( thumbColor: MaterialStatePropertyAll(globalThemeThumbColor), trackColor: MaterialStatePropertyAll(globalThemeTrackColor), trackOutlineColor: MaterialStatePropertyAll(globalThemeOutlineColor), trackOutlineWidth: MaterialStatePropertyAll(globalThemeOutlineWidth), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: SwitchTheme( data: const SwitchThemeData( thumbColor: MaterialStatePropertyAll(localThemeThumbColor), trackColor: MaterialStatePropertyAll(localThemeTrackColor), trackOutlineColor: MaterialStatePropertyAll(localThemeOutlineColor), trackOutlineWidth: MaterialStatePropertyAll(localThemeOutlineWidth), ), child: Switch(value: selected, onChanged: (bool value) {}, autofocus: autofocus), ), ), ); } await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: localThemeTrackColor) ..rrect(color: localThemeOutlineColor, strokeWidth: localThemeOutlineWidth) ..rrect() ..rrect() ..rrect() ..rrect(color: localThemeThumbColor), ); }); testWidgets('Material3 - Local SwitchTheme can override global SwitchTheme', ( WidgetTester tester, ) async { const globalThemeThumbColor = Color(0xfffffff1); const globalThemeTrackColor = Color(0xfffffff2); const globalThemeOutlineColor = Color(0xfffffff3); const globalThemeOutlineWidth = 6.0; const localThemeThumbColor = Color(0xffff0000); const localThemeTrackColor = Color(0xffff0000); const localThemeOutlineColor = Color(0xffff0000); const localThemeOutlineWidth = 4.0; final themeData = ThemeData( switchTheme: const SwitchThemeData( thumbColor: MaterialStatePropertyAll(globalThemeThumbColor), trackColor: MaterialStatePropertyAll(globalThemeTrackColor), trackOutlineColor: MaterialStatePropertyAll(globalThemeOutlineColor), trackOutlineWidth: MaterialStatePropertyAll(globalThemeOutlineWidth), ), ); Widget buildSwitch({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: themeData, home: Scaffold( body: SwitchTheme( data: const SwitchThemeData( thumbColor: MaterialStatePropertyAll(localThemeThumbColor), trackColor: MaterialStatePropertyAll(localThemeTrackColor), trackOutlineColor: MaterialStatePropertyAll(localThemeOutlineColor), trackOutlineWidth: MaterialStatePropertyAll(localThemeOutlineWidth), ), child: Switch(value: selected, onChanged: (bool value) {}, autofocus: autofocus), ), ), ); } await tester.pumpWidget(buildSwitch(selected: true)); await tester.pumpAndSettle(); expect( _getSwitchMaterial(tester), paints ..rrect(color: localThemeTrackColor) ..rrect(color: localThemeOutlineColor, strokeWidth: localThemeOutlineWidth) ..rrect(color: localThemeThumbColor), ); }); testWidgets('SwitchTheme padding is respected', (WidgetTester tester) async { Widget buildSwitch({EdgeInsets? padding}) { return MaterialApp( theme: ThemeData(switchTheme: SwitchThemeData(padding: padding)), home: Scaffold( body: Center(child: Switch(value: true, onChanged: (_) {})), ), ); } await tester.pumpWidget(buildSwitch()); expect(tester.getSize(find.byType(Switch)), const Size(60.0, 48.0)); await tester.pumpWidget(buildSwitch(padding: EdgeInsets.zero)); await tester.pumpAndSettle(); expect(tester.getSize(find.byType(Switch)), const Size(52.0, 48.0)); await tester.pumpWidget(buildSwitch(padding: const EdgeInsets.all(4.0))); await tester.pumpAndSettle(); expect(tester.getSize(find.byType(Switch)), const Size(60.0, 56.0)); }); } Future _pointGestureToSwitch(WidgetTester tester) async { final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse); await gesture.addPointer(); addTearDown(gesture.removePointer); await gesture.moveTo(tester.getCenter(find.byType(Switch))); } MaterialInkController? _getSwitchMaterial(WidgetTester tester) { return Material.of(tester.element(find.byType(Switch))); }