// 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('CheckboxThemeData copyWith, ==, hashCode basics', () { expect(const CheckboxThemeData(), const CheckboxThemeData().copyWith()); expect(const CheckboxThemeData().hashCode, const CheckboxThemeData().copyWith().hashCode); }); test('CheckboxThemeData lerp special cases', () { expect(CheckboxThemeData.lerp(null, null, 0), const CheckboxThemeData()); const data = CheckboxThemeData(); expect(identical(CheckboxThemeData.lerp(data, data, 0.5), data), true); }); test('CheckboxThemeData defaults', () { const themeData = CheckboxThemeData(); expect(themeData.mouseCursor, null); expect(themeData.fillColor, null); expect(themeData.checkColor, null); expect(themeData.overlayColor, null); expect(themeData.splashRadius, null); expect(themeData.materialTapTargetSize, null); expect(themeData.visualDensity, null); const theme = CheckboxTheme(data: CheckboxThemeData(), child: SizedBox()); expect(theme.data.mouseCursor, null); expect(theme.data.fillColor, null); expect(theme.data.checkColor, null); expect(theme.data.overlayColor, null); expect(theme.data.splashRadius, null); expect(theme.data.materialTapTargetSize, null); expect(theme.data.visualDensity, null); }); testWidgets('Default CheckboxThemeData debugFillProperties', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const CheckboxThemeData().debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, []); }); testWidgets('CheckboxThemeData implements debugFillProperties', (WidgetTester tester) async { final builder = DiagnosticPropertiesBuilder(); const CheckboxThemeData( mouseCursor: MaterialStatePropertyAll(SystemMouseCursors.click), fillColor: MaterialStatePropertyAll(Color(0xfffffff0)), checkColor: MaterialStatePropertyAll(Color(0xfffffff1)), overlayColor: MaterialStatePropertyAll(Color(0xfffffff2)), splashRadius: 1.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.standard, ).debugFillProperties(builder); final List description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect( description, equalsIgnoringHashCodes([ 'mouseCursor: WidgetStatePropertyAll(SystemMouseCursor(click))', 'fillColor: WidgetStatePropertyAll(${const Color(0xfffffff0)})', 'checkColor: WidgetStatePropertyAll(${const Color(0xfffffff1)})', 'overlayColor: WidgetStatePropertyAll(${const Color(0xfffffff2)})', 'splashRadius: 1.0', 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap', 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)', ]), ); }); testWidgets('Checkbox is themeable', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const MouseCursor mouseCursor = SystemMouseCursors.text; const defaultFillColor = Color(0xfffffff0); const selectedFillColor = Color(0xfffffff1); const defaultCheckColor = Color(0xfffffff2); const focusedCheckColor = Color(0xfffffff3); const focusOverlayColor = Color(0xfffffff4); const hoverOverlayColor = Color(0xfffffff5); const splashRadius = 1.0; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const visualDensity = VisualDensity(vertical: 1.0, horizontal: 1.0); Widget buildCheckbox({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( mouseCursor: const MaterialStatePropertyAll(mouseCursor), fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedFillColor; } return defaultFillColor; }), checkColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return focusedCheckColor; } return defaultCheckColor; }), overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return focusOverlayColor; } if (states.contains(WidgetState.hovered)) { return hoverOverlayColor; } return null; }), splashRadius: splashRadius, materialTapTargetSize: materialTapTargetSize, visualDensity: visualDensity, ), ), home: Scaffold( body: Checkbox(onChanged: (bool? value) {}, value: selected, autofocus: autofocus), ), ); } // Checkbox. await tester.pumpWidget(buildCheckbox()); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: defaultFillColor)); // Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity. expect( tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment, ); // Selected checkbox. await tester.pumpWidget(buildCheckbox(selected: true)); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: selectedFillColor)); expect( _getCheckboxMaterial(tester), paints ..path(color: selectedFillColor) ..path(color: defaultCheckColor), ); // Checkbox with hover. await tester.pumpWidget(buildCheckbox()); await _pointGestureToCheckbox(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getCheckboxMaterial(tester), paints..circle(color: hoverOverlayColor)); // Checkbox with focus. await tester.pumpWidget(buildCheckbox(autofocus: true, selected: true)); await tester.pumpAndSettle(); expect( _getCheckboxMaterial(tester), paints..circle(color: focusOverlayColor, radius: splashRadius), ); expect( _getCheckboxMaterial(tester), paints ..path(color: selectedFillColor) ..path(color: focusedCheckColor), ); }); testWidgets('Checkbox properties are taken over the theme values', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const MouseCursor themeMouseCursor = SystemMouseCursors.click; const themeDefaultFillColor = Color(0xfffffff0); const themeSelectedFillColor = Color(0xfffffff1); const themeCheckColor = Color(0xfffffff2); const themeFocusOverlayColor = Color(0xfffffff3); const themeHoverOverlayColor = Color(0xfffffff4); const themeSplashRadius = 1.0; const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; const VisualDensity themeVisualDensity = VisualDensity.standard; const MouseCursor mouseCursor = SystemMouseCursors.text; const defaultFillColor = Color(0xfffffff5); const selectedFillColor = Color(0xfffffff6); const checkColor = Color(0xfffffff7); const focusColor = Color(0xfffffff8); const hoverColor = Color(0xfffffff9); const splashRadius = 2.0; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const VisualDensity visualDensity = VisualDensity.standard; Widget buildCheckbox({bool selected = false, bool autofocus = false}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( mouseCursor: const MaterialStatePropertyAll(themeMouseCursor), fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedFillColor; } return themeDefaultFillColor; }), checkColor: const MaterialStatePropertyAll(themeCheckColor), overlayColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.focused)) { return themeFocusOverlayColor; } if (states.contains(WidgetState.hovered)) { return themeHoverOverlayColor; } return null; }), splashRadius: themeSplashRadius, materialTapTargetSize: themeMaterialTapTargetSize, visualDensity: themeVisualDensity, ), ), home: Scaffold( body: Checkbox( onChanged: (bool? value) {}, value: selected, autofocus: autofocus, mouseCursor: mouseCursor, fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return selectedFillColor; } return defaultFillColor; }), checkColor: checkColor, focusColor: focusColor, hoverColor: hoverColor, splashRadius: splashRadius, materialTapTargetSize: materialTapTargetSize, visualDensity: visualDensity, ), ), ); } // Checkbox. await tester.pumpWidget(buildCheckbox()); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: defaultFillColor)); // Size from MaterialTapTargetSize.shrinkWrap with added VisualDensity. expect( tester.getSize(find.byType(Checkbox)), const Size(40.0, 40.0) + visualDensity.baseSizeAdjustment, ); // Selected checkbox. await tester.pumpWidget(buildCheckbox(selected: true)); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: selectedFillColor)); expect( _getCheckboxMaterial(tester), paints ..path(color: selectedFillColor) ..path(color: checkColor), ); // Checkbox with hover. await tester.pumpWidget(buildCheckbox()); await _pointGestureToCheckbox(tester); await tester.pumpAndSettle(); expect( RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.text, ); expect(_getCheckboxMaterial(tester), paints..circle(color: hoverColor)); // Checkbox with focus. await tester.pumpWidget(buildCheckbox(autofocus: true)); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..circle(color: focusColor, radius: splashRadius)); }); testWidgets('Checkbox activeColor property is taken over the theme', (WidgetTester tester) async { const themeSelectedFillColor = Color(0xfffffff1); const themeDefaultFillColor = Color(0xfffffff0); const selectedFillColor = Color(0xfffffff6); Widget buildCheckbox({bool selected = false}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( fillColor: WidgetStateProperty.resolveWith((Set states) { if (states.contains(WidgetState.selected)) { return themeSelectedFillColor; } return themeDefaultFillColor; }), ), ), home: Scaffold( body: Checkbox( onChanged: (bool? value) {}, value: selected, activeColor: selectedFillColor, ), ), ); } // Unselected checkbox. await tester.pumpWidget(buildCheckbox()); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: themeDefaultFillColor)); // Selected checkbox. await tester.pumpWidget(buildCheckbox(selected: true)); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: selectedFillColor)); }); testWidgets('Checkbox 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; Widget buildCheckbox({required bool active}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( overlayColor: WidgetStateProperty.resolveWith(getOverlayColor), splashRadius: splashRadius, ), ), home: Scaffold( body: Checkbox(value: active, onChanged: (_) {}), ), ); } await tester.pumpWidget(buildCheckbox(active: false)); final TestGesture gesture1 = await tester.startGesture(tester.getCenter(find.byType(Checkbox))); await tester.pumpAndSettle(); expect( _getCheckboxMaterial(tester), paints..circle(color: inactivePressedOverlayColor, radius: splashRadius), reason: 'Inactive pressed Checkbox should have overlay color: $inactivePressedOverlayColor', ); await tester.pumpWidget(buildCheckbox(active: true)); final TestGesture gesture2 = await tester.startGesture(tester.getCenter(find.byType(Checkbox))); await tester.pumpAndSettle(); expect( _getCheckboxMaterial(tester), paints..circle(color: activePressedOverlayColor, radius: splashRadius), reason: 'Active pressed Checkbox should have overlay color: $activePressedOverlayColor', ); // Finish gesture to release resources. await gesture1.up(); await gesture2.up(); await tester.pumpAndSettle(); }); testWidgets('Local CheckboxTheme can override global CheckboxTheme', (WidgetTester tester) async { const globalThemeFillColor = Color(0xfffffff1); const globalThemeCheckColor = Color(0xff000000); const localThemeFillColor = Color(0xffff0000); const localThemeCheckColor = Color(0xffffffff); Widget buildCheckbox({required bool active}) { return MaterialApp( theme: ThemeData( checkboxTheme: const CheckboxThemeData( checkColor: MaterialStatePropertyAll(globalThemeCheckColor), fillColor: MaterialStatePropertyAll(globalThemeFillColor), ), ), home: Scaffold( body: CheckboxTheme( data: const CheckboxThemeData( fillColor: MaterialStatePropertyAll(localThemeFillColor), checkColor: MaterialStatePropertyAll(localThemeCheckColor), ), child: Checkbox(value: active, onChanged: (_) {}), ), ), ); } await tester.pumpWidget(buildCheckbox(active: true)); await tester.pumpAndSettle(); expect(_getCheckboxMaterial(tester), paints..path(color: localThemeFillColor)); expect( _getCheckboxMaterial(tester), paints ..path(color: localThemeFillColor) ..path(color: localThemeCheckColor), ); }); test('CheckboxThemeData lerp with null parameters', () { final CheckboxThemeData lerped = CheckboxThemeData.lerp(null, null, 0.25); expect(lerped.mouseCursor, null); expect(lerped.fillColor, null); expect(lerped.checkColor, null); expect(lerped.overlayColor, null); expect(lerped.splashRadius, null); expect(lerped.materialTapTargetSize, null); expect(lerped.visualDensity, null); expect(lerped.shape, null); expect(lerped.side, null); }); test('CheckboxThemeData lerp from populated to null parameters', () { final theme = CheckboxThemeData( fillColor: WidgetStateProperty.all(const Color(0xfffffff0)), checkColor: WidgetStateProperty.all(const Color(0xfffffff1)), overlayColor: WidgetStateProperty.all(const Color(0xfffffff2)), splashRadius: 3.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: const VisualDensity(vertical: 1.0, horizontal: 1.0), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), side: const BorderSide(width: 4.0), ); final CheckboxThemeData lerped = CheckboxThemeData.lerp(theme, null, 0.5); expect(lerped.fillColor!.resolve({}), isSameColorAs(const Color(0x80fffff0))); expect(lerped.checkColor!.resolve({}), isSameColorAs(const Color(0x80fffff1))); expect(lerped.overlayColor!.resolve({}), isSameColorAs(const Color(0x80fffff2))); expect(lerped.splashRadius, 1.5); expect(lerped.materialTapTargetSize, null); expect(lerped.visualDensity, null); expect( lerped.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.0))), ); expect(lerped.side!.width, 2.0); expect(lerped.side!.color, isSameColorAs(const Color(0x80000000))); }); test('CheckboxThemeData lerp from null to populated parameters', () { final theme = CheckboxThemeData( fillColor: WidgetStateProperty.all(const Color(0xfffffff0)), checkColor: WidgetStateProperty.all(const Color(0xfffffff1)), overlayColor: WidgetStateProperty.all(const Color(0xfffffff2)), splashRadius: 4.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: const VisualDensity(vertical: 1.0, horizontal: 1.0), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), side: const BorderSide(width: 4.0), ); final CheckboxThemeData lerped = CheckboxThemeData.lerp(null, theme, 0.25); expect( lerped.fillColor!.resolve(const {}), isSameColorAs(const Color(0x40fffff0)), ); expect( lerped.checkColor!.resolve(const {}), isSameColorAs(const Color(0x40fffff1)), ); expect( lerped.overlayColor!.resolve(const {}), isSameColorAs(const Color(0x40fffff2)), ); expect(lerped.splashRadius, 1); expect(lerped.materialTapTargetSize, null); expect(lerped.visualDensity, null); expect( lerped.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(1.0))), ); expect(lerped.side!.width, 1.0); expect(lerped.side!.color, isSameColorAs(const Color(0x40000000))); }); test('CheckboxThemeData lerp from populated parameters', () { final themeA = CheckboxThemeData( fillColor: WidgetStateProperty.all(const Color(0xfffffff0)), checkColor: WidgetStateProperty.all(const Color(0xfffffff1)), overlayColor: WidgetStateProperty.all(const Color(0xfffffff2)), splashRadius: 3.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: const VisualDensity(vertical: 1.0, horizontal: 1.0), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), side: const BorderSide(width: 4.0), ); final themeB = CheckboxThemeData( fillColor: WidgetStateProperty.all(const Color(0xfffffff3)), checkColor: WidgetStateProperty.all(const Color(0xfffffff4)), overlayColor: WidgetStateProperty.all(const Color(0xfffffff5)), splashRadius: 9.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: const VisualDensity(vertical: 2.0, horizontal: 2.0), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(1.0))), side: const BorderSide(width: 3.0), ); final CheckboxThemeData lerped = CheckboxThemeData.lerp(themeA, themeB, 0.5); expect(lerped.fillColor!.resolve({}), isSameColorAs(const Color(0xfffffff1))); expect(lerped.checkColor!.resolve({}), isSameColorAs(const Color(0xfffffff2))); expect(lerped.overlayColor!.resolve({}), isSameColorAs(const Color(0xfffffff3))); expect(lerped.splashRadius, 6); expect(lerped.materialTapTargetSize, MaterialTapTargetSize.shrinkWrap); expect(lerped.visualDensity, const VisualDensity(vertical: 2.0, horizontal: 2.0)); expect( lerped.shape, const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(2.5))), ); expect(lerped.side, const BorderSide(width: 3.5)); }); testWidgets('WidgetStateBorderSide properly lerp in CheckboxThemeData.side', ( WidgetTester tester, ) async { late ColorScheme colorScheme; Widget buildCheckbox({required Color seedColor}) { colorScheme = ColorScheme.fromSeed(seedColor: seedColor); return MaterialApp( theme: ThemeData( colorScheme: colorScheme, checkboxTheme: CheckboxThemeData( side: WidgetStateBorderSide.resolveWith((Set states) { return BorderSide(color: colorScheme.primary, width: 4.0); }), ), ), home: Scaffold(body: Checkbox(value: false, onChanged: (_) {})), ); } await tester.pumpWidget(buildCheckbox(seedColor: Colors.red)); await tester.pumpAndSettle(); RenderBox getCheckboxRenderBox() { return tester.renderObject(find.byType(Checkbox)); } expect(getCheckboxRenderBox(), paints..drrect(color: colorScheme.primary)); await tester.pumpWidget(buildCheckbox(seedColor: Colors.blue)); await tester.pump(kPressTimeout); expect(getCheckboxRenderBox(), paints..drrect(color: colorScheme.primary)); }); } Future _pointGestureToCheckbox(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(Checkbox))); } MaterialInkController? _getCheckboxMaterial(WidgetTester tester) { return Material.of(tester.element(find.byType(Checkbox))); }