date_picker_test.dart 39.8 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Ian Hickson's avatar
Ian Hickson committed
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'dart:ui';

7
import 'package:flutter/rendering.dart';
Ian Hickson's avatar
Ian Hickson committed
8
import 'package:flutter/material.dart';
9
import 'package:flutter/widgets.dart';
Ian Hickson's avatar
Ian Hickson committed
10
import 'package:flutter_test/flutter_test.dart';
11
import 'package:flutter/gestures.dart' show DragStartBehavior;
Ian Hickson's avatar
Ian Hickson committed
12

13
import '../widgets/semantics_tester.dart';
14 15
import 'feedback_tester.dart';

Ian Hickson's avatar
Ian Hickson committed
16
void main() {
17 18
  group('showDatePicker', () {
    _tests();
19
  });
20 21 22
}

void _tests() {
23 24 25
  DateTime firstDate;
  DateTime lastDate;
  DateTime initialDate;
26
  SelectableDayPredicate selectableDayPredicate;
27
  DatePickerMode initialDatePickerMode;
28 29
  final Finder nextMonthIcon = find.byWidgetPredicate((Widget w) => w is IconButton && (w.tooltip?.startsWith('Next month') ?? false));
  final Finder previousMonthIcon = find.byWidgetPredicate((Widget w) => w is IconButton && (w.tooltip?.startsWith('Previous month') ?? false));
30 31

  setUp(() {
32 33 34
    firstDate = DateTime(2001, DateTime.january, 1);
    lastDate = DateTime(2031, DateTime.december, 31);
    initialDate = DateTime(2016, DateTime.january, 15);
35 36
    selectableDayPredicate = null;
    initialDatePickerMode = null;
37 38
  });

39
  testWidgets('tap-select a day', (WidgetTester tester) async {
40 41
    final Key _datePickerKey = UniqueKey();
    DateTime _selectedDate = DateTime(2016, DateTime.july, 26);
Ian Hickson's avatar
Ian Hickson committed
42 43

    await tester.pumpWidget(
44 45
      MaterialApp(
        home: StatefulBuilder(
46
          builder: (BuildContext context, StateSetter setState) {
47
            return Container(
48
              width: 400.0,
49
              child: SingleChildScrollView(
50
                dragStartBehavior: DragStartBehavior.down,
51 52
                child: Material(
                  child: MonthPicker(
53
                    dragStartBehavior: DragStartBehavior.down,
54 55
                    firstDate: DateTime(0),
                    lastDate: DateTime(9999),
56 57 58 59 60 61 62
                    key: _datePickerKey,
                    selectedDate: _selectedDate,
                    onChanged: (DateTime value) {
                      setState(() {
                        _selectedDate = value;
                      });
                    },
63
                  ),
64 65 66 67 68
                ),
              ),
            );
          },
        ),
69
      ),
Ian Hickson's avatar
Ian Hickson committed
70
    );
71

72
    expect(_selectedDate, equals(DateTime(2016, DateTime.july, 26)));
Ian Hickson's avatar
Ian Hickson committed
73

74
    await tester.tapAt(const Offset(50.0, 100.0));
75
    await tester.pumpAndSettle();
Ian Hickson's avatar
Ian Hickson committed
76 77
    await tester.pump(const Duration(seconds: 2));

78 79
    await tester.tap(find.text('1'));
    await tester.pumpAndSettle();
80
    expect(_selectedDate, equals(DateTime(2016, DateTime.july, 1)));
Ian Hickson's avatar
Ian Hickson committed
81

82
    await tester.tap(nextMonthIcon);
83
    await tester.pumpAndSettle();
84
    expect(_selectedDate, equals(DateTime(2016, DateTime.july, 1)));
Ian Hickson's avatar
Ian Hickson committed
85

86 87
    await tester.tap(find.text('5'));
    await tester.pumpAndSettle();
88
    expect(_selectedDate, equals(DateTime(2016, DateTime.august, 5)));
Ian Hickson's avatar
Ian Hickson committed
89

90 91
    await tester.drag(find.byKey(_datePickerKey), const Offset(-400.0, 0.0));
    await tester.pumpAndSettle();
92
    expect(_selectedDate, equals(DateTime(2016, DateTime.august, 5)));
Ian Hickson's avatar
Ian Hickson committed
93

94 95
    await tester.tap(find.text('25'));
    await tester.pumpAndSettle();
96
    expect(_selectedDate, equals(DateTime(2016, DateTime.september, 25)));
Ian Hickson's avatar
Ian Hickson committed
97

98 99
    await tester.drag(find.byKey(_datePickerKey), const Offset(800.0, 0.0));
    await tester.pumpAndSettle();
100
    expect(_selectedDate, equals(DateTime(2016, DateTime.september, 25)));
Ian Hickson's avatar
Ian Hickson committed
101

102 103
    await tester.tap(find.text('17'));
    await tester.pumpAndSettle();
104
    expect(_selectedDate, equals(DateTime(2016, DateTime.august, 17)));
105
  });
Ian Hickson's avatar
Ian Hickson committed
106 107 108

  testWidgets('render picker with intrinsic dimensions', (WidgetTester tester) async {
    await tester.pumpWidget(
109 110
      MaterialApp(
        home: StatefulBuilder(
111
          builder: (BuildContext context, StateSetter setState) {
112 113 114 115 116 117 118
            return IntrinsicWidth(
              child: IntrinsicHeight(
                child: Material(
                  child: SingleChildScrollView(
                    child: MonthPicker(
                      firstDate: DateTime(0),
                      lastDate: DateTime(9999),
119
                      onChanged: (DateTime value) { },
120
                      selectedDate: DateTime(2000, DateTime.january, 1),
121 122
                    ),
                  ),
123 124 125 126 127
                ),
              ),
            );
          },
        ),
128
      ),
Ian Hickson's avatar
Ian Hickson committed
129 130
    );
    await tester.pump(const Duration(seconds: 5));
131
  });
Ian Hickson's avatar
Ian Hickson committed
132

133
  Future<void> preparePicker(WidgetTester tester, Future<void> callback(Future<DateTime> date)) async {
134
    BuildContext buttonContext;
135 136 137
    await tester.pumpWidget(MaterialApp(
      home: Material(
        child: Builder(
138
          builder: (BuildContext context) {
139
            return RaisedButton(
140 141 142
              onPressed: () {
                buttonContext = context;
              },
143
              child: const Text('Go'),
144 145 146 147 148 149 150 151 152
            );
          },
        ),
      ),
    ));

    await tester.tap(find.text('Go'));
    expect(buttonContext, isNotNull);

153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
    final Future<DateTime> date = initialDatePickerMode == null
        // Exercise the argument default for initialDatePickerMode.
        ?
            showDatePicker(
              context: buttonContext,
              initialDate: initialDate,
              firstDate: firstDate,
              lastDate: lastDate,
              selectableDayPredicate: selectableDayPredicate,
            )
        :
            showDatePicker(
              context: buttonContext,
              initialDate: initialDate,
              firstDate: firstDate,
              lastDate: lastDate,
              selectableDayPredicate: selectableDayPredicate,
              initialDatePickerMode: initialDatePickerMode,
            );
172

173
    await tester.pumpAndSettle(const Duration(seconds: 1));
174 175 176 177 178 179
    await callback(date);
  }

  testWidgets('Initial date is the default', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('OK'));
180
      expect(await date, equals(DateTime(2016, DateTime.january, 15)));
181 182 183 184 185 186 187 188 189 190 191 192 193 194
    });
  });

  testWidgets('Can cancel', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('CANCEL'));
      expect(await date, isNull);
    });
  });

  testWidgets('Can select a day', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('12'));
      await tester.tap(find.text('OK'));
