dropdown_test.dart 11.9 KB
Newer Older
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.

5 6
import 'dart:math' as math;

7 8 9
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

10 11
final List<String> menuItems = <String>['one', 'two', 'three', 'four'];

12 13 14 15 16 17 18
Widget buildFrame({
    Key buttonKey,
    String value: 'two',
    ValueChanged<String> onChanged,
    bool isDense: false,
    Widget hint,
  }) {
19 20 21 22 23 24
  return new MaterialApp(
    home: new Material(
      child: new Center(
        child: new DropdownButton<String>(
          key: buttonKey,
          value: value,
25
          hint: hint,
26 27
          onChanged: onChanged,
          isDense: isDense,
28
          items: menuItems.map((String item) {
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
            return new DropdownMenuItem<String>(
              key: new ValueKey<String>(item),
              value: item,
              child: new Text(item, key: new ValueKey<String>(item + "Text")),
            );
          }).toList(),
        ),
      ),
    ),
  );
}

// When the dropdown's menu is popped up, a RenderParagraph for the selected
// menu's text item will appear both in the dropdown button and in the menu.
// The RenderParagraphs should be aligned, i.e. they should have the same
// size and location.
void checkSelectedItemTextGeometry(WidgetTester tester, String value) {
  final List<RenderBox> boxes = tester.renderObjectList(find.byKey(new ValueKey<String>(value + 'Text'))).toList();
  expect(boxes.length, equals(2));
  final RenderBox box0 = boxes[0];
  final RenderBox box1 = boxes[1];
  expect(box0.localToGlobal(Point.origin), equals(box1.localToGlobal(Point.origin)));
  expect(box0.size, equals(box1.size));
}

bool sameGeometry(RenderBox box1, RenderBox box2) {
  expect(box1.localToGlobal(Point.origin), equals(box2.localToGlobal(Point.origin)));
  expect(box1.size.height, equals(box2.size.height));
  return true;
}


61
void main() {
62
  testWidgets('Dropdown button control test', (WidgetTester tester) async {
63
    String value = 'one';
64 65 66 67
    void didChangeValue(String newValue) {
      value = newValue;
    }

68
    Widget build() => buildFrame(value: value, onChanged: didChangeValue);
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

    await tester.pumpWidget(build());

    await tester.tap(find.text('one'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('one'));

    await tester.tap(find.text('three').last);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('three'));

    await tester.tap(find.text('three'));
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('three'));

    await tester.pumpWidget(build());

    await tester.tap(find.text('two').last);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('two'));
  });

101
  testWidgets('Dropdown button with no app', (WidgetTester tester) async {
102
    String value = 'one';
103 104 105 106 107 108 109 110 111 112 113 114
    void didChangeValue(String newValue) {
      value = newValue;
    }

    Widget build() {
      return new Navigator(
        initialRoute: '/',
        onGenerateRoute: (RouteSettings settings) {
          return new MaterialPageRoute<Null>(
            settings: settings,
            builder: (BuildContext context) {
              return new Material(
115
                child: buildFrame(value: 'one', onChanged: didChangeValue),
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
              );
            },
          );
        }
      );
    }

    await tester.pumpWidget(build());

    await tester.tap(find.text('one'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('one'));

    await tester.tap(find.text('three').last);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('three'));

    await tester.tap(find.text('three'));
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('three'));

    await tester.pumpWidget(build());

    await tester.tap(find.text('two').last);

    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    expect(value, equals('two'));
  });

