blob: 6b4dd3b1f0149ff73da901437207f73cb819bbcb [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/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'editable_text_utils.dart';
void main() {
const TextStyle textStyle = TextStyle();
const Color cursorColor = Color.fromARGB(0xFF, 0xFF, 0x00, 0x00);
late TextEditingController controller;
late FocusNode focusNode;
setUp(() async {
controller = TextEditingController();
focusNode = FocusNode(debugLabel: 'EditableText Node');
});
tearDown(() {
controller.dispose();
focusNode.dispose();
});
testWidgets(
'selection rects re-sent when refocused',
(WidgetTester tester) async {
final List<List<SelectionRect>> log = <List<SelectionRect>>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (
MethodCall methodCall,
) async {
if (methodCall.method == 'TextInput.setSelectionRects') {
final List<dynamic> args = methodCall.arguments as List<dynamic>;
final List<SelectionRect> selectionRects = <SelectionRect>[];
for (final dynamic rect in args) {
selectionRects.add(
SelectionRect(
position: (rect as List<dynamic>)[4] as int,
bounds: Rect.fromLTWH(
rect[0] as double,
rect[1] as double,
rect[2] as double,
rect[3] as double,
),
),
);
}
log.add(selectionRects);
}
return null;
});
final ScrollController scrollController = ScrollController();
addTearDown(scrollController.dispose);
controller.text = 'Text1';
Future<void> pumpEditableText({
double? width,
double? height,
TextAlign textAlign = TextAlign.start,
}) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: width,
height: height,
child: EditableText(
controller: controller,
textAlign: textAlign,
scrollController: scrollController,
maxLines: null,
focusNode: focusNode,
cursorWidth: 0,
style: Typography.material2018().black.titleMedium!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
),
),
),
),
),
);
}
const List<SelectionRect> expectedRects = <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, 0.0, 14.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(14.0, 0.0, 28.0, 14.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(28.0, 0.0, 42.0, 14.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(42.0, 0.0, 56.0, 14.0)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(56.0, 0.0, 70.0, 14.0)),
];
await pumpEditableText();
expect(log, isEmpty);
await tester.showKeyboard(find.byType(EditableText));
// First update.
expect(log.single, expectedRects);
log.clear();
await tester.pumpAndSettle();
expect(log, isEmpty);
focusNode.unfocus();
await tester.pumpAndSettle();
expect(log, isEmpty);
focusNode.requestFocus();
//await tester.showKeyboard(find.byType(EditableText));
await tester.pumpAndSettle();
// Should re-receive the same rects.
expect(log.single, expectedRects);
log.clear();
// On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'Selection changes during Scribble interaction should have the scribble cause',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
late SelectionChangedCause selectionCause;
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) {
if (cause != null) {
selectionCause = cause;
}
},
),
),
);
await tester.showKeyboard(find.byType(EditableText));
// A normal selection update from the framework has 'keyboard' as the cause.
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 2, extentOffset: 3),
),
);
await tester.pumpAndSettle();
expect(selectionCause, SelectionChangedCause.keyboard);
// A selection update during a scribble interaction has 'scribble' as the cause.
await tester.testTextInput.startScribbleInteraction();
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 3, extentOffset: 4),
),
);
await tester.pumpAndSettle();
expect(selectionCause, SelectionChangedCause.scribble);
await tester.testTextInput.finishScribbleInteraction();
},
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'Requests focus and changes the selection when onScribbleFocus is called',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
late SelectionChangedCause selectionCause;
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
onSelectionChanged: (TextSelection selection, SelectionChangedCause? cause) {
if (cause != null) {
selectionCause = cause;
}
},
),
),
);
await tester.testTextInput.scribbleFocusElement(
TextInput.scribbleClients.keys.first,
Offset.zero,
);
expect(focusNode.hasFocus, true);
expect(selectionCause, SelectionChangedCause.scribble);
// On web, we should rely on the browser's implementation of Scribble, so the selection changed cause
// will never be SelectionChangedCause.scribble.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'Declares itself for Scribble interaction if the bounds overlap the scribble rect and the widget is touchable',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
),
),
);
final List<dynamic> elementEntry = <dynamic>[
TextInput.scribbleClients.keys.first,
0.0,
0.0,
800.0,
600.0,
];
List<List<dynamic>> elements = await tester.testTextInput.scribbleRequestElementsInRect(
const Rect.fromLTWH(0, 0, 1, 1),
);
expect(elements.first, containsAll(elementEntry));
// Touch is outside the bounds of the widget.
elements = await tester.testTextInput.scribbleRequestElementsInRect(
const Rect.fromLTWH(-1, -1, 1, 1),
);
expect(elements.length, 0);
// Widget is read only.
await tester.pumpWidget(
MaterialApp(
home: EditableText(
readOnly: true,
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
),
),
);
elements = await tester.testTextInput.scribbleRequestElementsInRect(
const Rect.fromLTWH(0, 0, 1, 1),
);
expect(elements.length, 0);
// Widget is not touchable.
await tester.pumpWidget(
MaterialApp(
home: Stack(
children: <Widget>[
EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
),
Positioned(
left: 0,
top: 0,
right: 0,
bottom: 0,
child: Container(color: Colors.black),
),
],
),
),
);
elements = await tester.testTextInput.scribbleRequestElementsInRect(
const Rect.fromLTWH(0, 0, 1, 1),
);
expect(elements.length, 0);
// Widget has scribble disabled.
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
stylusHandwritingEnabled: false,
),
),
);
elements = await tester.testTextInput.scribbleRequestElementsInRect(
const Rect.fromLTWH(0, 0, 1, 1),
);
expect(elements.length, 0);
// On web, we should rely on the browser's implementation of Scribble, so the engine will
// never request the scribble elements.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'single line Scribble fields can show a horizontal placeholder',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
),
),
);
await tester.showKeyboard(find.byType(EditableText));
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 5, extentOffset: 5),
),
);
await tester.pumpAndSettle();
await tester.testTextInput.scribbleInsertPlaceholder();
await tester.pumpAndSettle();
TextSpan textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children!.length, 3);
expect((textSpan.children![0] as TextSpan).text, 'Lorem');
expect(textSpan.children![1] is WidgetSpan, true);
expect((textSpan.children![2] as TextSpan).text, ' ipsum dolor sit amet');
await tester.testTextInput.scribbleRemovePlaceholder();
await tester.pumpAndSettle();
textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children, null);
expect(textSpan.text, 'Lorem ipsum dolor sit amet');
// Widget has scribble disabled.
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
stylusHandwritingEnabled: false,
),
),
);
await tester.showKeyboard(find.byType(EditableText));
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 5, extentOffset: 5),
),
);
await tester.pumpAndSettle();
await tester.testTextInput.scribbleInsertPlaceholder();
await tester.pumpAndSettle();
textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children, null);
expect(textSpan.text, 'Lorem ipsum dolor sit amet');
// On web, we should rely on the browser's implementation of Scribble, so the framework
// will not handle placeholders.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'multiline Scribble fields can show a vertical placeholder',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
maxLines: 2,
),
),
);
await tester.showKeyboard(find.byType(EditableText));
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 5, extentOffset: 5),
),
);
await tester.pumpAndSettle();
await tester.testTextInput.scribbleInsertPlaceholder();
await tester.pumpAndSettle();
TextSpan textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children!.length, 4);
expect((textSpan.children![0] as TextSpan).text, 'Lorem');
expect(textSpan.children![1] is WidgetSpan, true);
expect(textSpan.children![2] is WidgetSpan, true);
expect((textSpan.children![3] as TextSpan).text, ' ipsum dolor sit amet');
await tester.testTextInput.scribbleRemovePlaceholder();
await tester.pumpAndSettle();
textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children, null);
expect(textSpan.text, 'Lorem ipsum dolor sit amet');
// Widget has scribble disabled.
await tester.pumpWidget(
MaterialApp(
home: EditableText(
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
maxLines: 2,
stylusHandwritingEnabled: false,
),
),
);
await tester.showKeyboard(find.byType(EditableText));
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 5, extentOffset: 5),
),
);
await tester.pumpAndSettle();
await tester.testTextInput.scribbleInsertPlaceholder();
await tester.pumpAndSettle();
textSpan = findRenderEditable(tester).text! as TextSpan;
expect(textSpan.children, null);
expect(textSpan.text, 'Lorem ipsum dolor sit amet');
// On web, we should rely on the browser's implementation of Scribble, so the framework
// will not handle placeholders.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'selection rects are sent when they change',
(WidgetTester tester) async {
addTearDown(tester.view.reset);
// Ensure selection rects are sent on iPhone (using SE 3rd gen size)
tester.view.physicalSize = const Size(750.0, 1334.0);
final List<List<SelectionRect>> log = <List<SelectionRect>>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (
MethodCall methodCall,
) {
if (methodCall.method == 'TextInput.setSelectionRects') {
final List<dynamic> args = methodCall.arguments as List<dynamic>;
final List<SelectionRect> selectionRects = <SelectionRect>[];
for (final dynamic rect in args) {
selectionRects.add(
SelectionRect(
position: (rect as List<dynamic>)[4] as int,
bounds: Rect.fromLTWH(
rect[0] as double,
rect[1] as double,
rect[2] as double,
rect[3] as double,
),
),
);
}
log.add(selectionRects);
}
return null;
});
final ScrollController scrollController = ScrollController();
addTearDown(scrollController.dispose);
controller.text = 'Text1';
Future<void> pumpEditableText({
double? width,
double? height,
TextAlign textAlign = TextAlign.start,
}) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: width,
height: height,
child: EditableText(
controller: controller,
textAlign: textAlign,
scrollController: scrollController,
maxLines: null,
focusNode: focusNode,
cursorWidth: 0,
style: Typography.material2018().black.titleMedium!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
),
),
),
),
),
);
}
await pumpEditableText();
expect(log, isEmpty);
await tester.showKeyboard(find.byType(EditableText));
// First update.
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, 0.0, 14.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(14.0, 0.0, 28.0, 14.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(28.0, 0.0, 42.0, 14.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(42.0, 0.0, 56.0, 14.0)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(56.0, 0.0, 70.0, 14.0)),
]);
log.clear();
await tester.pumpAndSettle();
expect(log, isEmpty);
await pumpEditableText();
expect(log, isEmpty);
// Change the width such that each character occupies a line.
await pumpEditableText(width: 20);
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, 0.0, 14.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(0.0, 14.0, 14.0, 28.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(0.0, 28.0, 14.0, 42.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(0.0, 42.0, 14.0, 56.0)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(0.0, 56.0, 14.0, 70.0)),
]);
log.clear();
await tester.enterText(find.byType(EditableText), 'Text1👨‍👩‍👦');
await tester.pump();
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, 0.0, 14.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(0.0, 14.0, 14.0, 28.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(0.0, 28.0, 14.0, 42.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(0.0, 42.0, 14.0, 56.0)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(0.0, 56.0, 14.0, 70.0)),
SelectionRect(position: 5, bounds: Rect.fromLTRB(0.0, 70.0, 42.0, 84.0)),
]);
log.clear();
// The 4th line will be partially visible.
await pumpEditableText(width: 20, height: 45);
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, 0.0, 14.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(0.0, 14.0, 14.0, 28.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(0.0, 28.0, 14.0, 42.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(0.0, 42.0, 14.0, 56.0)),
]);
log.clear();
await pumpEditableText(width: 20, height: 45, textAlign: TextAlign.right);
// This is 1px off from being completely right-aligned. The 1px width is
// reserved for caret.
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(5.0, 0.0, 19.0, 14.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(5.0, 14.0, 19.0, 28.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(5.0, 28.0, 19.0, 42.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(5.0, 42.0, 19.0, 56.0)),
// These 2 lines will be out of bounds.
// SelectionRect(position: 4, bounds: Rect.fromLTRB(5.0, 56.0, 19.0, 70.0)),
// SelectionRect(position: 5, bounds: Rect.fromLTRB(-23.0, 70.0, 19.0, 84.0)),
]);
log.clear();
expect(scrollController.offset, 0);
// Scrolling also triggers update.
scrollController.jumpTo(14);
await tester.pumpAndSettle();
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(5.0, -14.0, 19.0, 0.0)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(5.0, 0.0, 19.0, 14.0)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(5.0, 14.0, 19.0, 28.0)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(5.0, 28.0, 19.0, 42.0)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(5.0, 42.0, 19.0, 56.0)),
// This line is skipped because it's below the bottom edge of the render
// object.
// SelectionRect(position: 5, bounds: Rect.fromLTRB(5.0, 56.0, 47.0, 70.0)),
]);
log.clear();
// On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'selection rects are not sent if stylusHandwritingEnabled is false',
(WidgetTester tester) async {
final List<MethodCall> log = <MethodCall>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (
MethodCall methodCall,
) async {
log.add(methodCall);
return null;
});
controller.text = 'Text1';
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
EditableText(
key: ValueKey<String>(controller.text),
controller: controller,
focusNode: focusNode,
style: Typography.material2018().black.titleMedium!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
stylusHandwritingEnabled: false,
),
],
),
),
),
);
await tester.showKeyboard(find.byKey(ValueKey<String>(controller.text)));
// There should be a new platform message updating the selection rects.
expect(log.where((MethodCall m) => m.method == 'TextInput.setSelectionRects').length, 0);
// On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
testWidgets(
'selection rects sent even when character corners are outside of paintBounds',
(WidgetTester tester) async {
final List<List<SelectionRect>> log = <List<SelectionRect>>[];
tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(SystemChannels.textInput, (
MethodCall methodCall,
) {
if (methodCall.method == 'TextInput.setSelectionRects') {
final List<dynamic> args = methodCall.arguments as List<dynamic>;
final List<SelectionRect> selectionRects = <SelectionRect>[];
for (final dynamic rect in args) {
selectionRects.add(
SelectionRect(
position: (rect as List<dynamic>)[4] as int,
bounds: Rect.fromLTWH(
rect[0] as double,
rect[1] as double,
rect[2] as double,
rect[3] as double,
),
),
);
}
log.add(selectionRects);
}
return null;
});
final ScrollController scrollController = ScrollController();
addTearDown(scrollController.dispose);
controller.text = 'Text1';
final GlobalKey<EditableTextState> editableTextKey = GlobalKey();
Future<void> pumpEditableText({
double? width,
double? height,
TextAlign textAlign = TextAlign.start,
}) async {
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: SizedBox(
width: width,
height: height,
child: EditableText(
controller: controller,
textAlign: textAlign,
scrollController: scrollController,
maxLines: null,
focusNode: focusNode,
cursorWidth: 0,
key: editableTextKey,
style: Typography.material2018().black.titleMedium!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
),
),
),
),
),
);
}
// Set height to 1 pixel less than full height.
await pumpEditableText(height: 13);
expect(log, isEmpty);
// Scroll so that the top of each character is above the top of the renderEditable
// and the bottom of each character is below the bottom of the renderEditable.
final ViewportOffset offset = ViewportOffset.fixed(0.5);
addTearDown(offset.dispose);
editableTextKey.currentState!.renderEditable.offset = offset;
await tester.showKeyboard(find.byType(EditableText));
// We should get all the rects.
expect(log.single, const <SelectionRect>[
SelectionRect(position: 0, bounds: Rect.fromLTRB(0.0, -0.5, 14.0, 13.5)),
SelectionRect(position: 1, bounds: Rect.fromLTRB(14.0, -0.5, 28.0, 13.5)),
SelectionRect(position: 2, bounds: Rect.fromLTRB(28.0, -0.5, 42.0, 13.5)),
SelectionRect(position: 3, bounds: Rect.fromLTRB(42.0, -0.5, 56.0, 13.5)),
SelectionRect(position: 4, bounds: Rect.fromLTRB(56.0, -0.5, 70.0, 13.5)),
]);
log.clear();
// On web, we should rely on the browser's implementation of Scribble, so we will not send selection rects.
},
skip: kIsWeb, // [intended]
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
);
// Regression test for https://github.com/flutter/flutter/issues/159259.
testWidgets(
'showToolbar does nothing and returns false when already shown during Scribble selection',
(WidgetTester tester) async {
controller.text = 'Lorem ipsum dolor sit amet';
final GlobalKey<EditableTextState> editableTextKey = GlobalKey();
await tester.pumpWidget(
MaterialApp(
home: EditableText(
key: editableTextKey,
controller: controller,
backgroundCursorColor: Colors.grey,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionHandleControls,
contextMenuBuilder: (BuildContext context, EditableTextState editableTextState) {
return AdaptiveTextSelectionToolbar.editableText(
editableTextState: editableTextState,
);
},
),
),
);
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
await tester.showKeyboard(find.byType(EditableText));
await tester.testTextInput.startScribbleInteraction();
tester.testTextInput.updateEditingValue(
TextEditingValue(
text: controller.text,
selection: const TextSelection(baseOffset: 3, extentOffset: 4),
),
);
await tester.pumpAndSettle();
expect(find.byType(AdaptiveTextSelectionToolbar), findsNothing);
expect(editableTextKey.currentState!.showToolbar(), isTrue);
await tester.pumpAndSettle();
expect(find.byType(AdaptiveTextSelectionToolbar), findsOneWidget);
expect(editableTextKey.currentState!.showToolbar(), isFalse);
await tester.pump();
expect(find.byType(AdaptiveTextSelectionToolbar), findsOneWidget);
await tester.pumpAndSettle();
expect(find.byType(AdaptiveTextSelectionToolbar), findsOneWidget);
await tester.testTextInput.finishScribbleInteraction();
},
variant: const TargetPlatformVariant(<TargetPlatform>{TargetPlatform.iOS}),
skip: kIsWeb, // [intended]
);
}