From 626e51512a8345f3f91ca85470451e63bcd17ec0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:33:35 +0000 Subject: [PATCH 1/6] Initial plan From 697feea3e7217f7af06e5beffb48b92e6231f25c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 28 Aug 2026 13:35:10 +0000 Subject: [PATCH 2/6] Apply remaining changes Co-authored-by: bdlukaa <45696119+bdlukaa@users.noreply.github.com> --- CHANGELOG.md | 4 +++ lib/src/controls/form/combo_box.dart | 10 ++++-- test/combo_box_test.dart | 46 ++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35bb315fd..a78bc1ea5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 4.16.2 + +- fix: `ComboBox` no longer throws a layout assertion when opened while fully aligned to the bottom edge of the viewport ([#1350](https://github.com/bdlukaa/fluent_ui/issues/1350)) + ## 4.16.1 - fix: `ComboBox` no longer throws when opening a dropdown with a single item ([#1347](https://github.com/bdlukaa/fluent_ui/pull/1347)) diff --git a/lib/src/controls/form/combo_box.dart b/lib/src/controls/form/combo_box.dart index 50d3c7429..bb7033327 100644 --- a/lib/src/controls/form/combo_box.dart +++ b/lib/src/controls/form/combo_box.dart @@ -472,6 +472,10 @@ class _ComboBoxMenuRouteLayout extends SingleChildLayoutDelegate { size.height, route.selectedIndex ?? 0, ); + final top = menuLimits.top.clamp( + 0.0, + math.max(0.0, size.height - childSize.height), + ); assert(() { final container = Offset.zero & size; @@ -479,8 +483,8 @@ class _ComboBoxMenuRouteLayout extends SingleChildLayoutDelegate { // If the button was entirely on-screen, then verify // that the menu is also on-screen. // If the button was a bit off-screen, then, oh well. - assert(menuLimits.top >= 0.0); - assert(menuLimits.top + menuLimits.height <= size.height); + assert(top >= 0.0); + assert(top + childSize.height <= size.height); } return true; }()); @@ -493,7 +497,7 @@ class _ComboBoxMenuRouteLayout extends SingleChildLayoutDelegate { left = buttonRect.left.clamp(0.0, size.width - childSize.width); } - return Offset(left, menuLimits.top); + return Offset(left, top); } @override diff --git a/test/combo_box_test.dart b/test/combo_box_test.dart index 03d7825ad..25892c3cc 100644 --- a/test/combo_box_test.dart +++ b/test/combo_box_test.dart @@ -315,4 +315,50 @@ void main() { expect(selectedValue, 'something'); }); + + testWidgets('ComboBox positioned at bottom opens without layout assertions', ( + tester, + ) async { + const selectedValue = 'Two'; + + await tester.pumpWidget( + wrapApp( + child: SizedBox( + height: 602, + child: Align( + alignment: Alignment.bottomCenter, + child: ComboBox( + value: selectedValue, + items: const [ + ComboBoxItem( + key: Key('bottom-combo-item-one'), + value: 'One', + child: Text('One'), + ), + ComboBoxItem( + key: Key('bottom-combo-item-two'), + value: 'Two', + child: Text('Two'), + ), + ComboBoxItem( + key: Key('bottom-combo-item-three'), + value: 'Three', + child: Text('Three'), + ), + ], + onChanged: (_) {}, + ), + ), + ), + ), + ); + + await tester.tap(find.byType(ComboBox)); + await tester.pumpAndSettle(); + + expect(tester.takeException(), isNull); + expect(find.byKey(const Key('bottom-combo-item-one')), findsOneWidget); + expect(find.byKey(const Key('bottom-combo-item-two')), findsOneWidget); + expect(find.byKey(const Key('bottom-combo-item-three')), findsOneWidget); + }); } From 7484631a4e5474bb26a938db80879743cfa469f5 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 29 Aug 2026 15:26:23 -0300 Subject: [PATCH 3/6] fix: build failure --- analysis_options.yaml | 7 +++++++ example/analysis_options.yaml | 7 +++++++ example/lib/routes/forms.dart | 2 +- .../screens/forms/{combobox.dart => combo_box.dart} | 0 lib/src/controls/form/combo_box.dart | 13 +++++++------ 5 files changed, 22 insertions(+), 7 deletions(-) rename example/lib/screens/forms/{combobox.dart => combo_box.dart} (100%) diff --git a/analysis_options.yaml b/analysis_options.yaml index 4e7b3470c..1ecf5f983 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -8,6 +8,13 @@ analyzer: - lib/src/styles/color_resources.dart - bin/** - "**/*.g.dart" + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** language: strict-casts: true strict-inference: true diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 1c7be75db..93a4a4ada 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -4,6 +4,13 @@ analyzer: exclude: - "**/*.g.dart" - "**/*.freezed.dart" + - build/** + - android/** + - ios/** + - web/** + - windows/** + - macos/** + - linux/** linter: rules: diff --git a/example/lib/routes/forms.dart b/example/lib/routes/forms.dart index edeb34210..d343176f5 100644 --- a/example/lib/routes/forms.dart +++ b/example/lib/routes/forms.dart @@ -2,7 +2,7 @@ export '../screens/forms/auto_suggest_box.dart'; export '../screens/forms/calendar_date_picker.dart'; export '../screens/forms/calendar_view.dart'; export '../screens/forms/color_picker.dart'; -export '../screens/forms/combobox.dart'; +export '../screens/forms/combo_box.dart'; export '../screens/forms/date_picker.dart'; export '../screens/forms/number_box.dart'; export '../screens/forms/password_box.dart'; diff --git a/example/lib/screens/forms/combobox.dart b/example/lib/screens/forms/combo_box.dart similarity index 100% rename from example/lib/screens/forms/combobox.dart rename to example/lib/screens/forms/combo_box.dart diff --git a/lib/src/controls/form/combo_box.dart b/lib/src/controls/form/combo_box.dart index bb7033327..02be0cbe5 100644 --- a/lib/src/controls/form/combo_box.dart +++ b/lib/src/controls/form/combo_box.dart @@ -60,10 +60,10 @@ class _ComboBoxMenuPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final selectedItemOffset = getSelectedItemOffset(); - final maxTopOffset = math.max(0.0, size.height - scaledItemHeight); + final maxTopOffset = math.max(0, size.height - scaledItemHeight); final minBottomOffset = math.min(scaledItemHeight, size.height); final top = Tween( - begin: selectedItemOffset.clamp(0.0, maxTopOffset), + begin: clampDouble(selectedItemOffset, 0, maxTopOffset), end: 0, ); @@ -404,7 +404,7 @@ class _ComboBoxResizeClipper extends CustomClipper { @override RRect getClip(Size size) { final selectedItemOffset = getSelectedItemOffset(); - final maxTopOffset = math.max(0.0, size.height - scaledItemHeight); + final maxTopOffset = math.max(0, size.height - scaledItemHeight); final minBottomOffset = math.min(scaledItemHeight, size.height); final top = Tween( begin: selectedItemOffset.clamp(0.0, maxTopOffset), @@ -472,9 +472,10 @@ class _ComboBoxMenuRouteLayout extends SingleChildLayoutDelegate { size.height, route.selectedIndex ?? 0, ); - final top = menuLimits.top.clamp( - 0.0, - math.max(0.0, size.height - childSize.height), + final top = clampDouble( + menuLimits.top, + 0, + math.max(0, size.height - childSize.height), ); assert(() { From 6fd34338fda6cb01e8b080785418901aab881eb4 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 29 Aug 2026 15:44:35 -0300 Subject: [PATCH 4/6] refactor: Prefer using clampDouble instead num.clamp --- example/macos/Podfile | 2 +- example/macos/Podfile.lock | 4 +- .../macos/Runner.xcodeproj/project.pbxproj | 6 +-- lib/src/controls/flyouts/flyout.dart | 3 +- lib/src/controls/flyouts/menu_bar.dart | 5 ++- lib/src/controls/flyouts/tooltip.dart | 7 ++-- lib/src/controls/form/auto_suggest_box.dart | 13 +++++-- lib/src/controls/form/combo_box.dart | 27 ++++++++------ lib/src/controls/form/number_box.dart | 19 ++++++---- lib/src/controls/form/selection_controls.dart | 3 +- lib/src/controls/form/text_box.dart | 8 ++-- lib/src/controls/inputs/rating.dart | 19 +++++----- lib/src/controls/inputs/toggle_switch.dart | 2 +- .../controls/navigation/breadcrumb_bar.dart | 2 +- .../navigation_view/indicators.dart | 12 ++++-- .../navigation/navigation_view/pane.dart | 6 ++- .../navigation_view/pane_items.dart | 5 ++- .../navigation/navigation_view/title_bar.dart | 17 ++++++--- .../navigation/tab_view/tab_view.dart | 16 ++++---- lib/src/controls/navigation/tree_view.dart | 14 ++++--- .../pickers/color_picker/color_spectrum.dart | 37 ++++++++++++------- .../pickers/color_picker/color_state.dart | 20 +++++----- lib/src/controls/pickers/date_picker.dart | 8 ++-- lib/src/controls/pickers/pickers.dart | 2 +- lib/src/controls/pickers/time_picker.dart | 8 ++-- lib/src/controls/surfaces/acrylic.dart | 4 +- lib/src/controls/surfaces/expander.dart | 5 ++- lib/src/controls/surfaces/info_bar.dart | 5 ++- lib/src/controls/surfaces/list_tile.dart | 11 ++++-- .../surfaces/progress_indicators.dart | 4 +- lib/src/controls/utils/custom_icons.dart | 2 +- lib/src/utils.dart | 6 ++- test/menu_bar_test.dart | 7 ++-- 33 files changed, 184 insertions(+), 125 deletions(-) diff --git a/example/macos/Podfile b/example/macos/Podfile index 9ec46f8cd..0c76ccf54 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.15' +platform :osx, '12.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 9f5ca000f..1f752f4f9 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -9,8 +9,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral SPEC CHECKSUMS: - FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + FlutterMacOS: c232990155153907050900a2e175c7773903ba4e -PODFILE CHECKSUM: 0d3963a09fc94f580682bd88480486da345dc3f0 +PODFILE CHECKSUM: 505596d150d38022472859d890f709281982e016 COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 6c1453bcc..6898ed47d 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -397,7 +397,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -476,7 +476,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -523,7 +523,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.15; + MACOSX_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/lib/src/controls/flyouts/flyout.dart b/lib/src/controls/flyouts/flyout.dart index 25f24620c..c73daf57e 100644 --- a/lib/src/controls/flyouts/flyout.dart +++ b/lib/src/controls/flyouts/flyout.dart @@ -508,7 +508,8 @@ class _FlyoutPositionDelegate extends SingleChildLayoutDelegate { return clampDouble( y, margin, - (rootSize.height - flyoutSize.height - margin).clamp( + clampDouble( + rootSize.height - flyoutSize.height - margin, margin, rootSize.height - margin, ), diff --git a/lib/src/controls/flyouts/menu_bar.dart b/lib/src/controls/flyouts/menu_bar.dart index 2ff95b33f..fe7fe9ef2 100644 --- a/lib/src/controls/flyouts/menu_bar.dart +++ b/lib/src/controls/flyouts/menu_bar.dart @@ -268,8 +268,9 @@ class MenuBarState extends State { }, child: Container( constraints: BoxConstraints( - minHeight: (40 + theme.visualDensity.baseSizeAdjustment.dy).clamp( - 0.0, + minHeight: clampDouble( + 40 + theme.visualDensity.baseSizeAdjustment.dy, + 0, double.infinity, ), ), diff --git a/lib/src/controls/flyouts/tooltip.dart b/lib/src/controls/flyouts/tooltip.dart index 77b76201f..d8e45d24d 100644 --- a/lib/src/controls/flyouts/tooltip.dart +++ b/lib/src/controls/flyouts/tooltip.dart @@ -530,8 +530,9 @@ class TooltipThemeData with Diagnosticable { /// Creates the standard [TooltipThemeData] based on the given [theme]. factory TooltipThemeData.standard(FluentThemeData theme) { return TooltipThemeData( - height: (32 + theme.visualDensity.baseSizeAdjustment.dy).clamp( - 0.0, + height: clampDouble( + 32 + theme.visualDensity.baseSizeAdjustment.dy, + 0, double.infinity, ), verticalOffset: 24, @@ -670,7 +671,7 @@ class _TooltipContent extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTextStyle.merge( - style: FluentTheme.of(context).typography.body!, + style: FluentTheme.of(context).typography.body, child: Container( decoration: decoration, padding: padding, diff --git a/lib/src/controls/form/auto_suggest_box.dart b/lib/src/controls/form/auto_suggest_box.dart index 525b4fc07..fbe3328b3 100644 --- a/lib/src/controls/form/auto_suggest_box.dart +++ b/lib/src/controls/form/auto_suggest_box.dart @@ -653,12 +653,16 @@ class AutoSuggestBoxState extends State> { final double overlayOffsetY; final double maxHeight; if (showAbove) { - maxHeight = spaceAbove.clamp(0.0, widget.maxPopupHeight); + maxHeight = clampDouble(spaceAbove, 0, widget.maxPopupHeight); overlayOffsetY = -(maxHeight + 0.8); } else { overlayOffsetY = box.size.height + 0.8; final overlayY = globalOffset.dy + box.size.height; - maxHeight = (screenHeight - overlayY).clamp(0.0, widget.maxPopupHeight); + maxHeight = clampDouble( + screenHeight - overlayY, + 0, + widget.maxPopupHeight, + ); } Widget child = PositionedDirectional( @@ -977,8 +981,9 @@ class _AutoSuggestBoxOverlayState extends State<_AutoSuggestBoxOverlay> { /// Returns the tile height adjusted for the given visual density. @visibleForTesting static double adjustedTileHeight(VisualDensity density) { - return (_tileHeight + density.baseSizeAdjustment.dy).clamp( - 0.0, + return clampDouble( + _tileHeight + density.baseSizeAdjustment.dy, + 0, double.infinity, ); } diff --git a/lib/src/controls/form/combo_box.dart b/lib/src/controls/form/combo_box.dart index 02be0cbe5..95f26a526 100644 --- a/lib/src/controls/form/combo_box.dart +++ b/lib/src/controls/form/combo_box.dart @@ -68,7 +68,8 @@ class _ComboBoxMenuPainter extends CustomPainter { ); final bottom = Tween( - begin: (top.begin! + scaledItemHeight).clamp( + begin: clampDouble( + top.begin! + scaledItemHeight, minBottomOffset, size.height, ), @@ -407,12 +408,13 @@ class _ComboBoxResizeClipper extends CustomClipper { final maxTopOffset = math.max(0, size.height - scaledItemHeight); final minBottomOffset = math.min(scaledItemHeight, size.height); final top = Tween( - begin: selectedItemOffset.clamp(0.0, maxTopOffset), + begin: clampDouble(selectedItemOffset, 0, maxTopOffset), end: 0, ); final bottom = Tween( - begin: (top.begin! + scaledItemHeight).clamp( + begin: clampDouble( + top.begin! + scaledItemHeight, minBottomOffset, size.height, ), @@ -493,9 +495,9 @@ class _ComboBoxMenuRouteLayout extends SingleChildLayoutDelegate { final double left; switch (textDirection!) { case TextDirection.rtl: - left = buttonRect.right.clamp(0.0, size.width) - childSize.width; + left = clampDouble(buttonRect.right, 0, size.width) - childSize.width; case TextDirection.ltr: - left = buttonRect.left.clamp(0.0, size.width - childSize.width); + left = clampDouble(buttonRect.left, 0, size.width - childSize.width); } return Offset(left, top); @@ -811,14 +813,15 @@ class _ComboBoxItemContainer extends StatelessWidget { final textScaler = MediaQuery.textScalerOf(context); final densityAdjustment = theme.visualDensity.baseSizeAdjustment.dy; - final adjustedItemHeight = - (textScaler.scale(kComboBoxItemHeight) + densityAdjustment).clamp( - 0.0, - double.infinity, - ); + final adjustedItemHeight = clampDouble( + textScaler.scale(kComboBoxItemHeight) + densityAdjustment, + 0, + double.infinity, + ); final itemHeight = adjustedItemHeight; - final buttonHeight = (adjustedItemHeight - _kMenuItemBottomPadding).clamp( - 0.0, + final buttonHeight = clampDouble( + adjustedItemHeight - _kMenuItemBottomPadding, + 0, double.infinity, ); return Container( diff --git a/lib/src/controls/form/number_box.dart b/lib/src/controls/form/number_box.dart index c3ff63223..df7188ece 100644 --- a/lib/src/controls/form/number_box.dart +++ b/lib/src/controls/form/number_box.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:ui' as ui; +import 'dart:ui'; import 'package:fluent_ui/fluent_ui.dart'; import 'package:flutter/gestures.dart'; @@ -546,10 +547,12 @@ class NumberBoxState extends State> { if (boxContext == null) return const SizedBox.shrink(); final box = boxContext.findRenderObject()! as RenderBox; final isRtl = Directionality.of(context) == TextDirection.rtl; - final overlayHeight = - (kNumberBoxOverlayHeight + - FluentTheme.of(context).visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity); + final overlayHeight = clampDouble( + kNumberBoxOverlayHeight + + FluentTheme.of(context).visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ); final Widget child = PositionedDirectional( width: kNumberBoxOverlayWidth, @@ -913,9 +916,11 @@ class _NumberBoxCompactOverlay extends StatelessWidget { Widget build(BuildContext context) { assert(debugCheckHasFluentTheme(context)); final theme = FluentTheme.of(context); - final overlayHeight = - (kNumberBoxOverlayHeight + theme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity); + final overlayHeight = clampDouble( + kNumberBoxOverlayHeight + theme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ); return Padding( padding: const EdgeInsetsDirectional.only(start: 10), diff --git a/lib/src/controls/form/selection_controls.dart b/lib/src/controls/form/selection_controls.dart index 426fdc7da..1a6bcb3d2 100644 --- a/lib/src/controls/form/selection_controls.dart +++ b/lib/src/controls/form/selection_controls.dart @@ -411,7 +411,8 @@ class _WindowsTextSelectionControlsToolbarState assert(debugCheckHasMediaQuery(context)); final midpointAnchor = Offset( - (widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp( + clampDouble( + widget.selectionMidpoint.dx - widget.globalEditableRegion.left, MediaQuery.paddingOf(context).left, MediaQuery.widthOf(context) - MediaQuery.paddingOf(context).right, ), diff --git a/lib/src/controls/form/text_box.dart b/lib/src/controls/form/text_box.dart index 04021a075..dde2c76f1 100644 --- a/lib/src/controls/form/text_box.dart +++ b/lib/src/controls/form/text_box.dart @@ -1609,9 +1609,11 @@ class _TextBoxState extends State child: Container( foregroundDecoration: foregroundDecoration, constraints: BoxConstraints( - minHeight: - (32 + themeData.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity), + minHeight: clampDouble( + 32 + themeData.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ), ), child: _selectionGestureDetectorBuilder .buildGestureDetector( diff --git a/lib/src/controls/inputs/rating.dart b/lib/src/controls/inputs/rating.dart index d0f8d6535..8225287fc 100644 --- a/lib/src/controls/inputs/rating.dart +++ b/lib/src/controls/inputs/rating.dart @@ -211,14 +211,14 @@ class _RatingControlState extends State { void increase() { if (widget.rating == widget.amount) return; widget.onChanged?.call( - (widget.rating + 1).clamp(0, widget.amount).toDouble(), + clampDouble(widget.rating + 1, 0, widget.amount.toDouble()), ); } void decrease() { if (widget.rating == 0) return; widget.onChanged?.call( - (widget.rating - 1).clamp(0, widget.amount).toDouble(), + clampDouble(widget.rating - 1, 0, widget.amount.toDouble()), ); } @@ -253,19 +253,20 @@ class _RatingControlState extends State { /// downstream calculations can assume left-to-right ordering. double _adjustX(double x) { if (Directionality.of(context) == TextDirection.rtl) { - return (_totalWidth - x).clamp(0.0, _totalWidth); + return clampDouble(_totalWidth - x, 0, _totalWidth); } return x; } void _handleUpdate(double x) { final totalPerStar = widget.iconSize + widget.starSpacing; - final raw = (_adjustX(x) / totalPerStar).clamp( - 0.0, + final raw = clampDouble( + _adjustX(x) / totalPerStar, + 0, widget.amount.toDouble(), ); - final snapped = raw.ceil().clamp(0, widget.amount).toDouble(); - widget.onChanged?.call(snapped); + final snapped = raw.ceil().clamp(0, widget.amount); + widget.onChanged?.call(snapped.toDouble()); } void _handleHoverUpdate(double x) { @@ -334,7 +335,7 @@ class _RatingControlState extends State { final double r; if (!isEnabled) { - r = (value - index).clamp(0.0, 1.0); + r = clampDouble(value - index, 0, 1); starRatedColor = widget.ratedIconColor ?? resources.textFillColorDisabled; @@ -363,7 +364,7 @@ class _RatingControlState extends State { } } else { // Normal (non-hover) state - r = (value - index).clamp(0.0, 1.0); + r = clampDouble(value - index, 0, 1); starRatedColor = widget.ratedIconColor ?? accentColor; // SelectedForeground diff --git a/lib/src/controls/inputs/toggle_switch.dart b/lib/src/controls/inputs/toggle_switch.dart index d8d31e79d..9311de2bc 100644 --- a/lib/src/controls/inputs/toggle_switch.dart +++ b/lib/src/controls/inputs/toggle_switch.dart @@ -182,7 +182,7 @@ class _ToggleSwitchState extends State { ) { setState(() { _alignment = Alignment( - (localPosition.dx / sliderGestureWidth).clamp(-1, 1), + clampDouble(localPosition.dx / sliderGestureWidth, -1, 1), 0, ); }); diff --git a/lib/src/controls/navigation/breadcrumb_bar.dart b/lib/src/controls/navigation/breadcrumb_bar.dart index 525213af7..be93bffad 100644 --- a/lib/src/controls/navigation/breadcrumb_bar.dart +++ b/lib/src/controls/navigation/breadcrumb_bar.dart @@ -541,7 +541,7 @@ class RenderBreadcrumbBar extends RenderBox onIndexOverflow(overflowedIndexes); size = Size( - maxExtent.clamp(constraints.maxWidth, constraints.maxWidth), + clampDouble(maxExtent, constraints.maxWidth, constraints.maxWidth), height, ); } diff --git a/lib/src/controls/navigation/navigation_view/indicators.dart b/lib/src/controls/navigation/navigation_view/indicators.dart index 207afa1aa..4bb8d58a9 100644 --- a/lib/src/controls/navigation/navigation_view/indicators.dart +++ b/lib/src/controls/navigation/navigation_view/indicators.dart @@ -380,8 +380,9 @@ class _StickyNavigationIndicatorState final fluentTheme = FluentTheme.of(context); final densityAdjustment = fluentTheme.visualDensity.baseSizeAdjustment.dy; - final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp( - 0.0, + final paneItemMinHeight = clampDouble( + kPaneItemMinHeight + densityAdjustment, + 0, double.infinity, ); @@ -455,8 +456,11 @@ class _StickyNavigationIndicatorState final expandProgress = expandAnimation.value; final tileHeight = constraints.maxHeight; final distanceToBottom = tileHeight - widget.leftPadding; - topPadding = (distanceToBottom * (1.0 - expandProgress)) - .clamp(widget.leftPadding, distanceToBottom); + topPadding = clampDouble( + distanceToBottom * (1.0 - expandProgress), + widget.leftPadding, + distanceToBottom, + ); } return Padding( diff --git a/lib/src/controls/navigation/navigation_view/pane.dart b/lib/src/controls/navigation/navigation_view/pane.dart index 4f569a013..f1ca927e1 100644 --- a/lib/src/controls/navigation/navigation_view/pane.dart +++ b/lib/src/controls/navigation/navigation_view/pane.dart @@ -547,7 +547,11 @@ class NavigationPaneSize with Diagnosticable { /// Gets the width of the open pane with the constraints applied. double get openPaneWidth { - return openWidth.clamp(openMinWidth ?? 0, openMaxWidth ?? double.infinity); + return clampDouble( + openWidth, + openMinWidth ?? 0, + openMaxWidth ?? double.infinity, + ); } @override diff --git a/lib/src/controls/navigation/navigation_view/pane_items.dart b/lib/src/controls/navigation/navigation_view/pane_items.dart index 36b3d86f0..f7d923ca4 100644 --- a/lib/src/controls/navigation/navigation_view/pane_items.dart +++ b/lib/src/controls/navigation/navigation_view/pane_items.dart @@ -205,8 +205,9 @@ class PaneItem extends NavigationPaneItem { final theme = NavigationPaneTheme.of(context); final fluentTheme = FluentTheme.of(context); final densityAdjustment = fluentTheme.visualDensity.baseSizeAdjustment.dy; - final paneItemMinHeight = (kPaneItemMinHeight + densityAdjustment).clamp( - 0.0, + final paneItemMinHeight = clampDouble( + kPaneItemMinHeight + densityAdjustment, + 0, double.infinity, ); diff --git a/lib/src/controls/navigation/navigation_view/title_bar.dart b/lib/src/controls/navigation/navigation_view/title_bar.dart index 45689613d..93e6a8ed5 100644 --- a/lib/src/controls/navigation/navigation_view/title_bar.dart +++ b/lib/src/controls/navigation/navigation_view/title_bar.dart @@ -1,4 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; /// The title bar sits at the top of an app on the base layer. Its main purpose @@ -604,9 +605,11 @@ class PaneToggleButton extends StatelessWidget { Widget build(BuildContext context) { final view = NavigationView.dataOf(context); final fluentTheme = FluentTheme.of(context); - final paneItemHeight = - (kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity); + final paneItemHeight = clampDouble( + kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ); final width = view.pane?.size?.compactWidth ?? kCompactNavigationPaneWidth; return Container( @@ -663,9 +666,11 @@ class PaneBackButton extends StatelessWidget { final viewData = NavigationView.dataOf(context); final canPop = viewData.canPop; final fluentTheme = FluentTheme.of(context); - final paneItemHeight = - (kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity); + final paneItemHeight = clampDouble( + kPaneItemMinHeight + fluentTheme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ); final width = viewData.pane?.size?.compactWidth ?? kCompactNavigationPaneWidth; diff --git a/lib/src/controls/navigation/tab_view/tab_view.dart b/lib/src/controls/navigation/tab_view/tab_view.dart index b4612f767..2e0db0d8d 100644 --- a/lib/src/controls/navigation/tab_view/tab_view.dart +++ b/lib/src/controls/navigation/tab_view/tab_view.dart @@ -501,14 +501,14 @@ class _TabViewState extends State { 'You can only create a TabView in a box with defined width', ); - preferredTabWidth = - ((width - - (widget.showNewButton - ? _kButtonWidth - : 0) - - (widget.reservedStripWidth ?? 0)) / - widget.tabs.length) - .clamp(widget.minTabWidth, widget.maxTabWidth); + preferredTabWidth = clampDouble( + (width - + (widget.showNewButton ? _kButtonWidth : 0) - + (widget.reservedStripWidth ?? 0)) / + widget.tabs.length, + widget.minTabWidth, + widget.maxTabWidth, + ); final Widget listView = Listener( onPointerSignal: (e) { diff --git a/lib/src/controls/navigation/tree_view.dart b/lib/src/controls/navigation/tree_view.dart index 573706344..340ee0c34 100644 --- a/lib/src/controls/navigation/tree_view.dart +++ b/lib/src/controls/navigation/tree_view.dart @@ -1434,12 +1434,14 @@ class _TreeViewItem extends StatelessWidget { // Indentation and selection indicator for single selection mode. Container( constraints: BoxConstraints( - minHeight: - ((selectionMode == TreeViewSelectionMode.multiple - ? 28.0 - : 26.0) + - theme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity), + minHeight: clampDouble( + (selectionMode == TreeViewSelectionMode.multiple + ? 28.0 + : 26.0) + + theme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ), ), padding: EdgeInsetsDirectional.only( start: selectionMode == TreeViewSelectionMode.multiple diff --git a/lib/src/controls/pickers/color_picker/color_spectrum.dart b/lib/src/controls/pickers/color_picker/color_spectrum.dart index b885188f4..58edbee7b 100644 --- a/lib/src/controls/pickers/color_picker/color_spectrum.dart +++ b/lib/src/controls/pickers/color_picker/color_spectrum.dart @@ -2,6 +2,7 @@ import 'dart:math' as math; import 'dart:ui' as dart; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'color_state.dart'; @@ -273,8 +274,8 @@ class _ColorBoxSpectrumState extends State { final height = size.height; // Clamp position within bounds - final x = position.dx.clamp(0, width); - final y = position.dy.clamp(0, height); + final x = clampDouble(position.dx, 0, width); + final y = clampDouble(position.dy, 0, height); // Calculate HSV values // Hue from left to right (minHue to maxHue) @@ -461,12 +462,16 @@ class _RingSpectrumPainter extends CustomPainter { final labelWidth = textPainter.width + labelPadding.horizontal; final labelHeight = textPainter.height + labelPadding.vertical; - final labelX = (position.dx - labelWidth / 2) - .clamp(0, size.width - labelWidth) - .toDouble(); - var labelY = (position.dy - labelHeight - 30) - .clamp(0, size.height - labelHeight) - .toDouble(); + final labelX = clampDouble( + position.dx - labelWidth / 2, + 0, + size.width - labelWidth, + ); + var labelY = clampDouble( + position.dy - labelHeight - 30, + 0, + size.height - labelHeight, + ); // Check if label would overlap the indicator and adjust position final labelBottomY = labelY + labelHeight; @@ -676,12 +681,16 @@ class _BoxSpectrumPainter extends CustomPainter { final labelWidth = textPainter.width + labelPadding.horizontal; final labelHeight = textPainter.height + labelPadding.vertical; - final labelX = (position.dx - labelWidth / 2) - .clamp(0, size.width - labelWidth) - .toDouble(); - var labelY = (position.dy - labelHeight - 30) - .clamp(0, size.height - labelHeight) - .toDouble(); + final labelX = clampDouble( + position.dx - labelWidth / 2, + 0, + size.width - labelWidth, + ); + var labelY = clampDouble( + position.dy - labelHeight - 30, + 0, + size.height - labelHeight, + ); // Check if label would overlap the indicator and adjust position final labelBottomY = labelY + labelHeight; diff --git a/lib/src/controls/pickers/color_picker/color_state.dart b/lib/src/controls/pickers/color_picker/color_state.dart index cce16a1e6..efdfa23a7 100644 --- a/lib/src/controls/pickers/color_picker/color_state.dart +++ b/lib/src/controls/pickers/color_picker/color_state.dart @@ -1,6 +1,7 @@ import 'dart:math' as math; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; part 'color_names.dart'; @@ -120,14 +121,14 @@ class ColorState extends ChangeNotifier { /// Sets the saturation and updates the RGB values accordingly. void setSaturation(double newValue) { - _saturation = newValue.clamp(0, 1); + _saturation = clampDouble(newValue, 0, 1); _recalculateRGBFromHSV(); notifyListeners(); } /// Sets the value and updates the RGB values accordingly. void setValue(double newValue) { - _value = newValue.clamp(0, 1); + _value = clampDouble(newValue, 0, 1); if (_value == 0) { _red = 0; @@ -145,28 +146,28 @@ class ColorState extends ChangeNotifier { /// Sets the red component and updates the HSV values accordingly. void setRed(double newValue) { - _red = newValue.clamp(0, 1); + _red = clampDouble(newValue, 0, 1); _recalculateHSVFromRGB(); notifyListeners(); } /// Sets the green component and updates the HSV values accordingly. void setGreen(double newValue) { - _green = newValue.clamp(0, 1); + _green = clampDouble(newValue, 0, 1); _recalculateHSVFromRGB(); notifyListeners(); } /// Sets the blue component and updates the HSV values accordingly. void setBlue(double newValue) { - _blue = newValue.clamp(0, 1); + _blue = clampDouble(newValue, 0, 1); _recalculateHSVFromRGB(); notifyListeners(); } /// Sets the alpha component (0–1). void setAlpha(double newValue) { - _alpha = newValue.clamp(0, 1); + _alpha = clampDouble(newValue, 0, 1); notifyListeners(); } @@ -286,12 +287,13 @@ class ColorState extends ChangeNotifier { ); // Clamp values to allowed ranges - final clampedHue = _hue.clamp(minHue.toDouble(), maxHue.toDouble()); - final clampedSaturation = _saturation.clamp( + final clampedHue = clampDouble(_hue, minHue.toDouble(), maxHue.toDouble()); + final clampedSaturation = clampDouble( + _saturation, minSaturation / 100, maxSaturation / 100, ); - final clampedValue = _value.clamp(minValue / 100, maxValue / 100); + final clampedValue = clampDouble(_value, minValue / 100, maxValue / 100); // Only update and recalculate if values actually changed if (clampedHue != _hue || diff --git a/lib/src/controls/pickers/date_picker.dart b/lib/src/controls/pickers/date_picker.dart index 422ce82d0..2455de646 100644 --- a/lib/src/controls/pickers/date_picker.dart +++ b/lib/src/controls/pickers/date_picker.dart @@ -451,9 +451,11 @@ class DatePickerState extends State { duration: theme.fastAnimationDuration, curve: theme.animationCurve, constraints: BoxConstraints( - minHeight: - (kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity), + minHeight: clampDouble( + kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ), ), decoration: kPickerDecorationBuilder(context, states), child: DefaultTextStyle.merge( diff --git a/lib/src/controls/pickers/pickers.dart b/lib/src/controls/pickers/pickers.dart index 04940a227..d780d4e6e 100644 --- a/lib/src/controls/pickers/pickers.dart +++ b/lib/src/controls/pickers/pickers.dart @@ -395,7 +395,7 @@ class PickerState extends State { y = 0; } - y = y.clamp(0.0, rootHeight); + y = clampDouble(y, 0.0, rootHeight); final theme = FluentTheme.of(context); diff --git a/lib/src/controls/pickers/time_picker.dart b/lib/src/controls/pickers/time_picker.dart index 591ce0846..7c0653aff 100644 --- a/lib/src/controls/pickers/time_picker.dart +++ b/lib/src/controls/pickers/time_picker.dart @@ -318,9 +318,11 @@ class TimePickerState extends State duration: theme.fastAnimationDuration, curve: theme.animationCurve, constraints: BoxConstraints( - minHeight: - (kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity), + minHeight: clampDouble( + kPickerHeight + theme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ), ), decoration: kPickerDecorationBuilder(context, states), child: DefaultTextStyle.merge( diff --git a/lib/src/controls/surfaces/acrylic.dart b/lib/src/controls/surfaces/acrylic.dart index 084602ba2..c19c7f59b 100644 --- a/lib/src/controls/surfaces/acrylic.dart +++ b/lib/src/controls/surfaces/acrylic.dart @@ -573,7 +573,7 @@ class AcrylicHelper { static Color getLuminosityColor(Color tintColor, double? luminosityOpacity) { // If luminosity opacity is specified, just use the values as is if (luminosityOpacity != null) { - return tintColor.withValues(alpha: luminosityOpacity.clamp(0.0, 1.0)); + return tintColor.withValues(alpha: clampDouble(luminosityOpacity, 0, 1)); } else { // To create the Luminosity blend input color without luminosity opacity, // we're taking the TintColor input, converting to HSV, and clamping the V between these values @@ -582,7 +582,7 @@ class AcrylicHelper { final hsvTintColor = HSVColor.fromColor(tintColor); - final clampedHsvV = hsvTintColor.value.clamp(minHsvV, maxHsvV); + final clampedHsvV = clampDouble(hsvTintColor.value, minHsvV, maxHsvV); final hsvLuminosityColor = hsvTintColor.withValue(clampedHsvV); final rgbLuminosityColor = hsvLuminosityColor.toColor(); diff --git a/lib/src/controls/surfaces/expander.dart b/lib/src/controls/surfaces/expander.dart index cb5a7892f..412b9eadc 100644 --- a/lib/src/controls/surfaces/expander.dart +++ b/lib/src/controls/surfaces/expander.dart @@ -319,8 +319,9 @@ class ExpanderState extends State builder: (context, states) { return Container( constraints: BoxConstraints( - minHeight: (42 + theme.visualDensity.baseSizeAdjustment.dy).clamp( - 0.0, + minHeight: clampDouble( + 42 + theme.visualDensity.baseSizeAdjustment.dy, + 0, double.infinity, ), ), diff --git a/lib/src/controls/surfaces/info_bar.dart b/lib/src/controls/surfaces/info_bar.dart index a17f745b9..83149a032 100644 --- a/lib/src/controls/surfaces/info_bar.dart +++ b/lib/src/controls/surfaces/info_bar.dart @@ -397,8 +397,9 @@ class InfoBar extends StatelessWidget { }(); return Container( constraints: BoxConstraints( - minHeight: (48 + theme.visualDensity.baseSizeAdjustment.dy).clamp( - 0.0, + minHeight: clampDouble( + 48 + theme.visualDensity.baseSizeAdjustment.dy, + 0, double.infinity, ), ), diff --git a/lib/src/controls/surfaces/list_tile.dart b/lib/src/controls/surfaces/list_tile.dart index 21b69609d..22cec0795 100644 --- a/lib/src/controls/surfaces/list_tile.dart +++ b/lib/src/controls/surfaces/list_tile.dart @@ -1,4 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; /// The default height of a one-line list tile. @@ -329,10 +330,12 @@ class ListTile extends StatelessWidget { child: Container( decoration: ShapeDecoration(shape: shape, color: tileColor), constraints: BoxConstraints( - minHeight: - (kOneLineTileHeight + - theme.visualDensity.baseSizeAdjustment.dy) - .clamp(0.0, double.infinity), + minHeight: clampDouble( + kOneLineTileHeight + + theme.visualDensity.baseSizeAdjustment.dy, + 0, + double.infinity, + ), minWidth: 88, ), margin: margin, diff --git a/lib/src/controls/surfaces/progress_indicators.dart b/lib/src/controls/surfaces/progress_indicators.dart index 7944063f6..32f8e7b65 100644 --- a/lib/src/controls/surfaces/progress_indicators.dart +++ b/lib/src/controls/surfaces/progress_indicators.dart @@ -260,7 +260,7 @@ class _ProgressBarPainter extends CustomPainter { if (value != null) { drawLine( Offset(0, size.height), - Offset(value!.clamp(0.0, 1.0) * size.width, size.height), + Offset(clampDouble(value!, 0, 1) * size.width, size.height), activeColor, ); return; @@ -559,7 +559,7 @@ class _RingPainter extends CustomPainter { canvas.drawArc( offset & size, _startAngle, - (value! / 100).clamp(0, 1) * _sweep, + clampDouble(value! / 100, 0, 1) * _sweep, false, paint, ); diff --git a/lib/src/controls/utils/custom_icons.dart b/lib/src/controls/utils/custom_icons.dart index 4e4cae3ec..43b5fc326 100644 --- a/lib/src/controls/utils/custom_icons.dart +++ b/lib/src/controls/utils/custom_icons.dart @@ -41,7 +41,7 @@ class ChevronDown extends StatelessWidget { child: Icon(icon, size: iconSize, color: iconColor), builder: (context, value, child) { return Opacity( - opacity: value.clamp(0.0, 1.0), + opacity: clampDouble(value, 0.0, 1.0), child: Transform.translate( filterQuality: FilterQuality.high, offset: Offset(0, value == 1 ? 0 : value * 1), diff --git a/lib/src/utils.dart b/lib/src/utils.dart index cb3bb25a6..7ba70f267 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -143,7 +143,11 @@ Offset horizontalPositionDependentBox({ if (size.height - margin * 2.0 < childSize.height) { y = (size.height - childSize.height) / 2.0; } else { - final normalizedTargetY = target.dy.clamp(margin, size.height - margin); + final normalizedTargetY = clampDouble( + target.dy, + margin, + size.height - margin, + ); final edge = margin + childSize.height / 2.0; if (normalizedTargetY < edge) { y = margin; diff --git a/test/menu_bar_test.dart b/test/menu_bar_test.dart index 8d7721b84..202250536 100644 --- a/test/menu_bar_test.dart +++ b/test/menu_bar_test.dart @@ -89,7 +89,7 @@ void main() { testWidgets('MenuBar animates open and closes on hover switch', ( tester, ) async { - const Duration animDuration = Duration(milliseconds: 80); + const animDuration = Duration(milliseconds: 80); await tester.pumpWidget( wrapApp( child: FluentTheme( @@ -136,7 +136,7 @@ void main() { await tester.pumpWidget(wrapApp(child: MenuBar(items: manyItems))); // All items should render (wrapping to multiple lines) - for (int i = 0; i < 15; i++) { + for (var i = 0; i < 15; i++) { expect(find.text('Menu $i'), findsOneWidget); } @@ -167,7 +167,7 @@ void main() { ); // All items should render (scrollable) - for (int i = 0; i < 15; i++) { + for (var i = 0; i < 15; i++) { expect(find.text('Menu $i'), findsOneWidget); } @@ -209,7 +209,6 @@ void main() { MenuFlyoutItem( text: const Text('Do Something'), onPressed: () => pressed = true, - closeAfterClick: true, ), ], ), From c054ea3fe36e68873624222e69c1d63c38127179 Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 29 Aug 2026 15:44:44 -0300 Subject: [PATCH 5/6] fix: build --- lib/src/controls/navigation/breadcrumb_bar.dart | 1 + lib/src/controls/pickers/pickers.dart | 1 + lib/src/controls/utils/custom_icons.dart | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/src/controls/navigation/breadcrumb_bar.dart b/lib/src/controls/navigation/breadcrumb_bar.dart index be93bffad..50de1586b 100644 --- a/lib/src/controls/navigation/breadcrumb_bar.dart +++ b/lib/src/controls/navigation/breadcrumb_bar.dart @@ -1,4 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; /// The alignment of the chevron icon in the breadcrumb bar. diff --git a/lib/src/controls/pickers/pickers.dart b/lib/src/controls/pickers/pickers.dart index d780d4e6e..1e63020d5 100644 --- a/lib/src/controls/pickers/pickers.dart +++ b/lib/src/controls/pickers/pickers.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; /// The padding used on the content of [DatePicker] and [TimePicker] diff --git a/lib/src/controls/utils/custom_icons.dart b/lib/src/controls/utils/custom_icons.dart index 43b5fc326..6c17e6d82 100644 --- a/lib/src/controls/utils/custom_icons.dart +++ b/lib/src/controls/utils/custom_icons.dart @@ -1,4 +1,5 @@ import 'package:fluent_ui/fluent_ui.dart'; +import 'package:flutter/foundation.dart'; /// The chevron down icon. /// From eafa0101b98f4a87569eed04875bfd8430fdc35a Mon Sep 17 00:00:00 2001 From: Bruno D'Luka Date: Sat, 29 Aug 2026 16:41:27 -0300 Subject: [PATCH 6/6] fix: tests --- test/combo_box_test.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/combo_box_test.dart b/test/combo_box_test.dart index 25892c3cc..200816af7 100644 --- a/test/combo_box_test.dart +++ b/test/combo_box_test.dart @@ -357,8 +357,8 @@ void main() { await tester.pumpAndSettle(); expect(tester.takeException(), isNull); - expect(find.byKey(const Key('bottom-combo-item-one')), findsOneWidget); - expect(find.byKey(const Key('bottom-combo-item-two')), findsOneWidget); - expect(find.byKey(const Key('bottom-combo-item-three')), findsOneWidget); + expect(find.byKey(const Key('bottom-combo-item-one')), findsWidgets); + expect(find.byKey(const Key('bottom-combo-item-two')), findsWidgets); + expect(find.byKey(const Key('bottom-combo-item-three')), findsWidgets); }); }