form_test.dart 7.75 KB
Newer Older
1 2 3 4 5 6 7 8
// 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';

void main() {
Matt Perry's avatar
Matt Perry committed
9 10
  testWidgets('onSaved callback is called', (WidgetTester tester) async {
    GlobalKey<FormState> formKey = new GlobalKey<FormState>();
11 12 13 14 15 16
    String fieldValue;

    Widget builder() {
      return new Center(
        child: new Material(
          child: new Form(
Matt Perry's avatar
Matt Perry committed
17
            key: formKey,
18
            child: new TextField(
19 20
              onSaved: (InputValue value) { fieldValue = value.text; },
            ),
21
          )
22 23 24
        )
      );
    }
25

26
    await tester.pumpWidget(builder());
27

28
    expect(fieldValue, isNull);
29

30
    Future<Null> checkText(String testValue) async {
31
      await tester.enterText(find.byType(EditableText), testValue);
32
      await tester.idle();
Matt Perry's avatar
Matt Perry committed
33
      formKey.currentState.save();
34
      // pump'ing is unnecessary because callback happens regardless of frames
35 36
      expect(fieldValue, equals(testValue));
    }
37

38 39
    await checkText('Test');
    await checkText('');
40 41
  });

42 43 44 45 46 47 48
  testWidgets('onChanged callback is called', (WidgetTester tester) async {
    String fieldValue;

    Widget builder() {
      return new Center(
        child: new Material(
          child: new Form(
49
            child: new TextField(
50 51 52 53 54 55 56 57 58 59 60 61
              onChanged: (InputValue value) { fieldValue = value.text; },
            ),
          )
        )
      );
    }

    await tester.pumpWidget(builder());

    expect(fieldValue, isNull);

    Future<Null> checkText(String testValue) async {
62
      await tester.enterText(find.byType(EditableText), testValue);
63 64 65 66 67 68 69 70 71
      await tester.idle();
      // pump'ing is unnecessary because callback happens regardless of frames
      expect(fieldValue, equals(testValue));
    }

    await checkText('Test');
    await checkText('');
  });

72 73
  testWidgets('Validator sets the error text only when validate is called', (WidgetTester tester) async {
    GlobalKey<FormState> formKey = new GlobalKey<FormState>();
74
    GlobalKey inputKey = new GlobalKey();
Matt Perry's avatar
Matt Perry committed
75
    String errorText(InputValue input) => input.text + '/error';
76

77
    Widget builder(bool autovalidate) {
78 79 80
      return new Center(
        child: new Material(
          child: new Form(
81 82
            key: formKey,
            autovalidate: autovalidate,
83
            child: new TextField(
84
              key: inputKey,
85 86
              validator: errorText,
            ),
87
          )
88 89 90
        )
      );
    }
91

92 93
    // Start off not autovalidating.
    await tester.pumpWidget(builder(false));
94

95
    Future<Null> checkErrorText(String testValue) async {
96
      formKey.currentState.reset();
97
      await tester.enterText(find.byType(EditableText), testValue);
98
      await tester.idle();
99 100 101 102 103
      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();
104
      await tester.pump();
105 106 107 108
      expect(find.text(errorText(new InputValue(text: testValue))), findsOneWidget);

      // Try again with autovalidation. Should validate immediately.
      formKey.currentState.reset();
109
      await tester.enterText(find.byType(EditableText), testValue);
110 111 112
      await tester.idle();
      await tester.pumpWidget(builder(true));

Matt Perry's avatar
Matt Perry committed
113
      expect(find.text(errorText(new InputValue(text: testValue))), findsOneWidget);
114
    }
115

116 117
    await checkErrorText('Test');
    await checkErrorText('');
118 119
  });

120
  testWidgets('Multiple Inputs communicate', (WidgetTester tester) async {
Matt Perry's avatar
Matt Perry committed
121 122
    GlobalKey<FormState> formKey = new GlobalKey<FormState>();
    GlobalKey<FormFieldState<InputValue>> fieldKey = new GlobalKey<FormFieldState<InputValue>>();
123 124
    GlobalKey focusKey = new GlobalKey();
    // Input 2's validator depends on a input 1's value.
Matt Perry's avatar
Matt Perry committed
125
    String errorText(InputValue input) => fieldKey.currentState.value?.text.toString() + '/error';
126 127 128 129 130

    Widget builder() {
      return new Center(
        child: new Material(
          child: new Form(
Matt Perry's avatar
Matt Perry committed
131
            key: formKey,
132
            autovalidate: true,
133 134
            child: new Focus(
              key: focusKey,
135
              child: new ListView(
136
                children: <Widget>[
137
                  new TextField(
138
                    key: fieldKey
139
                  ),
140
                  new TextField(
141 142
                    validator: errorText,
                  ),
143
                ]
144
              )
145
            ),
146
          )
147 148 149
        )
      );
    }
150

151
    await tester.pumpWidget(builder());
152

153
    Future<Null> checkErrorText(String testValue) async {
154
      await tester.enterText(find.byType(EditableText).first, testValue);
155
      await tester.idle();
156
      await tester.pump();
157

158
      // Check for a new Text widget with our error text.
Matt Perry's avatar
Matt Perry committed
159
      expect(find.text(testValue + '/error'), findsOneWidget);
160
      return null;
161
    }
162

163 164
    await checkErrorText('Test');
    await checkErrorText('');
165
  });
166 167 168

  testWidgets('Provide initial value to input', (WidgetTester tester) async {
    String initialValue = 'hello';
Matt Perry's avatar
Matt Perry committed
169
    GlobalKey<FormFieldState<InputValue>> inputKey = new GlobalKey<FormFieldState<InputValue>>();
170 171 172

    Widget builder() {
      return new Center(
Matt Perry's avatar
Matt Perry committed
173 174
        child: new Material(
          child: new Form(
175
            child: new TextField(
Matt Perry's avatar
Matt Perry committed
176 177
              key: inputKey,
              initialValue: new InputValue(text: initialValue),
178
            ),
179
          )
Matt Perry's avatar
Matt Perry committed
180
        )
181 182 183 184
      );
    }

    await tester.pumpWidget(builder());
185
    await tester.showKeyboard(find.byType(EditableText));
186 187

    // initial value should be loaded into keyboard editing state
188 189
    expect(tester.testTextInput.editingState, isNotNull);
    expect(tester.testTextInput.editingState['text'], equals(initialValue));
190 191

    // initial value should also be visible in the raw input line
192
    EditableTextState editableText = tester.state(find.byType(EditableText));
193 194 195
    expect(editableText.config.value.text, equals(initialValue));

    // sanity check, make sure we can still edit the text and everything updates
Matt Perry's avatar
Matt Perry committed
196
    expect(inputKey.currentState.value.text, equals(initialValue));
197
    await tester.enterText(find.byType(EditableText), 'world');
198
    await tester.idle();
199
    await tester.pump();
Matt Perry's avatar
Matt Perry committed
200
    expect(inputKey.currentState.value.text, equals('world'));
201
    expect(editableText.config.value.text, equals('world'));
Matt Perry's avatar
Matt Perry committed
202 203 204 205 206 207 208 209 210 211 212 213
  });

  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,
214
            child: remove ? new Container() : new TextField(
215 216 217 218 219
              key: fieldKey,
              autofocus: true,
              onSaved: (InputValue value) { fieldValue = value.text; },
              validator: (InputValue value) { return value.text.isEmpty ? null : 'yes'; }
            ),
Matt Perry's avatar
Matt Perry committed
220 221 222 223 224 225 226 227
          )
        )
      );
    }

    await tester.pumpWidget(builder(false));

    expect(fieldValue, isNull);
228
    expect(formKey.currentState.validate(), isTrue);
Matt Perry's avatar
Matt Perry committed
229

230
    await tester.enterText(find.byType(EditableText), 'Test');
Matt Perry's avatar
Matt Perry committed
231 232 233
    await tester.idle();
    await tester.pumpWidget(builder(false));

234
    // Form wasn't saved yet.
Matt Perry's avatar
Matt Perry committed
235
    expect(fieldValue, null);
236
    expect(formKey.currentState.validate(), isFalse);
Matt Perry's avatar
Matt Perry committed
237 238 239 240 241

    formKey.currentState.save();

    // Now fieldValue is saved.
    expect(fieldValue, 'Test');
242
    expect(formKey.currentState.validate(), isFalse);
Matt Perry's avatar
Matt Perry committed
243 244 245

    // Now remove the field with an error.
    await tester.pumpWidget(builder(true));
246

Matt Perry's avatar
Matt Perry committed
247 248 249
    // Reset the form. Should not crash.
    formKey.currentState.reset();
    formKey.currentState.save();
250
    expect(formKey.currentState.validate(), isTrue);
251
  });
252
}