diff --git a/packages/flutter/lib/src/cupertino/desktop_text_selection_toolbar_button.dart b/packages/flutter/lib/src/cupertino/desktop_text_selection_toolbar_button.dart index 677530c1340..60e046d7a02 100644 --- a/packages/flutter/lib/src/cupertino/desktop_text_selection_toolbar_button.dart +++ b/packages/flutter/lib/src/cupertino/desktop_text_selection_toolbar_button.dart @@ -69,7 +69,7 @@ class CupertinoDesktopTextSelectionToolbarButton extends StatefulWidget { child = null; /// {@macro flutter.cupertino.CupertinoTextSelectionToolbarButton.onPressed} - final VoidCallback onPressed; + final VoidCallback? onPressed; /// {@macro flutter.cupertino.CupertinoTextSelectionToolbarButton.child} final Widget? child; diff --git a/packages/flutter/lib/src/cupertino/spell_check_suggestions_toolbar.dart b/packages/flutter/lib/src/cupertino/spell_check_suggestions_toolbar.dart index 436dd2b39de..fa449efb6a1 100644 --- a/packages/flutter/lib/src/cupertino/spell_check_suggestions_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/spell_check_suggestions_toolbar.dart @@ -66,7 +66,7 @@ class CupertinoSpellCheckSuggestionsToolbar extends StatelessWidget { CupertinoLocalizations.of(editableTextState.context); return [ ContextMenuButtonItem( - onPressed: () {}, + onPressed: null, label: localizations.noSpellCheckReplacementsLabel, ) ]; diff --git a/packages/flutter/lib/src/material/desktop_text_selection_toolbar_button.dart b/packages/flutter/lib/src/material/desktop_text_selection_toolbar_button.dart index 1b0ca204a41..48a8e09b2ec 100644 --- a/packages/flutter/lib/src/material/desktop_text_selection_toolbar_button.dart +++ b/packages/flutter/lib/src/material/desktop_text_selection_toolbar_button.dart @@ -51,7 +51,7 @@ class DesktopTextSelectionToolbarButton extends StatelessWidget { ); /// {@macro flutter.material.TextSelectionToolbarTextButton.onPressed} - final VoidCallback onPressed; + final VoidCallback? onPressed; /// {@macro flutter.material.TextSelectionToolbarTextButton.child} final Widget child; diff --git a/packages/flutter/lib/src/widgets/context_menu_button_item.dart b/packages/flutter/lib/src/widgets/context_menu_button_item.dart index c89bda3147b..8240d9b468c 100644 --- a/packages/flutter/lib/src/widgets/context_menu_button_item.dart +++ b/packages/flutter/lib/src/widgets/context_menu_button_item.dart @@ -47,7 +47,7 @@ class ContextMenuButtonItem { }); /// The callback to be called when the button is pressed. - final VoidCallback onPressed; + final VoidCallback? onPressed; /// The type of button this represents. final ContextMenuButtonType type; diff --git a/packages/flutter/test/cupertino/desktop_text_selection_toolbar_button_test.dart b/packages/flutter/test/cupertino/desktop_text_selection_toolbar_button_test.dart index 3a3af2ebacf..7c97c2e5dab 100644 --- a/packages/flutter/test/cupertino/desktop_text_selection_toolbar_button_test.dart +++ b/packages/flutter/test/cupertino/desktop_text_selection_toolbar_button_test.dart @@ -14,10 +14,10 @@ void main() { CupertinoApp( home: Center( child: CupertinoDesktopTextSelectionToolbarButton( - child: const Text('Tap me'), onPressed: () { pressed = true; }, + child: const Text('Tap me'), ), ), ), @@ -34,8 +34,8 @@ void main() { CupertinoApp( home: Center( child: CupertinoDesktopTextSelectionToolbarButton( - child: const Text('Tap me'), onPressed: () { }, + child: const Text('Tap me'), ), ), ), @@ -71,4 +71,21 @@ void main() { )); expect(opacity.opacity.value, 1.0); }); + + testWidgets('passing null to onPressed disables the button', (WidgetTester tester) async { + await tester.pumpWidget( + const CupertinoApp( + home: Center( + child: CupertinoDesktopTextSelectionToolbarButton( + onPressed: null, + child: Text('Tap me'), + ), + ), + ), + ); + + expect(find.byType(CupertinoButton), findsOneWidget); + final CupertinoButton button = tester.widget(find.byType(CupertinoButton)); + expect(button.enabled, isFalse); + }); } diff --git a/packages/flutter/test/cupertino/spell_check_suggestions_toolbar_test.dart b/packages/flutter/test/cupertino/spell_check_suggestions_toolbar_test.dart index 0ea80dae23a..dec2165a6c8 100644 --- a/packages/flutter/test/cupertino/spell_check_suggestions_toolbar_test.dart +++ b/packages/flutter/test/cupertino/spell_check_suggestions_toolbar_test.dart @@ -61,7 +61,7 @@ void main() { expect(labels, isNot(contains('yeller'))); }); - testWidgets('buildButtonItems builds a "No Replacements Found" button when no suggestions', (WidgetTester tester) async { + testWidgets('buildButtonItems builds a disabled "No Replacements Found" button when no suggestions', (WidgetTester tester) async { await tester.pumpWidget( CupertinoApp( home: _FakeEditableText(), @@ -73,8 +73,9 @@ void main() { CupertinoSpellCheckSuggestionsToolbar.buildButtonItems(editableTextState); expect(buttonItems, isNotNull); - expect(buttonItems!.length, 1); - expect(buttonItems.first.label, 'No Replacements Found'); + expect(buttonItems, hasLength(1)); + expect(buttonItems!.first.label, 'No Replacements Found'); + expect(buttonItems.first.onPressed, isNull); }); } diff --git a/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart b/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart index 6cf5b76fd00..708183bac8e 100644 --- a/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart +++ b/packages/flutter/test/cupertino/text_selection_toolbar_button_test.dart @@ -71,4 +71,20 @@ void main() { )); expect(opacity.opacity.value, 1.0); }); + + testWidgets('passing null to onPressed disables the button', (WidgetTester tester) async { + await tester.pumpWidget( + const CupertinoApp( + home: Center( + child: CupertinoTextSelectionToolbarButton( + child: Text('Tap me'), + ), + ), + ), + ); + + expect(find.byType(CupertinoButton), findsOneWidget); + final CupertinoButton button = tester.widget(find.byType(CupertinoButton)); + expect(button.enabled, isFalse); + }); } diff --git a/packages/flutter/test/material/desktop_text_selection_toolbar_button_test.dart b/packages/flutter/test/material/desktop_text_selection_toolbar_button_test.dart index 41350ab0252..80a82acaa25 100644 --- a/packages/flutter/test/material/desktop_text_selection_toolbar_button_test.dart +++ b/packages/flutter/test/material/desktop_text_selection_toolbar_button_test.dart @@ -14,10 +14,10 @@ void main() { MaterialApp( home: Center( child: DesktopTextSelectionToolbarButton( - child: const Text('Tap me'), onPressed: () { pressed = true; }, + child: const Text('Tap me'), ), ), ), @@ -28,4 +28,21 @@ void main() { await tester.tap(find.byType(DesktopTextSelectionToolbarButton)); expect(pressed, true); }); + + testWidgets('passing null to onPressed disables the button', (WidgetTester tester) async { + await tester.pumpWidget( + const MaterialApp( + home: Center( + child: DesktopTextSelectionToolbarButton( + onPressed: null, + child: Text('Cannot tap me'), + ), + ), + ), + ); + + expect(find.byType(TextButton), findsOneWidget); + final TextButton button = tester.widget(find.byType(TextButton)); + expect(button.enabled, isFalse); + }); } diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index ad25bf293a9..3042898190a 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -2424,7 +2424,7 @@ void main() { final ContextMenuButtonItem cutButton = items!.first; expect(cutButton.type, ContextMenuButtonType.cut); - cutButton.onPressed(); + cutButton.onPressed?.call(); await tester.pump(); expect(controller.text, isEmpty); @@ -2492,7 +2492,7 @@ void main() { final ContextMenuButtonItem copyButton = items!.first; expect(copyButton.type, ContextMenuButtonType.copy); - copyButton.onPressed(); + copyButton.onPressed?.call(); await tester.pump(); expect(controller.text, equals(text)); @@ -2560,7 +2560,7 @@ void main() { // Setting data which will be pasted into the clipboard. await Clipboard.setData(const ClipboardData(text: text)); - pasteButton.onPressed(); + pasteButton.onPressed?.call(); await tester.pump(); expect(controller.text, equals(text + text)); @@ -2619,7 +2619,7 @@ void main() { final ContextMenuButtonItem selectAllButton = items!.first; expect(selectAllButton.type, ContextMenuButtonType.selectAll); - selectAllButton.onPressed(); + selectAllButton.onPressed?.call(); await tester.pump(); expect(controller.text, equals(text)); @@ -15169,6 +15169,68 @@ testWidgets('Floating cursor ending with selection', (WidgetTester tester) async expect(find.text('DELETE'), matcher); }); + testWidgets('can show spell check suggestions toolbar when there are no spell check results on iOS', (WidgetTester tester) async { + tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = + true; + const TextEditingValue value = TextEditingValue( + text: 'tset test test', + selection: TextSelection(affinity: TextAffinity.upstream, baseOffset: 0, extentOffset: 4), + ); + controller.value = value; + await tester.pumpWidget( + CupertinoApp( + home: EditableText( + backgroundCursorColor: Colors.grey, + controller: controller, + focusNode: focusNode, + style: textStyle, + cursorColor: cursorColor, + selectionControls: materialTextSelectionControls, + spellCheckConfiguration: + const SpellCheckConfiguration( + misspelledTextStyle: CupertinoTextField.cupertinoMisspelledTextStyle, + spellCheckSuggestionsToolbarBuilder: CupertinoTextField.defaultSpellCheckSuggestionsToolbarBuilder, + ), + ), + ), + ); + + final EditableTextState state = + tester.state(find.byType(EditableText)); + + // Can't show the toolbar when there's no focus. + expect(state.showSpellCheckSuggestionsToolbar(), false); + await tester.pumpAndSettle(); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); + + // Can't show the toolbar when there are no spell check results. + expect(state.showSpellCheckSuggestionsToolbar(), false); + await tester.pumpAndSettle(); + expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); + + // Shows 'No Replacements Found' when there are spell check results but no + // suggestions. + state.spellCheckResults = const SpellCheckResults('test tset test', [SuggestionSpan(TextRange(start: 0, end: 4), [])]); + state.renderEditable.selectWordsInRange( + from: Offset.zero, + cause: SelectionChangedCause.tap, + ); + + await tester.pumpAndSettle(); + // Toolbar will only show on non-web platforms. + expect(state.showSpellCheckSuggestionsToolbar(), isTrue); + await tester.pumpAndSettle(); + + expect(find.byType(CupertinoTextSelectionToolbarButton), findsOneWidget); + expect(find.byType(CupertinoButton), findsOneWidget); + expect(find.text('No Replacements Found'), findsOneWidget); + final CupertinoButton button = tester.widget(find.byType(CupertinoButton)); + expect(button.enabled, isFalse); + }, + variant: const TargetPlatformVariant({ TargetPlatform.iOS }), + skip: kIsWeb, // [intended] + ); + testWidgets('cupertino spell check suggestions toolbar buttons correctly change the composing region', (WidgetTester tester) async { tester.binding.platformDispatcher.nativeSpellCheckServiceDefinedTestValue = true; diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index 7e3cdf45aff..7dabebc405e 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -1754,7 +1754,7 @@ void main() { expect(buttonItems[0].type, ContextMenuButtonType.copy); // Press `Copy` item - buttonItems[0].onPressed.call(); + buttonItems[0].onPressed?.call(); final SelectableRegionState regionState = tester.state(find.byType(SelectableRegion)); @@ -1808,7 +1808,7 @@ void main() { expect(buttonItems[1].type, ContextMenuButtonType.selectAll); // Press `Select All` item - buttonItems[1].onPressed.call(); + buttonItems[1].onPressed?.call(); final SelectableRegionState regionState = tester.state(find.byType(SelectableRegion));