// 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'; import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.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 CheckboxThemeData data = CheckboxThemeData(); expect(identical(CheckboxThemeData.lerp(data, data, 0.5), data), true); }); test('CheckboxThemeData defaults', () { const CheckboxThemeData 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 CheckboxTheme 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); }); testWidgetsWithLeakTracking('Default CheckboxThemeData debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const CheckboxThemeData().debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect(description, <String>[]); }); testWidgetsWithLeakTracking('CheckboxThemeData implements debugFillProperties', (WidgetTester tester) async { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); const CheckboxThemeData( mouseCursor: MaterialStatePropertyAll<MouseCursor?>(SystemMouseCursors.click), fillColor: MaterialStatePropertyAll<Color>(Color(0xfffffff0)), checkColor: MaterialStatePropertyAll<Color>(Color(0xfffffff1)), overlayColor: MaterialStatePropertyAll<Color>(Color(0xfffffff2)), splashRadius: 1.0, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, visualDensity: VisualDensity.standard, ).debugFillProperties(builder); final List<String> description = builder.properties .where((DiagnosticsNode node) => !node.isFiltered(DiagnosticLevel.info)) .map((DiagnosticsNode node) => node.toString()) .toList(); expect( description, equalsIgnoringHashCodes(<String>[ 'mouseCursor: MaterialStatePropertyAll(SystemMouseCursor(click))', 'fillColor: MaterialStatePropertyAll(Color(0xfffffff0))', 'checkColor: MaterialStatePropertyAll(Color(0xfffffff1))', 'overlayColor: MaterialStatePropertyAll(Color(0xfffffff2))', 'splashRadius: 1.0', 'materialTapTargetSize: MaterialTapTargetSize.shrinkWrap', 'visualDensity: VisualDensity#00000(h: 0.0, v: 0.0)', ]), ); }); testWidgetsWithLeakTracking('Checkbox is themeable', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const MouseCursor mouseCursor = SystemMouseCursors.text; const Color defaultFillColor = Color(0xfffffff0); const Color selectedFillColor = Color(0xfffffff1); const Color defaultCheckColor = Color(0xfffffff2); const Color focusedCheckColor = Color(0xfffffff3); const Color focusOverlayColor = Color(0xfffffff4); const Color hoverOverlayColor = Color(0xfffffff5); const double splashRadius = 1.0; const MaterialTapTargetSize materialTapTargetSize = MaterialTapTargetSize.shrinkWrap; const VisualDensity 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?>(mouseCursor), fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { return selectedFillColor; } return defaultFillColor; }), checkColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.focused)) { return focusedCheckColor; } return defaultCheckColor; }), overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.focused)) { return focusOverlayColor; } if (states.contains(MaterialState.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)); }); testWidgetsWithLeakTracking('Checkbox properties are taken over the theme values', (WidgetTester tester) async { tester.binding.focusManager.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; const MouseCursor themeMouseCursor = SystemMouseCursors.click; const Color themeDefaultFillColor = Color(0xfffffff0); const Color themeSelectedFillColor = Color(0xfffffff1); const Color themeCheckColor = Color(0xfffffff2); const Color themeFocusOverlayColor = Color(0xfffffff3); const Color themeHoverOverlayColor = Color(0xfffffff4); const double themeSplashRadius = 1.0; const MaterialTapTargetSize themeMaterialTapTargetSize = MaterialTapTargetSize.padded; const VisualDensity themeVisualDensity = VisualDensity.standard; const MouseCursor mouseCursor = SystemMouseCursors.text; const Color defaultFillColor = Color(0xfffffff5); const Color selectedFillColor = Color(0xfffffff6); const Color checkColor = Color(0xfffffff7); const Color focusColor = Color(0xfffffff8); const Color hoverColor = Color(0xfffffff9); const double 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<MouseCursor?>(themeMouseCursor), fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.selected)) { return themeSelectedFillColor; } return themeDefaultFillColor; }), checkColor: const MaterialStatePropertyAll<Color?>(themeCheckColor), overlayColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.focused)) { return themeFocusOverlayColor; } if (states.contains(MaterialState.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: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.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)); }); testWidgetsWithLeakTracking('Checkbox activeColor property is taken over the theme', (WidgetTester tester) async { const Color themeSelectedFillColor = Color(0xfffffff1); const Color themeDefaultFillColor = Color(0xfffffff0); const Color selectedFillColor = Color(0xfffffff6); Widget buildCheckbox({bool selected = false}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( fillColor: MaterialStateProperty.resolveWith((Set<MaterialState> states) { if (states.contains(MaterialState.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 Color activePressedOverlayColor = Color(0xFF000001); const Color inactivePressedOverlayColor = Color(0xFF000002); Color? getOverlayColor(Set<MaterialState> states) { if (states.contains(MaterialState.pressed)) { if (states.contains(MaterialState.selected)) { return activePressedOverlayColor; } return inactivePressedOverlayColor; } return null; } const double splashRadius = 24.0; Widget buildCheckbox({required bool active}) { return MaterialApp( theme: ThemeData( checkboxTheme: CheckboxThemeData( overlayColor: MaterialStateProperty.resolveWith(getOverlayColor), splashRadius: splashRadius, ), ), home: Scaffold( body: Checkbox( value: active, onChanged: (_) { }, ), ), ); } await tester.pumpWidget(buildCheckbox(active: false)); 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)); 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', ); }); testWidgetsWithLeakTracking('Local CheckboxTheme can override global CheckboxTheme', (WidgetTester tester) async { const Color globalThemeFillColor = Color(0xfffffff1); const Color globalThemeCheckColor = Color(0xff000000); const Color localThemeFillColor = Color(0xffff0000); const Color localThemeCheckColor = Color(0xffffffff); Widget buildCheckbox({required bool active}) { return MaterialApp( theme: ThemeData( checkboxTheme: const CheckboxThemeData( checkColor: MaterialStatePropertyAll<Color>(globalThemeCheckColor), fillColor: MaterialStatePropertyAll<Color>(globalThemeFillColor), ), ), home: Scaffold( body: CheckboxTheme( data: const CheckboxThemeData( fillColor: MaterialStatePropertyAll<Color>(localThemeFillColor), checkColor: MaterialStatePropertyAll<Color>(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 CheckboxThemeData theme = CheckboxThemeData( fillColor: MaterialStateProperty.all(const Color(0xfffffff0)), checkColor: MaterialStateProperty.all(const Color(0xfffffff1)), overlayColor: MaterialStateProperty.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(<MaterialState>{}), const Color(0x80fffff0)); expect(lerped.checkColor!.resolve(<MaterialState>{}), const Color(0x80fffff1)); expect(lerped.overlayColor!.resolve(<MaterialState>{}), 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)))); // Returns null if either lerp value is null. expect(lerped.side, null); }); test('CheckboxThemeData lerp from populated parameters', () { final CheckboxThemeData themeA = CheckboxThemeData( fillColor: MaterialStateProperty.all(const Color(0xfffffff0)), checkColor: MaterialStateProperty.all(const Color(0xfffffff1)), overlayColor: MaterialStateProperty.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 themeB = CheckboxThemeData( fillColor: MaterialStateProperty.all(const Color(0xfffffff3)), checkColor: MaterialStateProperty.all(const Color(0xfffffff4)), overlayColor: MaterialStateProperty.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(<MaterialState>{}), const Color(0xfffffff1)); expect(lerped.checkColor!.resolve(<MaterialState>{}), const Color(0xfffffff2)); expect(lerped.overlayColor!.resolve(<MaterialState>{}), 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)); }); } Future<void> _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))); }