195
      expect(await date, equals(DateTime(2016, DateTime.january, 12)));
196 197 198 199 200
    });
  });

  testWidgets('Can select a month', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
201
      await tester.tap(previousMonthIcon);
202
      await tester.pumpAndSettle(const Duration(seconds: 1));
203 204
      await tester.tap(find.text('25'));
      await tester.tap(find.text('OK'));
205
      expect(await date, equals(DateTime(2015, DateTime.december, 25)));
206 207 208 209 210 211 212
    });
  });

  testWidgets('Can select a year', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('2016'));
      await tester.pump();
213
      await tester.tap(find.text('2018'));
214
      await tester.tap(find.text('OK'));
215
      expect(await date, equals(DateTime(2018, DateTime.january, 15)));
216 217 218 219 220 221 222
    });
  });

  testWidgets('Can select a year and then a day', (WidgetTester tester) async {
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('2016'));
      await tester.pump();
223
      await tester.tap(find.text('2017'));
224
      await tester.pump();
Yegor's avatar
Yegor committed
225 226 227
      final MaterialLocalizations localizations = MaterialLocalizations.of(
        tester.element(find.byType(DayPicker))
      );
228
      final String dayLabel = localizations.formatMediumDate(DateTime(2017, DateTime.january, 15));
229 230 231 232
      await tester.tap(find.text(dayLabel));
      await tester.pump();
      await tester.tap(find.text('19'));
      await tester.tap(find.text('OK'));
233
      expect(await date, equals(DateTime(2017, DateTime.january, 19)));
234 235 236 237
    });
  });

  testWidgets('Current year is initially visible in year picker', (WidgetTester tester) async {
238 239 240
    initialDate = DateTime(2000);
    firstDate = DateTime(1900);
    lastDate = DateTime(2100);
241 242 243 244
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('2000'));
      await tester.pump();
      expect(find.text('2000'), findsNWidgets(2));
245 246
    });
  });
