mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
<!-- Thanks for filing a pull request! Reviewers are typically assigned within a week of filing a request. To learn more about code review, see our documentation on Tree Hygiene: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md --> **Live Text (OCR) button disappeared from text field menus on iOS after the Secure Paste M2 update. This PR adds it back.** **Fixes #169781** Note: This is a draft PR for initial review. Still need to add tests or split to framework and engine ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
741 lines
28 KiB
Dart
741 lines
28 KiB
Dart
// 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/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../system_context_menu_utils.dart';
|
|
|
|
void main() {
|
|
final TestWidgetsFlutterBinding binding = TestWidgetsFlutterBinding.ensureInitialized();
|
|
|
|
testWidgets(
|
|
'asserts when built on an unsupported device',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
// By default, MediaQueryData.supportsShowingSystemContextMenu is false.
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(editableTextState: editableTextState);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
|
|
expect(tester.takeException(), isAssertionError);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.all(),
|
|
);
|
|
|
|
testWidgets(
|
|
'asserts when built on web',
|
|
(WidgetTester tester) async {
|
|
// Disable the browser context menu so that contextMenuBuilder will be used.
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.contextMenu,
|
|
(MethodCall call) {
|
|
// Just complete successfully, so that BrowserContextMenu thinks that
|
|
// the engine successfully received its call.
|
|
return Future<void>.value();
|
|
},
|
|
);
|
|
await BrowserContextMenu.disableContextMenu();
|
|
addTearDown(() async {
|
|
await BrowserContextMenu.enableContextMenu();
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.contextMenu,
|
|
null,
|
|
);
|
|
});
|
|
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
// By default, MediaQueryData.supportsShowingSystemContextMenu is false.
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(editableTextState: editableTextState);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
|
|
expect(tester.takeException(), isAssertionError);
|
|
},
|
|
skip: !kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'can be shown and hidden like a normal context menu',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsOneWidget);
|
|
|
|
state.hideToolbar();
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'can customize the menu items',
|
|
(WidgetTester tester) async {
|
|
final List<List<IOSSystemContextMenuItemData>> itemsReceived =
|
|
<List<IOSSystemContextMenuItemData>>[];
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(MethodCall methodCall) async {
|
|
switch (methodCall.method) {
|
|
case 'ContextMenu.showSystemContextMenu':
|
|
final Map<String, dynamic> arguments = methodCall.arguments as Map<String, dynamic>;
|
|
final List<dynamic> untypedItems = arguments['items'] as List<dynamic>;
|
|
final List<IOSSystemContextMenuItemData> lastItems = untypedItems.map((
|
|
dynamic value,
|
|
) {
|
|
final Map<String, dynamic> itemJson = value as Map<String, dynamic>;
|
|
return systemContextMenuItemDataFromJson(itemJson);
|
|
}).toList();
|
|
itemsReceived.add(lastItems);
|
|
}
|
|
return;
|
|
},
|
|
);
|
|
addTearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
);
|
|
});
|
|
|
|
const List<IOSSystemContextMenuItem> items1 = <IOSSystemContextMenuItem>[
|
|
IOSSystemContextMenuItemCopy(),
|
|
IOSSystemContextMenuItemShare(title: 'My Share Title'),
|
|
IOSSystemContextMenuItemLiveText(),
|
|
];
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
items: items1,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
expect(itemsReceived, hasLength(0));
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsOneWidget);
|
|
|
|
expect(itemsReceived, hasLength(1));
|
|
expect(itemsReceived.last, hasLength(items1.length));
|
|
expect(itemsReceived.last[0], equals(const IOSSystemContextMenuItemDataCopy()));
|
|
expect(
|
|
itemsReceived.last[1],
|
|
equals(const IOSSystemContextMenuItemDataShare(title: 'My Share Title')),
|
|
);
|
|
expect(itemsReceived.last[2], equals(const IOSSystemContextMenuItemDataLiveText()));
|
|
|
|
state.hideToolbar();
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
"passing empty items builds the widget but doesn't show the system context menu",
|
|
(WidgetTester tester) async {
|
|
final List<List<IOSSystemContextMenuItemData>> itemsReceived =
|
|
<List<IOSSystemContextMenuItemData>>[];
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(MethodCall methodCall) async {
|
|
switch (methodCall.method) {
|
|
case 'ContextMenu.showSystemContextMenu':
|
|
final Map<String, dynamic> arguments = methodCall.arguments as Map<String, dynamic>;
|
|
final List<dynamic> untypedItems = arguments['items'] as List<dynamic>;
|
|
final List<IOSSystemContextMenuItemData> lastItems = untypedItems.map((
|
|
dynamic value,
|
|
) {
|
|
final Map<String, dynamic> itemJson = value as Map<String, dynamic>;
|
|
return systemContextMenuItemDataFromJson(itemJson);
|
|
}).toList();
|
|
itemsReceived.add(lastItems);
|
|
}
|
|
return;
|
|
},
|
|
);
|
|
addTearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
);
|
|
});
|
|
|
|
const List<IOSSystemContextMenuItem> items1 = <IOSSystemContextMenuItem>[];
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
items: items1,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
expect(itemsReceived, hasLength(0));
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pump();
|
|
expect(tester.takeException(), isNull);
|
|
expect(find.byType(SystemContextMenu), findsOneWidget);
|
|
expect(itemsReceived, hasLength(0));
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'items receive a default title',
|
|
(WidgetTester tester) async {
|
|
final List<List<IOSSystemContextMenuItemData>> itemsReceived =
|
|
<List<IOSSystemContextMenuItemData>>[];
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(MethodCall methodCall) async {
|
|
switch (methodCall.method) {
|
|
case 'ContextMenu.showSystemContextMenu':
|
|
final Map<String, dynamic> arguments = methodCall.arguments as Map<String, dynamic>;
|
|
final List<dynamic> untypedItems = arguments['items'] as List<dynamic>;
|
|
final List<IOSSystemContextMenuItemData> lastItems = untypedItems.map((
|
|
dynamic value,
|
|
) {
|
|
final Map<String, dynamic> itemJson = value as Map<String, dynamic>;
|
|
return systemContextMenuItemDataFromJson(itemJson);
|
|
}).toList();
|
|
itemsReceived.add(lastItems);
|
|
}
|
|
return;
|
|
},
|
|
);
|
|
addTearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
);
|
|
});
|
|
|
|
const List<IOSSystemContextMenuItem> items1 = <IOSSystemContextMenuItem>[
|
|
// Copy gets no title, it's set by the platform.
|
|
IOSSystemContextMenuItemCopy(),
|
|
// Share could take a title, but if not, it gets a localized default.
|
|
IOSSystemContextMenuItemShare(),
|
|
];
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
items: items1,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
expect(itemsReceived, hasLength(0));
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsOneWidget);
|
|
|
|
expect(itemsReceived, hasLength(1));
|
|
expect(itemsReceived.last, hasLength(items1.length));
|
|
expect(itemsReceived.last[0], equals(const IOSSystemContextMenuItemDataCopy()));
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(
|
|
itemsReceived.last[1],
|
|
equals(IOSSystemContextMenuItemDataShare(title: localizations.shareButtonLabel)),
|
|
);
|
|
|
|
state.hideToolbar();
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'can be updated.',
|
|
(WidgetTester tester) async {
|
|
final List<Map<String, double>> targetRects = <Map<String, double>>[];
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
(MethodCall methodCall) async {
|
|
if (methodCall.method == 'ContextMenu.showSystemContextMenu') {
|
|
final Map<String, dynamic> arguments = methodCall.arguments as Map<String, dynamic>;
|
|
final Map<String, dynamic> untypedTargetRect =
|
|
arguments['targetRect'] as Map<String, dynamic>;
|
|
final Map<String, double> lastTargetRect = untypedTargetRect.map((
|
|
String key,
|
|
dynamic value,
|
|
) {
|
|
return MapEntry<String, double>(key, value as double);
|
|
});
|
|
targetRects.add(lastTargetRect);
|
|
}
|
|
return;
|
|
},
|
|
);
|
|
addTearDown(() {
|
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
|
SystemChannels.platform,
|
|
null,
|
|
);
|
|
});
|
|
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(targetRects, isEmpty);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
|
|
expect(targetRects, hasLength(1));
|
|
expect(targetRects.last, containsPair('width', 0.0));
|
|
|
|
controller.selection = const TextSelection(baseOffset: 4, extentOffset: 7);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(targetRects, hasLength(2));
|
|
expect(targetRects.last['width'], greaterThan(0.0));
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'can be rebuilt',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller = TextEditingController(text: 'one two three');
|
|
addTearDown(controller.dispose);
|
|
late StateSetter setState;
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter localSetState) {
|
|
setState = localSetState;
|
|
return TextField(
|
|
controller: controller,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
editableTextState: editableTextState,
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
|
|
expect(state.showToolbar(), true);
|
|
await tester.pump();
|
|
|
|
setState(() {});
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.takeException(), isNull);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
testWidgets(
|
|
'can handle multiple instances',
|
|
(WidgetTester tester) async {
|
|
final TextEditingController controller1 = TextEditingController(text: 'one two three');
|
|
addTearDown(controller1.dispose);
|
|
final TextEditingController controller2 = TextEditingController(text: 'four five six');
|
|
addTearDown(controller2.dispose);
|
|
final GlobalKey field1Key = GlobalKey();
|
|
final GlobalKey field2Key = GlobalKey();
|
|
final GlobalKey menu1Key = GlobalKey();
|
|
final GlobalKey menu2Key = GlobalKey();
|
|
await tester.pumpWidget(
|
|
Builder(
|
|
builder: (BuildContext context) {
|
|
final MediaQueryData mediaQueryData = MediaQuery.of(context);
|
|
return MediaQuery(
|
|
data: mediaQueryData.copyWith(supportsShowingSystemContextMenu: true),
|
|
child: MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: Column(
|
|
children: <Widget>[
|
|
TextField(
|
|
key: field1Key,
|
|
controller: controller1,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
key: menu1Key,
|
|
editableTextState: editableTextState,
|
|
);
|
|
},
|
|
),
|
|
TextField(
|
|
key: field2Key,
|
|
controller: controller2,
|
|
contextMenuBuilder:
|
|
(BuildContext context, EditableTextState editableTextState) {
|
|
return SystemContextMenu.editableText(
|
|
key: menu2Key,
|
|
editableTextState: editableTextState,
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
|
|
await tester.tap(find.byKey(field1Key));
|
|
final EditableTextState state1 = tester.state<EditableTextState>(
|
|
find.descendant(of: find.byKey(field1Key), matching: find.byType(EditableText)),
|
|
);
|
|
expect(state1.showToolbar(), true);
|
|
await tester.pump();
|
|
expect(find.byKey(menu1Key), findsOneWidget);
|
|
expect(find.byKey(menu2Key), findsNothing);
|
|
|
|
// In a real app, this message is sent by iOS when the user taps anywhere
|
|
// outside of the system context menu.
|
|
final ByteData? messageBytes = const JSONMessageCodec().encodeMessage(<String, dynamic>{
|
|
'method': 'ContextMenu.onDismissSystemContextMenu',
|
|
});
|
|
await binding.defaultBinaryMessenger.handlePlatformMessage(
|
|
'flutter/platform',
|
|
messageBytes,
|
|
(ByteData? data) {},
|
|
);
|
|
await tester.pump();
|
|
expect(find.byType(SystemContextMenu), findsNothing);
|
|
|
|
await tester.tap(find.byKey(field2Key));
|
|
final EditableTextState state2 = tester.state<EditableTextState>(
|
|
find.descendant(of: find.byKey(field2Key), matching: find.byType(EditableText)),
|
|
);
|
|
expect(state2.showToolbar(), true);
|
|
await tester.pump();
|
|
expect(find.byKey(menu1Key), findsNothing);
|
|
expect(find.byKey(menu2Key), findsOneWidget);
|
|
},
|
|
skip: kIsWeb, // [intended]
|
|
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemCopy',
|
|
() {
|
|
const IOSSystemContextMenuItemCopy item = IOSSystemContextMenuItemCopy();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(item.getData(localizations), const IOSSystemContextMenuItemDataCopy());
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemCut',
|
|
() {
|
|
const IOSSystemContextMenuItemCut item = IOSSystemContextMenuItemCut();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(item.getData(localizations), const IOSSystemContextMenuItemDataCut());
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemPaste',
|
|
() {
|
|
const IOSSystemContextMenuItemPaste item = IOSSystemContextMenuItemPaste();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(item.getData(localizations), const IOSSystemContextMenuItemDataPaste());
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemSelectAll',
|
|
() {
|
|
const IOSSystemContextMenuItemSelectAll item = IOSSystemContextMenuItemSelectAll();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(item.getData(localizations), const IOSSystemContextMenuItemDataSelectAll());
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemLookUp',
|
|
() {
|
|
const IOSSystemContextMenuItemLookUp item = IOSSystemContextMenuItemLookUp();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(
|
|
item.getData(localizations),
|
|
IOSSystemContextMenuItemDataLookUp(title: localizations.lookUpButtonLabel),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemSearchWeb',
|
|
() {
|
|
const IOSSystemContextMenuItemSearchWeb item = IOSSystemContextMenuItemSearchWeb();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(
|
|
item.getData(localizations),
|
|
IOSSystemContextMenuItemDataSearchWeb(title: localizations.searchWebButtonLabel),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemShare',
|
|
() {
|
|
const IOSSystemContextMenuItemShare item = IOSSystemContextMenuItemShare();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
expect(
|
|
item.getData(localizations),
|
|
IOSSystemContextMenuItemDataShare(title: localizations.shareButtonLabel),
|
|
);
|
|
},
|
|
);
|
|
|
|
test(
|
|
'can get the IOSSystemContextMenuItemData representation of an IOSSystemContextMenuItemLiveText',
|
|
() {
|
|
const IOSSystemContextMenuItemLiveText item = IOSSystemContextMenuItemLiveText();
|
|
const WidgetsLocalizations localizations = DefaultWidgetsLocalizations();
|
|
final IOSSystemContextMenuItemData data = item.getData(localizations);
|
|
expect(data, isA<IOSSystemContextMenuItemDataLiveText>());
|
|
},
|
|
);
|
|
|
|
test('systemContextMenuItemDataFromJson handles Live Text', () {
|
|
final Map<String, dynamic> json = <String, dynamic>{'type': 'captureTextFromCamera'};
|
|
final IOSSystemContextMenuItemData item = systemContextMenuItemDataFromJson(json);
|
|
expect(item, isA<IOSSystemContextMenuItemDataLiveText>());
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/169696.
|
|
test('IOSSystemContextMenuItemLookUp debugFillProperties', () {
|
|
const String title = 'my title';
|
|
const IOSSystemContextMenuItemLookUp item = IOSSystemContextMenuItemLookUp(title: title);
|
|
final List<DiagnosticsNode> diagnosticsNodes = item.toDiagnosticsNode().getProperties();
|
|
expect(diagnosticsNodes, hasLength(1));
|
|
expect(diagnosticsNodes.first.name, 'title');
|
|
expect(diagnosticsNodes.first.value, title);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/169696.
|
|
test('IOSSystemContextMenuItemSearchWeb debugFillProperties', () {
|
|
const String title = 'my title';
|
|
const IOSSystemContextMenuItemSearchWeb item = IOSSystemContextMenuItemSearchWeb(title: title);
|
|
final List<DiagnosticsNode> diagnosticsNodes = item.toDiagnosticsNode().getProperties();
|
|
expect(diagnosticsNodes, hasLength(1));
|
|
expect(diagnosticsNodes.first.name, 'title');
|
|
expect(diagnosticsNodes.first.value, title);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/169696.
|
|
test('IOSSystemContextMenuItemShare debugFillProperties', () {
|
|
const String title = 'my title';
|
|
const IOSSystemContextMenuItemShare item = IOSSystemContextMenuItemShare(title: title);
|
|
final List<DiagnosticsNode> diagnosticsNodes = item.toDiagnosticsNode().getProperties();
|
|
expect(diagnosticsNodes, hasLength(1));
|
|
expect(diagnosticsNodes.first.name, 'title');
|
|
expect(diagnosticsNodes.first.value, title);
|
|
});
|
|
}
|