mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
970 lines
32 KiB
Dart
970 lines
32 KiB
Dart
// Copyright 2015 The Chromium 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 'dart:async';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
|
|
class MockClipboard {
|
|
Object _clipboardData = <String, dynamic>{
|
|
'text': null,
|
|
};
|
|
|
|
Future<dynamic> handleMethodCall(MethodCall methodCall) async {
|
|
switch (methodCall.method) {
|
|
case 'Clipboard.getData':
|
|
return _clipboardData;
|
|
case 'Clipboard.setData':
|
|
_clipboardData = methodCall.arguments;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget overlay(Widget child) {
|
|
return new Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
new OverlayEntry(
|
|
builder: (BuildContext context) => child,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
void main() {
|
|
final MockClipboard mockClipboard = new MockClipboard();
|
|
SystemChannels.platform.setMockMethodCallHandler(mockClipboard.handleMethodCall);
|
|
|
|
const String kThreeLines =
|
|
'First line of text is '
|
|
'Second line goes until '
|
|
'Third line of stuff ';
|
|
const String kFourLines =
|
|
kThreeLines +
|
|
'Fourth line won\'t display and ends at';
|
|
|
|
// Returns the first RenderEditable.
|
|
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;
|
|
}
|
|
|
|
Offset textOffsetToPosition(WidgetTester tester, int offset) {
|
|
final RenderEditable renderEditable = findRenderEditable(tester);
|
|
final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(
|
|
new TextSelection.collapsed(offset: offset),
|
|
);
|
|
expect(endpoints.length, 1);
|
|
return endpoints[0].point + const Offset(0.0, -2.0);
|
|
}
|
|
|
|
testWidgets('TextField has consistent size', (WidgetTester tester) async {
|
|
final Key textFieldKey = new UniqueKey();
|
|
String textFieldValue;
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
key: textFieldKey,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
onChanged: (String value) {
|
|
textFieldValue = value;
|
|
}
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
RenderBox findTextFieldBox() => tester.renderObject(find.byKey(textFieldKey));
|
|
|
|
final RenderBox inputBox = findTextFieldBox();
|
|
final Size emptyInputSize = inputBox.size;
|
|
|
|
Future<Null> checkText(String testValue) async {
|
|
return TestAsyncUtils.guard(() async {
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
// Check that the onChanged event handler fired.
|
|
expect(textFieldValue, equals(testValue));
|
|
|
|
await tester.pumpWidget(builder());
|
|
});
|
|
}
|
|
|
|
await checkText(' ');
|
|
|
|
expect(findTextFieldBox(), equals(inputBox));
|
|
expect(inputBox.size, equals(emptyInputSize));
|
|
|
|
await checkText('Test');
|
|
expect(findTextFieldBox(), equals(inputBox));
|
|
expect(inputBox.size, equals(emptyInputSize));
|
|
});
|
|
|
|
testWidgets('Cursor blinks', (WidgetTester tester) async {
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: const TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
|
|
final EditableTextState editableText = tester.state(find.byType(EditableText));
|
|
|
|
// Check that the cursor visibility toggles after each blink interval.
|
|
Future<Null> checkCursorToggle() async {
|
|
final bool initialShowCursor = editableText.cursorCurrentlyVisible;
|
|
await tester.pump(editableText.cursorBlinkInterval);
|
|
expect(editableText.cursorCurrentlyVisible, equals(!initialShowCursor));
|
|
await tester.pump(editableText.cursorBlinkInterval);
|
|
expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
|
|
await tester.pump(editableText.cursorBlinkInterval ~/ 10);
|
|
expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
|
|
await tester.pump(editableText.cursorBlinkInterval);
|
|
expect(editableText.cursorCurrentlyVisible, equals(!initialShowCursor));
|
|
await tester.pump(editableText.cursorBlinkInterval);
|
|
expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
|
|
}
|
|
|
|
await checkCursorToggle();
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
|
|
// Try the test again with a nonempty EditableText.
|
|
tester.testTextInput.updateEditingValue(const TextEditingValue(
|
|
text: 'X',
|
|
selection: const TextSelection.collapsed(offset: 1),
|
|
));
|
|
await checkCursorToggle();
|
|
});
|
|
|
|
testWidgets('obscureText control test', (WidgetTester tester) async {
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: const TextField(
|
|
obscureText: true,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
await tester.showKeyboard(find.byType(TextField));
|
|
|
|
const String testValue = 'ABC';
|
|
tester.testTextInput.updateEditingValue(const TextEditingValue(
|
|
text: testValue,
|
|
selection: const TextSelection.collapsed(offset: testValue.length),
|
|
));
|
|
|
|
await tester.pump();
|
|
});
|
|
|
|
testWidgets('Caret position is updated on tap', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
expect(controller.selection.baseOffset, -1);
|
|
expect(controller.selection.extentOffset, -1);
|
|
|
|
final String testValue = 'abc def ghi';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Tap to reposition the caret.
|
|
final int tapIndex = testValue.indexOf('e');
|
|
final Offset ePos = textOffsetToPosition(tester, tapIndex);
|
|
await tester.tapAt(ePos);
|
|
await tester.pump();
|
|
|
|
expect(controller.selection.baseOffset, tapIndex);
|
|
expect(controller.selection.extentOffset, tapIndex);
|
|
});
|
|
|
|
testWidgets('Can long press to select', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return new Overlay(
|
|
initialEntries: <OverlayEntry>[
|
|
new OverlayEntry(
|
|
builder: (BuildContext context) {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final String testValue = 'abc def ghi';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
expect(controller.value.text, testValue);
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
expect(controller.selection.isCollapsed, true);
|
|
|
|
// Long press the 'e' to select 'def'.
|
|
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
|
|
final TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
|
|
await tester.pump(const Duration(seconds: 2));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
// 'def' is selected.
|
|
expect(controller.selection.baseOffset, testValue.indexOf('d'));
|
|
expect(controller.selection.extentOffset, testValue.indexOf('f')+1);
|
|
});
|
|
|
|
testWidgets('Can drag handles to change selection', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final String testValue = 'abc def ghi';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Long press the 'e' to select 'def'.
|
|
final Offset ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
|
|
TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
|
|
await tester.pump(const Duration(seconds: 2));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
final TextSelection selection = controller.selection;
|
|
|
|
final RenderEditable renderEditable = findRenderEditable(tester);
|
|
final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(selection);
|
|
expect(endpoints.length, 2);
|
|
|
|
// Drag the right handle 2 letters to the right.
|
|
// Note: use a small offset because the endpoint is on the very corner
|
|
// of the handle.
|
|
Offset handlePos = endpoints[1].point + const Offset(1.0, 1.0);
|
|
Offset newHandlePos = textOffsetToPosition(tester, selection.extentOffset+2);
|
|
gesture = await tester.startGesture(handlePos, pointer: 7);
|
|
await tester.pump();
|
|
await gesture.moveTo(newHandlePos);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpWidget(builder());
|
|
|
|
expect(controller.selection.baseOffset, selection.baseOffset);
|
|
expect(controller.selection.extentOffset, selection.extentOffset+2);
|
|
|
|
// Drag the left handle 2 letters to the left.
|
|
handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
|
|
newHandlePos = textOffsetToPosition(tester, selection.baseOffset-2);
|
|
gesture = await tester.startGesture(handlePos, pointer: 7);
|
|
await tester.pump();
|
|
await gesture.moveTo(newHandlePos);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpWidget(builder());
|
|
|
|
expect(controller.selection.baseOffset, selection.baseOffset-2);
|
|
expect(controller.selection.extentOffset, selection.extentOffset+2);
|
|
});
|
|
|
|
testWidgets('Can use selection toolbar', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final String testValue = 'abc def ghi';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Tap the selection handle to bring up the "paste / select all" menu.
|
|
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
|
await tester.pumpWidget(builder());
|
|
RenderEditable renderEditable = findRenderEditable(tester);
|
|
List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(controller.selection);
|
|
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
|
|
await tester.pumpWidget(builder());
|
|
|
|
// SELECT ALL should select all the text.
|
|
await tester.tap(find.text('SELECT ALL'));
|
|
await tester.pumpWidget(builder());
|
|
expect(controller.selection.baseOffset, 0);
|
|
expect(controller.selection.extentOffset, testValue.length);
|
|
|
|
// COPY should reset the selection.
|
|
await tester.tap(find.text('COPY'));
|
|
await tester.pumpWidget(builder());
|
|
expect(controller.selection.isCollapsed, true);
|
|
|
|
// Tap again to bring back the menu.
|
|
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
|
await tester.pumpWidget(builder());
|
|
renderEditable = findRenderEditable(tester);
|
|
endpoints = renderEditable.getEndpointsForSelection(controller.selection);
|
|
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
|
|
await tester.pumpWidget(builder());
|
|
|
|
// PASTE right before the 'e'.
|
|
await tester.tap(find.text('PASTE'));
|
|
await tester.pumpWidget(builder());
|
|
expect(controller.text, 'abc d${testValue}ef ghi');
|
|
});
|
|
|
|
testWidgets('Selection toolbar fades in', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final String testValue = 'abc def ghi';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Tap the selection handle to bring up the "paste / select all" menu.
|
|
await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
|
|
await tester.pumpWidget(builder());
|
|
final RenderEditable renderEditable = findRenderEditable(tester);
|
|
final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(controller.selection);
|
|
await tester.tapAt(endpoints[0].point + const Offset(1.0, 1.0));
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Toolbar should fade in. Starting at 0% opacity.
|
|
final Element target = tester.element(find.text('SELECT ALL'));
|
|
Opacity opacity = target.ancestorWidgetOfExactType(Opacity);
|
|
expect(opacity, isNotNull);
|
|
expect(opacity.opacity, equals(0.0));
|
|
|
|
// Still fading in.
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
opacity = target.ancestorWidgetOfExactType(Opacity);
|
|
expect(opacity.opacity, greaterThan(0.0));
|
|
expect(opacity.opacity, lessThan(1.0));
|
|
|
|
// End the test here to ensure the animation is properly disposed of.
|
|
});
|
|
|
|
testWidgets('Multiline text will wrap up to maxLines', (WidgetTester tester) async {
|
|
final Key textFieldKey = new UniqueKey();
|
|
|
|
Widget builder(int maxLines) {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
key: textFieldKey,
|
|
style: const TextStyle(color: Colors.black, fontSize: 34.0),
|
|
maxLines: maxLines,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder(3));
|
|
|
|
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
|
|
|
|
final RenderBox inputBox = findInputBox();
|
|
final Size emptyInputSize = inputBox.size;
|
|
|
|
await tester.enterText(find.byType(TextField), 'No wrapping here.');
|
|
await tester.pumpWidget(builder(3));
|
|
expect(findInputBox(), equals(inputBox));
|
|
expect(inputBox.size, equals(emptyInputSize));
|
|
|
|
await tester.enterText(find.byType(TextField), kThreeLines);
|
|
await tester.pumpWidget(builder(3));
|
|
expect(findInputBox(), equals(inputBox));
|
|
expect(inputBox.size, greaterThan(emptyInputSize));
|
|
|
|
final Size threeLineInputSize = inputBox.size;
|
|
|
|
// An extra line won't increase the size because we max at 3.
|
|
await tester.enterText(find.byType(TextField), kFourLines);
|
|
await tester.pumpWidget(builder(3));
|
|
expect(findInputBox(), equals(inputBox));
|
|
expect(inputBox.size, threeLineInputSize);
|
|
|
|
// But now it will.
|
|
await tester.enterText(find.byType(TextField), kFourLines);
|
|
await tester.pumpWidget(builder(4));
|
|
expect(findInputBox(), equals(inputBox));
|
|
expect(inputBox.size, greaterThan(threeLineInputSize));
|
|
});
|
|
|
|
testWidgets('Can drag handles to change selection in multiline', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
controller: controller,
|
|
style: const TextStyle(color: Colors.black, fontSize: 34.0),
|
|
maxLines: 3,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final String testValue = kThreeLines;
|
|
final String cutValue = 'First line of stuff ';
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
// Check that the text spans multiple lines.
|
|
final Offset firstPos = textOffsetToPosition(tester, testValue.indexOf('First'));
|
|
final Offset secondPos = textOffsetToPosition(tester, testValue.indexOf('Second'));
|
|
final Offset thirdPos = textOffsetToPosition(tester, testValue.indexOf('Third'));
|
|
expect(firstPos.dx, secondPos.dx);
|
|
expect(firstPos.dx, thirdPos.dx);
|
|
expect(firstPos.dy, lessThan(secondPos.dy));
|
|
expect(secondPos.dy, lessThan(thirdPos.dy));
|
|
|
|
// Long press the 'n' in 'until' to select the word.
|
|
final Offset untilPos = textOffsetToPosition(tester, testValue.indexOf('until')+1);
|
|
TestGesture gesture = await tester.startGesture(untilPos, pointer: 7);
|
|
await tester.pump(const Duration(seconds: 2));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
expect(controller.selection.baseOffset, 39);
|
|
expect(controller.selection.extentOffset, 44);
|
|
|
|
final RenderEditable renderEditable = findRenderEditable(tester);
|
|
final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(controller.selection);
|
|
expect(endpoints.length, 2);
|
|
|
|
// Drag the right handle to the third line, just after 'Third'.
|
|
Offset handlePos = endpoints[1].point + const Offset(1.0, 1.0);
|
|
Offset newHandlePos = textOffsetToPosition(tester, testValue.indexOf('Third') + 5);
|
|
gesture = await tester.startGesture(handlePos, pointer: 7);
|
|
await tester.pump();
|
|
await gesture.moveTo(newHandlePos);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpWidget(builder());
|
|
|
|
expect(controller.selection.baseOffset, 39);
|
|
expect(controller.selection.extentOffset, 50);
|
|
|
|
// Drag the left handle to the first line, just after 'First'.
|
|
handlePos = endpoints[0].point + const Offset(-1.0, 1.0);
|
|
newHandlePos = textOffsetToPosition(tester, testValue.indexOf('First') + 5);
|
|
gesture = await tester.startGesture(handlePos, pointer: 7);
|
|
await tester.pump();
|
|
await gesture.moveTo(newHandlePos);
|
|
await tester.pump();
|
|
await gesture.up();
|
|
await tester.pumpWidget(builder());
|
|
|
|
expect(controller.selection.baseOffset, 5);
|
|
expect(controller.selection.extentOffset, 50);
|
|
|
|
await tester.tap(find.text('CUT'));
|
|
await tester.pumpWidget(builder());
|
|
expect(controller.selection.isCollapsed, true);
|
|
expect(controller.text, cutValue);
|
|
});
|
|
|
|
testWidgets('Can scroll multiline input', (WidgetTester tester) async {
|
|
final Key textFieldKey = new UniqueKey();
|
|
final TextEditingController controller = new TextEditingController();
|
|
|
|
Widget builder() {
|
|
return overlay(new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
key: textFieldKey,
|
|
controller: controller,
|
|
style: const TextStyle(color: Colors.black, fontSize: 34.0),
|
|
maxLines: 2,
|
|
),
|
|
),
|
|
));
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
await tester.enterText(find.byType(TextField), kFourLines);
|
|
|
|
await tester.pumpWidget(builder());
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
RenderBox findInputBox() => tester.renderObject(find.byKey(textFieldKey));
|
|
final RenderBox inputBox = findInputBox();
|
|
|
|
// Check that the last line of text is not displayed.
|
|
final Offset firstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
|
final Offset fourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
|
expect(firstPos.dx, fourthPos.dx);
|
|
expect(firstPos.dy, lessThan(fourthPos.dy));
|
|
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(firstPos)), isTrue);
|
|
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(fourthPos)), isFalse);
|
|
|
|
TestGesture gesture = await tester.startGesture(firstPos, pointer: 7);
|
|
await tester.pump();
|
|
await gesture.moveBy(const Offset(0.0, -1000.0));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
// Wait and drag again to trigger https://github.com/flutter/flutter/issues/6329
|
|
// (No idea why this is necessary, but the bug wouldn't repro without it.)
|
|
await gesture.moveBy(const Offset(0.0, -1000.0));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await gesture.up();
|
|
await tester.pump();
|
|
|
|
// Now the first line is scrolled up, and the fourth line is visible.
|
|
Offset newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
|
Offset newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
|
|
|
expect(newFirstPos.dy, lessThan(firstPos.dy));
|
|
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isFalse);
|
|
expect(inputBox.hitTest(new 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, kFourLines.indexOf('Fourth line')+8);
|
|
gesture = await tester.startGesture(untilPos, pointer: 7);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
final RenderEditable renderEditable = findRenderEditable(tester);
|
|
final List<TextSelectionPoint> endpoints = renderEditable.getEndpointsForSelection(controller.selection);
|
|
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 newHandlePos = textOffsetToPosition(tester, kFourLines.indexOf('First') + 5);
|
|
gesture = await tester.startGesture(handlePos, pointer: 7);
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await gesture.moveTo(newHandlePos + const Offset(0.0, -10.0));
|
|
await tester.pump(const Duration(seconds: 1));
|
|
await gesture.up();
|
|
await tester.pump(const Duration(seconds: 1));
|
|
|
|
// The text should have scrolled up with the handle to keep the active
|
|
// cursor visible, back to its original position.
|
|
newFirstPos = textOffsetToPosition(tester, kFourLines.indexOf('First'));
|
|
newFourthPos = textOffsetToPosition(tester, kFourLines.indexOf('Fourth'));
|
|
expect(newFirstPos.dy, firstPos.dy);
|
|
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFirstPos)), isTrue);
|
|
expect(inputBox.hitTest(new HitTestResult(), position: inputBox.globalToLocal(newFourthPos)), isFalse);
|
|
});
|
|
|
|
testWidgets('TextField smoke test', (WidgetTester tester) async {
|
|
String textFieldValue;
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
decoration: null,
|
|
onChanged: (String value) {
|
|
textFieldValue = value;
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
Future<Null> checkText(String testValue) {
|
|
return TestAsyncUtils.guard(() async {
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
// Check that the onChanged event handler fired.
|
|
expect(textFieldValue, equals(testValue));
|
|
|
|
await tester.pumpWidget(builder());
|
|
});
|
|
}
|
|
|
|
await checkText('Hello World');
|
|
});
|
|
|
|
testWidgets('TextField with global key', (WidgetTester tester) async {
|
|
final GlobalKey textFieldKey = new GlobalKey(debugLabel: 'textFieldKey');
|
|
String textFieldValue;
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
key: textFieldKey,
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
onChanged: (String value) { textFieldValue = value; },
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
Future<Null> checkText(String testValue) async {
|
|
return TestAsyncUtils.guard(() async {
|
|
await tester.enterText(find.byType(TextField), testValue);
|
|
|
|
// Check that the onChanged event handler fired.
|
|
expect(textFieldValue, equals(testValue));
|
|
|
|
await tester.pumpWidget(builder());
|
|
});
|
|
}
|
|
|
|
await checkText('Hello World');
|
|
});
|
|
|
|
testWidgets('TextField with default hintStyle', (WidgetTester tester) async {
|
|
final TextStyle style = new TextStyle(
|
|
color: Colors.pink[500],
|
|
fontSize: 10.0,
|
|
);
|
|
final ThemeData themeData = new ThemeData(
|
|
hintColor: Colors.blue[500],
|
|
);
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Theme(
|
|
data: themeData,
|
|
child: new Material(
|
|
child: new TextField(
|
|
decoration: const InputDecoration(
|
|
hintText: 'Placeholder',
|
|
),
|
|
style: style,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final Text hintText = tester.widget(find.text('Placeholder'));
|
|
expect(hintText.style.color, themeData.hintColor);
|
|
expect(hintText.style.fontSize, style.fontSize);
|
|
});
|
|
|
|
testWidgets('TextField with specified hintStyle', (WidgetTester tester) async {
|
|
final TextStyle hintStyle = new TextStyle(
|
|
color: Colors.pink[500],
|
|
fontSize: 10.0,
|
|
);
|
|
|
|
Widget builder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new TextField(
|
|
decoration: new InputDecoration(
|
|
hintText: 'Placeholder',
|
|
hintStyle: hintStyle,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
final Text hintText = tester.widget(find.text('Placeholder'));
|
|
expect(hintText.style, hintStyle);
|
|
});
|
|
|
|
testWidgets('TextField label text animates', (WidgetTester tester) async {
|
|
final Key secondKey = new UniqueKey();
|
|
|
|
Widget innerBuilder() {
|
|
return new Center(
|
|
child: new Material(
|
|
child: new Column(
|
|
children: <Widget>[
|
|
const TextField(
|
|
decoration: const InputDecoration(
|
|
labelText: 'First',
|
|
),
|
|
),
|
|
new TextField(
|
|
key: secondKey,
|
|
decoration: const InputDecoration(
|
|
labelText: 'Second',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
Widget builder() => overlay(innerBuilder());
|
|
|
|
await tester.pumpWidget(builder());
|
|
|
|
Offset pos = tester.getTopLeft(find.text('Second'));
|
|
|
|
// Focus the Input. The label should start animating upwards.
|
|
await tester.tap(find.byKey(secondKey));
|
|
await tester.idle();
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
|
|
Offset newPos = tester.getTopLeft(find.text('Second'));
|
|
expect(newPos.dy, lessThan(pos.dy));
|
|
|
|
// Label should still be sliding upward.
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
pos = newPos;
|
|
newPos = tester.getTopLeft(find.text('Second'));
|
|
expect(newPos.dy, lessThan(pos.dy));
|
|
});
|
|
|
|
testWidgets('No space between Input icon and text', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
new Center(
|
|
child: new Material(
|
|
child: const TextField(
|
|
decoration: const InputDecoration(
|
|
icon: const Icon(Icons.phone),
|
|
labelText: 'label',
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double iconRight = tester.getTopRight(find.byType(Icon)).dx;
|
|
expect(iconRight, equals(tester.getTopLeft(find.text('label')).dx));
|
|
expect(iconRight, equals(tester.getTopLeft(find.byType(EditableText)).dx));
|
|
});
|
|
|
|
testWidgets('Collapsed hint text placement', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
overlay(new Center(
|
|
child: new Material(
|
|
child: const TextField(
|
|
decoration: const InputDecoration.collapsed(
|
|
hintText: 'hint',
|
|
),
|
|
),
|
|
),
|
|
)),
|
|
);
|
|
|
|
expect(tester.getTopLeft(find.text('hint')), equals(tester.getTopLeft(find.byType(TextField))));
|
|
});
|
|
|
|
testWidgets('Can align to center', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
overlay(new Center(
|
|
child: new Material(
|
|
child: new Container(
|
|
width: 300.0,
|
|
child: const TextField(
|
|
textAlign: TextAlign.center,
|
|
decoration: null,
|
|
),
|
|
),
|
|
),
|
|
)),
|
|
);
|
|
|
|
final RenderEditable editable = findRenderEditable(tester);
|
|
Offset topLeft = editable.localToGlobal(
|
|
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
|
|
);
|
|
|
|
expect(topLeft.dx, equals(399.0));
|
|
|
|
await tester.enterText(find.byType(TextField), 'abcd');
|
|
await tester.pump();
|
|
|
|
topLeft = editable.localToGlobal(
|
|
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
|
|
);
|
|
|
|
expect(topLeft.dx, equals(399.0));
|
|
});
|
|
|
|
testWidgets('Can align to center within center', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
overlay(new Center(
|
|
child: new Material(
|
|
child: new Container(
|
|
width: 300.0,
|
|
child: const Center(
|
|
child: const TextField(
|
|
textAlign: TextAlign.center,
|
|
decoration: null,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
)),
|
|
);
|
|
|
|
final RenderEditable editable = findRenderEditable(tester);
|
|
Offset topLeft = editable.localToGlobal(
|
|
editable.getLocalRectForCaret(const TextPosition(offset: 0)).topLeft,
|
|
);
|
|
|
|
expect(topLeft.dx, equals(399.0));
|
|
|
|
await tester.enterText(find.byType(TextField), 'abcd');
|
|
await tester.pump();
|
|
|
|
topLeft = editable.localToGlobal(
|
|
editable.getLocalRectForCaret(const TextPosition(offset: 2)).topLeft,
|
|
);
|
|
|
|
expect(topLeft.dx, equals(399.0));
|
|
});
|
|
|
|
testWidgets('Controller can update server', (WidgetTester tester) async {
|
|
final TextEditingController controller = new TextEditingController(
|
|
text: 'Initial Text',
|
|
);
|
|
final TextEditingController controller2 = new TextEditingController(
|
|
text: 'More Text',
|
|
);
|
|
|
|
TextEditingController currentController = controller;
|
|
StateSetter setState;
|
|
|
|
await tester.pumpWidget(
|
|
overlay(new Center(
|
|
child: new Material(
|
|
child: new StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter setter) {
|
|
setState = setter;
|
|
return new TextField(controller: currentController);
|
|
}
|
|
),
|
|
),
|
|
),
|
|
));
|
|
expect(tester.testTextInput.editingState['text'], isEmpty);
|
|
|
|
await tester.tap(find.byType(TextField));
|
|
await tester.pump();
|
|
expect(tester.testTextInput.editingState['text'], equals('Initial Text'));
|
|
|
|
controller.text = 'Updated Text';
|
|
await tester.idle();
|
|
expect(tester.testTextInput.editingState['text'], equals('Updated Text'));
|
|
|
|
setState(() {
|
|
currentController = controller2;
|
|
});
|
|
await tester.pump();
|
|
expect(tester.testTextInput.editingState['text'], equals('More Text'));
|
|
|
|
controller.text = 'Ignored Text';
|
|
await tester.idle();
|
|
expect(tester.testTextInput.editingState['text'], equals('More Text'));
|
|
|
|
controller2.text = 'Additional Text';
|
|
await tester.idle();
|
|
expect(tester.testTextInput.editingState['text'], equals('Additional Text'));
|
|
|
|
controller2.selection = const TextSelection(baseOffset: 0, extentOffset: 5);
|
|
await tester.idle();
|
|
expect(tester.testTextInput.editingState['selectionBase'], equals(0));
|
|
expect(tester.testTextInput.editingState['selectionExtent'], equals(5));
|
|
|
|
controller2.clear();
|
|
await tester.idle();
|
|
expect(tester.testTextInput.editingState['text'], equals(''));
|
|
});
|
|
}
|