247 248

  testWidgets('Cannot select a day outside bounds', (WidgetTester tester) async {
249
    initialDate = DateTime(2017, DateTime.january, 15);
250 251 252 253 254 255
    firstDate = initialDate;
    lastDate = initialDate;
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('10')); // Earlier than firstDate. Should be ignored.
      await tester.tap(find.text('20')); // Later than lastDate. Should be ignored.
      await tester.tap(find.text('OK'));
256
      // We should still be on the initial date.
257 258 259 260 261
      expect(await date, equals(initialDate));
    });
  });

  testWidgets('Cannot select a month past last date', (WidgetTester tester) async {
262
    initialDate = DateTime(2017, DateTime.january, 15);
263
    firstDate = initialDate;
264
    lastDate = DateTime(2017, DateTime.february, 20);
265
    await preparePicker(tester, (Future<DateTime> date) async {
266
      await tester.tap(nextMonthIcon);
267
      await tester.pumpAndSettle(const Duration(seconds: 1));
268
      // Shouldn't be possible to keep going into March.
269
      expect(nextMonthIcon, findsNothing);
270 271 272 273
    });
  });

  testWidgets('Cannot select a month before first date', (WidgetTester tester) async {
274 275
    initialDate = DateTime(2017, DateTime.january, 15);
    firstDate = DateTime(2016, DateTime.december, 10);
276 277
    lastDate = initialDate;
    await preparePicker(tester, (Future<DateTime> date) async {
278
      await tester.tap(previousMonthIcon);
279
      await tester.pumpAndSettle(const Duration(seconds: 1));
280
      // Shouldn't be possible to keep going into November.
281
      expect(previousMonthIcon, findsNothing);
282 283
    });
  });
284

285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
  testWidgets('Selecting firstDate year respects firstDate', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/17309
    initialDate = DateTime(2018, DateTime.may, 4);
    firstDate = DateTime(2016, DateTime.june, 9);
    lastDate = DateTime(2019, DateTime.january, 15);
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('2018'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('2016'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
      expect(await date, DateTime(2016, DateTime.june, 9));
    });
  });

  testWidgets('Selecting lastDate year respects lastDate', (WidgetTester tester) async {
    // Regression test for https://github.com/flutter/flutter/issues/17309
    initialDate = DateTime(2018, DateTime.may, 4);
    firstDate = DateTime(2016, DateTime.june, 9);
    lastDate = DateTime(2019, DateTime.january, 15);
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('2018'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('2019'));
      await tester.pumpAndSettle();
      await tester.tap(find.text('OK'));
      await tester.pumpAndSettle();
      expect(await date, DateTime(2019, DateTime.january, 15));
    });
  });


