input_test.dart 11.9 KB
Newer Older
Hixie's avatar
Hixie committed
1 2 3 4
// 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.

Adam Barth's avatar
Adam Barth committed
5 6
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';
7
import 'package:flutter/rendering.dart';
8
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
9

10 11 12
class MockKeyboard extends mojom.KeyboardProxy {
  MockKeyboard() : super.unbound();

13
  mojom.KeyboardClient client;
14

15
  @override
16
  void setClient(mojom.KeyboardClientStub client, mojom.KeyboardConfiguration configuraiton) {
17 18 19
    this.client = client.impl;
  }

20
  @override
21
  void show() {}
22

23
  @override
24
  void hide() {}
25

26
  @override
27
  void setEditingState(mojom.EditingState state) {}
28 29
}

30 31 32
class MockClipboard extends mojom.ClipboardProxy {
  MockClipboard() : super.unbound();

33 34 35 36 37 38 39 40 41 42 43 44 45
  mojom.ClipboardData _clip;

  @override
  void setClipboardData(mojom.ClipboardData clip) {
    _clip = clip;
  }

  @override
  dynamic getClipboardData(String format,[Function responseFactory = null]) {
    return new mojom.ClipboardGetClipboardDataResponseParams()..clip = _clip;
  }
}

46
void main() {
47
  MockKeyboard mockKeyboard = new MockKeyboard();
48
  serviceMocker.registerMockService(mockKeyboard);
49
  MockClipboard mockClipboard = new MockClipboard();
50
  serviceMocker.registerMockService(mockClipboard);
51

52 53 54 55 56 57 58 59 60
  void enterText(String testValue) {
    // Simulate entry of text through the keyboard.
    expect(mockKeyboard.client, isNotNull);
    mockKeyboard.client.updateEditingState(new mojom.EditingState()
      ..text = testValue
      ..composingBase = 0
      ..composingExtent = testValue.length);
  }

61
  testWidgets('Editable text has consistent size', (WidgetTester tester) async {
62 63 64 65 66 67 68 69 70 71 72
    GlobalKey inputKey = new GlobalKey();
    InputValue inputValue = InputValue.empty;

    Widget builder() {
      return new Center(
        child: new Material(
          child: new Input(
            value: inputValue,
            key: inputKey,
            hintText: 'Placeholder',
            onChanged: (InputValue value) { inputValue = value; }
73
          )
74 75 76
        )
      );
    }
77

78
    await tester.pumpWidget(builder());
79

80
    RenderBox findInputBox() => tester.renderObject(find.byKey(inputKey));
81

82 83
    RenderBox inputBox = findInputBox();
    Size emptyInputSize = inputBox.size;
84

85
    Future<Null> checkText(String testValue) {
86
      enterText(testValue);
87

88 89
      // Check that the onChanged event handler fired.
      expect(inputValue.text, equals(testValue));
90

91
      return tester.pumpWidget(builder());
92
    }
93

94
    await checkText(' ');
95 96
    expect(findInputBox(), equals(inputBox));
    expect(inputBox.size, equals(emptyInputSize));
97

98
    await checkText('Test');
99 100
    expect(findInputBox(), equals(inputBox));
    expect(inputBox.size, equals(emptyInputSize));
101
  });
102

103
  testWidgets('Cursor blinks', (WidgetTester tester) async {
104
    GlobalKey inputKey = new GlobalKey();
105

106 107 108 109 110 111 112 113 114 115
    Widget builder() {
      return new Center(
        child: new Material(
          child: new Input(
            key: inputKey,
            hintText: 'Placeholder'
          )
        )
      );
    }
116

117
    await tester.pumpWidget(builder());
118 119 120 121

    RawInputLineState editableText = tester.state(find.byType(RawInputLine));

    // Check that the cursor visibility toggles after each blink interval.
122
    Future<Null> checkCursorToggle() async {
123
      bool initialShowCursor = editableText.cursorCurrentlyVisible;
124
      await tester.pump(editableText.cursorBlinkInterval);
125
      expect(editableText.cursorCurrentlyVisible, equals(!initialShowCursor));
126
      await tester.pump(editableText.cursorBlinkInterval);
127
      expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
128
      await tester.pump(editableText.cursorBlinkInterval ~/ 10);
129
      expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
130
      await tester.pump(editableText.cursorBlinkInterval);
131
      expect(editableText.cursorCurrentlyVisible, equals(!initialShowCursor));
132
      await tester.pump(editableText.cursorBlinkInterval);
133 134
      expect(editableText.cursorCurrentlyVisible, equals(initialShowCursor));
    }
135

136
    await checkCursorToggle();
137

138 139 140 141 142
    // Try the test again with a nonempty EditableText.
    mockKeyboard.client.updateEditingState(new mojom.EditingState()
      ..text = 'X'
      ..selectionBase = 1
      ..selectionExtent = 1);
143
    await checkCursorToggle();
144
  });
145

146
  testWidgets('hideText control test', (WidgetTester tester) async {
147 148 149 150 151 152 153 154 155
    GlobalKey inputKey = new GlobalKey();

    Widget builder() {
      return new Center(
        child: new Material(
          child: new Input(
            key: inputKey,
            hideText: true,
            hintText: 'Placeholder'
Adam Barth's avatar
Adam Barth committed
156
          )
157 158 159
        )
      );
    }
Adam Barth's avatar
Adam Barth committed
160

161
    await tester.pumpWidget(builder());
Adam Barth's avatar
Adam Barth committed
162

163 164 165 166 167
    const String testValue = 'ABC';
    mockKeyboard.client.updateEditingState(new mojom.EditingState()
      ..text = testValue
      ..selectionBase = testValue.length
      ..selectionExtent = testValue.length);
Adam Barth's avatar
Adam Barth committed
168

169
    await tester.pump();
Adam Barth's avatar
Adam Barth committed
170
  });
171 172 173

  // Returns the first RenderEditableLine.
  RenderEditableLine findRenderEditableLine(WidgetTester tester) {
174
    RenderObject root = tester.renderObject(find.byType(RawInputLine));
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    expect(root, isNotNull);

    RenderEditableLine renderLine;
    void recursiveFinder(RenderObject child) {
      if (child is RenderEditableLine) {
        renderLine = child;
        return;
      }
      child.visitChildren(recursiveFinder);
    }
    root.visitChildren(recursiveFinder);
    expect(renderLine, isNotNull);
    return renderLine;
  }

  Point textOffsetToPosition(WidgetTester tester, int offset) {
    RenderEditableLine renderLine = findRenderEditableLine(tester);
    List<TextSelectionPoint> endpoints = renderLine.getEndpointsForSelection(
        new TextSelection.collapsed(offset: offset));
    expect(endpoints.length, 1);
    return endpoints[0].point;
  }

198
  testWidgets('Can long press to select', (WidgetTester tester) async {
199 200 201 202 203 204 205 206 207 208 209 210 211 212
    GlobalKey inputKey = new GlobalKey();
    InputValue inputValue = InputValue.empty;

    Widget builder() {
      return new Overlay(
        initialEntries: <OverlayEntry>[
          new OverlayEntry(
            builder: (BuildContext context) {
              return new Center(
                child: new Material(
                  child: new Input(
                    value: inputValue,
                    key: inputKey,
                    onChanged: (InputValue value) { inputValue = value; }
213
                  )
214 215 216 217 218 219 220
                )
              );
            }
          )
        ]
      );
    }
221

222
    await tester.pumpWidget(builder());
223

224 225 226
    String testValue = 'abc def ghi';
    enterText(testValue);
    expect(inputValue.text, testValue);
227

228
    await tester.pumpWidget(builder());
229

230
    expect(inputValue.selection.isCollapsed, true);
231

232 233
    // Long press the 'e' to select 'def'.
    Point ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
234 235 236 237
    TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
    await tester.pump(const Duration(seconds: 2));
    await gesture.up();
    await tester.pump();
238

239 240 241
    // 'def' is selected.
    expect(inputValue.selection.baseOffset, testValue.indexOf('d'));
    expect(inputValue.selection.extentOffset, testValue.indexOf('f')+1);
242 243
  });

244
  testWidgets('Can drag handles to change selection', (WidgetTester tester) async {
245 246 247 248 249 250 251 252 253 254 255 256 257 258
    GlobalKey inputKey = new GlobalKey();
    InputValue inputValue = InputValue.empty;

    Widget builder() {
      return new Overlay(
        initialEntries: <OverlayEntry>[
          new OverlayEntry(
            builder: (BuildContext context) {
              return new Center(
                child: new Material(
                  child: new Input(
                    value: inputValue,
                    key: inputKey,
                    onChanged: (InputValue value) { inputValue = value; }
259
                  )
260 261 262 263 264 265 266
                )
              );
            }
          )
        ]
      );
    }
267

268
    await tester.pumpWidget(builder());
269

270 271
    String testValue = 'abc def ghi';
    enterText(testValue);
272

273
    await tester.pumpWidget(builder());
274

275 276
    // Long press the 'e' to select 'def'.
    Point ePos = textOffsetToPosition(tester, testValue.indexOf('e'));
277 278 279 280
    TestGesture gesture = await tester.startGesture(ePos, pointer: 7);
    await tester.pump(const Duration(seconds: 2));
    await gesture.up();
    await tester.pump();
281 282

    TextSelection selection = inputValue.selection;
283

284 285 286 287 288 289 290 291 292 293
    RenderEditableLine renderLine = findRenderEditableLine(tester);
    List<TextSelectionPoint> endpoints = renderLine.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.
    Point handlePos = endpoints[1].point + new Offset(1.0, 1.0);
    Point newHandlePos = textOffsetToPosition(tester, selection.extentOffset+2);
294 295 296 297 298 299
    gesture = await tester.startGesture(handlePos, pointer: 7);
    await tester.pump();
    await gesture.moveTo(newHandlePos);
    await tester.pump();
    await gesture.up();
    await tester.pump();
300 301 302 303 304 305 306

    expect(inputValue.selection.baseOffset, selection.baseOffset);
    expect(inputValue.selection.extentOffset, selection.extentOffset+2);

    // Drag the left handle 2 letters to the left.
    handlePos = endpoints[0].point + new Offset(-1.0, 1.0);
    newHandlePos = textOffsetToPosition(tester, selection.baseOffset-2);
307 308 309 310 311 312
    gesture = await tester.startGesture(handlePos, pointer: 7);
    await tester.pump();
    await gesture.moveTo(newHandlePos);
    await tester.pump();
    await gesture.up();
    await tester.pumpWidget(builder());
313 314 315

    expect(inputValue.selection.baseOffset, selection.baseOffset-2);
    expect(inputValue.selection.extentOffset, selection.extentOffset+2);
316 317
  });

318
  testWidgets('Can use selection toolbar', (WidgetTester tester) async {
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
    GlobalKey inputKey = new GlobalKey();
    InputValue inputValue = InputValue.empty;

    Widget builder() {
      return new Overlay(
        initialEntries: <OverlayEntry>[
          new OverlayEntry(
            builder: (BuildContext context) {
              return new Center(
                child: new Material(
                  child: new Input(
                    value: inputValue,
                    key: inputKey,
                    onChanged: (InputValue value) { inputValue = value; }
                  )
                )
              );
            }
          )
        ]
      );
    }

342
    await tester.pumpWidget(builder());
343 344 345

    String testValue = 'abc def ghi';
    enterText(testValue);
346
    await tester.pumpWidget(builder());
347

348
    // Tap the selection handle to bring up the "paste / select all" menu.
349 350
    await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
    await tester.pumpWidget(builder());
351 352 353
    RenderEditableLine renderLine = findRenderEditableLine(tester);
    List<TextSelectionPoint> endpoints = renderLine.getEndpointsForSelection(
        inputValue.selection);
354 355
    await tester.tapAt(endpoints[0].point + new Offset(1.0, 1.0));
    await tester.pumpWidget(builder());
356 357

    // SELECT ALL should select all the text.
358 359
    await tester.tap(find.text('SELECT ALL'));
    await tester.pumpWidget(builder());
360 361 362 363
    expect(inputValue.selection.baseOffset, 0);
    expect(inputValue.selection.extentOffset, testValue.length);

    // COPY should reset the selection.
364 365
    await tester.tap(find.text('COPY'));
    await tester.pumpWidget(builder());
366 367 368
    expect(inputValue.selection.isCollapsed, true);

    // Tap again to bring back the menu.
369 370
    await tester.tapAt(textOffsetToPosition(tester, testValue.indexOf('e')));
    await tester.pumpWidget(builder());
371 372
    renderLine = findRenderEditableLine(tester);
    endpoints = renderLine.getEndpointsForSelection(inputValue.selection);
373 374
    await tester.tapAt(endpoints[0].point + new Offset(1.0, 1.0));
    await tester.pumpWidget(builder());
375 376

    // PASTE right before the 'e'.
377 378
    await tester.tap(find.text('PASTE'));
    await tester.pumpWidget(builder());
379 380
    expect(inputValue.text, 'abc d${testValue}ef ghi');
  });
381
}