Unverified Commit e070417a authored by Bruno Leroux's avatar Bruno Leroux Committed by GitHub

Add 'Share' button to the selection toolbar on Android (#139479)

## Description

This PR adds the 'Share' button to the text selection toolbar on Android.

## Related Issue

Fixes https://github.com/flutter/flutter/issues/138728

## Tests

Refactor a lot of existing tests in order to:
- make them more readable (avoid duplication by introducing helper functions, specify explictly check which buttons are expected).
- make them more accurate (check that expected buttons are visible instead of just checking the number of buttons).

For instance, previous tests contained sections such as:

```dart

      // Collapsed toolbar shows 3 buttons.
      expect(
        find.byType(CupertinoButton),
        isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)
      );

```

Where the comment is obsolete, the two cases (6 widgets and 3 widgets) are not explicit (which buttons are expected?), and not accurate (will pass if the number of buttons is right but the buttons are the wrong ones).
parent c6741614
......@@ -226,7 +226,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget {
case ContextMenuButtonType.searchWeb:
return localizations.searchWebButtonLabel;
case ContextMenuButtonType.share:
return localizations.searchWebButtonLabel;
return localizations.shareButtonLabel;
case ContextMenuButtonType.liveTextInput:
return localizations.scanTextButtonLabel;
case ContextMenuButtonType.custom:
......
......@@ -1882,6 +1882,10 @@ class EditableText extends StatefulWidget {
// If the paste button is enabled, don't render anything until the state
// of the clipboard is known, since it's used to determine if paste is
// shown.
// On Android, the share button is before the select all button.
final bool showShareBeforeSelectAll = defaultTargetPlatform == TargetPlatform.android;
resultButtonItem.addAll(<ContextMenuButtonItem>[
if (onCut != null)
ContextMenuButtonItem(
......@@ -1898,6 +1902,11 @@ class EditableText extends StatefulWidget {
onPressed: onPaste,
type: ContextMenuButtonType.paste,
),
if (onShare != null && showShareBeforeSelectAll)
ContextMenuButtonItem(
onPressed: onShare,
type: ContextMenuButtonType.share,
),
if (onSelectAll != null)
ContextMenuButtonItem(
onPressed: onSelectAll,
......@@ -1913,7 +1922,7 @@ class EditableText extends StatefulWidget {
onPressed: onSearchWeb,
type: ContextMenuButtonType.searchWeb,
),
if (onShare != null)
if (onShare != null && !showShareBeforeSelectAll)
ContextMenuButtonItem(
onPressed: onShare,
type: ContextMenuButtonType.share,
......@@ -2300,13 +2309,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
@override
bool get shareEnabled {
if (defaultTargetPlatform != TargetPlatform.iOS) {
return false;
switch (defaultTargetPlatform) {
case TargetPlatform.android:
case TargetPlatform.iOS:
return !widget.obscureText
&& !textEditingValue.selection.isCollapsed
&& textEditingValue.selection.textInside(textEditingValue.text).trim() != '';
case TargetPlatform.macOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return false;
}
return !widget.obscureText
&& !textEditingValue.selection.isCollapsed
&& textEditingValue.selection.textInside(textEditingValue.text).trim() != '';
}
@override
......@@ -2516,9 +2530,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
/// Launch the share interface for the current selection,
/// as in the "Share" edit menu button on iOS.
/// as in the "Share..." edit menu button on iOS.
///
/// Currently this is only implemented for iOS.
/// Currently this is only implemented for iOS and Android.
///
/// When 'obscureText' is true or the selection is empty,
/// this function will not do anything
......
......@@ -10,6 +10,7 @@ import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/live_text_utils.dart';
import '../widgets/text_selection_toolbar_utils.dart';
void main() {
final MockClipboard mockClipboard = MockClipboard();
......@@ -31,13 +32,6 @@ void main() {
);
});
Finder findOverflowNextButton() {
return find.byWidgetPredicate((Widget widget) =>
widget is CustomPaint &&
'${widget.painter?.runtimeType}' == '_RightCupertinoChevronPainter',
);
}
testWidgetsWithLeakTracking('Builds the right toolbar on each platform, including web, and shows buttonItems', (WidgetTester tester) async {
const String buttonText = 'Click me';
......@@ -188,27 +182,55 @@ void main() {
));
expect(find.byKey(key), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(6));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(findCupertinoOverflowNextButton(), findsOneWidget);
await tapCupertinoOverflowNextButton(tester);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4));
expect(findCupertinoOverflowBackButton(), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
case TargetPlatform.fuchsia:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(6));
case TargetPlatform.iOS:
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(6));
expect(findOverflowNextButton(), findsOneWidget);
await tester.tapAt(tester.getCenter(findOverflowNextButton()));
await tester.pumpAndSettle();
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(findCupertinoOverflowNextButton(), findsOneWidget);
await tapCupertinoOverflowNextButton(tester);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4));
expect(findCupertinoOverflowBackButton(), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
case TargetPlatform.macOS:
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(8));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
}
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
......
......@@ -20,12 +20,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart' show OverflowWidgetTextEditingController;
import '../widgets/editable_text_utils.dart' show OverflowWidgetTextEditingController, isContextMenuProvidedByPlatform;
import '../widgets/live_text_utils.dart';
import '../widgets/semantics_tester.dart';
// On web, the context menu (aka toolbar) is provided by the browser.
const bool isContextMenuProvidedByPlatform = isBrowser;
import '../widgets/text_selection_toolbar_utils.dart';
class MockTextSelectionControls extends TextSelectionControls {
@override
......@@ -208,7 +206,6 @@ void main() {
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
testWidgetsWithLeakTracking(
'Live Text button shows and hides correctly when LiveTextStatus changes',
(WidgetTester tester) async {
......@@ -353,7 +350,7 @@ void main() {
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
);
testWidgetsWithLeakTracking('Share shows up on iOS only', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Share shows up on iOS and Android', (WidgetTester tester) async {
String? lastShare;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
......@@ -378,8 +375,6 @@ void main() {
),
);
final bool isTargetPlatformiOS = defaultTargetPlatform == TargetPlatform.iOS;
// Long press to put the cursor after the "s".
const int index = 3;
await tester.longPressAt(textOffsetToPosition(tester, index));
......@@ -392,12 +387,10 @@ void main() {
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 4));
expect(find.text('Share...'), isTargetPlatformiOS? findsOneWidget : findsNothing);
expect(find.text('Share...'), findsOneWidget);
if (isTargetPlatformiOS) {
await tester.tap(find.text('Share...'));
expect(lastShare, 'Test');
}
await tester.tap(find.text('Share...'));
expect(lastShare, 'Test');
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }),
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
......@@ -694,7 +687,7 @@ void main() {
await tester.tap(find.byKey(key2));
await tester.enterText(find.byKey(key2), 'dcba');
await tester.pump();
await tester.pumpAndSettle();
// Focus and selection is active on first TextField, so the second TextFields
// selectionColor should be dropped.
......@@ -705,7 +698,7 @@ void main() {
expect(state1.widget.selectionColor, selectionColor);
expect(state2.widget.selectionColor, null);
// Focus and selection is active on second TextField, so the first TextFields
// Focus and selection is active on second TextField, so the first TextField
// selectionColor should be dropped.
await tester.tap(find.byKey(key2));
await tester.pump();
......@@ -1850,7 +1843,7 @@ void main() {
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
);
testWidgetsWithLeakTracking('text field toolbar options correctly changes options on non-Apple Platforms', (WidgetTester tester) async {
testWidgetsWithLeakTracking('text field toolbar options correctly changes options on non-Apple platforms', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(
text: 'Atwater Peel Sherbrooke Bonaventure',
);
......@@ -1975,6 +1968,7 @@ void main() {
await tester.tap(find.text('Select All'));
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
await tester.tap(find.text('Cut'));
await tester.pump();
......@@ -2061,7 +2055,12 @@ void main() {
expect(controller.selection.baseOffset, isTargetPlatformIOS ? 7 : 6);
// Toolbar shows on mobile.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(2) : findsNothing);
if (isTargetPlatformIOS) {
expectCupertinoToolbarForCollapsedSelection();
} else {
// After a tap, macOS does not show a selection toolbar for a collapsed selection.
expectNoCupertinoToolbar();
}
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking(
......@@ -2109,7 +2108,7 @@ void main() {
// the selection has not changed we toggle the toolbar.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(2));
expectCupertinoToolbarForCollapsedSelection();
// Tap the 'v' position again to hide the toolbar.
await tester.tapAt(vPos);
......@@ -2127,7 +2126,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.upstream);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(2));
expectCupertinoToolbarForCollapsedSelection();
// Tap at the same position to toggle the toolbar.
await tester.tapAt(endPos);
......@@ -2135,7 +2134,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.upstream);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Tap at the beginning of the second line to move the cursor to the front of the first word on the
// second line, where the word wrap is. Since there is a word wrap here, and the direction of the text is LTR,
......@@ -2145,7 +2144,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.downstream);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -2194,8 +2193,7 @@ void main() {
const TextSelection(baseOffset: 24, extentOffset: 35),
);
// Selected text shows 5 toolbar buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Tap the selected word to hide the toolbar and retain the selection.
await tester.tapAt(vPos);
......@@ -2213,13 +2211,15 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 24, extentOffset: 35),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Tap past the selected word to move the cursor and hide the toolbar.
await tester.tapAt(ePos);
await tester.pumpAndSettle();
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), findsNothing);
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
......@@ -2270,8 +2270,8 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 4 toolbar buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
// The toolbar now shows up.
expectCupertinoToolbarForPartialSelection();
// Tap somewhere else to move the cursor.
await tester.tapAt(textOffsetToPosition(tester, index));
......@@ -2323,20 +2323,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
if (isContextMenuProvidedByPlatform) {
expect(find.byType(CupertinoButton), findsNothing);
} else {
switch (defaultTargetPlatform) {
case TargetPlatform.macOS:
expect(find.byType(CupertinoButton), findsNWidgets(3));
case TargetPlatform.iOS:
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.byType(CupertinoButton), findsNWidgets(6));
}
}
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -2484,7 +2471,7 @@ void main() {
// Toolbar should re-appear after a drag.
await gesture.up();
await tester.pump();
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
// Skip the magnifier hide animation, so it can release resources.
await tester.pump(const Duration(milliseconds: 150));
......@@ -2552,8 +2539,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 3 toolbar buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking(
......@@ -2587,22 +2573,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
final Matcher matchToolbarButtons;
if (isContextMenuProvidedByPlatform) {
matchToolbarButtons = findsNothing;
} else {
switch (defaultTargetPlatform) {
case TargetPlatform.macOS:
case TargetPlatform.iOS:
matchToolbarButtons = findsNWidgets(3);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
matchToolbarButtons = findsNWidgets(4);
}
}
expect(find.byType(CupertinoButton), matchToolbarButtons);
expectCupertinoToolbarForPartialSelection();
await gesture.up();
await tester.pump();
......@@ -2612,9 +2583,8 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), matchToolbarButtons);
},
);
expectCupertinoToolbarForPartialSelection();
}, variant: TargetPlatformVariant.all());
testWidgetsWithLeakTracking(
'tap after a double tap select is not affected',
......@@ -2890,7 +2860,7 @@ void main() {
);
// The selection menu is not present.
expect(find.byType(CupertinoButton), findsNWidgets(0));
expectNoCupertinoToolbar();
await gesture.up();
await tester.pump();
......@@ -2900,7 +2870,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 35, extentOffset: 35),
);
expect(find.byType(CupertinoButton), findsNWidgets(0));
expectNoCupertinoToolbar();
},
);
......@@ -2939,7 +2909,7 @@ void main() {
);
// The selection menu is not present.
expect(find.byType(CupertinoButton), findsNWidgets(0));
expectNoCupertinoToolbar();
await gesture.up();
await tester.pump();
......@@ -2949,7 +2919,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 35),
);
expect(find.byType(CupertinoButton), findsNWidgets(0));
expectNoCupertinoToolbar();
},
);
......@@ -2986,7 +2956,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 35),
);
// Selected text shows paste toolbar buttons.
// Selected text shows paste toolbar button.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(1));
await gesture.up();
......@@ -2997,6 +2967,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 35),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(1));
},
);
......@@ -3074,10 +3045,9 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7, affinity: TextAffinity.upstream),
);
// Non-Collapsed toolbar shows 4 buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS}),
);
testWidgetsWithLeakTracking(
......@@ -3112,15 +3082,7 @@ void main() {
const TextSelection.collapsed(offset: 3, affinity: TextAffinity.upstream),
);
// Collapsed toolbar shows 2 buttons.
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform
? findsNothing
: defaultTargetPlatform == TargetPlatform.iOS
? findsNWidgets(2)
: findsNWidgets(1),
);
expectCupertinoToolbarForCollapsedSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3151,17 +3113,14 @@ void main() {
await tester.longPressAt(ePos);
await tester.pump(const Duration(milliseconds: 50));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
if (kIsWeb) {
expect(find.byType(CupertinoButton), findsNothing);
} else {
expect(find.byType(CupertinoButton), findsNWidgets(isTargetPlatformIOS ? 2 : 1));
}
expectCupertinoToolbarForCollapsedSelection();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 6);
// Tap in a slightly different position to avoid hitting the context menu
// on desktop.
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
final Offset secondTapPos = isTargetPlatformIOS
? ePos
: ePos + const Offset(-1.0, 0.0);
......@@ -3173,7 +3132,7 @@ void main() {
expect(controller.selection.baseOffset, 6);
// The toolbar from the long press is now dismissed by the second tap.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking(
......@@ -3205,7 +3164,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7, affinity: TextAffinity.upstream),
);
// Toolbar only shows up on long press up.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
......@@ -3215,7 +3174,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 12, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(200, 0));
await tester.pump();
......@@ -3225,7 +3184,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 23, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -3235,8 +3194,9 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 23, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3274,7 +3234,7 @@ void main() {
const TextSelection.collapsed(offset: 3, affinity: TextAffinity.upstream),
);
// Toolbar only shows up on long press up.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(50, 0));
await tester.pump();
......@@ -3284,7 +3244,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 6, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(50, 0));
await tester.pump();
......@@ -3294,7 +3254,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 9, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -3305,14 +3265,7 @@ void main() {
const TextSelection.collapsed(offset: 9, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform
? findsNothing
: defaultTargetPlatform == TargetPlatform.iOS
? findsNWidgets(2)
: findsNWidgets(1),
);
expectCupertinoToolbarForCollapsedSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3385,8 +3338,9 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 66, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectCupertinoToolbarForFullSelection();
lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -3479,16 +3433,7 @@ void main() {
const TextSelection.collapsed(offset: 66, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
final int toolbarButtons;
if (isContextMenuProvidedByPlatform) {
toolbarButtons = 0;
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
toolbarButtons = 2;
} else {
// MacOS has no 'Select all' button.
toolbarButtons = 1;
}
expect(find.byType(CupertinoButton), findsNWidgets(toolbarButtons));
expectCupertinoToolbarForCollapsedSelection();
lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -3547,16 +3492,7 @@ void main() {
);
// Long press toolbar.
final int toolbarButtons;
if (isContextMenuProvidedByPlatform) {
toolbarButtons = 0;
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
toolbarButtons = 2;
} else {
// MacOS has no 'Select all' button.
toolbarButtons = 1;
}
expect(find.byType(CupertinoButton), findsNWidgets(toolbarButtons));
expectCupertinoToolbarForCollapsedSelection();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking(
......@@ -3593,11 +3529,7 @@ void main() {
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 3);
if (isContextMenuProvidedByPlatform) {
expect(find.byType(CupertinoButton), findsNothing);
} else {
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(2) : findsNWidgets(1));
}
expectCupertinoToolbarForCollapsedSelection();
await tester.tapAt(pPos);
await tester.pump(const Duration(milliseconds: 50));
......@@ -3615,8 +3547,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Shows toolbar.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
testWidgetsWithLeakTracking(
......@@ -3637,7 +3568,6 @@ void main() {
);
final Offset textFieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(textFieldStart + const Offset(50.0, 5.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -3651,7 +3581,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Double tap selecting the same word somewhere else is fine.
await tester.tapAt(textFieldStart + const Offset(100.0, 5.0));
......@@ -3671,7 +3601,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textFieldStart + const Offset(150.0, 5.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -3687,7 +3617,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
group('Triple tap/click', () {
......@@ -3967,7 +3897,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pumpAndSettle();
......@@ -3981,7 +3911,8 @@ void main() {
// First tap hides the toolbar and moves the selection.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 6);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Second tap shows the toolbar and selects the word.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
await tester.pump();
......@@ -3989,7 +3920,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
// Third tap shows the toolbar and selects the paragraph.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
......@@ -3998,7 +3929,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 35),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectCupertinoToolbarForFullSelection();
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -4013,7 +3944,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectCupertinoToolbarForPartialSelection();
// Third tap selects the paragraph and shows the toolbar.
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
......@@ -4022,7 +3953,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 35),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectCupertinoToolbarForFullSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }),
);
......@@ -4048,7 +3979,6 @@ void main() {
);
final Offset textfieldStart = tester.getTopLeft(find.byType(CupertinoTextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -4061,7 +3991,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pumpAndSettle(kDoubleTapTimeout);
......@@ -4087,7 +4017,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
// Third tap shows the toolbar and selects the paragraph.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
......@@ -4096,7 +4026,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 36),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(150.0, 25.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -4114,7 +4044,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 44, extentOffset: 50),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
// Third tap selects the paragraph and shows the toolbar.
await tester.tapAt(textfieldStart + const Offset(150.0, 25.0));
......@@ -4123,7 +4053,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 36, extentOffset: 66),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : (isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -5114,22 +5044,7 @@ void main() {
await gesture.up();
await tester.pumpAndSettle();
// Shows toolbar.
final Matcher matchToolbarButtons;
if (isContextMenuProvidedByPlatform) {
matchToolbarButtons = findsNothing;
} else {
switch (defaultTargetPlatform) {
case TargetPlatform.macOS:
case TargetPlatform.iOS:
matchToolbarButtons = findsNWidgets(3);
case TargetPlatform.android:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
matchToolbarButtons = findsNWidgets(4);
}
}
expect(find.byType(CupertinoButton), matchToolbarButtons);
expectCupertinoToolbarForPartialSelection();
});
testWidgetsWithLeakTracking('force press on unsupported devices falls back to tap', (WidgetTester tester) async {
......
......@@ -11,6 +11,7 @@ import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart';
import '../widgets/live_text_utils.dart';
import '../widgets/text_selection_toolbar_utils.dart';
void main() {
final MockClipboard mockClipboard = MockClipboard();
......@@ -26,13 +27,6 @@ void main() {
await Clipboard.setData(const ClipboardData(text: 'Clipboard data'));
});
Finder findOverflowNextButton() {
return find.byWidgetPredicate((Widget widget) =>
widget is CustomPaint &&
'${widget.painter?.runtimeType}' == '_RightCupertinoChevronPainter',
);
}
testWidgetsWithLeakTracking('Builds the right toolbar on each platform, including web, and shows buttonItems', (WidgetTester tester) async {
const String buttonText = 'Click me';
......@@ -206,30 +200,74 @@ void main() {
);
expect(find.byKey(key), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(6));
case TargetPlatform.fuchsia:
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(8));
expect(find.text('Look Up'), findsOneWidget);
expect(findMaterialOverflowNextButton(), findsOneWidget); // Material overflow buttons are not TextSelectionToolbarTextButton.
await tapMaterialOverflowNextButton(tester);
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(2));
expect(find.text('Search Web'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
expect(findMaterialOverflowBackButton(), findsOneWidget); // Material overflow buttons are not TextSelectionToolbarTextButton.
case TargetPlatform.iOS:
expect(find.text('Select All'), findsOneWidget);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(6));
await tester.tapAt(tester.getCenter(findOverflowNextButton()));
await tester.pumpAndSettle();
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(findCupertinoOverflowNextButton(), findsOneWidget);
await tapCupertinoOverflowNextButton(tester);
expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4));
expect(findCupertinoOverflowBackButton(), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
case TargetPlatform.fuchsia:
expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(8));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(8));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
case TargetPlatform.macOS:
expect(find.text('Select All'), findsOneWidget);
expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(8));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(findLiveTextButton(), findsOneWidget);
}
},
skip: kIsWeb, // [intended] on web the browser handles the context menu.
......
......@@ -29,6 +29,7 @@ import '../widgets/clipboard_utils.dart';
import '../widgets/editable_text_utils.dart';
import '../widgets/live_text_utils.dart';
import '../widgets/semantics_tester.dart';
import '../widgets/text_selection_toolbar_utils.dart';
import 'feedback_tester.dart';
void main() {
......@@ -278,7 +279,7 @@ void main() {
);
// Initially, the menu is not shown and there is no selection.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
expect(controller.selection, const TextSelection(baseOffset: -1, extentOffset: -1));
final Offset midBlah1 = textOffsetToPosition(tester, 2);
......@@ -303,7 +304,7 @@ void main() {
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2');
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 5));
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Paste it at the end.
await gesture.down(textOffsetToPosition(tester, controller.text.length));
......@@ -332,7 +333,7 @@ void main() {
await tester.pumpAndSettle();
expect(controller.text, ' blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 0));
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS }),
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
......@@ -353,7 +354,7 @@ void main() {
);
// Initially, the menu is not shown and there is no selection.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
expect(controller.selection, const TextSelection(baseOffset: -1, extentOffset: -1));
final Offset midBlah1 = textOffsetToPosition(tester, 2);
......@@ -412,7 +413,7 @@ void main() {
await tester.pumpAndSettle();
expect(controller.text, 'blah1 blah2');
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 5));
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Paste it at the end.
gesture = await tester.startGesture(
......@@ -476,7 +477,7 @@ void main() {
await tester.pumpAndSettle();
expect(controller.text, ' blah2blah1');
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 0));
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.windows }),
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
......@@ -580,7 +581,7 @@ void main() {
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
);
testWidgetsWithLeakTracking('Share shows up on iOS only', (WidgetTester tester) async {
testWidgetsWithLeakTracking('Share shows up on iOS and Android', (WidgetTester tester) async {
String? lastShare;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
......@@ -618,12 +619,15 @@ void main() {
await tester.pumpAndSettle();
expect(controller.selection, const TextSelection(baseOffset: 0, extentOffset: 4));
expect(find.text('Share...'), isTargetPlatformiOS? findsOneWidget : findsNothing);
if (isTargetPlatformiOS) {
expect(find.text('Share...'), findsOneWidget);
await tester.tap(find.text('Share...'));
expect(lastShare, 'Test');
} else {
expect(find.text('Share'), findsOneWidget);
await tester.tap(find.text('Share'));
}
expect(lastShare, 'Test');
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.android }),
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.
......@@ -9256,7 +9260,7 @@ void main() {
);
// But don't trigger the toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -9325,7 +9329,7 @@ void main() {
);
// But don't trigger the toolbar.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }));
testWidgetsWithLeakTracking(
......@@ -9360,8 +9364,13 @@ void main() {
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 7 : 6);
// Toolbar shows on iOS.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformMobile ? findsNWidgets(2) : findsNothing);
// Toolbar shows on mobile only.
if (isTargetPlatformMobile) {
expectCupertinoToolbarForCollapsedSelection();
} else {
// After a tap, macOS does not show a selection toolbar for a collapsed selection.
expectNoCupertinoToolbar();
}
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -9404,7 +9413,7 @@ void main() {
// at the end of the word 'Bonaventure|'.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await tester.tapAt(vPos);
await tester.pumpAndSettle(const Duration(milliseconds: 500));
......@@ -9412,14 +9421,14 @@ void main() {
// the selection has not changed we toggle the toolbar.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(2));
expectCupertinoToolbarForCollapsedSelection();
// Tap the 'v' position again to hide the toolbar.
await tester.tapAt(vPos);
await tester.pumpAndSettle();
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Long press at the end of the first line to move the cursor to the end of the first line
// where the word wrap is. Since there is a word wrap here, and the direction of the text is LTR,
......@@ -9430,7 +9439,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.upstream);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(2));
expectCupertinoToolbarForCollapsedSelection();
// Tap at the same position to toggle the toolbar.
await tester.tapAt(endPos);
......@@ -9438,7 +9447,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.upstream);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Tap at the beginning of the second line to move the cursor to the front of the first word on the
// second line, where the word wrap is. Since there is a word wrap here, and the direction of the text is LTR,
......@@ -9448,7 +9457,7 @@ void main() {
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 46);
expect(controller.selection.affinity, TextAffinity.downstream);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -9498,8 +9507,8 @@ void main() {
const TextSelection(baseOffset: 24, extentOffset: 35),
);
// Selected text shows 4 toolbar buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
// The toolbar shows up.
expectCupertinoToolbarForPartialSelection();
// Tap the selected word to hide the toolbar and retain the selection.
await tester.tapAt(vPos);
......@@ -9508,7 +9517,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 24, extentOffset: 35),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Tap the selected word to show the toolbar and retain the selection.
await tester.tapAt(vPos);
......@@ -9517,14 +9526,14 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 24, extentOffset: 35),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Tap past the selected word to move the cursor and hide the toolbar.
await tester.tapAt(ePos);
await tester.pumpAndSettle();
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 35);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -9571,8 +9580,8 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 5 toolbar buttons.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
// The toolbar shows up.
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -9651,8 +9660,8 @@ void main() {
const TextSelection.collapsed(offset: 35),
);
// Selected text shows nothing.
expect(find.byType(CupertinoButton), findsNothing);
// Selected text shows no toolbar.
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -9698,8 +9707,8 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 4 toolbar buttons: cut, copy, paste, select all
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
// The toolbar shows up.
expectMaterialToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }),
);
......@@ -9733,6 +9742,10 @@ void main() {
// Selected text shows 4 toolbar buttons: cut, copy, paste, select all
expect(find.byType(TextButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
},
variant: TargetPlatformVariant.all(),
skip: isContextMenuProvidedByPlatform, // [intended] only applies to platforms where we supply the context menu.,
......@@ -9884,14 +9897,14 @@ void main() {
await tester.pumpAndSettle();
// Toolbar should be hidden during a drag.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
expect(controller.selection.baseOffset, testValue.indexOf('d'));
expect(controller.selection.extentOffset, testValue.indexOf('i') + 1);
// Toolbar should re-appear after a drag.
await gesture.up();
await tester.pump();
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectMaterialToolbarForPartialSelection();
},
);
......@@ -10160,7 +10173,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectMaterialToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pumpAndSettle();
......@@ -10174,7 +10187,7 @@ void main() {
// First tap hides the toolbar and moves the selection.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 6);
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
// Second tap shows the toolbar and selects the word.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
await tester.pump();
......@@ -10182,7 +10195,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectMaterialToolbarForPartialSelection();
// Third tap shows the toolbar and selects the paragraph.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
......@@ -10191,14 +10204,14 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 35),
);
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectMaterialToolbarForFullSelection();
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
// First tap moved the cursor and hid the toolbar.
expect(controller.selection.isCollapsed, true);
expect(controller.selection.baseOffset, 9);
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
// Second tap selects the word.
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pump();
......@@ -10206,7 +10219,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectMaterialToolbarForPartialSelection();
// Third tap selects the paragraph and shows the toolbar.
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
......@@ -10215,7 +10228,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 35),
);
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectMaterialToolbarForFullSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }),
);
......@@ -10252,7 +10265,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pumpAndSettle(kDoubleTapTimeout);
......@@ -10268,7 +10281,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 36),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Second tap shows the toolbar and selects the word.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
await tester.pump();
......@@ -10276,7 +10289,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Third tap shows the toolbar and selects the paragraph.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
......@@ -10285,7 +10298,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 36),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(150.0, 50.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -10294,7 +10307,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 50, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Second tap selects the word.
await tester.tapAt(textfieldStart + const Offset(150.0, 50.0));
await tester.pump();
......@@ -10302,7 +10315,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 44, extentOffset: 50),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Third tap selects the paragraph and shows the toolbar.
await tester.tapAt(textfieldStart + const Offset(150.0, 50.0));
......@@ -10311,7 +10324,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 36, extentOffset: 66),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -11300,8 +11313,8 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Selected text shows 4 toolbar buttons: cut, copy, paste, select all
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
// The toolbar shows up.
expectMaterialToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }),
);
......@@ -11503,7 +11516,6 @@ void main() {
);
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -11517,8 +11529,8 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 4 toolbar buttons on iOS, and 3 on macOS.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
// The toolbar shows up.
expectCupertinoToolbarForPartialSelection();
await gesture.up();
await tester.pump();
......@@ -11530,7 +11542,7 @@ void main() {
);
// The toolbar is still showing.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11580,7 +11592,7 @@ void main() {
expect(controller.selection.baseOffset, isTargetPlatformMobile ? 7 : 6);
// No toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11619,12 +11631,7 @@ void main() {
const TextSelection.collapsed(offset: 3),
);
// Collapsed toolbar shows 2 buttons.
final int buttons = defaultTargetPlatform == TargetPlatform.iOS ? 2 : 1;
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(buttons),
);
expectCupertinoToolbarForCollapsedSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11648,7 +11655,6 @@ void main() {
);
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.longPressAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pumpAndSettle();
......@@ -11659,11 +11665,8 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Collapsed toolbar shows 3 buttons.
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3)
);
// The toolbar shows up.
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11696,8 +11699,8 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Collapsed toolbar shows 4 buttons: cut, copy, paste, select all
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
// The toolbar shows up.
expectMaterialToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.linux, TargetPlatform.windows }),
);
......@@ -11743,7 +11746,7 @@ void main() {
expect(controller.selection.baseOffset, 6);
// The toolbar from the long press is now dismissed by the second tap.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11776,7 +11779,7 @@ void main() {
const TextSelection(baseOffset: 13, extentOffset: 23),
);
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
......@@ -11787,7 +11790,7 @@ void main() {
const TextSelection(baseOffset: 13, extentOffset: 35),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
// The selection is moved on a backwards drag.
await gesture.moveBy(const Offset(-200, 0));
......@@ -11799,7 +11802,7 @@ void main() {
const TextSelection(baseOffset: 23, extentOffset: 8),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
await gesture.moveBy(const Offset(-100, 0));
await tester.pump();
......@@ -11810,7 +11813,7 @@ void main() {
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// Still no toolbar.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -11821,7 +11824,7 @@ void main() {
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// The toolbar now shows up.
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(4));
expectMaterialToolbarForPartialSelection();
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11860,7 +11863,7 @@ void main() {
const TextSelection.collapsed(offset: 3),
);
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(50, 0));
await tester.pump();
......@@ -11871,7 +11874,7 @@ void main() {
const TextSelection.collapsed(offset: 6),
);
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(50, 0));
await tester.pump();
......@@ -11882,7 +11885,7 @@ void main() {
const TextSelection.collapsed(offset: 9),
);
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -11893,11 +11896,7 @@ void main() {
const TextSelection.collapsed(offset: 9),
);
// The toolbar now shows up.
final int buttons = defaultTargetPlatform == TargetPlatform.iOS ? 2 : 1;
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(buttons),
);
expectCupertinoToolbarForCollapsedSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11921,7 +11920,6 @@ void main() {
);
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
final TestGesture gesture =
await tester.startGesture(textfieldStart + const Offset(50.0, 9.0));
......@@ -11933,7 +11931,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Cursor move doesn't trigger a toolbar initially.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
......@@ -11944,7 +11942,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 12),
);
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(100, 0));
await tester.pump();
......@@ -11955,7 +11953,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 23),
);
// Still no toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -11966,7 +11964,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 23),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -11995,7 +11993,7 @@ void main() {
);
expect(lastCharEndpoint.length, 1);
// Just testing the test and making sure that the last character is off
// Just testing the text and making sure that the last character is off
// the right side of the screen.
expect(lastCharEndpoint[0].point.dx, 1056);
......@@ -12009,7 +12007,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7, affinity: TextAffinity.upstream),
);
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
await gesture.moveBy(const Offset(900, 5));
// To the edge of the screen basically.
......@@ -12031,7 +12029,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 66, affinity: TextAffinity.upstream),
); // We're at the edge now.
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -12042,7 +12040,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 66, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
expect(find.byType(TextButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(3));
expectMaterialToolbarForFullSelection();
lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -12078,7 +12076,6 @@ void main() {
);
final RenderEditable renderEditable = findRenderEditable(tester);
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
List<TextSelectionPoint> lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -12099,7 +12096,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(900, 5));
// To the edge of the screen basically.
......@@ -12121,7 +12118,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 66, affinity: TextAffinity.upstream),
); // We're at the edge now.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -12132,7 +12129,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 66, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForFullSelection();
lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -12192,7 +12189,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 19, affinity: TextAffinity.upstream),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.moveBy(const Offset(600, 0));
// To the edge of the screen basically.
......@@ -12214,7 +12211,7 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 66, affinity: TextAffinity.upstream),
); // We're at the edge now.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -12225,11 +12222,7 @@ void main() {
const TextSelection.collapsed(offset: 66, affinity: TextAffinity.upstream),
);
// The toolbar now shows up.
final int buttons = defaultTargetPlatform == TargetPlatform.iOS ? 2 : 1;
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(buttons),
);
expectCupertinoToolbarForCollapsedSelection();
lastCharEndpoint = renderEditable.getEndpointsForSelection(
const TextSelection.collapsed(offset: 66), // Last character's position.
......@@ -12295,7 +12288,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 19, extentOffset: 66),
); // We're at the edge now.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.up();
await tester.pumpAndSettle();
......@@ -12667,12 +12660,8 @@ void main() {
const TextSelection.collapsed(offset: 6),
);
// Long press toolbar.
final int buttons = defaultTargetPlatform == TargetPlatform.iOS ? 2 : 1;
expect(
find.byType(CupertinoButton),
isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(buttons),
);
// The toolbar shows up.
expectCupertinoToolbarForCollapsedSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -12730,7 +12719,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformMobile ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -12789,7 +12778,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// The text selection toolbar isn't shown on Mac without a right click.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: TargetPlatformVariant.desktop(),
);
......@@ -12814,7 +12803,6 @@ void main() {
);
final Offset textfieldStart = tester.getTopLeft(find.byType(TextField));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(textfieldStart + const Offset(50.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -12828,7 +12816,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
// Double tap selecting the same word somewhere else is fine.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
......@@ -12838,7 +12826,8 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Second tap shows the toolbar and retains the selection.
await tester.tapAt(textfieldStart + const Offset(100.0, 9.0));
// Wait for the consecutive tap timer to timeout so the next
......@@ -12848,7 +12837,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForPartialSelection();
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -12857,14 +12846,14 @@ void main() {
controller.selection,
const TextSelection.collapsed(offset: 12, affinity: TextAffinity.upstream)
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await tester.tapAt(textfieldStart + const Offset(150.0, 9.0));
await tester.pumpAndSettle();
expect(
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : isTargetPlatformIOS ? findsNWidgets(6) : findsNWidgets(3));
expectCupertinoToolbarForPartialSelection();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -12915,7 +12904,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// Double tap selecting the same word somewhere else is fine.
await gesture.down(textFieldStart + const Offset(100.0, 9.0));
......@@ -12937,7 +12926,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
await gesture.down(textFieldStart + const Offset(150.0, 9.0));
await tester.pump();
......@@ -12956,7 +12945,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS, TargetPlatform.windows, TargetPlatform.linux }),
);
......@@ -13199,7 +13188,7 @@ void main() {
// We don't want this gesture to select any word on Android.
expect(controller.selection, const TextSelection.collapsed(offset: -1));
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.fuchsia }));
testWidgetsWithLeakTracking('Force press sets selection on desktop platforms that do not support it', (WidgetTester tester) async {
......@@ -13242,7 +13231,7 @@ void main() {
// We don't want this gesture to select any word on Android.
expect(controller.selection, const TextSelection.collapsed(offset: 9));
expect(find.byType(TextButton), findsNothing);
expectNoMaterialToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.linux, TargetPlatform.windows }));
testWidgetsWithLeakTracking('force press selects word', (WidgetTester tester) async {
......@@ -13291,7 +13280,7 @@ void main() {
await gesture.up();
await tester.pumpAndSettle();
expect(find.byType(CupertinoButton), isContextMenuProvidedByPlatform ? findsNothing : findsNWidgets(6));
expectCupertinoToolbarForPartialSelection();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgetsWithLeakTracking('tap on non-force-press-supported devices work', (WidgetTester tester) async {
......@@ -13344,7 +13333,7 @@ void main() {
await tester.pump();
// Single taps shouldn't trigger the toolbar.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
// TODO(gspencergoog): Add in TargetPlatform.macOS in the line below when we figure out what global state is leaking.
// https://github.com/flutter/flutter/issues/43445
......@@ -15900,7 +15889,7 @@ void main() {
);
// Initially, the menu is not shown and there is no selection.
expect(find.byType(CupertinoButton), findsNothing);
expectNoCupertinoToolbar();
expect(controller.selection, const TextSelection(baseOffset: -1, extentOffset: -1));
final Offset midBlah1 = textOffsetToPosition(tester, 2);
......
......@@ -407,8 +407,9 @@ void main() {
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsNothing);
expect(find.text('Share'), findsNothing);
expect(find.text('Select all'), findsNothing);
expect(find.byType(IconButton), findsNothing);
expect(find.byType(IconButton), findsNothing); // 'More' button.
// Tap to place the cursor and tap again to show the menu without a
// selection.
......@@ -426,6 +427,7 @@ void main() {
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsNothing);
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(IconButton), findsNothing);
......@@ -436,8 +438,9 @@ void main() {
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsNothing);
expect(find.text('Select all'), findsNothing);
expect(find.byType(IconButton), findsNothing);
expect(find.byType(IconButton), findsOneWidget); // 'More' button.
final Offset cutOffset = tester.getTopLeft(find.text('Cut'));
// Tap to clear the selection.
......@@ -447,37 +450,39 @@ void main() {
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsNothing);
expect(find.text('Select all'), findsNothing);
expect(find.byType(IconButton), findsNothing);
expect(find.byType(IconButton), findsNothing); // 'More' button.
// Long press to show the menu.
await tester.longPressAt(textOffsetToPosition(tester, 1));
await tester.pumpAndSettle();
// The last button is missing, and a more button is shown.
// The last buttons (share and select all) are missing, and a more button is shown.
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsNothing);
expect(find.text('Select all'), findsNothing);
expect(find.byType(IconButton), findsOneWidget);
expect(find.byType(IconButton), findsOneWidget); // 'More' button.
// Tapping the button shows the overflow menu.
// Tapping the more button shows the overflow menu.
await tester.tap(find.byType(IconButton));
await tester.pumpAndSettle();
expect(find.text('Cut'), findsNothing);
expect(find.text('Copy'), findsNothing);
expect(find.text('Paste'), findsNothing);
expect(find.text('Share'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
expect(find.byType(IconButton), findsOneWidget);
expect(find.byType(IconButton), findsOneWidget); // Back button.
// Tapping Select all changes the menu items so that there is no longer
// any overflow.
// Tapping 'Select all' closes the overflow menu.
await tester.tap(find.text('Select all'));
await tester.pumpAndSettle();
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsNothing);
expect(find.text('Select all'), findsNothing);
expect(find.byType(IconButton), findsNothing);
expect(find.byType(IconButton), findsOneWidget); // 'More' button.
final Offset newCutOffset = tester.getTopLeft(find.text('Cut'));
expect(newCutOffset, equals(cutOffset));
},
......
......@@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
/// A mock class to control the return result of Live Text input functions.
......@@ -34,7 +35,23 @@ class LiveTextInputTester {
}
/// A function to find the live text button.
Finder findLiveTextButton() => find.byWidgetPredicate((Widget widget) =>
widget is CustomPaint &&
'${widget.painter?.runtimeType}' == '_LiveTextIconPainter',
);
///
/// LiveText button is displayed either using a custom painter,
/// a Text with an empty label, or a Text with the 'Scan text' label.
Finder findLiveTextButton() {
final bool isMobile = defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.fuchsia ||
defaultTargetPlatform == TargetPlatform.iOS;
if (isMobile) {
return find.byWidgetPredicate((Widget widget) {
return (widget is CustomPaint && '${widget.painter?.runtimeType}' == '_LiveTextIconPainter')
|| (widget is Text && widget.data == 'Scan text'); // Android and Fuchsia when inside a MaterialApp.
});
}
if (defaultTargetPlatform == TargetPlatform.macOS) {
return find.ancestor(of: find.text(''), matching: find.byType(CupertinoDesktopTextSelectionToolbarButton));
}
return find.byWidgetPredicate((Widget widget) {
return widget is Text && (widget.data == '' || widget.data == 'Scan text');
});
}
......@@ -108,7 +108,6 @@ Widget boilerplate({ Widget? child }) {
);
}
Future<void> skipPastScrollingAnimation(WidgetTester tester) async {
await tester.pump();
await tester.pump(const Duration(milliseconds: 200));
......@@ -144,6 +143,41 @@ void main() {
return renderEditable;
}
// Check that the Cupertino text selection toolbar is the expected one on iOS and macOS.
// TODO(bleroux): try to merge this into text_selection_toolbar_utils.dart
// (for instance by adding a 'readOnly' flag).
void expectCupertinoSelectionToolbar() {
// This function is valid only for tests running on Apple platforms.
expect(defaultTargetPlatform == TargetPlatform.iOS || defaultTargetPlatform == TargetPlatform.macOS, isTrue);
if (defaultTargetPlatform == TargetPlatform.iOS) {
expect(find.byType(CupertinoButton), findsNWidgets(4));
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
} else {
expect(find.byType(CupertinoButton), findsNWidgets(1));
expect(find.text('Copy'), findsOneWidget);
}
}
// Check that the Material text selection toolbar is the expected one.
// TODO(bleroux): Try to merge this into text_selection_toolbar_utils.dart
// (for instance by adding a 'readOnly' flag).
void expectMaterialSelectionToolbar() {
if (defaultTargetPlatform == TargetPlatform.android) {
expect(find.byType(TextButton), findsNWidgets(3));
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
} else {
expect(find.byType(TextButton), findsNWidgets(2));
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
}
}
List<TextSelectionPoint> globalize(Iterable<TextSelectionPoint> points, RenderBox box) {
return points.map<TextSelectionPoint>((TextSelectionPoint point) {
return TextSelectionPoint(
......@@ -272,14 +306,14 @@ void main() {
await gesture.moveTo(newHandlePos);
await tester.pump();
// Unmount the SelectableText during handle drag
// Unmount the SelectableText during handle drag.
setter(() {
isShow = false;
});
await tester.pump();
await gesture.moveTo(newHandlePos1);
await tester.pump(); // Do not crash here
await tester.pump(); // Do not crash here.
await gesture.up();
await tester.pump();
......@@ -288,7 +322,7 @@ void main() {
testWidgetsWithLeakTracking('has expected defaults', (WidgetTester tester) async {
await tester.pumpWidget(
boilerplate(
child: const SelectableText('selectable text'),
child: const SelectableText('selectable text'),
),
);
......@@ -308,29 +342,29 @@ void main() {
child: Directionality(
textDirection: TextDirection.ltr,
child: SelectableText.rich(
TextSpan(
text: 'First line!',
style: TextStyle(
fontSize: 14,
TextSpan(
text: 'First line!',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
children: <TextSpan>[
TextSpan(
text: 'Second line!\n',
style: TextStyle(
fontSize: 30,
fontFamily: 'Roboto',
),
children: <TextSpan>[
TextSpan(
text: 'Second line!\n',
style: TextStyle(
fontSize: 30,
fontFamily: 'Roboto',
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
],
),
),
],
),
),
),
),
......@@ -353,33 +387,33 @@ void main() {
child: Directionality(
textDirection: TextDirection.ltr,
child: SelectableText.rich(
TextSpan(
text: 'First line!',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
children: <InlineSpan>[
WidgetSpan(
child: SizedBox(
width: 120,
height: 50,
child: Card(
child: Center(
child: Text('Hello World!'),
),
),
TextSpan(
text: 'First line!',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
children: <InlineSpan>[
WidgetSpan(
child: SizedBox(
width: 120,
height: 50,
child: Card(
child: Center(
child: Text('Hello World!'),
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
),
],
),
),
TextSpan(
text: 'Third line!\n',
style: TextStyle(
fontSize: 14,
fontFamily: 'Roboto',
),
),
],
),
),
),
),
......@@ -389,9 +423,9 @@ void main() {
testWidgetsWithLeakTracking('no text keyboard when widget is focused', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('selectable text'),
),
overlay(
child: const SelectableText('selectable text'),
),
);
await tester.tap(find.byType(SelectableText));
await tester.idle();
......@@ -421,9 +455,9 @@ void main() {
testWidgetsWithLeakTracking('Selectable Text has adaptive size', (WidgetTester tester) async {
await tester.pumpWidget(
boilerplate(
child: const SelectableText('s'),
),
boilerplate(
child: const SelectableText('s'),
),
);
RenderBox findSelectableTextBox() => tester.renderObject(find.byType(SelectableText));
......@@ -432,9 +466,9 @@ void main() {
expect(textBox.size, const Size(17.0, 14.0));
await tester.pumpWidget(
boilerplate(
child: const SelectableText('very very long'),
),
boilerplate(
child: const SelectableText('very very long'),
),
);
final RenderBox longtextBox = findSelectableTextBox();
......@@ -575,9 +609,9 @@ void main() {
testWidgetsWithLeakTracking('Caret position is updated on tap', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('abc def ghi'),
),
overlay(
child: const SelectableText('abc def ghi'),
),
);
final EditableText editableText = tester.widget(find.byType(EditableText));
expect(editableText.controller.selection.baseOffset, -1);
......@@ -595,12 +629,12 @@ void main() {
testWidgetsWithLeakTracking('enableInteractiveSelection = false, tap', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText(
'abc def ghi',
enableInteractiveSelection: false,
),
overlay(
child: const SelectableText(
'abc def ghi',
enableInteractiveSelection: false,
),
),
);
final EditableText editableText = tester.widget(find.byType(EditableText));
expect(editableText.controller.selection.baseOffset, -1);
......@@ -618,12 +652,12 @@ void main() {
testWidgetsWithLeakTracking('enableInteractiveSelection = false, long-press', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText(
'abc def ghi',
enableInteractiveSelection: false,
),
overlay(
child: const SelectableText(
'abc def ghi',
enableInteractiveSelection: false,
),
),
);
final EditableText editableText = tester.widget(find.byType(EditableText));
expect(editableText.controller.selection.baseOffset, -1);
......@@ -643,9 +677,9 @@ void main() {
testWidgetsWithLeakTracking('Can long press to select', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('abc def ghi'),
),
overlay(
child: const SelectableText('abc def ghi'),
),
);
final EditableText editableText = tester.widget(find.byType(EditableText));
......@@ -672,9 +706,9 @@ void main() {
testWidgetsWithLeakTracking("Slight movements in longpress don't hide/show handles", (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('abc def ghi'),
),
overlay(
child: const SelectableText('abc def ghi'),
),
);
// Long press the 'e' to select 'def', but don't release the gesture.
final Offset ePos = textOffsetToPosition(tester, 5);
......@@ -682,13 +716,13 @@ void main() {
await tester.pump(const Duration(seconds: 2));
await tester.pumpAndSettle();
// Handles are shown
// Handles are shown.
final Finder fadeFinder = find.byType(FadeTransition);
expect(fadeFinder, findsNWidgets(2)); // 2 handles, 1 toolbar
FadeTransition handle = tester.widget(fadeFinder.at(0));
expect(handle.opacity.value, equals(1.0));
// Move the gesture very slightly
// Move the gesture very slightly.
await gesture.moveBy(const Offset(1.0, 1.0));
await tester.pump(SelectionOverlay.fadeDuration * 0.5);
handle = tester.widget(fadeFinder.at(0));
......@@ -699,9 +733,9 @@ void main() {
testWidgetsWithLeakTracking('Mouse long press is just like a tap', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('abc def ghi'),
),
overlay(
child: const SelectableText('abc def ghi'),
),
);
final EditableText editableText = tester.widget(find.byType(EditableText));
......@@ -721,12 +755,12 @@ void main() {
testWidgetsWithLeakTracking('selectable text basic', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText('selectable'),
),
overlay(
child: const SelectableText('selectable'),
),
);
final EditableText editableTextWidget = tester.widget(find.byType(EditableText));
// selectable text cannot open keyboard.
// Selectable text cannot open keyboard.
await tester.showKeyboard(find.byType(SelectableText));
expect(tester.testTextInput.hasAnyClients, false);
await skipPastScrollingAnimation(tester);
......@@ -1138,14 +1172,14 @@ void main() {
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, emptyInputSize.height);
// maxLines: 3 makes the SelectableText 3 lines tall
// maxLines: 3 makes the SelectableText 3 lines tall.
await tester.pumpWidget(selectableTextBuilder(maxLines: 3));
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, greaterThan(emptyInputSize.height));
final Size threeLineInputSize = textBox.size;
// Filling with 3 lines of text stays the same size
// Filling with 3 lines of text stays the same size.
await tester.pumpWidget(selectableTextBuilder(text: kThreeLines, maxLines: 3));
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, threeLineInputSize.height);
......@@ -1155,14 +1189,14 @@ void main() {
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, threeLineInputSize.height);
// But now it will... but it will max at four
// But now it will... but it will max at four.
await tester.pumpWidget(selectableTextBuilder(text: kMoreThanFourLines, maxLines: 4));
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, greaterThan(threeLineInputSize.height));
final Size fourLineInputSize = textBox.size;
// Now it won't max out until the end
// Now it won't max out until the end.
await tester.pumpWidget(selectableTextBuilder(maxLines: null));
expect(findTextBox(), equals(textBox));
expect(textBox.size, equals(emptyInputSize));
......@@ -1386,7 +1420,7 @@ void main() {
final RenderBox textBox = findTextBox();
final Size emptyInputSize = textBox.size;
// Even if the text is a one liner, minimum height of SelectableText will determined by minLines
// Even if the text is a one liner, minimum height of SelectableText will determined by minLines.
await tester.pumpWidget(selectableTextBuilder(text: 'No wrapping here.', minLines: 2, maxLines: 3));
expect(findTextBox(), equals(textBox));
expect(textBox.size.height, emptyInputSize.height * 2);
......@@ -1473,7 +1507,7 @@ void main() {
firstFieldFocus.nextFocus();
await tester.pump();
// expecting focus to skip straight to the second field
// Expecting focus to skip straight to the second field.
expect(firstFieldFocus.hasFocus, isFalse);
expect(lastFieldFocus.hasFocus, isTrue);
});
......@@ -1789,7 +1823,7 @@ void main() {
controller.selection = const TextSelection.collapsed(offset: 0);
await tester.pump();
// Select the first 5 characters
// Select the first 5 characters.
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
for (int i = 0; i < 5; i += 1) {
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
......@@ -1797,7 +1831,7 @@ void main() {
}
await tester.sendKeyUpEvent(LogicalKeyboardKey.shift);
// Copy them
// Copy them.
await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight);
await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC);
await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC);
......@@ -1835,7 +1869,7 @@ void main() {
await tester.tap(find.byType(SelectableText));
await tester.pumpAndSettle();
// Select All
// Select All.
await tester.sendKeyDownEvent(LogicalKeyboardKey.control);
await tester.sendKeyEvent(LogicalKeyboardKey.keyA);
await tester.sendKeyUpEvent(LogicalKeyboardKey.control);
......@@ -1881,7 +1915,7 @@ void main() {
controller.selection = const TextSelection.collapsed(offset: 0);
await tester.pump();
// Select the first 5 characters
// Select the first 5 characters.
await tester.sendKeyDownEvent(LogicalKeyboardKey.shift);
for (int i = 0; i < 5; i += 1) {
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
......@@ -2055,11 +2089,11 @@ void main() {
testWidgetsWithLeakTracking('Caret works when maxLines is null', (WidgetTester tester) async {
await tester.pumpWidget(
overlay(
child: const SelectableText(
'x',
),
overlay(
child: const SelectableText(
'x',
),
),
);
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
......@@ -2549,7 +2583,7 @@ void main() {
),
);
// Test show on screen.
// Test shows on screen.
expect(controller.offset, 0.0);
tester.binding.pipelineOwner.semanticsOwner!.performAction(8, SemanticsAction.showOnScreen);
await tester.pumpAndSettle();
......@@ -2575,7 +2609,7 @@ void main() {
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// Focus the selectable text
// Focus the selectable text.
await tester.tap(find.byKey(key));
await tester.pump();
......@@ -2607,7 +2641,7 @@ void main() {
],
), ignoreTransform: true, ignoreRect: true));
// move cursor back once
// Move cursor back once.
semanticsOwner.performAction(inputFieldId, SemanticsAction.setSelection, <dynamic, dynamic>{
'base': 4,
'extent': 4,
......@@ -2615,7 +2649,7 @@ void main() {
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 4));
// move cursor to front
// Move cursor to front.
semanticsOwner.performAction(inputFieldId, SemanticsAction.setSelection, <dynamic, dynamic>{
'base': 0,
'extent': 0,
......@@ -2623,7 +2657,7 @@ void main() {
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 0));
// select all
// Select all.
semanticsOwner.performAction(inputFieldId, SemanticsAction.setSelection, <dynamic, dynamic>{
'base': 0,
'extent': 5,
......@@ -2776,7 +2810,7 @@ void main() {
);
}
// Empty TextStyle is overridden by theme
// Empty TextStyle is overridden by theme.
await tester.pumpWidget(buildFrame(const TextStyle()));
EditableText editableText = tester.widget(find.byType(EditableText));
expect(editableText.style.color, defaultStyle.color);
......@@ -2786,13 +2820,13 @@ void main() {
expect(editableText.style.locale, defaultStyle.locale);
expect(editableText.style.wordSpacing, defaultStyle.wordSpacing);
// Properties set on TextStyle override theme
// Properties set on TextStyle override theme.
const Color setColor = Colors.red;
await tester.pumpWidget(buildFrame(const TextStyle(color: setColor)));
editableText = tester.widget(find.byType(EditableText));
expect(editableText.style.color, setColor);
// inherit: false causes nothing to be merged in from theme
// inherit: false causes nothing to be merged in from theme.
await tester.pumpWidget(buildFrame(const TextStyle(
fontSize: 24.0,
textBaseline: TextBaseline.alphabetic,
......@@ -2821,7 +2855,7 @@ void main() {
)));
expect(tester.takeException(), isNull);
// With inherit not set to false, will pickup required fields from theme
// With inherit not set to false, will pickup required fields from theme.
await tester.pumpWidget(buildFrame(const TextStyle(
fontSize: 12.0,
)));
......@@ -2946,7 +2980,6 @@ void main() {
),
);
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
// This tap just puts the cursor somewhere different than where the double
......@@ -2974,8 +3007,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 1 toolbar buttons on MacOS, 2 on iOS.
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3020,8 +3052,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 2 toolbar buttons: copy, select all
expect(find.byType(TextButton), findsNWidgets(2));
expectMaterialSelectionToolbar();
},
);
......@@ -3055,7 +3086,7 @@ void main() {
await tester.tapAt(textOffsetToPosition(tester, index));
await tester.pump(const Duration(milliseconds: 50));
// First tap doesn't change the selection
// First tap doesn't change the selection.
expect(
controller.selection,
const TextSelection.collapsed(offset: index),
......@@ -3069,8 +3100,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Selected text shows 2 toolbar buttons: copy, select all
expect(find.byType(TextButton), findsNWidgets(2));
expectMaterialSelectionToolbar();
},
);
......@@ -3088,7 +3118,6 @@ void main() {
);
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(selectableTextStart + const Offset(150.0, 5.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -3105,8 +3134,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// Selected text shows 2 toolbar buttons for iOS, 1 for macOS.
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
await gesture.up();
await tester.pump();
......@@ -3117,7 +3145,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
// The toolbar is still showing.
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3216,7 +3244,6 @@ void main() {
);
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.longPressAt(selectableTextStart + const Offset(50.0, 5.0));
await tester.pump();
......@@ -3224,7 +3251,7 @@ void main() {
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// The longpressed word is selected.
// The long pressed word is selected.
expect(
controller.selection,
const TextSelection(
......@@ -3233,8 +3260,7 @@ void main() {
),
);
// Toolbar shows one button.
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3266,20 +3292,19 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Collapsed toolbar shows 2 buttons: copy, select all
expect(find.byType(TextButton), findsNWidgets(2));
expectMaterialSelectionToolbar();
},
);
testWidgetsWithLeakTracking(
'long press selects word and shows custom toolbar (Android)',
'long press selects word and shows custom toolbar (Cupertino)',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Center(
child: SelectableText('Atwater Peel Sherbrooke Bonaventure',
selectionControls: cupertinoTextSelectionControls,
selectionControls: cupertinoTextSelectionControls,
),
),
),
......@@ -3294,7 +3319,7 @@ void main() {
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// The longpressed word is selected.
// The long pressed word is selected.
expect(
controller.selection,
const TextSelection(
......@@ -3303,14 +3328,15 @@ void main() {
),
);
// Toolbar shows one button.
// Toolbar shows one button (copy).
expect(find.byType(CupertinoButton), findsNWidgets(1));
expect(find.text('Copy'), findsOneWidget);
},
variant: TargetPlatformVariant.all(),
);
testWidgetsWithLeakTracking(
'long press selects word and shows custom toolbar (iOS)',
'long press selects word and shows custom toolbar (Material)',
(WidgetTester tester) async {
await tester.pumpWidget(
......@@ -3318,7 +3344,7 @@ void main() {
home: Material(
child: Center(
child: SelectableText('Atwater Peel Sherbrooke Bonaventure',
selectionControls: materialTextSelectionControls,
selectionControls: materialTextSelectionControls,
),
),
),
......@@ -3340,6 +3366,8 @@ void main() {
// Collapsed toolbar shows 2 buttons: copy, select all
expect(find.byType(TextButton), findsNWidgets(2));
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
},
variant: TargetPlatformVariant.all(),
);
......@@ -3476,8 +3504,8 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 23, extentOffset: 0),
);
// The toolbar now shows up.
expect(find.byType(TextButton), findsNWidgets(2));
expectMaterialSelectionToolbar();
},
variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3499,11 +3527,10 @@ void main() {
await tester.startGesture(textOffsetToPosition(tester, 18));
await tester.pump(const Duration(milliseconds: 500));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// The longpressed word is selected.
// The long pressed word is selected.
expect(
controller.selection,
const TextSelection(
......@@ -3570,7 +3597,7 @@ void main() {
),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -3597,7 +3624,7 @@ void main() {
final EditableText editableTextWidget = tester.widget(find.byType(EditableText).first);
final TextEditingController controller = editableTextWidget.controller;
// The longpressed word is selected.
// The long pressed word is selected.
expect(
controller.selection,
const TextSelection(
......@@ -3648,7 +3675,7 @@ void main() {
),
);
// The toolbar now shows up.
expect(find.byType(CupertinoButton), findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS }),
);
......@@ -3735,7 +3762,7 @@ void main() {
extentOffset: 66,
),
);
// The toolbar now shows up.
// The toolbar shows up with one button (copy).
expect(find.byType(CupertinoButton), findsNWidgets(1));
lastCharEndpoint = renderEditable.getEndpointsForSelection(
......@@ -3797,8 +3824,7 @@ void main() {
const TextSelection(baseOffset: 0, extentOffset: 7),
);
// Long press toolbar.
expect(find.byType(CupertinoButton), findsNWidgets(4));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }),
);
......@@ -3841,8 +3867,8 @@ void main() {
const TextSelection(baseOffset: 7, extentOffset: 8),
);
// Long press toolbar.
expect(find.byType(CupertinoButton), findsNWidgets(1));
// The toolbar shows up.
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS }),
);
......@@ -3861,7 +3887,6 @@ void main() {
);
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.longPressAt(selectableTextStart + const Offset(50.0, 5.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -3892,7 +3917,7 @@ void main() {
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -3909,7 +3934,6 @@ void main() {
),
);
final Offset selectableTextStart = tester.getTopLeft(find.byType(SelectableText));
final bool isTargetPlatformIOS = defaultTargetPlatform == TargetPlatform.iOS;
await tester.tapAt(selectableTextStart + const Offset(50.0, 5.0));
await tester.pump(const Duration(milliseconds: 50));
......@@ -3927,7 +3951,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
// Double tap selecting the same word somewhere else is fine.
await tester.pumpAndSettle(kDoubleTapTimeout);
......@@ -3944,7 +3968,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 0, extentOffset: 7),
);
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
// Hide the toolbar so it doesn't interfere with taps on the text.
final EditableTextState editableTextState =
......@@ -3966,7 +3990,7 @@ void main() {
controller.selection,
const TextSelection(baseOffset: 8, extentOffset: 12),
);
expect(find.byType(CupertinoButton), isTargetPlatformIOS ? findsNWidgets(4) : findsNWidgets(1));
expectCupertinoSelectionToolbar();
},
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }),
);
......@@ -4045,7 +4069,7 @@ void main() {
await gesture.up();
await tester.pump();
expect(find.byType(CupertinoButton), findsNWidgets(4));
expectCupertinoSelectionToolbar();
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS }));
testWidgetsWithLeakTracking('tap on non-force-press-supported devices work', (WidgetTester tester) async {
......@@ -4111,7 +4135,7 @@ void main() {
testWidgetsWithLeakTracking('SelectableText implements debugFillProperties', (WidgetTester tester) async {
final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder();
// Not checking controller, inputFormatters, focusNode
// Not checking controller, inputFormatters, focusNode.
const SelectableText(
'something',
style: TextStyle(color: Color(0xff00ff00)),
......
// 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/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/platform.dart';
import 'package:flutter_test/flutter_test.dart';
import '../widgets/editable_text_utils.dart';
Finder findCupertinoOverflowNextButton() {
return find.byWidgetPredicate((Widget widget) {
return widget is CustomPaint && '${widget.painter?.runtimeType}' == '_RightCupertinoChevronPainter';
});
}
Finder findCupertinoOverflowBackButton() {
return find.byWidgetPredicate((Widget widget) {
return widget is CustomPaint && '${widget.painter?.runtimeType}' == '_LeftCupertinoChevronPainter';
});
}
Future<void> tapCupertinoOverflowNextButton(WidgetTester tester) async{
await tester.tapAt(tester.getCenter(findCupertinoOverflowNextButton()));
await tester.pumpAndSettle();
}
void expectNoCupertinoToolbar() {
expect(find.byType(CupertinoButton), findsNothing);
}
// Check that the Cupertino text selection toolbars show the expected buttons
// when the content is partially selected.
void expectCupertinoToolbarForPartialSelection() {
if (isContextMenuProvidedByPlatform) {
expectNoCupertinoToolbar();
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(CupertinoButton), findsNWidgets(5));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
case TargetPlatform.iOS:
expect(find.byType(CupertinoButton), findsNWidgets(6));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
case TargetPlatform.macOS:
expect(find.byType(CupertinoButton), findsNWidgets(3));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.byType(CupertinoButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
}
}
// Check that the Cupertino text selection toolbar shows the expected buttons
// when the content is fully selected.
void expectCupertinoToolbarForFullSelection() {
if (isContextMenuProvidedByPlatform) {
expectNoCupertinoToolbar();
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(CupertinoButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
case TargetPlatform.iOS:
expect(find.byType(CupertinoButton), findsNWidgets(6));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
expect(find.text('Look Up'), findsOneWidget);
expect(find.text('Search Web'), findsOneWidget);
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
expect(find.byType(CupertinoButton), findsNWidgets(3));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
}
}
// Check that the Cupertino text selection toolbar is correct for a collapsed selection.
void expectCupertinoToolbarForCollapsedSelection() {
if (isContextMenuProvidedByPlatform) {
expectNoCupertinoToolbar();
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(CupertinoButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share...'), findsOneWidget);
case TargetPlatform.iOS:
expect(find.byType(CupertinoButton), findsNWidgets(2));
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select All'), findsOneWidget);
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
case TargetPlatform.macOS:
expect(find.byType(CupertinoButton), findsNWidgets(1));
expect(find.text('Paste'), findsOneWidget);
}
}
void expectNoMaterialToolbar() {
expect(find.byType(TextButton), findsNothing);
}
// Check that the Material text selection toolbars show the expected buttons
// when the content is partially selected.
void expectMaterialToolbarForPartialSelection() {
if (isContextMenuProvidedByPlatform) {
expectNoMaterialToolbar();
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(TextButton), findsNWidgets(5));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
expect(find.byType(TextButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Select all'), findsOneWidget);
}
}
// Check that the Material text selection toolbar shows the expected buttons
// when the content is fully selected.
void expectMaterialToolbarForFullSelection() {
if (isContextMenuProvidedByPlatform) {
expectNoMaterialToolbar();
return;
}
switch (defaultTargetPlatform) {
case TargetPlatform.android:
expect(find.byType(TextButton), findsNWidgets(4));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
expect(find.text('Share'), findsOneWidget);
case TargetPlatform.iOS:
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.macOS:
case TargetPlatform.windows:
expect(find.byType(TextButton), findsNWidgets(3));
expect(find.text('Cut'), findsOneWidget);
expect(find.text('Copy'), findsOneWidget);
expect(find.text('Paste'), findsOneWidget);
}
}
Finder findMaterialOverflowNextButton() {
return find.byIcon(Icons.more_vert);
}
Finder findMaterialOverflowBackButton() {
return find.byIcon(Icons.arrow_back);
}
Future<void> tapMaterialOverflowNextButton(WidgetTester tester) async {
await tester.tapAt(tester.getCenter(findMaterialOverflowNextButton()));
await tester.pumpAndSettle();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment