// 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/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import '../widgets/clipboard_utils.dart'; import '../widgets/editable_text_utils.dart'; void main() { final MockClipboard mockClipboard = MockClipboard(); setUp(() async { TestWidgetsFlutterBinding.ensureInitialized(); TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( SystemChannels.platform, mockClipboard.handleMethodCall, ); // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); }); testWidgets('Builds the right toolbar on each platform, including web, and shows buttonItems', (WidgetTester tester) async { const String buttonText = 'Click me'; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: AdaptiveTextSelectionToolbar.buttonItems( anchors: const TextSelectionToolbarAnchors( primaryAnchor: Offset.zero, ), buttonItems: <ContextMenuButtonItem>[ ContextMenuButtonItem( label: buttonText, onPressed: () { }, ), ], ), ), ), ), ); expect(find.text(buttonText), findsOneWidget); switch (defaultTargetPlatform) { case TargetPlatform.android: expect(find.byType(TextSelectionToolbar), findsOneWidget); expect(find.byType(CupertinoTextSelectionToolbar), findsNothing); expect(find.byType(DesktopTextSelectionToolbar), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbar), findsNothing); break; case TargetPlatform.iOS: expect(find.byType(TextSelectionToolbar), findsNothing); expect(find.byType(CupertinoTextSelectionToolbar), findsOneWidget); expect(find.byType(DesktopTextSelectionToolbar), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbar), findsNothing); break; case TargetPlatform.macOS: expect(find.byType(TextSelectionToolbar), findsNothing); expect(find.byType(CupertinoTextSelectionToolbar), findsNothing); expect(find.byType(DesktopTextSelectionToolbar), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbar), findsOneWidget); break; case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: expect(find.byType(TextSelectionToolbar), findsNothing); expect(find.byType(CupertinoTextSelectionToolbar), findsNothing); expect(find.byType(DesktopTextSelectionToolbar), findsOneWidget); expect(find.byType(CupertinoDesktopTextSelectionToolbar), findsNothing); break; } }, variant: TargetPlatformVariant.all(), skip: isBrowser, // [intended] see https://github.com/flutter/flutter/issues/108382 ); testWidgets('Can build children directly as well', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: AdaptiveTextSelectionToolbar( anchors: const TextSelectionToolbarAnchors( primaryAnchor: Offset.zero, ), children: <Widget>[ Container(key: key), ], ), ), ), ), ); expect(find.byKey(key), findsOneWidget); }); testWidgets('Can build from EditableTextState', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: SizedBox( width: 400, child: EditableText( controller: TextEditingController(), backgroundCursorColor: const Color(0xff00ffff), focusNode: FocusNode(), style: const TextStyle(), cursorColor: const Color(0xff00ffff), selectionControls: materialTextSelectionHandleControls, contextMenuBuilder: ( BuildContext context, EditableTextState editableTextState, ) { return AdaptiveTextSelectionToolbar.editableText( key: key, editableTextState: editableTextState, ); }, ), ), ), ), ), ); await tester.pump(); // Wait for autofocus to take effect. expect(find.byKey(key), findsNothing); // Long-press to bring up the context menu. final Finder textFinder = find.byType(EditableText); await tester.longPress(textFinder); tester.state<EditableTextState>(textFinder).showToolbar(); await tester.pumpAndSettle(); expect(find.byKey(key), findsOneWidget); expect(find.text('Copy'), findsNothing); expect(find.text('Cut'), findsNothing); expect(find.text('Select all'), findsNothing); expect(find.text('Paste'), findsOneWidget); switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: expect(find.byType(TextSelectionToolbarTextButton), findsOneWidget); break; case TargetPlatform.iOS: expect(find.byType(CupertinoTextSelectionToolbarButton), findsOneWidget); break; case TargetPlatform.linux: case TargetPlatform.windows: expect(find.byType(DesktopTextSelectionToolbarButton), findsOneWidget); break; case TargetPlatform.macOS: expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsOneWidget); break; } }, skip: kIsWeb, // [intended] on web the browser handles the context menu. variant: TargetPlatformVariant.all(), ); testWidgets('Can build for editable text from raw parameters', (WidgetTester tester) async { final GlobalKey key = GlobalKey(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: AdaptiveTextSelectionToolbar.editable( key: key, anchors: const TextSelectionToolbarAnchors( primaryAnchor: Offset.zero, ), clipboardStatus: ClipboardStatus.pasteable, onCopy: () {}, onCut: () {}, onPaste: () {}, onSelectAll: () {}, ), ), ), ), ); 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: case TargetPlatform.fuchsia: expect(find.text('Select all'), findsOneWidget); expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(4)); break; case TargetPlatform.iOS: expect(find.text('Select All'), findsOneWidget); expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(4)); break; case TargetPlatform.linux: case TargetPlatform.windows: expect(find.text('Select all'), findsOneWidget); expect(find.byType(DesktopTextSelectionToolbarButton), findsNWidgets(4)); break; case TargetPlatform.macOS: expect(find.text('Select All'), findsOneWidget); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNWidgets(4)); break; } }, skip: kIsWeb, // [intended] on web the browser handles the context menu. variant: TargetPlatformVariant.all(), ); group('buttonItems', () { testWidgets('getEditableTextButtonItems builds the correct button items per-platform', (WidgetTester tester) async { // Fill the clipboard so that the Paste option is available in the text // selection menu. await Clipboard.setData(const ClipboardData(text: 'Clipboard data')); Set<ContextMenuButtonType> buttonTypes = <ContextMenuButtonType>{}; final TextEditingController controller = TextEditingController(); await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: EditableText( controller: controller, backgroundCursorColor: Colors.grey, focusNode: FocusNode(), style: const TextStyle(), cursorColor: Colors.red, selectionControls: materialTextSelectionHandleControls, contextMenuBuilder: ( BuildContext context, EditableTextState editableTextState, ) { buttonTypes = editableTextState.contextMenuButtonItems .map((ContextMenuButtonItem buttonItem) => buttonItem.type) .toSet(); return const SizedBox.shrink(); }, ), ), ), ), ); final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText)); // With no text in the field. await tester.tapAt(textOffsetToPosition(tester, 0)); await tester.pump(); expect(state.showToolbar(), true); await tester.pump(); expect(buttonTypes, isNot(contains(ContextMenuButtonType.cut))); expect(buttonTypes, isNot(contains(ContextMenuButtonType.copy))); expect(buttonTypes, contains(ContextMenuButtonType.paste)); expect(buttonTypes, isNot(contains(ContextMenuButtonType.selectAll))); // With text but no selection. controller.text = 'lorem ipsum'; await tester.pump(); expect(buttonTypes, isNot(contains(ContextMenuButtonType.cut))); expect(buttonTypes, isNot(contains(ContextMenuButtonType.copy))); expect(buttonTypes, contains(ContextMenuButtonType.paste)); switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); break; case TargetPlatform.macOS: expect(buttonTypes, isNot(contains(ContextMenuButtonType.selectAll))); break; } // With text and selection. controller.value = controller.value.copyWith( selection: const TextSelection( baseOffset: 0, extentOffset: 'lorem'.length, ), ); await tester.pump(); expect(buttonTypes, contains(ContextMenuButtonType.cut)); expect(buttonTypes, contains(ContextMenuButtonType.copy)); expect(buttonTypes, contains(ContextMenuButtonType.paste)); switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: expect(buttonTypes, contains(ContextMenuButtonType.selectAll)); break; case TargetPlatform.iOS: case TargetPlatform.macOS: expect(buttonTypes, isNot(contains(ContextMenuButtonType.selectAll))); break; } }, variant: TargetPlatformVariant.all(), skip: kIsWeb, // [intended] ); testWidgets('getAdaptiveButtons builds the correct button widgets per-platform', (WidgetTester tester) async { const String buttonText = 'Click me'; await tester.pumpWidget( MaterialApp( home: Scaffold( body: Center( child: Builder( builder: (BuildContext context) { final List<ContextMenuButtonItem> buttonItems = <ContextMenuButtonItem>[ ContextMenuButtonItem( label: buttonText, onPressed: () { }, ), ]; return ListView( children: AdaptiveTextSelectionToolbar.getAdaptiveButtons( context, buttonItems, ).toList(), ); }, ), ), ), ), ); expect(find.text(buttonText), findsOneWidget); switch (defaultTargetPlatform) { case TargetPlatform.fuchsia: case TargetPlatform.android: expect(find.byType(TextSelectionToolbarTextButton), findsOneWidget); expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); expect(find.byType(DesktopTextSelectionToolbarButton), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNothing); break; case TargetPlatform.iOS: expect(find.byType(TextSelectionToolbarTextButton), findsNothing); expect(find.byType(CupertinoTextSelectionToolbarButton), findsOneWidget); expect(find.byType(DesktopTextSelectionToolbarButton), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNothing); break; case TargetPlatform.macOS: expect(find.byType(TextSelectionToolbarTextButton), findsNothing); expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); expect(find.byType(DesktopTextSelectionToolbarButton), findsNothing); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsOneWidget); break; case TargetPlatform.linux: case TargetPlatform.windows: expect(find.byType(TextSelectionToolbarTextButton), findsNothing); expect(find.byType(CupertinoTextSelectionToolbarButton), findsNothing); expect(find.byType(DesktopTextSelectionToolbarButton), findsOneWidget); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNothing); break; } }, variant: TargetPlatformVariant.all(), ); }); }