// Copyright 2016 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 '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; Widget builder() { return new Center( child: new Material( child: new Form( key: formKey, child: new TextField( onSaved: (InputValue value) { fieldValue = value.text; }, ), ) ) ); } await tester.pumpWidget(builder()); await showKeyboard(tester); expect(fieldValue, isNull); Future<Null> checkText(String testValue) async { enterText(testValue); await tester.idle(); formKey.currentState.save(); // pump'ing is unnecessary because callback happens regardless of frames expect(fieldValue, equals(testValue)); } await checkText('Test'); await checkText(''); }); testWidgets('onChanged callback is called', (WidgetTester tester) async { String fieldValue; Widget builder() { return new Center( child: new Material( child: new Form( child: new TextField( onChanged: (InputValue value) { fieldValue = value.text; }, ), ) ) ); } await tester.pumpWidget(builder()); await showKeyboard(tester); expect(fieldValue, isNull); Future<Null> checkText(String testValue) async { enterText(testValue); await tester.idle(); // pump'ing is unnecessary because callback happens regardless of frames expect(fieldValue, equals(testValue)); } await checkText('Test'); await checkText(''); }); testWidgets('Validator sets the error text only when validate is called', (WidgetTester tester) async { GlobalKey<FormState> formKey = new GlobalKey<FormState>(); GlobalKey inputKey = new GlobalKey(); String errorText(InputValue input) => input.text + '/error'; Widget builder(bool autovalidate) { return new Center( child: new Material( child: new Form( key: formKey, autovalidate: autovalidate, child: new TextField( key: inputKey, validator: errorText, ), ) ) ); } // 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.idle(); await tester.pumpWidget(builder(false)); // We have to manually validate if we're not autovalidating. expect(find.text(errorText(new InputValue(text: testValue))), findsNothing); formKey.currentState.validate(); await tester.pump(); expect(find.text(errorText(new InputValue(text: testValue))), findsOneWidget); // Try again with autovalidation. Should validate immediately. formKey.currentState.reset(); enterText(testValue); await tester.idle(); await tester.pumpWidget(builder(true)); expect(find.text(errorText(new InputValue(text: testValue))), findsOneWidget); } await checkErrorText('Test'); await checkErrorText(''); }); testWidgets('Multiple Inputs communicate', (WidgetTester tester) async { GlobalKey<FormState> formKey = new GlobalKey<FormState>(); GlobalKey<FormFieldState<InputValue>> fieldKey = new GlobalKey<FormFieldState<InputValue>>(); GlobalKey focusKey = new GlobalKey(); // Input 2's validator depends on a input 1's value. String errorText(InputValue input) => fieldKey.currentState.value?.text.toString() + '/error'; Widget builder() { return new Center( child: new Material( child: new Form( key: formKey, autovalidate: true, child: new Focus( key: focusKey, child: new ScrollView( children: <Widget>[ new TextField( key: fieldKey ), new TextField( validator: errorText, ), ] ) ), ) ) ); } await tester.pumpWidget(builder()); await showKeyboard(tester); Future<Null> checkErrorText(String testValue) async { enterText(testValue); await tester.idle(); await tester.pump(); // Check for a new Text widget with our error text. expect(find.text(testValue + '/error'), findsOneWidget); return null; } await checkErrorText('Test'); await checkErrorText(''); }); testWidgets('Provide initial value to input', (WidgetTester tester) async { String initialValue = 'hello'; GlobalKey<FormFieldState<InputValue>> inputKey = new GlobalKey<FormFieldState<InputValue>>(); Widget builder() { return new Center( child: new Material( child: new Form( child: new TextField( key: inputKey, initialValue: new InputValue(text: initialValue), ), ) ) ); } await tester.pumpWidget(builder()); await showKeyboard(tester); // initial value should be loaded into keyboard editing state expect(mockTextInput.editingState, isNotNull); expect(mockTextInput.editingState['text'], equals(initialValue)); // initial value should also be visible in the raw input line EditableTextState editableText = tester.state(find.byType(EditableText)); expect(editableText.config.value.text, equals(initialValue)); // sanity check, make sure we can still edit the text and everything updates expect(inputKey.currentState.value.text, equals(initialValue)); enterText('world'); await tester.idle(); await tester.pump(); expect(inputKey.currentState.value.text, equals('world')); expect(editableText.config.value.text, equals('world')); }); testWidgets('No crash when a FormField is removed from the tree', (WidgetTester tester) async { GlobalKey<FormState> formKey = new GlobalKey<FormState>(); GlobalKey fieldKey = new GlobalKey(); String fieldValue; Widget builder(bool remove) { return new Center( child: new Material( child: new Form( key: formKey, child: remove ? new Container() : new TextField( key: fieldKey, autofocus: true, onSaved: (InputValue value) { fieldValue = value.text; }, validator: (InputValue value) { return value.text.isEmpty ? null : 'yes'; } ), ) ) ); } await tester.pumpWidget(builder(false)); await showKeyboard(tester); expect(fieldValue, isNull); expect(formKey.currentState.validate(), isTrue); enterText('Test'); await tester.idle(); await tester.pumpWidget(builder(false)); // Form wasn't saved yet. expect(fieldValue, null); expect(formKey.currentState.validate(), isFalse); formKey.currentState.save(); // Now fieldValue is saved. expect(fieldValue, 'Test'); expect(formKey.currentState.validate(), isFalse); // Now remove the field with an error. await tester.pumpWidget(builder(true)); // Reset the form. Should not crash. formKey.currentState.reset(); formKey.currentState.save(); expect(formKey.currentState.validate(), isTrue); }); }