154
  testWidgets('Dropdown screen edges', (WidgetTester tester) async {
155
    int value = 4;
156
    final List<DropdownMenuItem<int>> items = <DropdownMenuItem<int>>[];
157
    for (int i = 0; i < 20; ++i)
158
      items.add(new DropdownMenuItem<int>(value: i, child: new Text('$i')));
159 160 161 162 163

    void handleChanged(int newValue) {
      value = newValue;
    }

164
    final DropdownButton<int> button = new DropdownButton<int>(
165 166
      value: value,
      onChanged: handleChanged,
167
      items: items,
168 169
    );

170
    await tester.pumpWidget(
171 172 173 174
      new MaterialApp(
        home: new Material(
          child: new Align(
            alignment: FractionalOffset.topCenter,
175 176 177 178
            child: button,
          ),
        ),
      ),
179 180
    );

181 182 183
    await tester.tap(find.text('4'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation
184

185 186
    // We should have two copies of item 5, one in the menu and one in the
    // button itself.
187
    expect(tester.elementList(find.text('5')), hasLength(2));
188 189 190

    // We should only have one copy of item 19, which is in the button itself.
    // The copy in the menu shouldn't be in the tree because it's off-screen.
191
    expect(tester.elementList(find.text('19')), hasLength(1));
192

193
    expect(value, 4);
194
    await tester.tap(find.byConfig(button));
195
    expect(value, 4);
196 197
    // this waits for the route's completer to complete, which calls handleChanged
    await tester.idle();
198
    expect(value, 4);
199 200 201

    // TODO(abarth): Remove these calls to pump once navigator cleans up its
    // pop transitions.
202 203
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation
204
  });
205

206
  testWidgets('Dropdown button aligns selected menu item', (WidgetTester tester) async {
207 208
    final Key buttonKey = new UniqueKey();
    final String value = 'two';
209 210 211 212

    Widget build() => buildFrame(buttonKey: buttonKey, value: value);

    await tester.pumpWidget(build());
213
    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
214
    assert(buttonBox.attached);
215
    final Point buttonOriginBeforeTap = buttonBox.localToGlobal(Point.origin);
216 217 218 219 220 221 222 223 224 225 226

    await tester.tap(find.text('two'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    // Tapping the dropdown button should not cause it to move.
    expect(buttonBox.localToGlobal(Point.origin), equals(buttonOriginBeforeTap));

    // The selected dropdown item is both in menu we just popped up, and in
    // the IndexedStack contained by the dropdown button. Both of them should
    // have the same origin and height as the dropdown button.
227
    final List<RenderObject> itemBoxes = tester.renderObjectList(find.byKey(const ValueKey<String>('two'))).toList();
228 229 230 231 232 233 234 235 236 237 238 239
    expect(itemBoxes.length, equals(2));
    for(RenderBox itemBox in itemBoxes) {
      assert(itemBox.attached);
      expect(buttonBox.localToGlobal(Point.origin), equals(itemBox.localToGlobal(Point.origin)));
      expect(buttonBox.size.height, equals(itemBox.size.height));
    }

    // The two RenderParagraph objects, for the 'two' items' Text children,
    // should have the same size and location.
    checkSelectedItemTextGeometry(tester, 'two');
  });

240
  testWidgets('Dropdown button with isDense:true aligns selected menu item', (WidgetTester tester) async {
241 242
    final Key buttonKey = new UniqueKey();
    final String value = 'two';
243 244 245 246

    Widget build() => buildFrame(buttonKey: buttonKey, value: value, isDense: true);

    await tester.pumpWidget(build());
247
    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
248 249 250 251 252 253 254 255 256
    assert(buttonBox.attached);

    await tester.tap(find.text('two'));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    // The selected dropdown item is both in menu we just popped up, and in
    // the IndexedStack contained by the dropdown button. Both of them should
    // have the same vertical center as the button.
257
    final List<RenderBox> itemBoxes = tester.renderObjectList(find.byKey(const ValueKey<String>('two'))).toList();
258 259 260 261
    expect(itemBoxes.length, equals(2));

    // When isDense is true, the button's height is reduced. The menu items'
    // heights are not.
262
    final double menuItemHeight = itemBoxes.map((RenderBox box) => box.size.height).reduce(math.max);
263 264 265 266
    expect(menuItemHeight, greaterThan(buttonBox.size.height));

    for(RenderBox itemBox in itemBoxes) {
      assert(itemBox.attached);
267 268
      final Point buttonBoxCenter = buttonBox.size.center(buttonBox.localToGlobal(Point.origin));
      final Point itemBoxCenter =  itemBox.size.center(itemBox.localToGlobal(Point.origin));
269 270 271 272 273 274 275
      expect(buttonBoxCenter.y, equals(itemBoxCenter.y));
    }

    // The two RenderParagraph objects, for the 'two' items' Text children,
    // should have the same size and location.
    checkSelectedItemTextGeometry(tester, 'two');
  });
276 277

  testWidgets('Size of DropdownButton with null value', (WidgetTester tester) async {
278
    final Key buttonKey = new UniqueKey();
279 280 281 282 283
    String value;

    Widget build() => buildFrame(buttonKey: buttonKey, value: value);

    await tester.pumpWidget(build());
284
    final RenderBox buttonBoxNullValue = tester.renderObject(find.byKey(buttonKey));
285 286 287 288 289
    assert(buttonBoxNullValue.attached);


    value = 'three';
    await tester.pumpWidget(build());
290
    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
291 292
    assert(buttonBox.attached);

293
    // A Dropdown button with a null value should be the same size as a
294 295 296 297 298 299
    // one with a non-null value.
    expect(buttonBox.localToGlobal(Point.origin), equals(buttonBoxNullValue.localToGlobal(Point.origin)));
    expect(buttonBox.size, equals(buttonBoxNullValue.size));
  });

  testWidgets('Layout of a DropdownButton with null value', (WidgetTester tester) async {
300
    final Key buttonKey = new UniqueKey();
301 302 303 304 305 306 307 308 309
    String value;

    void onChanged(String newValue) {
      value = newValue;
    }

    Widget build() => buildFrame(buttonKey: buttonKey, value: value, onChanged: onChanged);

    await tester.pumpWidget(build());
310
    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326
    assert(buttonBox.attached);

    // Show the menu.
    await tester.tap(find.byKey(buttonKey));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    // Tap on item 'one', which must appear over the button.
    await tester.tap(find.byKey(buttonKey));
    await tester.pump();
    await tester.pump(const Duration(seconds: 1)); // finish the menu animation

    await tester.pumpWidget(build());
    expect(value, equals('one'));
  });

327
  testWidgets('Size of DropdownButton with null value and a hint', (WidgetTester tester) async {
328
    final Key buttonKey = new UniqueKey();
329 330 331
    String value;

    // The hint will define the dropdown's width
332
    Widget build() => buildFrame(buttonKey: buttonKey, value: value, hint: const Text('onetwothree'));
333 334 335

    await tester.pumpWidget(build());
    expect(find.text('onetwothree'), findsOneWidget);
336
    final RenderBox buttonBoxHintValue = tester.renderObject(find.byKey(buttonKey));
337 338 339 340 341
    assert(buttonBoxHintValue.attached);


    value = 'three';
    await tester.pumpWidget(build());
342
    final RenderBox buttonBox = tester.renderObject(find.byKey(buttonKey));
343 344
    assert(buttonBox.attached);

345
    // A Dropdown button with a null value and a hint should be the same size as a
346 347 348 349 350
    // one with a non-null value.
    expect(buttonBox.localToGlobal(Point.origin), equals(buttonBoxHintValue.localToGlobal(Point.origin)));
    expect(buttonBox.size, equals(buttonBoxHintValue.size));
  });

351
}