318
  testWidgets('Only predicate days are selectable', (WidgetTester tester) async {
319 320 321
    initialDate = DateTime(2017, DateTime.january, 16);
    firstDate = DateTime(2017, DateTime.january, 10);
    lastDate = DateTime(2017, DateTime.january, 20);
322 323 324 325 326 327
    selectableDayPredicate = (DateTime day) => day.day.isEven;
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.tap(find.text('10')); // Even, works.
      await tester.tap(find.text('13')); // Odd, doesn't work.
      await tester.tap(find.text('17')); // Odd, doesn't work.
      await tester.tap(find.text('OK'));
328
      expect(await date, equals(DateTime(2017, DateTime.january, 10)));
329 330
    });
  });
331

332
  testWidgets('Can select initial date picker mode', (WidgetTester tester) async {
333
    initialDate = DateTime(2014, DateTime.january, 15);
334 335 336 337 338 339 340
    initialDatePickerMode = DatePickerMode.year;
    await preparePicker(tester, (Future<DateTime> date) async {
      await tester.pump();
      // 2018 wouldn't be available if the year picker wasn't showing.
      // The initial current year is 2014.
      await tester.tap(find.text('2018'));
      await tester.tap(find.text('OK'));
341
      expect(await date, equals(DateTime(2018, DateTime.january, 15)));
342 343 344
    });
  });

345
  group('haptic feedback', () {
346
    const Duration kHapticFeedbackInterval = Duration(milliseconds: 10);
347
    FeedbackTester feedback;
348 349

    setUp(() {
350 351 352 353
      feedback = FeedbackTester();
      initialDate = DateTime(2017, DateTime.january, 16);
      firstDate = DateTime(2017, DateTime.january, 10);
      lastDate = DateTime(2018, DateTime.january, 20);
354 355 356
      selectableDayPredicate = (DateTime date) => date.day.isEven;
    });

357 358 359 360
    tearDown(() {
      feedback?.dispose();
    });

361 362 363 364
    testWidgets('tap-select date vibrates', (WidgetTester tester) async {
      await preparePicker(tester, (Future<DateTime> date) async {
        await tester.tap(find.text('10'));
        await tester.pump(kHapticFeedbackInterval);
365
        expect(feedback.hapticCount, 1);
366 367
        await tester.tap(find.text('12'));
        await tester.pump(kHapticFeedbackInterval);
368
        expect(feedback.hapticCount, 2);
369 370
        await tester.tap(find.text('14'));
        await tester.pump(kHapticFeedbackInterval);
371
        expect(feedback.hapticCount, 3);
372 373 374 375 376 377 378
      });
    });

    testWidgets('tap-select unselectable date does not vibrate', (WidgetTester tester) async {
      await preparePicker(tester, (Future<DateTime> date) async {
        await tester.tap(find.text('11'));
        await tester.pump(kHapticFeedbackInterval);
379
        expect(feedback.hapticCount, 0);
380 381
        await tester.tap(find.text('13'));
        await tester.pump(kHapticFeedbackInterval);
382
        expect(feedback.hapticCount, 0);
383 384
        await tester.tap(find.text('15'));
        await tester.pump(kHapticFeedbackInterval);
385
        expect(feedback.hapticCount, 0);
386 387 388 389 390 391 392
      });
    });

    testWidgets('mode, year change vibrates', (WidgetTester tester) async {
      await preparePicker(tester, (Future<DateTime> date) async {
        await tester.tap(find.text('2017'));
        await tester.pump(kHapticFeedbackInterval);
393
        expect(feedback.hapticCount, 1);
394 395
        await tester.tap(find.text('2018'));
        await tester.pump(kHapticFeedbackInterval);
396
        expect(feedback.hapticCount, 2);
397 398
      });
    });
399

400
  });
