Unverified Commit 581df52a authored by sandrasandeep's avatar sandrasandeep Committed by GitHub

Make EditableText cursor configurable (#18888)

* fixed segmented control golden test

* fixed segmented control golden test

* added cursorWidth, cursorRadius

* added default value for cursorWidth based on Apple specs

* test default cursorWidth

* removed cursorHeight stuff

* added functionality to keep cursor from blinking

* cursor width and radius is configurable + tests

* changed goldens repo version in goldens.version

* working version of configurable cursor (erased debugKeepCursorOn)

* minor changes

* docs

* changed textfield test that was failing due to new default cursorwidth

* added default value of cursorwidth in RenderEditable

* only run golden file tests on Mac

* cursor tests

* the tests are actually there now

* weak warning fixed

* switching to Linux

* changed default cursorWidth: 2.0 -> 1.0

* assorted changes, including changing text field test

* re-paint -> re-layout when changing cursorWidth
parent 989f5741
......@@ -16,7 +16,6 @@ import 'viewport_offset.dart';
const double _kCaretGap = 1.0; // pixels
const double _kCaretHeightOffset = 2.0; // pixels
const double _kCaretWidth = 1.0; // pixels
/// Signature for the callback that reports when the user changes the selection
/// (including the cursor location).
......@@ -134,6 +133,8 @@ class RenderEditable extends RenderBox {
this.ignorePointer = false,
bool obscureText = false,
Locale locale,
double cursorWidth = 1.0,
Radius cursorRadius,
}) : assert(textAlign != null),
assert(textDirection != null, 'RenderEditable created without a textDirection.'),
assert(maxLines == null || maxLines > 0),
......@@ -155,6 +156,8 @@ class RenderEditable extends RenderBox {
_selectionColor = selectionColor,
_selection = selection,
_offset = offset,
_cursorWidth = cursorWidth,
_cursorRadius = cursorRadius,
_obscureText = obscureText {
assert(_showCursor != null);
assert(!_showCursor.value || cursorColor != null);
......@@ -382,6 +385,26 @@ class RenderEditable extends RenderBox {
markNeedsLayout();
}
/// How thick the cursor will be.
double get cursorWidth => _cursorWidth;
double _cursorWidth = 1.0;
set cursorWidth(double value) {
if (_cursorWidth == value)
return;
_cursorWidth = value;
markNeedsLayout();
}
/// How rounded the corners of the cursor should be.
Radius get cursorRadius => _cursorRadius;
Radius _cursorRadius;
set cursorRadius(Radius value) {
if (_cursorRadius == value)
return;
_cursorRadius = value;
markNeedsPaint();
}
@override
void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config);
......@@ -546,7 +569,7 @@ class RenderEditable extends RenderBox {
_layoutText(constraints.maxWidth);
final Offset caretOffset = _textPainter.getOffsetForCaret(caretPosition, _caretPrototype);
// This rect is the same as _caretPrototype but without the vertical padding.
return new Rect.fromLTWH(0.0, 0.0, _kCaretWidth, preferredLineHeight).shift(caretOffset + _paintOffset);
return new Rect.fromLTWH(0.0, 0.0, cursorWidth, preferredLineHeight).shift(caretOffset + _paintOffset);
}
@override
......@@ -683,7 +706,7 @@ class RenderEditable extends RenderBox {
assert(constraintWidth != null);
if (_textLayoutLastWidth == constraintWidth)
return;
const double caretMargin = _kCaretGap + _kCaretWidth;
final double caretMargin = _kCaretGap + cursorWidth;
final double availableWidth = math.max(0.0, constraintWidth - caretMargin);
final double maxWidth = _isMultiline ? availableWidth : double.infinity;
_textPainter.layout(minWidth: availableWidth, maxWidth: maxWidth);
......@@ -693,7 +716,7 @@ class RenderEditable extends RenderBox {
@override
void performLayout() {
_layoutText(constraints.maxWidth);
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, _kCaretWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset);
_caretPrototype = new Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset);
_selectionRects = null;
// We grab _textPainter.size here because assigning to `size` on the next
// line will trigger us to validate our intrinsic sizes, which will change
......@@ -705,7 +728,7 @@ class RenderEditable extends RenderBox {
// See also RenderParagraph which has a similar issue.
final Size textPainterSize = _textPainter.size;
size = new Size(constraints.maxWidth, constraints.constrainHeight(_preferredHeight(constraints.maxWidth)));
final Size contentSize = new Size(textPainterSize.width + _kCaretGap + _kCaretWidth, textPainterSize.height);
final Size contentSize = new Size(textPainterSize.width + _kCaretGap + cursorWidth, textPainterSize.height);
final double _maxScrollExtent = _getMaxScrollExtent(contentSize);
_hasVisualOverflow = _maxScrollExtent > 0.0;
offset.applyViewportDimension(_viewportExtent);
......@@ -715,9 +738,18 @@ class RenderEditable extends RenderBox {
void _paintCaret(Canvas canvas, Offset effectiveOffset) {
assert(_textLayoutLastWidth == constraints.maxWidth);
final Offset caretOffset = _textPainter.getOffsetForCaret(_selection.extent, _caretPrototype);
final Paint paint = new Paint()..color = _cursorColor;
final Paint paint = new Paint()
..color = _cursorColor;
final Rect caretRect = _caretPrototype.shift(caretOffset + effectiveOffset);
canvas.drawRect(caretRect, paint);
if (cursorRadius == null) {
canvas.drawRect(caretRect, paint);
} else {
final RRect caretRRect = RRect.fromRectAndRadius(caretRect, cursorRadius);
canvas.drawRRect(caretRRect, paint);
}
if (caretRect != _lastCaretRect) {
_lastCaretRect = caretRect;
if (onCaretChanged != null)
......
......@@ -208,6 +208,8 @@ class EditableText extends StatefulWidget {
this.onSelectionChanged,
List<TextInputFormatter> inputFormatters,
this.rendererIgnoresPointer = false,
this.cursorWidth = 1.0,
this.cursorRadius,
}) : assert(controller != null),
assert(focusNode != null),
assert(obscureText != null),
......@@ -353,6 +355,16 @@ class EditableText extends StatefulWidget {
/// This property is false by default.
final bool rendererIgnoresPointer;
/// How thick the cursor will be.
///
/// Defaults to 1.0
final double cursorWidth;
/// How rounded the corners of the cursor should be.
///
/// By default, the cursor has a Radius of zero.
final Radius cursorRadius;
@override
EditableTextState createState() => new EditableTextState();
......@@ -810,6 +822,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
FocusScope.of(context).reparentIfNeeded(widget.focusNode);
super.build(context); // See AutomaticKeepAliveClientMixin.
final TextSelectionControls controls = widget.selectionControls;
return new Scrollable(
excludeFromSemantics: true,
axisDirection: _isMultiline ? AxisDirection.down : AxisDirection.right,
......@@ -841,6 +854,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
onSelectionChanged: _handleSelectionChanged,
onCaretChanged: _handleCaretChanged,
rendererIgnoresPointer: widget.rendererIgnoresPointer,
cursorWidth: widget.cursorWidth,
cursorRadius: widget.cursorRadius,
),
),
);
......@@ -902,6 +917,8 @@ class _Editable extends LeafRenderObjectWidget {
this.onSelectionChanged,
this.onCaretChanged,
this.rendererIgnoresPointer = false,
this.cursorWidth,
this.cursorRadius,
}) : assert(textDirection != null),
assert(rendererIgnoresPointer != null),
super(key: key);
......@@ -923,6 +940,8 @@ class _Editable extends LeafRenderObjectWidget {
final SelectionChangedHandler onSelectionChanged;
final CaretChangedHandler onCaretChanged;
final bool rendererIgnoresPointer;
final double cursorWidth;
final Radius cursorRadius;
@override
RenderEditable createRenderObject(BuildContext context) {
......@@ -943,6 +962,8 @@ class _Editable extends LeafRenderObjectWidget {
onCaretChanged: onCaretChanged,
ignorePointer: rendererIgnoresPointer,
obscureText: obscureText,
cursorWidth: cursorWidth,
cursorRadius: cursorRadius,
);
}
......@@ -964,6 +985,8 @@ class _Editable extends LeafRenderObjectWidget {
..onSelectionChanged = onSelectionChanged
..onCaretChanged = onCaretChanged
..ignorePointer = rendererIgnoresPointer
..obscureText = obscureText;
..obscureText = obscureText
..cursorWidth = cursorWidth
..cursorRadius = cursorRadius;
}
}
......@@ -967,6 +967,7 @@ void main() {
final Offset center = tester.getCenter(find.text('B'));
await tester.startGesture(center);
await tester.pumpAndSettle();
await expectLater(
find.byType(RepaintBoundary),
matchesGoldenFile('segmented_control_test.1.0.png'),
......
......@@ -1208,7 +1208,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
);
expect(topLeft.dx, equals(399.0));
expect(topLeft.dx, equals(399));
await tester.enterText(find.byType(TextField), 'abcd');
await tester.pump();
......@@ -1217,7 +1217,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
);
expect(topLeft.dx, equals(399.0));
expect(topLeft.dx, equals(399));
});
testWidgets('Can align to center within center', (WidgetTester tester) async {
......@@ -1240,7 +1240,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
);
expect(topLeft.dx, equals(399.0));
expect(topLeft.dx, equals(399));
await tester.enterText(find.byType(TextField), 'abcd');
await tester.pump();
......@@ -1249,7 +1249,7 @@ void main() {
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
);
expect(topLeft.dx, equals(399.0));
expect(topLeft.dx, equals(399));
});
testWidgets('Controller can update server', (WidgetTester tester) async {
......
......@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
......@@ -56,7 +57,8 @@ void main() {
controller.text = 'test';
await tester.idle();
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputAction'], equals(serializedActionName));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals(serializedActionName));
}
testWidgets('has expected defaults', (WidgetTester tester) async {
......@@ -77,6 +79,26 @@ void main() {
expect(editableText.maxLines, equals(1));
expect(editableText.obscureText, isFalse);
expect(editableText.autocorrect, isTrue);
expect(editableText.cursorWidth, 1.0);
});
testWidgets('cursor has expected width and radius',
(WidgetTester tester) async {
await tester.pumpWidget(new Directionality(
textDirection: TextDirection.ltr,
child: new EditableText(
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
cursorWidth: 10.0,
cursorRadius: const Radius.circular(2.0),
)));
final EditableText editableText =
tester.firstWidget(find.byType(EditableText));
expect(editableText.cursorWidth, 10.0);
expect(editableText.cursorRadius.x, 2.0);
});
testWidgets('text keyboard is requested when maxLines is default',
......@@ -104,13 +126,15 @@ void main() {
tester.firstWidget(find.byType(EditableText));
expect(editableText.maxLines, equals(1));
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputType']['name'], equals('TextInputType.text'));
expect(tester.testTextInput.setClientArgs['inputType']['name'],
equals('TextInputType.text'));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals('TextInputAction.done'));
});
testWidgets('Keyboard is configured for "unspecified" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "unspecified" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.unspecified,
......@@ -118,8 +142,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "none" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "none" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.none,
......@@ -127,8 +152,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "done" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "done" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.done,
......@@ -136,8 +162,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "send" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "send" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.send,
......@@ -145,8 +172,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "go" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "go" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.go,
......@@ -154,8 +182,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "search" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "search" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.search,
......@@ -163,8 +192,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "send" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "send" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.send,
......@@ -172,8 +202,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "next" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "next" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.next,
......@@ -181,8 +212,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "previous" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "previous" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.previous,
......@@ -190,8 +222,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "continue" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "continue" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.continueAction,
......@@ -199,8 +232,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "join" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "join" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.join,
......@@ -208,8 +242,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "route" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "route" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.route,
......@@ -217,8 +252,9 @@ void main() {
);
});
testWidgets('Keyboard is configured for "emergencyCall" action when explicitly requested',
(WidgetTester tester) async {
testWidgets(
'Keyboard is configured for "emergencyCall" action when explicitly requested',
(WidgetTester tester) async {
await _desiredKeyboardActionIsRequested(
tester: tester,
action: TextInputAction.emergencyCall,
......@@ -250,11 +286,14 @@ void main() {
controller.text = 'test';
await tester.idle();
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputType']['name'], equals('TextInputType.multiline'));
expect(tester.testTextInput.setClientArgs['inputAction'], equals('TextInputAction.newline'));
expect(tester.testTextInput.setClientArgs['inputType']['name'],
equals('TextInputType.multiline'));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals('TextInputAction.newline'));
});
testWidgets('Correct keyboard is requested when set explicitly and maxLines > 1',
testWidgets(
'Correct keyboard is requested when set explicitly and maxLines > 1',
(WidgetTester tester) async {
await tester.pumpWidget(
new Directionality(
......@@ -279,8 +318,10 @@ void main() {
controller.text = 'test';
await tester.idle();
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputType']['name'], equals('TextInputType.phone'));
expect(tester.testTextInput.setClientArgs['inputAction'], equals('TextInputAction.done'));
expect(tester.testTextInput.setClientArgs['inputType']['name'],
equals('TextInputType.phone'));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals('TextInputAction.done'));
});
testWidgets('multiline keyboard is requested when set implicitly',
......@@ -307,8 +348,10 @@ void main() {
controller.text = 'test';
await tester.idle();
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputType']['name'], equals('TextInputType.multiline'));
expect(tester.testTextInput.setClientArgs['inputAction'], equals('TextInputAction.newline'));
expect(tester.testTextInput.setClientArgs['inputType']['name'],
equals('TextInputType.multiline'));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals('TextInputAction.newline'));
});
testWidgets('single line inputs have correct default keyboard',
......@@ -335,12 +378,16 @@ void main() {
controller.text = 'test';
await tester.idle();
expect(tester.testTextInput.editingState['text'], equals('test'));
expect(tester.testTextInput.setClientArgs['inputType']['name'], equals('TextInputType.text'));
expect(tester.testTextInput.setClientArgs['inputAction'], equals('TextInputAction.done'));
expect(tester.testTextInput.setClientArgs['inputType']['name'],
equals('TextInputType.text'));
expect(tester.testTextInput.setClientArgs['inputAction'],
equals('TextInputAction.done'));
});
testWidgets('Fires onChanged when text changes via TextSelectionOverlay', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
testWidgets('Fires onChanged when text changes via TextSelectionOverlay',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
String changedValue;
final Widget widget = new MaterialApp(
......@@ -361,9 +408,10 @@ void main() {
// Populate a fake clipboard.
const String clipboardContent = 'Dobunezumi mitai ni utsukushiku naritai';
SystemChannels.platform.setMockMethodCallHandler((MethodCall methodCall) async {
SystemChannels.platform
.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'Clipboard.getData')
return const <String, dynamic>{ 'text': clipboardContent };
return const <String, dynamic>{'text': clipboardContent};
return null;
});
......@@ -378,39 +426,111 @@ void main() {
expect(changedValue, clipboardContent);
});
testWidgets('Loses focus by default when "done" action is pressed', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
final FocusNode focusNode = new FocusNode();
testWidgets('cursor layout has correct width', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
String changedValue;
final Widget widget = new MaterialApp(
home: new EditableText(
key: editableTextKey,
controller: new TextEditingController(),
focusNode: focusNode,
style: new Typography(platform: TargetPlatform.android).black.subhead,
cursorColor: Colors.blue,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
home: new RepaintBoundary(
key: const ValueKey<int>(1),
child: new EditableText(
key: editableTextKey,
controller: new TextEditingController(),
focusNode: new FocusNode(),
style: new Typography(platform: TargetPlatform.android).black.subhead,
cursorColor: Colors.blue,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
onChanged: (String value) {
changedValue = value;
},
cursorWidth: 15.0,
),
),
);
await tester.pumpWidget(widget);
// Select EditableText to give it focus.
// Populate a fake clipboard.
const String clipboardContent = ' ';
SystemChannels.platform
.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'Clipboard.getData')
return const <String, dynamic>{'text': clipboardContent};
return null;
});
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
await tester.tap(textFinder);
await tester.longPress(textFinder);
await tester.pump();
assert(focusNode.hasFocus);
await tester.tap(find.text('PASTE'));
await tester.pump();
await tester.testTextInput.receiveAction(TextInputAction.done);
expect(changedValue, clipboardContent);
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('editable_text_test.0.0.png'),
);
}, skip: !Platform.isLinux);
testWidgets('cursor layout has correct radius', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
String changedValue;
final Widget widget = new MaterialApp(
home: new RepaintBoundary(
key: const ValueKey<int>(1),
child: new EditableText(
key: editableTextKey,
controller: new TextEditingController(),
focusNode: new FocusNode(),
style: new Typography(platform: TargetPlatform.android).black.subhead,
cursorColor: Colors.blue,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
onChanged: (String value) {
changedValue = value;
},
cursorWidth: 15.0,
cursorRadius: const Radius.circular(3.0),
),
),
);
await tester.pumpWidget(widget);
// Populate a fake clipboard.
const String clipboardContent = ' ';
SystemChannels.platform
.setMockMethodCallHandler((MethodCall methodCall) async {
if (methodCall.method == 'Clipboard.getData')
return const <String, dynamic>{'text': clipboardContent};
return null;
});
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
await tester.longPress(textFinder);
await tester.pump();
// Lost focus because "done" was pressed.
expect(focusNode.hasFocus, false);
});
await tester.tap(find.text('PASTE'));
await tester.pump();
testWidgets('Does not lose focus by default when "next" action is pressed', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
expect(changedValue, clipboardContent);
await expectLater(
find.byKey(const ValueKey<int>(1)),
matchesGoldenFile('editable_text_test.1.0.png'),
);
}, skip: !Platform.isLinux);
testWidgets('Does not lose focus by default when "next" action is pressed',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
final FocusNode focusNode = new FocusNode();
final Widget widget = new MaterialApp(
......@@ -440,9 +560,11 @@ void main() {
expect(focusNode.hasFocus, true);
});
testWidgets('Does not lose focus by default when "done" action is pressed and onEditingComplete is provided',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
testWidgets(
'Does not lose focus by default when "done" action is pressed and onEditingComplete is provided',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
final FocusNode focusNode = new FocusNode();
final Widget widget = new MaterialApp(
......@@ -476,9 +598,11 @@ void main() {
expect(focusNode.hasFocus, true);
});
testWidgets('When "done" is pressed callbacks are invoked: onEditingComplete > onSubmitted',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
testWidgets(
'When "done" is pressed callbacks are invoked: onEditingComplete > onSubmitted',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
final FocusNode focusNode = new FocusNode();
bool onEditingCompleteCalled = false;
......@@ -518,9 +642,11 @@ void main() {
// and onSubmission callbacks.
});
testWidgets('When "next" is pressed callbacks are invoked: onEditingComplete > onSubmitted',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
testWidgets(
'When "next" is pressed callbacks are invoked: onEditingComplete > onSubmitted',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
final FocusNode focusNode = new FocusNode();
bool onEditingCompleteCalled = false;
......@@ -560,10 +686,14 @@ void main() {
// and onSubmission callbacks.
});
testWidgets('Changing controller updates EditableText', (WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey = new GlobalKey<EditableTextState>();
final TextEditingController controller1 = new TextEditingController(text: 'Wibble');
final TextEditingController controller2 = new TextEditingController(text: 'Wobble');
testWidgets('Changing controller updates EditableText',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
new GlobalKey<EditableTextState>();
final TextEditingController controller1 =
new TextEditingController(text: 'Wibble');
final TextEditingController controller2 =
new TextEditingController(text: 'Wobble');
TextEditingController currentController = controller1;
StateSetter setState;
......@@ -579,11 +709,13 @@ void main() {
key: editableTextKey,
controller: currentController,
focusNode: new FocusNode(),
style: new Typography(platform: TargetPlatform.android).black.subhead,
style: new Typography(platform: TargetPlatform.android)
.black
.subhead,
cursorColor: Colors.blue,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
onChanged: (String value) { },
onChanged: (String value) {},
),
),
),
......@@ -591,6 +723,7 @@ void main() {
},
);
}
await tester.pumpWidget(builder());
await tester.showKeyboard(find.byType(EditableText));
......@@ -605,7 +738,9 @@ void main() {
await tester.pump();
expect(log, hasLength(1));
expect(log.single, isMethodCall(
expect(
log.single,
isMethodCall(
'TextInput.setEditingState',
arguments: const <String, dynamic>{
'text': 'Wobble',
......@@ -616,10 +751,11 @@ void main() {
'composingBase': -1,
'composingExtent': -1,
},
));
));
});
testWidgets('EditableText identifies as text field (w/ focus) in semantics', (WidgetTester tester) async {
testWidgets('EditableText identifies as text field (w/ focus) in semantics',
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
......@@ -638,18 +774,25 @@ void main() {
),
);
expect(semantics, includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField]));
expect(semantics,
includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField]));
await tester.tap(find.byType(EditableText));
await tester.idle();
await tester.pump();
expect(semantics, includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isFocused]));
expect(
semantics,
includesNodeWith(flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused
]));
semantics.dispose();
});
testWidgets('EditableText includes text as value in semantics', (WidgetTester tester) async {
testWidgets('EditableText includes text as value in semantics',
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
const String value1 = 'EditableText content';
......@@ -671,58 +814,73 @@ void main() {
),
);
expect(semantics, includesNodeWith(
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
value: value1,
));
expect(
semantics,
includesNodeWith(
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
value: value1,
));
const String value2 = 'Changed the EditableText content';
controller.text = value2;
await tester.idle();
await tester.pump();
expect(semantics, includesNodeWith(
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
value: value2,
));
expect(
semantics,
includesNodeWith(
flags: <SemanticsFlag>[SemanticsFlag.isTextField],
value: value2,
));
semantics.dispose();
});
testWidgets('changing selection with keyboard does not show handles', (WidgetTester tester) async {
testWidgets('changing selection with keyboard does not show handles',
(WidgetTester tester) async {
const String value1 = 'Hello World';
controller.text = value1;
await tester.pumpWidget(new MaterialApp(
home: new EditableText(
controller: controller,
selectionControls: materialTextSelectionControls,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
await tester.pumpWidget(
new MaterialApp(
home: new EditableText(
controller: controller,
selectionControls: materialTextSelectionControls,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
),
));
);
// Simulate selection change via tap to show handles.
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
render.onSelectionChanged(const TextSelection.collapsed(offset: 4), render, SelectionChangedCause.tap);
final RenderEditable render = tester.allRenderObjects
.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
render.onSelectionChanged(const TextSelection.collapsed(offset: 4), render,
SelectionChangedCause.tap);
await tester.pumpAndSettle();
final EditableTextState textState = tester.state(find.byType(EditableText));
expect(textState.selectionOverlay.handlesAreVisible, isTrue);
expect(textState.selectionOverlay.selectionDelegate.textEditingValue.selection, const TextSelection.collapsed(offset: 4));
expect(
textState.selectionOverlay.selectionDelegate.textEditingValue.selection,
const TextSelection.collapsed(offset: 4));
// Simulate selection change via keyboard and expect handles to disappear.
render.onSelectionChanged(const TextSelection.collapsed(offset: 10), render, SelectionChangedCause.keyboard);
render.onSelectionChanged(const TextSelection.collapsed(offset: 10), render,
SelectionChangedCause.keyboard);
await tester.pumpAndSettle();
expect(textState.selectionOverlay.handlesAreVisible, isFalse);
expect(textState.selectionOverlay.selectionDelegate.textEditingValue.selection, const TextSelection.collapsed(offset: 10));
expect(
textState.selectionOverlay.selectionDelegate.textEditingValue.selection,
const TextSelection.collapsed(offset: 10));
});
testWidgets('exposes correct cursor movement semantics', (WidgetTester tester) async {
testWidgets('exposes correct cursor movement semantics',
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
controller.text = 'test';
......@@ -736,46 +894,56 @@ void main() {
),
));
expect(semantics, includesNodeWith(
value: 'test',
));
expect(
semantics,
includesNodeWith(
value: 'test',
));
controller.selection = new TextSelection.collapsed(offset: controller.text.length);
controller.selection =
new TextSelection.collapsed(offset: controller.text.length);
await tester.pumpAndSettle();
// At end, can only go backwards.
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
],
));
controller.selection = new TextSelection.collapsed(offset: controller.text.length - 2);
controller.selection =
new TextSelection.collapsed(offset: controller.text.length - 2);
await tester.pumpAndSettle();
// Somewhere in the middle, can go in both directions.
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
controller.selection = const TextSelection.collapsed(offset: 0);
await tester.pumpAndSettle();
// At beginning, can only go forward.
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
semantics.dispose();
});
......@@ -785,7 +953,8 @@ void main() {
const bool doNotExtendSelection = false;
controller.text = 'test';
controller.selection = new TextSelection.collapsed(offset: controller.text.length);
controller.selection =
new TextSelection.collapsed(offset: controller.text.length);
await tester.pumpWidget(new MaterialApp(
home: new EditableText(
......@@ -796,54 +965,66 @@ void main() {
),
));
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
],
));
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final RenderEditable render = tester.allRenderObjects
.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final int semanticsId = render.debugSemantics.id;
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 4);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 3);
expect(controller.selection.extentOffset, 3);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 0);
expect(controller.selection.extentOffset, 0);
await tester.pumpAndSettle();
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
......@@ -852,13 +1033,15 @@ void main() {
semantics.dispose();
});
testWidgets('can extend selection with a11y means', (WidgetTester tester) async {
testWidgets('can extend selection with a11y means',
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
const bool extendSelection = true;
const bool doNotExtendSelection = false;
controller.text = 'test';
controller.selection = new TextSelection.collapsed(offset: controller.text.length);
controller.selection =
new TextSelection.collapsed(offset: controller.text.length);
await tester.pumpWidget(new MaterialApp(
home: new EditableText(
......@@ -869,60 +1052,73 @@ void main() {
),
));
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
],
));
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final RenderEditable render = tester.allRenderObjects
.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final int semanticsId = render.debugSemantics.id;
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 4);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 3);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 0);
await tester.pumpAndSettle();
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.setSelection,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset, 1);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, extendSelection);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId,
SemanticsAction.moveCursorForwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
......@@ -931,7 +1127,8 @@ void main() {
semantics.dispose();
});
testWidgets('password fields have correct semantics', (WidgetTester tester) async {
testWidgets('password fields have correct semantics',
(WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
controller.text = 'super-secret-password!!1';
......@@ -948,30 +1145,40 @@ void main() {
final String expectedValue = '•' * controller.text.length;
expect(semantics, hasSemantics(new TestSemantics(
children: <TestSemantics>[
new TestSemantics.rootChild(
children: <TestSemantics>[
expect(
semantics,
hasSemantics(
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.isTextField, SemanticsFlag.isObscured],
value: expectedValue,
textDirection: TextDirection.ltr,
children: <TestSemantics>[
new TestSemantics.rootChild(
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
new TestSemantics(
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isObscured
],
value: expectedValue,
textDirection: TextDirection.ltr,
),
],
),
],
),
],
),
],
),
],
), ignoreTransform: true, ignoreRect: true, ignoreId: true));
ignoreTransform: true,
ignoreRect: true,
ignoreId: true));
semantics.dispose();
});
group('a11y copy/cut/paste', () {
Future<Null> _buildApp(MockTextSelectionControls controls, WidgetTester tester) {
Future<Null> _buildApp(
MockTextSelectionControls controls, WidgetTester tester) {
return tester.pumpWidget(new MaterialApp(
home: new EditableText(
controller: controller,
......@@ -987,11 +1194,13 @@ void main() {
setUp(() {
controller.text = 'test';
controller.selection = new TextSelection.collapsed(offset: controller.text.length);
controller.selection =
new TextSelection.collapsed(offset: controller.text.length);
controls = new MockTextSelectionControls();
when(controls.buildHandle(any, any, any)).thenReturn(new Container());
when(controls.buildToolbar(any, any, any, any)).thenReturn(new Container());
when(controls.buildToolbar(any, any, any, any))
.thenReturn(new Container());
});
testWidgets('are exposed', (WidgetTester tester) async {
......@@ -1005,63 +1214,73 @@ void main() {
await tester.tap(find.byType(EditableText));
await tester.pump();
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
],
));
when(controls.canCopy(any)).thenReturn(true);
await _buildApp(controls, tester);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.copy,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.copy,
],
));
when(controls.canCopy(any)).thenReturn(false);
when(controls.canPaste(any)).thenReturn(true);
await _buildApp(controls, tester);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
));
when(controls.canPaste(any)).thenReturn(false);
when(controls.canCut(any)).thenReturn(true);
await _buildApp(controls, tester);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.cut,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.cut,
],
));
when(controls.canCopy(any)).thenReturn(true);
when(controls.canCut(any)).thenReturn(true);
when(controls.canPaste(any)).thenReturn(true);
await _buildApp(controls, tester);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.cut,
SemanticsAction.copy,
SemanticsAction.paste,
],
));
expect(
semantics,
includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.cut,
SemanticsAction.copy,
SemanticsAction.paste,
],
));
semantics.dispose();
});
......@@ -1079,38 +1298,44 @@ void main() {
final SemanticsOwner owner = tester.binding.pipelineOwner.semanticsOwner;
const int expectedNodeId = 4;
expect(semantics, hasSemantics(new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: 1,
children: <TestSemantics>[
new TestSemantics(
id: 2,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
expect(
semantics,
hasSemantics(
new TestSemantics.root(
children: <TestSemantics>[
new TestSemantics.rootChild(
id: expectedNodeId,
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused
],
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.copy,
SemanticsAction.cut,
SemanticsAction.paste
id: 1,
children: <TestSemantics>[
new TestSemantics(
id: 2,
flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
children: <TestSemantics>[
new TestSemantics.rootChild(
id: expectedNodeId,
flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isFocused
],
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.setSelection,
SemanticsAction.copy,
SemanticsAction.cut,
SemanticsAction.paste
],
value: 'test',
textSelection: new TextSelection.collapsed(
offset: controller.text.length),
textDirection: TextDirection.ltr,
),
],
),
],
value: 'test',
textSelection: new TextSelection.collapsed(offset: controller.text.length),
textDirection: TextDirection.ltr,
),
],
),
],
),
],
), ignoreRect: true, ignoreTransform: true));
ignoreRect: true,
ignoreTransform: true));
owner.performAction(expectedNodeId, SemanticsAction.copy);
verify(controls.handleCopy(any)).called(1);
......@@ -1125,7 +1350,8 @@ void main() {
});
});
testWidgets('allows customizing text style in subclasses', (WidgetTester tester) async {
testWidgets('allows customizing text style in subclasses',
(WidgetTester tester) async {
controller.text = 'Hello World';
await tester.pumpWidget(new MaterialApp(
......@@ -1138,11 +1364,13 @@ void main() {
));
// Simulate selection change via tap to show handles.
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final RenderEditable render = tester.allRenderObjects
.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
expect(render.text.style.fontStyle, FontStyle.italic);
});
testWidgets('autofocus sets cursor to the end of text', (WidgetTester tester) async {
testWidgets('autofocus sets cursor to the end of text',
(WidgetTester tester) async {
const String text = 'hello world';
final FocusScopeNode focusScopeNode = new FocusScopeNode();
final FocusNode focusNode = new FocusNode();
......@@ -1177,12 +1405,12 @@ class CustomStyleEditableText extends EditableText {
Color cursorColor,
FocusNode focusNode,
TextStyle style,
}): super(
controller:controller,
cursorColor: cursorColor,
focusNode: focusNode,
style: style,
);
}) : super(
controller: controller,
cursorColor: cursorColor,
focusNode: focusNode,
style: style,
);
@override
CustomStyleEditableTextState createState() =>
new CustomStyleEditableTextState();
......@@ -1192,8 +1420,8 @@ class CustomStyleEditableTextState extends EditableTextState {
@override
TextSpan buildTextSpan() {
return new TextSpan(
style: const TextStyle(fontStyle: FontStyle.italic),
text: widget.controller.value.text,
style: const TextStyle(fontStyle: FontStyle.italic),
text: widget.controller.value.text,
);
}
}
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