Commit af587a15 authored by Hans Muller's avatar Hans Muller Committed by GitHub

Support for tests that respond to text input (#7915)

parent c08bac83
......@@ -5,21 +5,7 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
import 'mock_text_input.dart';
void main() {
MockTextInput mockTextInput = new MockTextInput()..register();
void enterText(String text) {
mockTextInput.enterText(text);
}
Future<Null> showKeyboard(WidgetTester tester) async {
EditableTextState editable = tester.state(find.byType(EditableText).first);
editable.requestKeyboard();
await tester.pump();
}
testWidgets('onSaved callback is called', (WidgetTester tester) async {
GlobalKey<FormState> formKey = new GlobalKey<FormState>();
String fieldValue;
......@@ -38,12 +24,11 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
expect(fieldValue, isNull);
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
formKey.currentState.save();
// pump'ing is unnecessary because callback happens regardless of frames
......@@ -70,12 +55,11 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
expect(fieldValue, isNull);
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
// pump'ing is unnecessary because callback happens regardless of frames
expect(fieldValue, equals(testValue));
......@@ -107,11 +91,10 @@ void main() {
// Start off not autovalidating.
await tester.pumpWidget(builder(false));
await showKeyboard(tester);
Future<Null> checkErrorText(String testValue) async {
formKey.currentState.reset();
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder(false));
......@@ -123,7 +106,7 @@ void main() {
// Try again with autovalidation. Should validate immediately.
formKey.currentState.reset();
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder(true));
......@@ -166,10 +149,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkErrorText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText).first, testValue);
await tester.idle();
await tester.pump();
......@@ -200,11 +182,11 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
await tester.showKeyboard(find.byType(EditableText));
// initial value should be loaded into keyboard editing state
expect(mockTextInput.editingState, isNotNull);
expect(mockTextInput.editingState['text'], equals(initialValue));
expect(tester.testTextInput.editingState, isNotNull);
expect(tester.testTextInput.editingState['text'], equals(initialValue));
// initial value should also be visible in the raw input line
EditableTextState editableText = tester.state(find.byType(EditableText));
......@@ -212,7 +194,7 @@ void main() {
// sanity check, make sure we can still edit the text and everything updates
expect(inputKey.currentState.value.text, equals(initialValue));
enterText('world');
await tester.enterText(find.byType(EditableText), 'world');
await tester.idle();
await tester.pump();
expect(inputKey.currentState.value.text, equals('world'));
......@@ -241,12 +223,11 @@ void main() {
}
await tester.pumpWidget(builder(false));
await showKeyboard(tester);
expect(fieldValue, isNull);
expect(formKey.currentState.validate(), isTrue);
enterText('Test');
await tester.enterText(find.byType(EditableText), 'Test');
await tester.idle();
await tester.pumpWidget(builder(false));
......
......@@ -10,8 +10,6 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'mock_text_input.dart';
class MockClipboard {
Object _clipboardData = <String, dynamic>{
'text': null
......@@ -41,7 +39,6 @@ Widget overlay(Widget child) {
}
void main() {
MockTextInput mockTextInput = new MockTextInput()..register();
MockClipboard mockClipboard = new MockClipboard();
PlatformMessages.setMockJSONMessageHandler('flutter/platform', mockClipboard.handleJSONMessage);
......@@ -53,20 +50,6 @@ void main() {
kThreeLines +
'Fourth line won\'t display and ends at abcdef ghi. ';
void updateEditingState(TextEditingState state) {
mockTextInput.updateEditingState(state);
}
void enterText(String text) {
mockTextInput.enterText(text);
}
Future<Null> showKeyboard(WidgetTester tester) async {
EditableTextState editable = tester.state(find.byType(EditableText).first);
editable.requestKeyboard();
await tester.pump();
}
// Returns the first RenderEditable.
RenderEditable findRenderEditable(WidgetTester tester) {
RenderObject root = tester.renderObject(find.byType(EditableText));
......@@ -101,7 +84,6 @@ void main() {
return new Center(
child: new Material(
child: new Input(
autofocus: true,
value: inputValue,
key: inputKey,
hintText: 'Placeholder',
......@@ -119,7 +101,7 @@ void main() {
Size emptyInputSize = inputBox.size;
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
// Check that the onChanged event handler fired.
......@@ -152,7 +134,7 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
await tester.showKeyboard(find.byType(EditableText));
EditableTextState editableText = tester.state(find.byType(EditableText));
......@@ -172,9 +154,10 @@ void main() {
}
await checkCursorToggle();
await tester.showKeyboard(find.byType(EditableText));
// Try the test again with a nonempty EditableText.
updateEditingState(const TextEditingState(
tester.testTextInput.updateEditingState(const TextEditingState(
text: 'X',
selectionBase: 1,
selectionExtent: 1,
......@@ -198,10 +181,10 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
await tester.showKeyboard(find.byType(EditableText));
const String testValue = 'ABC';
updateEditingState(const TextEditingState(
tester.testTextInput.updateEditingState(const TextEditingState(
text: testValue,
selectionBase: testValue.length,
selectionExtent: testValue.length,
......@@ -235,10 +218,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi';
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
expect(inputValue.text, testValue);
......@@ -283,10 +265,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi';
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -359,10 +340,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi';
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -425,10 +405,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = 'abc def ghi';
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -476,20 +455,19 @@ void main() {
}
await tester.pumpWidget(builder(3));
await showKeyboard(tester);
RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey));
RenderBox inputBox = findInputBox();
Size emptyInputSize = inputBox.size;
enterText('No wrapping here.');
await tester.enterText(find.byType(EditableText), 'No wrapping here.');
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, equals(emptyInputSize));
enterText(kThreeLines);
await tester.enterText(find.byType(EditableText), kThreeLines);
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
......@@ -498,14 +476,14 @@ void main() {
Size threeLineInputSize = inputBox.size;
// An extra line won't increase the size because we max at 3.
enterText(kFourLines);
await tester.enterText(find.byType(EditableText), kFourLines);
await tester.idle();
await tester.pumpWidget(builder(3));
expect(findInputBox(), equals(inputBox));
expect(inputBox.size, threeLineInputSize);
// But now it will.
enterText(kFourLines);
await tester.enterText(find.byType(EditableText), kFourLines);
await tester.idle();
await tester.pumpWidget(builder(4));
expect(findInputBox(), equals(inputBox));
......@@ -539,11 +517,10 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
String testValue = kThreeLines;
String cutValue = 'First line of stuff keeps going until abcdef ghijk. ';
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -632,9 +609,8 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
enterText(kFourLines);
await tester.enterText(find.byType(EditableText), kFourLines);
await tester.idle();
await tester.pumpWidget(builder());
......@@ -718,10 +694,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
// Check that the onChanged event handler fired.
......@@ -751,10 +726,9 @@ void main() {
}
await tester.pumpWidget(builder());
await showKeyboard(tester);
Future<Null> checkText(String testValue) async {
enterText(testValue);
await tester.enterText(find.byType(EditableText), testValue);
await tester.idle();
// Check that the onChanged event handler fired.
......@@ -828,5 +802,4 @@ void main() {
expect(iconRight, equals(tester.getTopLeft(find.text('label')).x));
expect(iconRight, equals(tester.getTopLeft(find.byType(InputField)).x));
});
}
......@@ -15,5 +15,6 @@ export 'src/matchers.dart';
export 'src/test_async_utils.dart';
export 'src/stack_manipulation.dart';
export 'src/test_pointer.dart';
export 'src/test_text_input.dart';
export 'src/test_vsync.dart';
export 'src/widget_tester.dart';
......@@ -20,6 +20,7 @@ import 'package:vector_math/vector_math_64.dart';
import 'stack_manipulation.dart';
import 'test_async_utils.dart';
import 'test_text_input.dart';
/// Phases that can be reached by [WidgetTester.pumpWidget] and
/// [TestWidgetsFlutterBinding.pump].
......@@ -125,6 +126,7 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
);
});
};
_testTextInput = new TestTextInput()..register();
super.initInstances();
}
......@@ -188,6 +190,20 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
super.dispatchEvent(event, result);
}
/// A stub for the system's onscreen keyboard. Callers must set the
/// [focusedEditable] before using this value.
TestTextInput get testTextInput => _testTextInput;
TestTextInput _testTextInput;
/// The current client of the onscreen keyboard. Callers must pump
/// an additional frame after setting this property to complete the
/// the focus change.
EditableTextState get focusedEditable => _focusedEditable;
EditableTextState _focusedEditable;
set focusedEditable (EditableTextState editable) {
_focusedEditable = editable..requestKeyboard();
}
/// Returns the exception most recently caught by the Flutter framework.
///
/// Call this if you expect an exception during a test. If an exception is
......
......@@ -6,17 +6,27 @@ import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/services.dart';
import 'widget_tester.dart';
const String _kTextInputClientChannel = 'flutter/textinputclient';
class MockTextInput {
/// A testing stub for the system's onscreen keyboard.
///
/// Typical app tests will not need to use this class directly.
///
/// See also:
///
/// * [WidgetTester.enterText], which uses this class to simulate keyboard input.
/// * [WidgetTester.showKeyboard], which uses this class to simulate showing the
/// popup keyboard and initializing its text.
class TestTextInput {
void register() {
PlatformMessages.setMockJSONMessageHandler('flutter/textinput', handleJSONMessage);
}
int client = 0;
int _client = 0;
Map<String, dynamic> editingState;
Future<dynamic> handleJSONMessage(dynamic message) async {
......@@ -24,7 +34,7 @@ class MockTextInput {
final List<dynamic> args= message['args'];
switch (method) {
case 'TextInput.setClient':
client = args[0];
_client = args[0];
break;
case 'TextInput.setEditingState':
editingState = args[0];
......@@ -33,10 +43,10 @@ class MockTextInput {
}
void updateEditingState(TextEditingState state) {
expect(client, isNonZero);
expect(_client, isNonZero);
String message = JSON.encode(<String, dynamic>{
'method': 'TextInputClient.updateEditingState',
'args': <dynamic>[client, state.toJSON()],
'args': <dynamic>[_client, state.toJSON()],
});
Uint8List encoded = UTF8.encoder.convert(message);
PlatformMessages.handlePlatformMessage(
......
......@@ -15,6 +15,7 @@ import 'binding.dart';
import 'controller.dart';
import 'finders.dart';
import 'test_async_utils.dart';
import 'test_text_input.dart';
export 'package:test/test.dart' hide expect;
......@@ -399,6 +400,36 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker
void _endOfTestVerifications() {
verifyTickersWereDisposed('at the end of the test');
}
/// Returns the TestTextInput singleton.
///
/// Typical app tests will not need to use this value. To add text to widgets
/// like [Input] or [TextField], call [enterText].
TestTextInput get testTextInput => binding.testTextInput;
/// Give the EditableText widget specified by [finder] the focus, as if the
/// onscreen keyboard had appeared.
///
/// Tests that just need to add text to widgets like [Input] or [TextField]
/// only need to call [enterText].
Future<Null> showKeyboard(Finder finder) async {
// TODO(hansmuller): Once find.descendant (#7789) lands replace the following
// RHS with state(find.descendant(finder), find.byType(EditableText)).
final EditableTextState editable = state(finder);
if (editable != binding.focusedEditable) {
binding.focusedEditable = editable;
await pump();
}
return null;
}
/// Give the EditableText widget specified by [finder] the focus and
/// enter [text] as if it been provided by the onscreen keyboard.
Future<Null> enterText(Finder finder, String text) async {
await showKeyboard(finder);
testTextInput.enterText(text);
return null;
}
}
typedef void _TickerDisposeCallback(_TestTicker ticker);
......
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