401 402 403 404 405 406 407 408 409

  test('days in month', () {
    expect(DayPicker.getDaysInMonth(2017, 10), 31);
    expect(DayPicker.getDaysInMonth(2017, 6), 30);
    expect(DayPicker.getDaysInMonth(2017, 2), 28);
    expect(DayPicker.getDaysInMonth(2016, 2), 29);
    expect(DayPicker.getDaysInMonth(2000, 2), 29);
    expect(DayPicker.getDaysInMonth(1900, 2), 28);
  });
410 411 412 413 414 415 416 417 418 419 420 421 422

  testWidgets('month header tap', (WidgetTester tester) async {
    selectableDayPredicate = null;
    await preparePicker(tester, (Future<DateTime> date) async {
      // Switch into the year selector.
      await tester.tap(find.text('January 2016'));
      await tester.pump();
      expect(find.text('2020'), isNotNull);

      await tester.tap(find.text('CANCEL'));
      expect(await date, isNull);
    });
  });
423 424

  testWidgets('exports semantics', (WidgetTester tester) async {
425
    final SemanticsTester semantics = SemanticsTester(tester);
426
    await preparePicker(tester, (Future<DateTime> date) async {
427
      final TestSemantics expected = TestSemantics(
428
        flags: <SemanticsFlag>[SemanticsFlag.scopesRoute],
429
        children: <TestSemantics>[
430
          TestSemantics(
431 432
            elevation: 24.0,
            thickness: 0.0,
433
            children: <TestSemantics>[
434
              TestSemantics(
435
                flags: <SemanticsFlag>[SemanticsFlag.isFocusable],
436 437 438 439 440
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: '2016',
                textDirection: TextDirection.ltr,
              ),
              TestSemantics(
441 442 443 444
                flags: <SemanticsFlag>[
                  SemanticsFlag.isSelected,
                  SemanticsFlag.isFocusable,
                ],
445 446 447 448
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: 'Fri, Jan 15',
                textDirection: TextDirection.ltr,
              ),
449
              TestSemantics(
450
                children: <TestSemantics>[
451
                  TestSemantics(
452
                    id: 55,
453
                    actions: <SemanticsAction>[SemanticsAction.scrollLeft, SemanticsAction.scrollRight],
454
                    children: <TestSemantics>[
455
                      TestSemantics(
456
                        children: <TestSemantics>[
457
                          TestSemantics(
458
                            children: <TestSemantics>[
459
                              TestSemantics(
460 461
                                id: 11,
                                flags: <SemanticsFlag>[SemanticsFlag.hasImplicitScrolling],
462
                                children: <TestSemantics>[
463 464 465 466 467 468 469 470 471 472 473 474 475
                                  // TODO(dnfield): These shouldn't be here. https://github.com/flutter/flutter/issues/34431
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
                                  TestSemantics(),
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '1, Friday, January 1, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '2, Saturday, January 2, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '3, Sunday, January 3, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '4, Monday, January 4, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '5, Tuesday, January 5, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '6, Wednesday, January 6, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '7, Thursday, January 7, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '8, Friday, January 8, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '9, Saturday, January 9, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '10, Sunday, January 10, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '11, Monday, January 11, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '12, Tuesday, January 12, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '13, Wednesday, January 13, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '14, Thursday, January 14, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    flags: <SemanticsFlag>[SemanticsFlag.isSelected],
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '15, Friday, January 15, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '16, Saturday, January 16, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '17, Sunday, January 17, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '18, Monday, January 18, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '19, Tuesday, January 19, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '20, Wednesday, January 20, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '21, Thursday, January 21, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '22, Friday, January 22, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '23, Saturday, January 23, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '24, Sunday, January 24, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '25, Monday, January 25, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '26, Tuesday, January 26, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '27, Wednesday, January 27, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '28, Thursday, January 28, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '29, Friday, January 29, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '30, Saturday, January 30, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                  TestSemantics(
                                    actions: <SemanticsAction>[SemanticsAction.tap],
                                    label: '31, Sunday, January 31, 2016',
                                    textDirection: TextDirection.ltr,
                                  ),
                                ],
633 634
                              ),
                            ],
635 636 637 638 639 640 641
                          ),
                        ],
                      ),
                    ],
                  ),
                ],
              ),
642
              TestSemantics(
643 644 645 646 647 648
                flags: <SemanticsFlag>[
                  SemanticsFlag.hasEnabledState,
                  SemanticsFlag.isButton,
                  SemanticsFlag.isEnabled,
                  SemanticsFlag.isFocusable,
                ],
649 650 651 652 653
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: 'Previous month December 2015',
                textDirection: TextDirection.ltr,
              ),
              TestSemantics(
654 655 656 657 658 659
                flags: <SemanticsFlag>[
                  SemanticsFlag.hasEnabledState,
                  SemanticsFlag.isButton,
                  SemanticsFlag.isEnabled,
                  SemanticsFlag.isFocusable,
                ],
660 661 662 663 664
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: 'Next month February 2016',
                textDirection: TextDirection.ltr,
              ),
              TestSemantics(
665 666 667 668 669 670
                flags: <SemanticsFlag>[
                  SemanticsFlag.hasEnabledState,
                  SemanticsFlag.isButton,
                  SemanticsFlag.isEnabled,
                  SemanticsFlag.isFocusable,
                ],
671 672 673 674 675
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: 'CANCEL',
                textDirection: TextDirection.ltr,
              ),
              TestSemantics(
676 677 678 679 680 681
                flags: <SemanticsFlag>[
                  SemanticsFlag.hasEnabledState,
                  SemanticsFlag.isButton,
                  SemanticsFlag.isEnabled,
                  SemanticsFlag.isFocusable,
                ],
682 683 684 685
                actions: <SemanticsAction>[SemanticsAction.tap],
                label: 'OK',
                textDirection: TextDirection.ltr,
              ),
686 687 688 689 690 691
            ],
          ),
        ],
      );

      expect(semantics, hasSemantics(
692 693
        TestSemantics.root(children: <TestSemantics>[
          TestSemantics(
694 695 696
            children: <TestSemantics>[expected],
          ),
        ]),
697 698 699 700 701
        ignoreId: true,
        ignoreTransform: true,
        ignoreRect: true,
      ));
    });
702 703

    semantics.dispose();
704
  });
