blob: c5315c9ceec38af46689cbfce78c021a042f0fd2 [file] [log] [blame]
// 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 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.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 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),
);
testWidgets(
'asserts when built with no text input connection',
experimentalLeakTesting:
LeakTesting.settings.withIgnoredAll(), // leaking by design because of exception
(WidgetTester tester) async {
SystemContextMenu? systemContextMenu;
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: StatefulBuilder(
builder: (BuildContext context, StateSetter localSetState) {
setState = localSetState;
return Column(
children: <Widget>[
const TextField(),
if (systemContextMenu != null) systemContextMenu!,
],
);
},
),
),
),
);
},
),
);
// No SystemContextMenu yet, so no assertion error.
expect(tester.takeException(), isNull);
// Add the SystemContextMenu and receive an assertion since there is no
// active text input connection.
setState(() {
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
systemContextMenu = SystemContextMenu.editableText(editableTextState: state);
});
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
dynamic exception;
FlutterError.onError = (FlutterErrorDetails details) {
exception ??= details.exception;
};
addTearDown(() {
FlutterError.onError = oldHandler;
});
await tester.pump();
expect(exception, isAssertionError);
expect(exception.toString(), contains('only be shown for an active text input connection'));
},
skip: kIsWeb, // [intended]
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
testWidgets(
'does not assert when built with an active text input connection',
(WidgetTester tester) async {
SystemContextMenu? systemContextMenu;
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: StatefulBuilder(
builder: (BuildContext context, StateSetter localSetState) {
setState = localSetState;
return Column(
children: <Widget>[
const TextField(),
if (systemContextMenu != null) systemContextMenu!,
],
);
},
),
),
),
);
},
),
);
// No SystemContextMenu yet, so no assertion error.
expect(tester.takeException(), isNull);
// Tap the field to open a text input connection.
await tester.tap(find.byType(TextField));
await tester.pump();
// Add the SystemContextMenu and expect no error.
setState(() {
final EditableTextState state = tester.state<EditableTextState>(find.byType(EditableText));
systemContextMenu = SystemContextMenu.editableText(editableTextState: state);
});
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
dynamic exception;
FlutterError.onError = (FlutterErrorDetails details) {
exception ??= details.exception;
};
addTearDown(() {
FlutterError.onError = oldHandler;
});
await tester.pump();
expect(exception, isNull);
},
skip: kIsWeb, // [intended]
variant: TargetPlatformVariant.only(TargetPlatform.iOS),
);
}