Unverified Commit 32b9c2f0 authored by xster's avatar xster Committed by GitHub

Add more RenderEditable test coverage (#27003)

parent 37108c4c
......@@ -1516,7 +1516,7 @@ class RenderEditable extends RenderBox {
_textPainter.paint(context.canvas, effectiveOffset);
if (_selection != null && !_floatingCursorOn) {
if (_selection.isCollapsed && cursorColor != null && _hasFocus) {
if (_selection.isCollapsed && _showCursor.value && cursorColor != null) {
_paintCaret(context.canvas, effectiveOffset, _selection.extent);
} else if (!_selection.isCollapsed && _selectionColor != null) {
_selectionRects ??= _textPainter.getBoxesForSelection(_selection);
......
......@@ -565,7 +565,8 @@ class EditableText extends StatefulWidget {
/// State for a [EditableText].
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText> implements TextInputClient, TextSelectionDelegate {
Timer _cursorTimer;
final ValueNotifier<bool> _showCursor = ValueNotifier<bool>(false);
bool _targetCursorVisibility = false;
final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
final GlobalKey _editableKey = GlobalKey();
TextInputConnection _textInputConnection;
......@@ -1006,12 +1007,13 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void _onCursorColorTick() {
renderEditable.cursorColor = widget.cursorColor.withOpacity(_cursorBlinkOpacityController.value);
_cursorVisibilityNotifier.value = _cursorBlinkOpacityController.value > 0;
}
/// Whether the blinking cursor is actually visible at this precise moment
/// (it's hidden half the time, since it blinks).
@visibleForTesting
bool get cursorCurrentlyVisible => _showCursor.value;
bool get cursorCurrentlyVisible => _cursorBlinkOpacityController.value > 0;
/// The cursor blink interval (the amount of time the cursor is in the "on"
/// state or the "off" state). A complete cursor blink period is twice this
......@@ -1027,7 +1029,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
int _obscureLatestCharIndex;
void _cursorTick(Timer timer) {
_showCursor.value = !_showCursor.value;
_targetCursorVisibility = !_targetCursorVisibility;
final double targetOpacity = _targetCursorVisibility ? 1.0 : 0.0;
if (widget.cursorOpacityAnimates) {
// If we want to show the cursor, we will animate the opacity to the value
// of 1.0, and likewise if we want to make it disappear, to 0.0. An easing
......@@ -1036,10 +1039,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
//
// These values and curves have been obtained through eyeballing, so are
// likely not exactly the same as the values for native iOS.
final double toValue = _showCursor.value ? 1.0 : 0.0;
_cursorBlinkOpacityController.animateTo(toValue, curve: Curves.easeOut);
_cursorBlinkOpacityController.animateTo(targetOpacity, curve: Curves.easeOut);
} else {
_cursorBlinkOpacityController.value = _showCursor.value ? 1.0 : 0.0;
_cursorBlinkOpacityController.value = targetOpacity;
}
if (_obscureShowCharTicksPending > 0) {
......@@ -1056,7 +1058,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
}
void _startCursorTimer() {
_showCursor.value = true;
_targetCursorVisibility = true;
_cursorBlinkOpacityController.value = 1.0;
if (EditableText.debugDeterministicCursor)
return;
......@@ -1070,7 +1072,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
void _stopCursorTimer({ bool resetCharTicks = true }) {
_cursorTimer?.cancel();
_cursorTimer = null;
_showCursor.value = false;
_targetCursorVisibility = false;
_cursorBlinkOpacityController.value = 0.0;
if (EditableText.debugDeterministicCursor)
return;
......@@ -1196,7 +1198,9 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
value: _value,
cursorColor: _cursorColor,
backgroundCursorColor: widget.backgroundCursorColor,
showCursor: EditableText.debugDeterministicCursor ? ValueNotifier<bool>(true) : _showCursor,
showCursor: EditableText.debugDeterministicCursor
? ValueNotifier<bool>(true)
: _cursorVisibilityNotifier,
hasFocus: _hasFocus,
maxLines: widget.maxLines,
selectionColor: widget.selectionColor,
......
......@@ -125,12 +125,12 @@ void main() {
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
const String kThreeLines =
'First line of text is '
'Second line goes until '
'Third line of stuff ';
'First line of text is\n'
'Second line goes until\n'
'Third line of stuff';
const String kMoreThanFourLines =
kThreeLines +
'Fourth line won\'t display and ends at';
'\nFourth line won\'t display and ends at';
// Returns the first RenderEditable.
RenderEditable findRenderEditable(WidgetTester tester) {
......@@ -903,7 +903,7 @@ void main() {
);
const String testValue = kThreeLines;
const String cutValue = 'First line of stuff ';
const String cutValue = 'First line of stuff';
await tester.enterText(find.byType(TextField), testValue);
await skipPastScrollingAnimation(tester);
......@@ -973,7 +973,9 @@ void main() {
testWidgets('Can scroll multiline input', (WidgetTester tester) async {
final Key textFieldKey = UniqueKey();
final TextEditingController controller = TextEditingController();
final TextEditingController controller = TextEditingController(
text: kMoreThanFourLines,
);
await tester.pumpWidget(
overlay(
......@@ -986,12 +988,6 @@ void main() {
),
),
);
await tester.pump(const Duration(seconds: 1));
await tester.enterText(find.byType(TextField), kMoreThanFourLines);
await tester.pump();
await tester.pump(const Duration(seconds: 1));
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
final RenderBox inputBox = findInputBox();
......@@ -1016,6 +1012,7 @@ void main() {
await tester.pump(const Duration(seconds: 1));
await gesture.up();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
// Now the first line is scrolled up, and the fourth line is visible.
Offset newFirstPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First'));
......@@ -1026,15 +1023,21 @@ void main() {
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isTrue);
// Now try scrolling by dragging the selection handle.
// Long press the 'i' in 'Fourth line' to select the word.
await tester.pump(const Duration(seconds: 1));
final Offset untilPos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('Fourth line')+8);
gesture = await tester.startGesture(untilPos, pointer: 7);
final Offset selectedWordPos = textOffsetToPosition(
tester,
kMoreThanFourLines.indexOf('Fourth line') + 8,
);
gesture = await tester.startGesture(selectedWordPos, pointer: 7);
await tester.pump(const Duration(seconds: 1));
await gesture.up();
await tester.pump();
await tester.pump(const Duration(seconds: 1));
expect(controller.selection.base.offset, 91);
expect(controller.selection.extent.offset, 94);
final RenderEditable renderEditable = findRenderEditable(tester);
final List<TextSelectionPoint> endpoints = globalize(
renderEditable.getEndpointsForSelection(controller.selection),
......@@ -1043,7 +1046,7 @@ void main() {
expect(endpoints.length, 2);
// Drag the left handle to the first line, just after 'First'.
final Offset handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
final Offset handlePos = endpoints[0].point + const Offset(-1, 1);
final Offset newHandlePos = textOffsetToPosition(tester, kMoreThanFourLines.indexOf('First') + 5);
gesture = await tester.startGesture(handlePos, pointer: 7);
await tester.pump(const Duration(seconds: 1));
......@@ -1059,9 +1062,7 @@ void main() {
expect(newFirstPos.dy, firstPos.dy);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
expect(inputBox.hitTest(HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
},
// This test fails on some Mac environments when libtxt is enabled.
skip: Platform.isMacOS);
});
testWidgets('TextField smoke test', (WidgetTester tester) async {
String textFieldValue;
......
......@@ -10,6 +10,7 @@ import 'package:flutter/foundation.dart';
import '../rendering/mock_canvas.dart';
import '../rendering/recording_canvas.dart';
import 'rendering_tester.dart';
class FakeEditableTextState extends TextSelectionDelegate {
@override
......@@ -26,10 +27,6 @@ class FakeEditableTextState extends TextSelectionDelegate {
}
void main() {
final TextEditingController controller = TextEditingController();
const TextStyle textStyle = TextStyle();
test('editable intrinsics', () {
final TextSelectionDelegate delegate = FakeEditableTextState();
final RenderEditable editable = RenderEditable(
......@@ -99,86 +96,76 @@ void main() {
);
});
RenderEditable findRenderEditable(WidgetTester tester) {
final RenderObject root = tester.renderObject(find.byType(EditableText));
expect(root, isNotNull);
RenderEditable renderEditable;
void recursiveFinder(RenderObject child) {
if (child is RenderEditable) {
renderEditable = child;
return;
}
child.visitChildren(recursiveFinder);
}
root.visitChildren(recursiveFinder);
expect(renderEditable, isNotNull);
return renderEditable;
}
testWidgets('Floating cursor is painted', (WidgetTester tester) async {
debugDefaultTargetPlatformOverride = TargetPlatform.iOS;
const String text = 'hello world this is fun and cool and awesome!';
controller.text = text;
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
MaterialApp(
home: Padding(
padding: const EdgeInsets.only(top: 0.25),
child: Material(
child: TextField(
controller: controller,
focusNode: focusNode,
style: textStyle,
),
),
test('Can change cursor color, radius, visibility', () {
final TextSelectionDelegate delegate = FakeEditableTextState();
final ValueNotifier<bool> showCursor = ValueNotifier<bool>(true);
EditableText.debugDeterministicCursor = true;
final RenderEditable editable = RenderEditable(
backgroundCursorColor: Colors.grey,
textDirection: TextDirection.ltr,
cursorColor: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00),
offset: ViewportOffset.zero(),
textSelectionDelegate: delegate,
text: const TextSpan(
text: 'test',
style: TextStyle(
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
),
),
selection: const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
),
);
await tester.tap(find.byType(EditableText));
final RenderEditable editable = findRenderEditable(tester);
editable.selection = const TextSelection(baseOffset: 29, extentOffset: 29);
final EditableTextState editableTextState = tester.firstState(find.byType(EditableText));
editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Start));
editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.Update,
offset: const Offset(20, 20)));
await tester.pump();
expect(editable, paints
..rrect(rrect: RRect.fromRectAndRadius(
Rect.fromLTRB(464.6666564941406, -1.5833333730697632, 466.6666564941406, 16.41666603088379),
const Radius.circular(2.0)),
color: const Color(0xff8e8e93))
..rrect(rrect: RRect.fromRectAndRadius(
Rect.fromLTRB(465.1666564941406, -2.416666269302368, 468.1666564941406, 17.58333396911621),
const Radius.circular(1.0)),
color: const Color(0xbf2196f3))
);
layout(editable);
// Moves the cursor right a few characters.
editableTextState.updateFloatingCursor(
RawFloatingCursorPoint(
state: FloatingCursorDragState.Update,
offset: const Offset(-250, 20)));
expect(find.byType(EditableText), paints
..rrect(rrect: RRect.fromRectAndRadius(
Rect.fromLTRB(192.6666717529297, -1.5833333730697632, 194.6666717529297, 16.41666603088379),
const Radius.circular(2.0)),
color: const Color(0xff8e8e93))
..rrect(rrect: RRect.fromRectAndRadius(
Rect.fromLTRB(195.16665649414062, -2.416666269302368, 198.16665649414062, 17.58333396911621),
const Radius.circular(1.0)),
color: const Color(0xbf2196f3))
editable.layout(BoxConstraints.loose(const Size(100, 100)));
expect(
editable,
// Draw no cursor by default.
paintsExactlyCountTimes(#drawRect, 0),
);
editableTextState.updateFloatingCursor(RawFloatingCursorPoint(state: FloatingCursorDragState.End));
editable.showCursor = showCursor;
pumpFrame();
expect(editable, paints..rect(
color: const Color.fromARGB(0xFF, 0xFF, 0x00, 0x00),
rect: Rect.fromLTWH(40, 2, 1, 6),
));
// Now change to a rounded caret.
editable.cursorColor = const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF);
editable.cursorWidth = 4;
editable.cursorRadius = const Radius.circular(3);
pumpFrame();
expect(editable, paints..rrect(
color: const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF),
rrect: RRect.fromRectAndRadius(
Rect.fromLTWH(40, 2, 4, 6),
const Radius.circular(3),
),
));
editable.textScaleFactor = 2;
pumpFrame();
// Now the caret height is much bigger due to the bigger font scale.
expect(editable, paints..rrect(
color: const Color.fromARGB(0xFF, 0x00, 0x00, 0xFF),
rrect: RRect.fromRectAndRadius(
Rect.fromLTWH(80, 2, 4, 16),
const Radius.circular(3),
),
));
await tester.pumpAndSettle();
// Can turn off caret.
showCursor.value = false;
pumpFrame();
debugDefaultTargetPlatformOverride = null;
expect(editable, paintsExactlyCountTimes(#drawRRect, 0));
});
}
This diff is collapsed.
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