705 706

  testWidgets('chervons animate when scrolling month picker', (WidgetTester tester) async {
707 708
    final Key _datePickerKey = UniqueKey();
    DateTime _selectedDate = DateTime(2016, DateTime.july, 26);
709 710

    await tester.pumpWidget(
711 712
      MaterialApp(
        home: StatefulBuilder(
713
          builder: (BuildContext context, StateSetter setState) {
714
            return Container(
715
              width: 400.0,
716 717 718 719 720
              child: SingleChildScrollView(
                child: Material(
                  child: MonthPicker(
                    firstDate: DateTime(0),
                    lastDate: DateTime(9999),
721 722 723 724 725 726 727 728 729 730 731 732 733
                    key: _datePickerKey,
                    selectedDate: _selectedDate,
                    onChanged: (DateTime value) {
                      setState(() {
                        _selectedDate = value;
                      });
                    },
                  ),
                ),
              ),
            );
          },
        ),
734
      ),
735 736 737
    );

    final Finder chevronFinder = find.byType(IconButton);
738 739 740 741
    final List<RenderAnimatedOpacity> chevronRenderers = chevronFinder
      .evaluate()
      .map((Element element) => element.findAncestorRenderObjectOfType<RenderAnimatedOpacity>())
      .toList();
742 743 744

    // Initial chevron animation state should be dismissed
    // An AlwaysStoppedAnimation is also found and is ignored
745
    for (final RenderAnimatedOpacity renderer in chevronRenderers) {
746 747 748 749 750 751 752 753
      expect(renderer.opacity.value, equals(1.0));
      expect(renderer.opacity.status, equals(AnimationStatus.dismissed));
    }

    // Drag and hold the picker to test for the opacity change
    final TestGesture gesture = await tester.startGesture(const Offset(100.0, 100.0));
    await gesture.moveBy(const Offset(50.0, 100.0));
    await tester.pumpAndSettle();
754
    for (final RenderAnimatedOpacity renderer in chevronRenderers) {
755
      expect(renderer.opacity.value, equals(0.0));
756 757 758 759 760 761
      expect(renderer.opacity.status, equals(AnimationStatus.completed));
    }

    // Release the drag and test for the opacity to return to original value
    await gesture.up();
    await tester.pumpAndSettle();
762
    for (final RenderAnimatedOpacity renderer in chevronRenderers) {
763 764 765 766
      expect(renderer.opacity.value, equals(1.0));
      expect(renderer.opacity.status, equals(AnimationStatus.dismissed));
    }
  });
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816

  testWidgets('builder parameter', (WidgetTester tester) async {
    Widget buildFrame(TextDirection textDirection) {
      return MaterialApp(
        home: Material(
          child: Center(
            child: Builder(
              builder: (BuildContext context) {
                return RaisedButton(
                  child: const Text('X'),
                  onPressed: () {
                    showDatePicker(
                      context: context,
                      initialDate: DateTime.now(),
                      firstDate: DateTime(2018),
                      lastDate: DateTime(2030),
                      builder: (BuildContext context, Widget child) {
                        return Directionality(
                          textDirection: textDirection,
                          child: child,
                        );
                      },
                    );
                  },
                );
              },
            ),
          ),
        ),
      );
    }

    await tester.pumpWidget(buildFrame(TextDirection.ltr));
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();
    final double ltrOkRight = tester.getBottomRight(find.text('OK')).dx;

    await tester.tap(find.text('OK')); // dismiss the dialog
    await tester.pumpAndSettle();

    await tester.pumpWidget(buildFrame(TextDirection.rtl));
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle();

    // Verify that the time picker is being laid out RTL.
    // We expect the left edge of the 'OK' button in the RTL
    // layout to match the gap between right edge of the 'OK'
    // button and the right edge of the 800 wide window.
    expect(tester.getBottomLeft(find.text('OK')).dx, 800 - ltrOkRight);
  });
817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900

  group('screen configurations', () {
    // Test various combinations of screen sizes, orientations and text scales
    // to ensure the layout doesn't overflow and cause an exception to be thrown.

    // Regression tests for https://github.com/flutter/flutter/issues/21383
    // Regression tests for https://github.com/flutter/flutter/issues/19744
    // Regression tests for https://github.com/flutter/flutter/issues/17745

    // Common screen size roughly based on a Pixel 1
    const Size kCommonScreenSizePortrait = Size(1070, 1770);
    const Size kCommonScreenSizeLandscape = Size(1770, 1070);

    // Small screen size based on a LG K130
    const Size kSmallScreenSizePortrait = Size(320, 521);
    const Size kSmallScreenSizeLandscape = Size(521, 320);

    Future<void> _showPicker(WidgetTester tester, Size size, [double textScaleFactor = 1.0]) async {
      tester.binding.window.physicalSizeTestValue = size;
      tester.binding.window.devicePixelRatioTestValue = 1.0;
      await tester.pumpWidget(
        MaterialApp(
          home: Builder(
            builder: (BuildContext context) {
              return RaisedButton(
                child: const Text('X'),
                onPressed: () {
                  showDatePicker(
                    context: context,
                    initialDate: initialDate,
                    firstDate: firstDate,
                    lastDate: lastDate,
                  );
                },
              );
            },
          ),
        ),
      );
      await tester.tap(find.text('X'));
      await tester.pumpAndSettle();
    }

    testWidgets('common screen size - portrait', (WidgetTester tester) async {
      await _showPicker(tester, kCommonScreenSizePortrait);
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - landscape', (WidgetTester tester) async {
      await _showPicker(tester, kCommonScreenSizeLandscape);
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - portrait - textScale 1.3', (WidgetTester tester) async {
      await _showPicker(tester, kCommonScreenSizePortrait, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - landscape - textScale 1.3', (WidgetTester tester) async {
      await _showPicker(tester, kCommonScreenSizeLandscape, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgets('small screen size - portrait', (WidgetTester tester) async {
      await _showPicker(tester, kSmallScreenSizePortrait);
      expect(tester.takeException(), isNull);
    });

    testWidgets('small screen size - landscape', (WidgetTester tester) async {
      await _showPicker(tester, kSmallScreenSizeLandscape);
      expect(tester.takeException(), isNull);
    });

    testWidgets('small screen size - portrait -textScale 1.3', (WidgetTester tester) async {
      await _showPicker(tester, kSmallScreenSizePortrait, 1.3);
      expect(tester.takeException(), isNull);
    });

    testWidgets('small screen size - landscape - textScale 1.3', (WidgetTester tester) async {
      await _showPicker(tester, kSmallScreenSizeLandscape, 1.3);
      expect(tester.takeException(), isNull);
    });
  });

901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986
  testWidgets('uses root navigator by default', (WidgetTester tester) async {
    final DatePickerObserver rootObserver = DatePickerObserver();
    final DatePickerObserver nestedObserver = DatePickerObserver();

    await tester.pumpWidget(MaterialApp(
      navigatorObservers: <NavigatorObserver>[rootObserver],
      home: Navigator(
        observers: <NavigatorObserver>[nestedObserver],
        onGenerateRoute: (RouteSettings settings) {
          return MaterialPageRoute<dynamic>(
            builder: (BuildContext context) {
              return RaisedButton(
                onPressed: () {
                  showDatePicker(
                    context: context,
                    initialDate: DateTime.now(),
                    firstDate: DateTime(2018),
                    lastDate: DateTime(2030),
                    builder: (BuildContext context, Widget child) {
                      return const SizedBox();
                    },
                  );
                },
                child: const Text('Show Date Picker'),
              );
            },
          );
        },
      ),
    ));

    // Open the dialog.
    await tester.tap(find.byType(RaisedButton));

    expect(rootObserver.datePickerCount, 1);
    expect(nestedObserver.datePickerCount, 0);
  });

  testWidgets('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
    final DatePickerObserver rootObserver = DatePickerObserver();
    final DatePickerObserver nestedObserver = DatePickerObserver();

    await tester.pumpWidget(MaterialApp(
      navigatorObservers: <NavigatorObserver>[rootObserver],
      home: Navigator(
        observers: <NavigatorObserver>[nestedObserver],
        onGenerateRoute: (RouteSettings settings) {
          return MaterialPageRoute<dynamic>(
            builder: (BuildContext context) {
              return RaisedButton(
                onPressed: () {
                  showDatePicker(
                    context: context,
                    useRootNavigator: false,
                    initialDate: DateTime.now(),
                    firstDate: DateTime(2018),
                    lastDate: DateTime(2030),
                    builder: (BuildContext context, Widget child) => const SizedBox(),
                  );
                },
                child: const Text('Show Date Picker'),
              );
            },
          );
        },
      ),
    ));

    // Open the dialog.
    await tester.tap(find.byType(RaisedButton));

    expect(rootObserver.datePickerCount, 0);
    expect(nestedObserver.datePickerCount, 1);
  });
}

class DatePickerObserver extends NavigatorObserver {
  int datePickerCount = 0;

  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    if (route.toString().contains('_DialogRoute')) {
      datePickerCount++;
    }
    super.didPush(route, previousRoute);
  }
Ian Hickson's avatar
Ian Hickson committed
987
}