2843 lines
90 KiB
Dart
2843 lines
90 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
// reduced-test-set:
|
|
// This file is run as part of a reduced test set in CI on Mac and Windows
|
|
// machines.
|
|
@Tags(<String>['reduced-test-set'])
|
|
library;
|
|
|
|
import 'dart:math' as math;
|
|
import 'dart:ui';
|
|
|
|
import 'package:flutter/cupertino.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
// A number of the hit tests below say "warnIfMissed: false". This is because
|
|
// the way the CupertinoPicker works, the hits don't actually reach the labels,
|
|
// the scroll view intercepts them.
|
|
|
|
// scrolling by this offset will move the picker to the next item
|
|
const Offset _kRowOffset = Offset(0.0, -50.0);
|
|
|
|
void main() {
|
|
group('Countdown timer picker', () {
|
|
testWidgets('initialTimerDuration falls within limit', (WidgetTester tester) async {
|
|
expect(() {
|
|
CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(days: 1),
|
|
);
|
|
}, throwsAssertionError);
|
|
|
|
expect(() {
|
|
CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(seconds: -1),
|
|
);
|
|
}, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('minuteInterval is positive and is a factor of 60', (WidgetTester tester) async {
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, minuteInterval: 0);
|
|
}, throwsAssertionError);
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, minuteInterval: -1);
|
|
}, throwsAssertionError);
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, minuteInterval: 7);
|
|
}, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('secondInterval is positive and is a factor of 60', (WidgetTester tester) async {
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, secondInterval: 0);
|
|
}, throwsAssertionError);
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, secondInterval: -1);
|
|
}, throwsAssertionError);
|
|
expect(() {
|
|
CupertinoTimerPicker(onTimerDurationChanged: (_) {}, secondInterval: 7);
|
|
}, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('background color default value', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(home: CupertinoTimerPicker(onTimerDurationChanged: (_) {})),
|
|
);
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != null), false);
|
|
});
|
|
|
|
testWidgets('background color can be null', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(home: CupertinoTimerPicker(onTimerDurationChanged: (_) {})),
|
|
);
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
backgroundColor: CupertinoColors.black,
|
|
),
|
|
),
|
|
);
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(
|
|
pickers.any((CupertinoPicker picker) => picker.backgroundColor != CupertinoColors.black),
|
|
false,
|
|
);
|
|
});
|
|
|
|
testWidgets('specified item extent value is applied', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(home: CupertinoTimerPicker(itemExtent: 42, onTimerDurationChanged: (_) {})),
|
|
);
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(pickers.any((CupertinoPicker picker) => picker.itemExtent != 42), false);
|
|
});
|
|
|
|
testWidgets('columns are ordered correctly when text direction is ltr', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(hours: 12, minutes: 30, seconds: 59),
|
|
),
|
|
),
|
|
);
|
|
|
|
Offset lastOffset = tester.getTopLeft(find.text('12'));
|
|
|
|
expect(tester.getTopLeft(find.text('hours')).dx > lastOffset.dx, true);
|
|
lastOffset = tester.getTopLeft(find.text('hours'));
|
|
|
|
expect(tester.getTopLeft(find.text('30')).dx > lastOffset.dx, true);
|
|
lastOffset = tester.getTopLeft(find.text('30'));
|
|
|
|
expect(tester.getTopLeft(find.text('min.')).dx > lastOffset.dx, true);
|
|
lastOffset = tester.getTopLeft(find.text('min.'));
|
|
|
|
expect(tester.getTopLeft(find.text('59')).dx > lastOffset.dx, true);
|
|
lastOffset = tester.getTopLeft(find.text('59'));
|
|
|
|
expect(tester.getTopLeft(find.text('sec.')).dx > lastOffset.dx, true);
|
|
});
|
|
|
|
testWidgets('columns are ordered correctly when text direction is rtl', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(hours: 12, minutes: 30, seconds: 59),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Offset lastOffset = tester.getTopLeft(find.text('12'));
|
|
|
|
expect(tester.getTopLeft(find.text('hours')).dx > lastOffset.dx, false);
|
|
lastOffset = tester.getTopLeft(find.text('hours'));
|
|
|
|
expect(tester.getTopLeft(find.text('30')).dx > lastOffset.dx, false);
|
|
lastOffset = tester.getTopLeft(find.text('30'));
|
|
|
|
expect(tester.getTopLeft(find.text('min.')).dx > lastOffset.dx, false);
|
|
lastOffset = tester.getTopLeft(find.text('min.'));
|
|
|
|
expect(tester.getTopLeft(find.text('59')).dx > lastOffset.dx, false);
|
|
lastOffset = tester.getTopLeft(find.text('59'));
|
|
|
|
expect(tester.getTopLeft(find.text('sec.')).dx > lastOffset.dx, false);
|
|
});
|
|
|
|
testWidgets('width of picker is consistent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(hours: 12, minutes: 30, seconds: 59),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first column and the last column.
|
|
final double distance =
|
|
tester.getCenter(find.text('sec.')).dx - tester.getCenter(find.text('12')).dx;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: SizedBox(
|
|
height: 400.0,
|
|
width: 800.0,
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (_) {},
|
|
initialTimerDuration: const Duration(hours: 12, minutes: 30, seconds: 59),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first and the last column should be the same.
|
|
expect(
|
|
tester.getCenter(find.text('sec.')).dx - tester.getCenter(find.text('12')).dx,
|
|
distance,
|
|
);
|
|
});
|
|
|
|
testWidgets('onScrollEnd behavior reports changes correctly', (WidgetTester tester) async {
|
|
final selectedDurations = <Duration>[];
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoTimerPicker(
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 30, seconds: 15),
|
|
changeReportingBehavior: ChangeReportingBehavior.onScrollEnd,
|
|
onTimerDurationChanged: (Duration duration) => selectedDurations.add(duration),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final Offset initialOffset = tester.getTopLeft(find.text('30'));
|
|
|
|
final TestGesture scrollGesture = await tester.startGesture(initialOffset);
|
|
// Should not report changes until the gesture ends.
|
|
await scrollGesture.moveBy(const Offset(0.0, 32.0));
|
|
expect(selectedDurations, isEmpty);
|
|
|
|
await scrollGesture.moveBy(const Offset(0.0, 32.0));
|
|
expect(selectedDurations, isEmpty);
|
|
|
|
await scrollGesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Only reports the last change.
|
|
expect(selectedDurations, hasLength(1));
|
|
expect(selectedDurations.first, const Duration(hours: 1, minutes: 28, seconds: 15));
|
|
});
|
|
});
|
|
|
|
testWidgets('showDayOfWeek is only supported in date mode', (WidgetTester tester) async {
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showDayOfWeek: true,
|
|
),
|
|
returnsNormally,
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showDayOfWeek: true,
|
|
),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('showDayOfWeek is only supported in date mode'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showDayOfWeek: true,
|
|
),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('showDayOfWeek is only supported in date mode'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(onDateTimeChanged: (DateTime _) {}, showDayOfWeek: true),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('showDayOfWeek is only supported in date mode'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('picker honors minuteInterval and secondInterval', (WidgetTester tester) async {
|
|
late Duration duration;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoTimerPicker(
|
|
minuteInterval: 10,
|
|
secondInterval: 12,
|
|
initialTimerDuration: const Duration(hours: 10, minutes: 40, seconds: 48),
|
|
onTimerDurationChanged: (Duration d) {
|
|
duration = d;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
await tester.drag(find.text('40'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.drag(find.text('48'), -_kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(duration, const Duration(hours: 10, minutes: 50, seconds: 36));
|
|
});
|
|
|
|
group('Date picker', () {
|
|
testWidgets('initial date is set to default value', (WidgetTester tester) async {
|
|
final picker = CupertinoDatePicker(onDateTimeChanged: (_) {});
|
|
expect(picker.initialDateTime, isNotNull);
|
|
});
|
|
|
|
testWidgets('background color default value', (WidgetTester tester) async {
|
|
await tester.pumpWidget(CupertinoApp(home: CupertinoDatePicker(onDateTimeChanged: (_) {})));
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(pickers.any((CupertinoPicker picker) => picker.backgroundColor != null), false);
|
|
});
|
|
|
|
testWidgets('background color can be null', (WidgetTester tester) async {
|
|
await tester.pumpWidget(CupertinoApp(home: CupertinoDatePicker(onDateTimeChanged: (_) {})));
|
|
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
testWidgets('specified background color is applied', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoDatePicker(
|
|
onDateTimeChanged: (_) {},
|
|
backgroundColor: CupertinoColors.black,
|
|
),
|
|
),
|
|
);
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(
|
|
pickers.any((CupertinoPicker picker) => picker.backgroundColor != CupertinoColors.black),
|
|
false,
|
|
);
|
|
});
|
|
|
|
testWidgets('specified item extent value is applied', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(home: CupertinoDatePicker(itemExtent: 55, onDateTimeChanged: (_) {})),
|
|
);
|
|
|
|
final Iterable<CupertinoPicker> pickers = tester.allWidgets.whereType<CupertinoPicker>();
|
|
expect(pickers.any((CupertinoPicker picker) => picker.itemExtent != 55), false);
|
|
});
|
|
|
|
testWidgets('initial date honors minuteInterval', (WidgetTester tester) async {
|
|
late DateTime newDateTime;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: 400,
|
|
height: 400,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime d) => newDateTime = d,
|
|
initialDateTime: DateTime(2018, 10, 10, 10, 3),
|
|
minuteInterval: 3,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Drag the minute picker to the next slot (03 -> 06).
|
|
// The `initialDateTime` and the `minuteInterval` values are specifically chosen
|
|
// so that `find.text` finds exactly one widget.
|
|
await tester.drag(find.text('03'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
|
|
expect(newDateTime.minute, 6);
|
|
});
|
|
|
|
test('initial date honors minimumDate & maximumDate', () {
|
|
expect(() {
|
|
CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime d) {},
|
|
initialDateTime: DateTime(2018, 10, 10),
|
|
minimumDate: DateTime(2018, 10, 11),
|
|
);
|
|
}, throwsAssertionError);
|
|
|
|
expect(() {
|
|
CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime d) {},
|
|
initialDateTime: DateTime(2018, 10, 10),
|
|
maximumDate: DateTime(2018, 10, 9),
|
|
);
|
|
}, throwsAssertionError);
|
|
});
|
|
|
|
testWidgets('changing initialDateTime after first build does not do anything', (
|
|
WidgetTester tester,
|
|
) async {
|
|
late DateTime selectedDateTime;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime dateTime) => selectedDateTime = dateTime,
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('10'),
|
|
const Offset(0.0, 32.0),
|
|
pointer: 1,
|
|
touchSlopY: 0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(selectedDateTime, DateTime(2018, 1, 1, 9, 30));
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime dateTime) => selectedDateTime = dateTime,
|
|
// Change the initial date, but it shouldn't affect the present state.
|
|
initialDateTime: DateTime(2016, 4, 5, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('9'),
|
|
const Offset(0.0, 32.0),
|
|
pointer: 1,
|
|
touchSlopY: 0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
// Moving up an hour is still based on the original initial date time.
|
|
expect(selectedDateTime, DateTime(2018, 1, 1, 8, 30));
|
|
});
|
|
|
|
testWidgets('date picker has expected string', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('September'), findsOneWidget);
|
|
expect(find.text('9'), findsOneWidget);
|
|
expect(find.text('2018'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('datetime picker has expected string', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 9, 15, 3, 14),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Sat Sep 15'), findsOneWidget);
|
|
expect(find.text('3'), findsOneWidget);
|
|
expect(find.text('14'), findsOneWidget);
|
|
expect(find.text('AM'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('monthYear picker has expected string', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 9),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('September'), findsOneWidget);
|
|
expect(find.text('2018'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('width of picker in date and time mode is consistent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first column and the last column.
|
|
final double distance =
|
|
tester.getCenter(find.text('Mon Jan 1 ')).dx - tester.getCenter(find.text('AM')).dx;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 800.0,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first and the last column should be the same.
|
|
expect(
|
|
tester.getCenter(find.text('Mon Jan 1 ')).dx - tester.getCenter(find.text('AM')).dx,
|
|
distance,
|
|
);
|
|
});
|
|
|
|
testWidgets('width of picker in date mode is consistent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first column and the last column.
|
|
final double distance =
|
|
tester.getCenter(find.text('January')).dx - tester.getCenter(find.text('2018')).dx;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 800.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first and the last column should be the same.
|
|
expect(
|
|
tester.getCenter(find.text('January')).dx - tester.getCenter(find.text('2018')).dx,
|
|
distance,
|
|
);
|
|
});
|
|
|
|
testWidgets('width of picker in time mode is consistent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first column and the last column.
|
|
final double distance =
|
|
tester.getCenter(find.text('10')).dx - tester.getCenter(find.text('AM')).dx;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 800.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first and the last column should be the same.
|
|
expect(
|
|
tester.getCenter(find.text('10')).dx - tester.getCenter(find.text('AM')).dx,
|
|
moreOrLessEquals(distance),
|
|
);
|
|
});
|
|
|
|
testWidgets('width of picker in monthYear mode is consistent', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first column and the last column.
|
|
final double distance =
|
|
tester.getCenter(find.text('January')).dx - tester.getCenter(find.text('2018')).dx;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 800.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Distance between the first and the last column should be the same.
|
|
expect(
|
|
tester.getCenter(find.text('January')).dx - tester.getCenter(find.text('2018')).dx,
|
|
distance,
|
|
);
|
|
});
|
|
|
|
testWidgets('wheel does not bend outwards', (WidgetTester tester) async {
|
|
final Widget dateWidget = CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 1, 1, 10, 30),
|
|
);
|
|
|
|
const centerMonth = 'January';
|
|
const visibleMonthsExceptTheCenter = <String>[
|
|
'September',
|
|
'October',
|
|
'November',
|
|
'December',
|
|
'February',
|
|
'March',
|
|
'April',
|
|
'May',
|
|
];
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: Center(child: SizedBox(height: 200.0, width: 300.0, child: dateWidget)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The wheel does not bend outwards.
|
|
for (final month in visibleMonthsExceptTheCenter) {
|
|
expect(
|
|
tester.getBottomLeft(find.text(centerMonth)).dx,
|
|
lessThan(tester.getBottomLeft(find.text(month)).dx),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoPageScaffold(
|
|
child: Center(child: SizedBox(height: 200.0, width: 3000.0, child: dateWidget)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The wheel does not bend outwards at large widths.
|
|
for (final month in visibleMonthsExceptTheCenter) {
|
|
expect(
|
|
tester.getBottomLeft(find.text(centerMonth)).dx,
|
|
lessThan(tester.getBottomLeft(find.text(month)).dx),
|
|
);
|
|
}
|
|
});
|
|
|
|
testWidgets('non-selectable dates are greyed out, '
|
|
'when minimum date is unconstrained', (WidgetTester tester) async {
|
|
final maximum = DateTime(2018, 6, 15);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 6, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// unconstrained bounds are not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('14')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
// the selected day is not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('15')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
// out of bounds and should be greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('16')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
});
|
|
|
|
testWidgets('non-selectable dates are greyed out, '
|
|
'when maximum date is unconstrained', (WidgetTester tester) async {
|
|
final minimum = DateTime(2018, 6, 15);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
minimumDate: minimum,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 6, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// out of bounds and should be greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('14')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
// the selected day is not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('15')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
// unconstrained bounds are not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('16')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
});
|
|
|
|
testWidgets('non-selectable dates are greyed out, '
|
|
'months should be taken into account when greying out days', (WidgetTester tester) async {
|
|
final minimum = DateTime(2018, 5, 15);
|
|
final maximum = DateTime(2018, 7, 15);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 6, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// days of a different min/max month are not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('14')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('16')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
});
|
|
|
|
testWidgets('non-selectable dates are greyed out, '
|
|
'years should be taken into account when greying out days', (WidgetTester tester) async {
|
|
final minimum = DateTime(2017, 6, 15);
|
|
final maximum = DateTime(2019, 6, 15);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 6, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// days of a different min/max year are not affected.
|
|
expect(
|
|
tester.widget<Text>(find.text('14')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('16')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
});
|
|
|
|
testWidgets('picker automatically scrolls away from invalid date on month change', (
|
|
WidgetTester tester,
|
|
) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2018, 3, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('March'),
|
|
const Offset(0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
|
|
// Momentarily, the 2018 and the incorrect 30 of February is aligned.
|
|
expect(tester.getTopLeft(find.text('2018')).dy, tester.getTopLeft(find.text('30')).dy);
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pump(); // Once to start the DrivenScrollActivity.
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 2, 28));
|
|
expect(tester.getTopLeft(find.text('2018')).dy, tester.getTopLeft(find.text('28')).dy);
|
|
});
|
|
|
|
testWidgets('date picker automatically scrolls away from invalid date, '
|
|
"and onDateTimeChanged doesn't report these dates", (WidgetTester tester) async {
|
|
late DateTime date;
|
|
// 2016 is a leap year.
|
|
final minimum = DateTime(2016, 2, 29);
|
|
final maximum = DateTime(2018, 12, 31);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
// Callback doesn't transiently go into invalid dates.
|
|
expect(newDate.isAtSameMomentAs(minimum) || newDate.isAfter(minimum), isTrue);
|
|
expect(newDate.isAtSameMomentAs(maximum) || newDate.isBefore(maximum), isTrue);
|
|
},
|
|
initialDateTime: DateTime(2017, 2, 28),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 2017 has 28 days in Feb so 29 is greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('29')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('2017'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(); // Now the autoscrolling should happen.
|
|
|
|
expect(date, DateTime(2016, 2, 29));
|
|
|
|
// 2016 has 29 days in Feb so 29 is not greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('29')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('2016'),
|
|
const Offset(0.0, -32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(date, DateTime(2017, 2, 28));
|
|
|
|
expect(
|
|
tester.widget<Text>(find.text('29')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
});
|
|
|
|
testWidgets('dateTime picker automatically scrolls away from invalid date, '
|
|
"and onDateTimeChanged doesn't report these dates", (WidgetTester tester) async {
|
|
late DateTime date;
|
|
final minimum = DateTime(2019, 11, 11, 3, 30);
|
|
final maximum = DateTime(2019, 11, 11, 14, 59, 59);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
// Callback doesn't transiently go into invalid dates.
|
|
expect(minimum.isAfter(newDate), isFalse);
|
|
expect(maximum.isBefore(newDate), isFalse);
|
|
},
|
|
initialDateTime: DateTime(2019, 11, 11, 4),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 3:00 is valid but 2:00 should be invalid.
|
|
expect(
|
|
tester.widget<Text>(find.text('3')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
expect(
|
|
tester.widget<Text>(find.text('2')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
// 'PM' is greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('PM')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('AM'),
|
|
const Offset(0.0, -32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(); // Now the autoscrolling should happen.
|
|
|
|
expect(date, DateTime(2019, 11, 11, 14, 59));
|
|
|
|
// 3'o clock and 'AM' are now greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('AM')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('3')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('PM'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pumpAndSettle();
|
|
|
|
// Returns to min date.
|
|
expect(date, DateTime(2019, 11, 11, 3, 30));
|
|
});
|
|
|
|
testWidgets('time picker automatically scrolls away from invalid date, '
|
|
"and onDateTimeChanged doesn't report these dates", (WidgetTester tester) async {
|
|
late DateTime date;
|
|
final minimum = DateTime(2019, 11, 11, 3, 30);
|
|
final maximum = DateTime(2019, 11, 11, 14, 59, 59);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
// Callback doesn't transiently go into invalid dates.
|
|
expect(minimum.isAfter(newDate), isFalse);
|
|
expect(maximum.isBefore(newDate), isFalse);
|
|
},
|
|
initialDateTime: DateTime(2019, 11, 11, 4),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 3:00 is valid but 2:00 should be invalid.
|
|
expect(
|
|
tester.widget<Text>(find.text('3')).style!.color,
|
|
isNot(isSameColorAs(CupertinoColors.inactiveGray.color)),
|
|
);
|
|
|
|
expect(
|
|
tester.widget<Text>(find.text('2')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
// 'PM' is greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('PM')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('AM'),
|
|
const Offset(0.0, -32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(); // Now the autoscrolling should happen.
|
|
|
|
expect(date, DateTime(2019, 11, 11, 14, 59));
|
|
|
|
// 3'o clock and 'AM' are now greyed out.
|
|
expect(
|
|
tester.widget<Text>(find.text('AM')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
expect(
|
|
tester.widget<Text>(find.text('3')).style!.color,
|
|
isSameColorAs(CupertinoColors.inactiveGray.color),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('PM'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pumpAndSettle();
|
|
|
|
// Returns to min date.
|
|
expect(date, DateTime(2019, 11, 11, 3, 30));
|
|
});
|
|
|
|
testWidgets('monthYear picker automatically scrolls away from invalid date, '
|
|
"and onDateTimeChanged doesn't report these dates", (WidgetTester tester) async {
|
|
late DateTime date;
|
|
final minimum = DateTime(2016, 2);
|
|
final maximum = DateTime(2018, 12);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
// Callback doesn't transiently go into invalid dates.
|
|
expect(newDate.isAtSameMomentAs(minimum) || newDate.isAfter(minimum), isTrue);
|
|
expect(newDate.isAtSameMomentAs(maximum) || newDate.isBefore(maximum), isTrue);
|
|
},
|
|
initialDateTime: DateTime(2017, 2),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('2017'),
|
|
const Offset(0.0, 100.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pumpAndSettle(); // Now the autoscrolling should happen.
|
|
|
|
expect(date, DateTime(2016, 2));
|
|
|
|
await tester.drag(
|
|
find.text('2016'),
|
|
const Offset(0.0, -100.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(date, DateTime(2018, 12));
|
|
|
|
await tester.drag(
|
|
find.text('2016'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(date, DateTime(2017, 12));
|
|
});
|
|
|
|
testWidgets('picker automatically scrolls away from invalid date on day change', (
|
|
WidgetTester tester,
|
|
) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2018, 2, 27), // 2018 has 28 days in Feb.
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(
|
|
find.text('27'),
|
|
const Offset(0.0, -32.0),
|
|
pointer: 1,
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
expect(date, DateTime(2018, 2, 28));
|
|
|
|
await tester.drag(
|
|
find.text('28'),
|
|
const Offset(0.0, -32.0),
|
|
pointer: 1,
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump(); // Once to trigger the post frame animate call.
|
|
|
|
// Callback doesn't transiently go into invalid dates.
|
|
expect(date, DateTime(2018, 2, 28));
|
|
// Momentarily, the invalid 29th of Feb is dragged into the middle.
|
|
expect(tester.getTopLeft(find.text('2018')).dy, tester.getTopLeft(find.text('29')).dy);
|
|
|
|
await tester.pump(); // Once to start the DrivenScrollActivity.
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 2, 28));
|
|
expect(tester.getTopLeft(find.text('2018')).dy, tester.getTopLeft(find.text('28')).dy);
|
|
});
|
|
|
|
testWidgets(
|
|
'date picker should only take into account the date part of minimumDate and maximumDate',
|
|
(WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/49606.
|
|
late DateTime date;
|
|
final minDate = DateTime(2020, 1, 1, 12);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
minimumDate: minDate,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2020, 1, 12),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Scroll to 2019.
|
|
await tester.drag(
|
|
find.text('2020'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pumpAndSettle();
|
|
expect(date.year, minDate.year);
|
|
expect(date.month, minDate.month);
|
|
expect(date.day, minDate.day);
|
|
},
|
|
);
|
|
|
|
testWidgets(
|
|
'date picker does not display previous day of minimumDate if it is set at midnight',
|
|
(WidgetTester tester) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/72932
|
|
final minDate = DateTime(2019, 12, 31);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
minimumDate: minDate,
|
|
onDateTimeChanged: (DateTime newDate) {},
|
|
initialDateTime: minDate.add(const Duration(days: 1)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('Mon Dec 30'), findsNothing);
|
|
},
|
|
);
|
|
|
|
group('Picker handles initial noon/midnight times', () {
|
|
testWidgets('midnight', (WidgetTester tester) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2019, 1, 1, 0, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 0:15 -> 0:16
|
|
await tester.drag(find.text('15'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 0, 16));
|
|
});
|
|
|
|
testWidgets('noon', (WidgetTester tester) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2019, 1, 1, 12, 15),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 12:15 -> 12:16
|
|
await tester.drag(find.text('15'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 12, 16));
|
|
});
|
|
|
|
testWidgets('noon in 24 hour time', (WidgetTester tester) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
use24hFormat: true,
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2019, 1, 1, 12, 25),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 12:25 -> 12:26
|
|
await tester.drag(find.text('25'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 12, 26));
|
|
});
|
|
});
|
|
|
|
testWidgets('picker persists am/pm value when scrolling hours', (WidgetTester tester) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2019, 1, 1, 3),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// 3:00 -> 15:00
|
|
await tester.drag(find.text('AM'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 15));
|
|
|
|
// 15:00 -> 16:00
|
|
await tester.drag(find.text('3'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 16));
|
|
|
|
// 16:00 -> 4:00
|
|
await tester.drag(find.text('PM'), -_kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 4));
|
|
|
|
// 4:00 -> 3:00
|
|
await tester.drag(find.text('4'), -_kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2019, 1, 1, 3));
|
|
});
|
|
|
|
testWidgets(
|
|
'picker automatically scrolls the am/pm column when the hour column changes enough',
|
|
(WidgetTester tester) async {
|
|
late DateTime date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
initialDateTime: DateTime(2018, 1, 1, 11, 59),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
const deltaOffset = Offset(0.0, -18.0);
|
|
|
|
// 11:59 -> 12:59
|
|
await tester.drag(find.text('11'), _kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 1, 1, 12, 59));
|
|
|
|
// 12:59 -> 11:59
|
|
await tester.drag(find.text('12'), -_kRowOffset, warnIfMissed: false); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 1, 1, 11, 59));
|
|
|
|
// 11:59 -> 9:59
|
|
await tester.drag(
|
|
find.text('11'),
|
|
-((_kRowOffset - deltaOffset) * 2 + deltaOffset),
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 1, 1, 9, 59));
|
|
|
|
// 9:59 -> 15:59
|
|
await tester.drag(
|
|
find.text('9'),
|
|
(_kRowOffset - deltaOffset) * 6 + deltaOffset,
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
await tester.pump(const Duration(milliseconds: 500));
|
|
|
|
expect(date, DateTime(2018, 1, 1, 15, 59));
|
|
},
|
|
);
|
|
|
|
testWidgets('date picker given too narrow space horizontally shows message', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
// This is too small to draw the picker out fully.
|
|
width: 100,
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: DateTime(2019, 1, 1, 4),
|
|
onDateTimeChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final dynamic exception = tester.takeException();
|
|
expect(exception, isFlutterError);
|
|
expect(
|
|
exception.toString(),
|
|
contains('Insufficient horizontal space to render the CupertinoDatePicker'),
|
|
);
|
|
});
|
|
|
|
testWidgets('DatePicker golden tests', (WidgetTester tester) async {
|
|
Widget buildApp(CupertinoDatePickerMode mode) {
|
|
return CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: 500,
|
|
height: 400,
|
|
child: RepaintBoundary(
|
|
child: CupertinoDatePicker(
|
|
key: ValueKey<CupertinoDatePickerMode>(mode),
|
|
mode: mode,
|
|
initialDateTime: DateTime(2019, 1, 1, 4, 12, 30),
|
|
onDateTimeChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(CupertinoDatePickerMode.time));
|
|
await expectLater(
|
|
find.byType(CupertinoDatePicker),
|
|
matchesGoldenFile('date_picker_test.time.initial.png'),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(CupertinoDatePickerMode.date));
|
|
await expectLater(
|
|
find.byType(CupertinoDatePicker),
|
|
matchesGoldenFile('date_picker_test.date.initial.png'),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(CupertinoDatePickerMode.monthYear));
|
|
await expectLater(
|
|
find.byType(CupertinoDatePicker),
|
|
matchesGoldenFile('date_picker_test.monthyear.initial.png'),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(CupertinoDatePickerMode.dateAndTime));
|
|
await expectLater(
|
|
find.byType(CupertinoDatePicker),
|
|
matchesGoldenFile('date_picker_test.datetime.initial.png'),
|
|
);
|
|
|
|
// Slightly drag the hour component to make the current hour off-center.
|
|
await tester.drag(
|
|
find.text('4'),
|
|
Offset(0, _kRowOffset.dy / 2),
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
|
|
await expectLater(
|
|
find.byType(CupertinoDatePicker),
|
|
matchesGoldenFile('date_picker_test.datetime.drag.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('DatePicker displays the date in correct order', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
dateOrder: DatePickerDateOrder.ydm,
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime newDate) {},
|
|
initialDateTime: DateTime(2018, 1, 14, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getTopLeft(find.text('2018')).dx,
|
|
lessThan(tester.getTopLeft(find.text('14')).dx),
|
|
);
|
|
|
|
expect(
|
|
tester.getTopLeft(find.text('14')).dx,
|
|
lessThan(tester.getTopLeft(find.text('January')).dx),
|
|
);
|
|
});
|
|
|
|
testWidgets('monthYear DatePicker displays the date in correct order', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Widget buildApp(DatePickerDateOrder order) {
|
|
return CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
key: ValueKey<DatePickerDateOrder>(order),
|
|
dateOrder: order,
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (DateTime newDate) {},
|
|
initialDateTime: DateTime(2018, 1, 14, 10, 30),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
await tester.pumpWidget(buildApp(DatePickerDateOrder.dmy));
|
|
expect(
|
|
tester.getTopLeft(find.text('January')).dx,
|
|
lessThan(tester.getTopLeft(find.text('2018')).dx),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(DatePickerDateOrder.mdy));
|
|
expect(
|
|
tester.getTopLeft(find.text('January')).dx,
|
|
lessThan(tester.getTopLeft(find.text('2018')).dx),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(DatePickerDateOrder.ydm));
|
|
expect(
|
|
tester.getTopLeft(find.text('2018')).dx,
|
|
lessThan(tester.getTopLeft(find.text('January')).dx),
|
|
);
|
|
|
|
await tester.pumpWidget(buildApp(DatePickerDateOrder.ymd));
|
|
expect(
|
|
tester.getTopLeft(find.text('2018')).dx,
|
|
lessThan(tester.getTopLeft(find.text('January')).dx),
|
|
);
|
|
});
|
|
|
|
testWidgets('DatePicker displays hours and minutes correctly in RTL', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Directionality(
|
|
textDirection: TextDirection.rtl,
|
|
child: Center(
|
|
child: SizedBox(
|
|
width: 500,
|
|
height: 400,
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: DateTime(2019, 1, 1, 4),
|
|
onDateTimeChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final double hourLeft = tester.getTopLeft(find.text('4')).dx;
|
|
final double minuteLeft = tester.getTopLeft(find.text('00')).dx;
|
|
expect(hourLeft, lessThan(minuteLeft));
|
|
});
|
|
|
|
testWidgets('onScrollEnd behavior reports changes correctly', (WidgetTester tester) async {
|
|
final selectedDateTime = <DateTime>[];
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
changeReportingBehavior: ChangeReportingBehavior.onScrollEnd,
|
|
onDateTimeChanged: (DateTime dateTime) => selectedDateTime.add(dateTime),
|
|
initialDateTime: DateTime(2025),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
final Offset initialOffset = tester.getTopLeft(find.text('2025'));
|
|
|
|
final TestGesture scrollGesture = await tester.startGesture(initialOffset);
|
|
// Should not report changes until the gesture ends.
|
|
await scrollGesture.moveBy(const Offset(0.0, -32.0));
|
|
expect(selectedDateTime, isEmpty);
|
|
|
|
await scrollGesture.moveBy(const Offset(0.0, -32.0));
|
|
expect(selectedDateTime, isEmpty);
|
|
|
|
await scrollGesture.up();
|
|
await tester.pumpAndSettle();
|
|
|
|
// Only reports the last change.
|
|
expect(selectedDateTime, hasLength(1));
|
|
expect(selectedDateTime.first, DateTime(2027));
|
|
});
|
|
});
|
|
|
|
testWidgets('TimerPicker golden tests', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
// Also check if the picker respects the theme.
|
|
theme: const CupertinoThemeData(
|
|
textTheme: CupertinoTextThemeData(
|
|
pickerTextStyle: TextStyle(color: Color(0xFF663311), fontSize: 21),
|
|
),
|
|
),
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: 320,
|
|
height: 216,
|
|
child: RepaintBoundary(
|
|
child: CupertinoTimerPicker(
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 23, minutes: 59),
|
|
onTimerDurationChanged: (_) {},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await expectLater(
|
|
find.byType(CupertinoTimerPicker),
|
|
matchesGoldenFile('timer_picker_test.datetime.initial.png'),
|
|
);
|
|
|
|
// Slightly drag the minute component to make the current minute off-center.
|
|
await tester.drag(
|
|
find.text('59'),
|
|
Offset(0, _kRowOffset.dy / 2),
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
await tester.pump();
|
|
|
|
await expectLater(
|
|
find.byType(CupertinoTimerPicker),
|
|
matchesGoldenFile('timer_picker_test.datetime.drag.png'),
|
|
);
|
|
});
|
|
|
|
testWidgets('TimerPicker only changes hour label after scrolling stops', (
|
|
WidgetTester tester,
|
|
) async {
|
|
Duration? duration;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: 320,
|
|
height: 216,
|
|
child: CupertinoTimerPicker(
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 2, minutes: 30),
|
|
onTimerDurationChanged: (Duration d) {
|
|
duration = d;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(duration, isNull);
|
|
expect(find.text('hour'), findsNothing);
|
|
expect(find.text('hours'), findsOneWidget);
|
|
|
|
await tester.drag(
|
|
find.text('2'),
|
|
Offset(0, -_kRowOffset.dy),
|
|
warnIfMissed: false,
|
|
); // see top of file
|
|
// Duration should change but not the label.
|
|
expect(duration!.inHours, 1);
|
|
expect(find.text('hour'), findsNothing);
|
|
expect(find.text('hours'), findsOneWidget);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Now the label should change.
|
|
expect(duration!.inHours, 1);
|
|
expect(find.text('hours'), findsNothing);
|
|
expect(find.text('hour'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('TimerPicker has intrinsic width and height', (WidgetTester tester) async {
|
|
const key = Key('key');
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoTimerPicker(
|
|
key: key,
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 2, minutes: 30),
|
|
onTimerDurationChanged: (Duration d) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSize(find.descendant(of: find.byKey(key), matching: find.byType(Row))),
|
|
const Size(320, 216),
|
|
);
|
|
|
|
// Different modes shouldn't share state.
|
|
await tester.pumpWidget(const Placeholder());
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoTimerPicker(
|
|
key: key,
|
|
mode: CupertinoTimerPickerMode.ms,
|
|
initialTimerDuration: const Duration(minutes: 30, seconds: 3),
|
|
onTimerDurationChanged: (Duration d) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSize(find.descendant(of: find.byKey(key), matching: find.byType(Row))),
|
|
const Size(320, 216),
|
|
);
|
|
|
|
// Different modes shouldn't share state.
|
|
await tester.pumpWidget(const Placeholder());
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: CupertinoTimerPicker(
|
|
key: key,
|
|
initialTimerDuration: const Duration(hours: 5, minutes: 17, seconds: 19),
|
|
onTimerDurationChanged: (Duration d) {},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSize(find.descendant(of: find.byKey(key), matching: find.byType(Row))),
|
|
const Size(342, 216),
|
|
);
|
|
});
|
|
|
|
testWidgets('scrollController can be removed or added', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
late int lastSelectedItem;
|
|
void onSelectedItemChanged(int index) {
|
|
lastSelectedItem = index;
|
|
}
|
|
|
|
final scrollController1 = FixedExtentScrollController();
|
|
addTearDown(scrollController1.dispose);
|
|
await tester.pumpWidget(
|
|
_buildPicker(controller: scrollController1, onSelectedItemChanged: onSelectedItemChanged),
|
|
);
|
|
|
|
tester.binding.pipelineOwner.semanticsOwner!.performAction(1, SemanticsAction.increase);
|
|
await tester.pumpAndSettle();
|
|
expect(lastSelectedItem, 1);
|
|
|
|
await tester.pumpWidget(_buildPicker(onSelectedItemChanged: onSelectedItemChanged));
|
|
|
|
tester.binding.pipelineOwner.semanticsOwner!.performAction(1, SemanticsAction.increase);
|
|
await tester.pumpAndSettle();
|
|
expect(lastSelectedItem, 2);
|
|
|
|
final scrollController2 = FixedExtentScrollController();
|
|
addTearDown(scrollController2.dispose);
|
|
await tester.pumpWidget(
|
|
_buildPicker(controller: scrollController2, onSelectedItemChanged: onSelectedItemChanged),
|
|
);
|
|
|
|
tester.binding.pipelineOwner.semanticsOwner!.performAction(1, SemanticsAction.increase);
|
|
await tester.pumpAndSettle();
|
|
expect(lastSelectedItem, 3);
|
|
|
|
handle.dispose();
|
|
});
|
|
|
|
testWidgets('CupertinoDataPicker does not provide invalid MediaQuery', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// Regression test for https://github.com/flutter/flutter/issues/47989.
|
|
Brightness brightness = Brightness.light;
|
|
late StateSetter setState;
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
theme: const CupertinoThemeData(
|
|
textTheme: CupertinoTextThemeData(
|
|
dateTimePickerTextStyle: TextStyle(
|
|
color: CupertinoDynamicColor.withBrightness(
|
|
color: Color(0xFFFFFFFF),
|
|
darkColor: Color(0xFF000000),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
home: StatefulBuilder(
|
|
builder: (BuildContext context, StateSetter stateSetter) {
|
|
setState = stateSetter;
|
|
return MediaQuery(
|
|
data: MediaQuery.of(context).copyWith(platformBrightness: brightness),
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: DateTime(2019),
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime date) {},
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.widget<Text>(find.text('2019')).style!.color,
|
|
isSameColorAs(const Color(0xFFFFFFFF)),
|
|
);
|
|
|
|
setState(() {
|
|
brightness = Brightness.dark;
|
|
});
|
|
await tester.pump();
|
|
|
|
expect(
|
|
tester.widget<Text>(find.text('2019')).style!.color,
|
|
isSameColorAs(const Color(0xFF000000)),
|
|
);
|
|
});
|
|
|
|
testWidgets('picker exports semantics', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
debugResetSemanticsIdCounter();
|
|
int? lastSelectedItem;
|
|
await tester.pumpWidget(
|
|
_buildPicker(
|
|
onSelectedItemChanged: (int index) {
|
|
lastSelectedItem = index;
|
|
},
|
|
),
|
|
);
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(CupertinoPicker)),
|
|
matchesSemantics(
|
|
children: <Matcher>[
|
|
matchesSemantics(
|
|
hasIncreaseAction: true,
|
|
increasedValue: '1',
|
|
value: '0',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
tester.binding.pipelineOwner.semanticsOwner!.performAction(1, SemanticsAction.increase);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(
|
|
tester.getSemantics(find.byType(CupertinoPicker)),
|
|
matchesSemantics(
|
|
children: <Matcher>[
|
|
matchesSemantics(
|
|
hasIncreaseAction: true,
|
|
hasDecreaseAction: true,
|
|
increasedValue: '2',
|
|
decreasedValue: '0',
|
|
value: '1',
|
|
textDirection: TextDirection.ltr,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
expect(lastSelectedItem, 1);
|
|
handle.dispose();
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/98567
|
|
testWidgets('picker semantics action test', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
debugResetSemanticsIdCounter();
|
|
final initialDate = DateTime(2018, 6, 8);
|
|
late DateTime? date;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime newDate) => date = newDate,
|
|
initialDateTime: initialDate,
|
|
maximumDate: initialDate.add(const Duration(days: 2)),
|
|
minimumDate: initialDate.subtract(const Duration(days: 2)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
tester.binding.pipelineOwner.semanticsOwner!.performAction(4, SemanticsAction.decrease);
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(date, DateTime(2018, 6, 7));
|
|
|
|
handle.dispose();
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker semantics excludes disabled dates', (WidgetTester tester) async {
|
|
final SemanticsHandle handle = tester.ensureSemantics();
|
|
debugResetSemanticsIdCounter();
|
|
final minimumDate = DateTime(2018, 6, 10);
|
|
final maximumDate = DateTime(2018, 6, 20);
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
minimumDate: minimumDate,
|
|
maximumDate: maximumDate,
|
|
initialDateTime: minimumDate, // Start at minimum date
|
|
onDateTimeChanged: (DateTime newDateTime) {},
|
|
mode: CupertinoDatePickerMode.date,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the day picker column semantics node
|
|
// The day picker should have increase action (to go to day 11) but NO decrease action
|
|
// (because day 9 is disabled and wrapped with ExcludeSemantics)
|
|
final SemanticsNode rootNode = tester.binding.pipelineOwner.semanticsOwner!.rootSemanticsNode!;
|
|
|
|
// Find semantics node with value '10' (the current day)
|
|
SemanticsNode? findNodeWithValue(SemanticsNode node, String value) {
|
|
if (node.value == value) {
|
|
return node;
|
|
}
|
|
SemanticsNode? result;
|
|
node.visitChildren((SemanticsNode child) {
|
|
result ??= findNodeWithValue(child, value);
|
|
return result == null;
|
|
});
|
|
return result;
|
|
}
|
|
|
|
final SemanticsNode? dayPickerNode = findNodeWithValue(rootNode, '10');
|
|
expect(dayPickerNode, isNotNull, reason: 'Should find day picker at day 10');
|
|
|
|
// At the minimum date (day 10), the day picker should NOT have a decrease action
|
|
// because day 9 is disabled (wrapped with ExcludeSemantics)
|
|
final SemanticsData data = dayPickerNode!.getSemanticsData();
|
|
expect(
|
|
data.hasAction(SemanticsAction.decrease),
|
|
isFalse,
|
|
reason: 'Day picker at minimum date should not have decrease action (day 9 is disabled)',
|
|
);
|
|
expect(
|
|
data.hasAction(SemanticsAction.increase),
|
|
isTrue,
|
|
reason: 'Day picker at minimum date should have increase action (day 11 is valid)',
|
|
);
|
|
|
|
handle.dispose();
|
|
});
|
|
|
|
// TODO(justinmc): Don't test Material interactions in Cupertino tests.
|
|
// https://github.com/flutter/flutter/issues/177028
|
|
testWidgets('DatePicker adapts to MaterialApp dark mode', (WidgetTester tester) async {
|
|
Widget buildDatePicker(Brightness brightness) {
|
|
return MaterialApp(
|
|
theme: ThemeData(brightness: brightness),
|
|
home: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime neData) {},
|
|
initialDateTime: DateTime(2018, 10, 10),
|
|
),
|
|
);
|
|
}
|
|
|
|
// CupertinoDatePicker with light theme.
|
|
await tester.pumpWidget(buildDatePicker(Brightness.light));
|
|
RenderParagraph paragraph = tester.renderObject(find.text('October').first);
|
|
expect(paragraph.text.style!.color, CupertinoColors.label);
|
|
// Text style should not return unresolved color.
|
|
expect(paragraph.text.style!.color.toString().contains('UNRESOLVED'), isFalse);
|
|
|
|
// CupertinoDatePicker with dark theme.
|
|
await tester.pumpWidget(buildDatePicker(Brightness.dark));
|
|
paragraph = tester.renderObject(find.text('October').first);
|
|
expect(paragraph.text.style!.color, CupertinoColors.label);
|
|
// Text style should not return unresolved color.
|
|
expect(paragraph.text.style!.color.toString().contains('UNRESOLVED'), isFalse);
|
|
});
|
|
|
|
// TODO(justinmc): Don't test Material interactions in Cupertino tests.
|
|
// https://github.com/flutter/flutter/issues/177028
|
|
testWidgets('TimerPicker adapts to MaterialApp dark mode', (WidgetTester tester) async {
|
|
Widget buildTimerPicker(Brightness brightness) {
|
|
return MaterialApp(
|
|
theme: ThemeData(brightness: brightness),
|
|
home: CupertinoTimerPicker(
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
onTimerDurationChanged: (Duration newDuration) {},
|
|
initialTimerDuration: const Duration(hours: 12, minutes: 30, seconds: 59),
|
|
),
|
|
);
|
|
}
|
|
|
|
// CupertinoTimerPicker with light theme.
|
|
await tester.pumpWidget(buildTimerPicker(Brightness.light));
|
|
RenderParagraph paragraph = tester.renderObject(find.text('hours'));
|
|
expect(paragraph.text.style!.color, CupertinoColors.label);
|
|
// Text style should not return unresolved color.
|
|
expect(paragraph.text.style!.color.toString().contains('UNRESOLVED'), isFalse);
|
|
|
|
// CupertinoTimerPicker with light theme.
|
|
await tester.pumpWidget(buildTimerPicker(Brightness.dark));
|
|
paragraph = tester.renderObject(find.text('hours'));
|
|
expect(paragraph.text.style!.color, CupertinoColors.label);
|
|
// Text style should not return unresolved color.
|
|
expect(paragraph.text.style!.color.toString().contains('UNRESOLVED'), isFalse);
|
|
});
|
|
|
|
testWidgets('TimerPicker minDate - maxDate with minuteInterval', (WidgetTester tester) async {
|
|
late DateTime date;
|
|
final minimum = DateTime(2022, 6, 14, 3, 31);
|
|
final initial = DateTime(2022, 6, 14, 3, 40);
|
|
final maximum = DateTime(2022, 6, 14, 3, 49);
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: initial,
|
|
minimumDate: minimum,
|
|
maximumDate: maximum,
|
|
minuteInterval: 5,
|
|
use24hFormat: true,
|
|
onDateTimeChanged: (DateTime newDate) {
|
|
date = newDate;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Drag picker minutes to min date
|
|
await tester.drag(
|
|
find.text('40'),
|
|
const Offset(0.0, 32.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Returns to min date.
|
|
expect(date, DateTime(2022, 6, 14, 3, 35));
|
|
|
|
// Drag picker minutes to max date
|
|
await tester.drag(
|
|
find.text('50'),
|
|
const Offset(0.0, -64.0),
|
|
touchSlopY: 0.0,
|
|
warnIfMissed: false,
|
|
);
|
|
await tester.pumpAndSettle();
|
|
|
|
// Returns to max date.
|
|
expect(date, DateTime(2022, 6, 14, 3, 45));
|
|
});
|
|
|
|
testWidgets('date picker has expected day of week', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
height: 400.0,
|
|
width: 400.0,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
showDayOfWeek: true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text('September'), findsOneWidget);
|
|
expect(find.textContaining('Sat').last, findsOneWidget);
|
|
expect(find.textContaining('15').last, findsOneWidget);
|
|
expect(find.text('2018'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder with monthYear mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoDatePickerMode.monthYear
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(2));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder with date mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoDatePickerMode.date
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(3));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder with time mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoDatePickerMode.time
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(3));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder with dateAndTime mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoDatePickerMode.dateAndTime
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(4));
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker selectionOverlayBuilder with hms mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoTimerPickerMode.hms
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (Duration duration) {},
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(3));
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker selectionOverlayBuilder with ms mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoTimerPickerMode.ms
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (Duration duration) {},
|
|
mode: CupertinoTimerPickerMode.ms,
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(2));
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker selectionOverlayBuilder with hm mode', (
|
|
WidgetTester tester,
|
|
) async {
|
|
final Widget selectionOverlay = Container(color: const Color(0x12345678));
|
|
|
|
// For mode = CupertinoTimerPickerMode.hm
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (Duration duration) {},
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return selectionOverlay;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Find the selection overlay.
|
|
expect(find.byWidget(selectionOverlay), findsExactly(2));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder returns null', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(CupertinoPicker), isNot(paints..rrect()));
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker selectionOverlayBuilder returns null', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (Duration duration) {},
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1),
|
|
selectionOverlayBuilder:
|
|
(BuildContext context, {required int selectedIndex, required int columnCount}) {
|
|
return null;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(CupertinoPicker), isNot(paints..rrect()));
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker selectionOverlayBuilder is explicitly passed null', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoTimerPicker(
|
|
onTimerDurationChanged: (Duration duration) {},
|
|
mode: CupertinoTimerPickerMode.hm,
|
|
initialTimerDuration: const Duration(hours: 1, minutes: 1, seconds: 1),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(CupertinoPickerDefaultSelectionOverlay), findsExactly(2));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker selectionOverlayBuilder is explicitly passed null', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.byType(CupertinoPickerDefaultSelectionOverlay), findsExactly(4));
|
|
});
|
|
|
|
testWidgets('CupertinoDatePicker accommodates widest text using table codepoints', (
|
|
WidgetTester tester,
|
|
) async {
|
|
// |---------|
|
|
// | 0x2002 | // EN SPACE - 1/2 Advance
|
|
// | 0x2005 | // FOUR-PER-EM SPACE - 1/4 Advance
|
|
// |---------|
|
|
final testWords = <String>[
|
|
'\u2002' * 10, // Output: 10 * 1/2 = 5
|
|
'\u2005' * 20, // Output: 20 * 1/4 = 5
|
|
];
|
|
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime date) {},
|
|
initialDateTime: DateTime(2018, 9, 15),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final BuildContext context = tester.element(find.byType(CupertinoDatePicker));
|
|
|
|
const textStyle = TextStyle(
|
|
fontSize: 21,
|
|
letterSpacing: 0.4,
|
|
fontWeight: FontWeight.normal,
|
|
color: CupertinoColors.label,
|
|
);
|
|
|
|
final List<double> widths = testWords
|
|
.map((String word) => getColumnWidth(word, textStyle, context))
|
|
.toList();
|
|
|
|
final double largestWidth = widths.reduce(math.max);
|
|
|
|
final double testWidth = CupertinoDatePicker.getColumnWidth(
|
|
texts: testWords,
|
|
context: context,
|
|
textStyle: textStyle,
|
|
);
|
|
|
|
expect(testWidth, equals(largestWidth));
|
|
expect(widths.indexOf(largestWidth), equals(1));
|
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/39998
|
|
|
|
test('showTimeSeparator is only supported in time or dateAndTime mode', () async {
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showTimeSeparator: true,
|
|
),
|
|
returnsNormally,
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(onDateTimeChanged: (DateTime _) {}, showTimeSeparator: true),
|
|
returnsNormally,
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showTimeSeparator: true,
|
|
),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('showTimeSeparator is only supported in time or dateAndTime modes'),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.monthYear,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
showTimeSeparator: true,
|
|
),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('showTimeSeparator is only supported in time or dateAndTime modes'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('Time separator widget should be rendered when flag is set to true', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime dateTime) {},
|
|
showTimeSeparator: true,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text(':'), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Time separator widget should not be rendered when flag is set to false', (
|
|
WidgetTester tester,
|
|
) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.time,
|
|
onDateTimeChanged: (DateTime _) {},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
expect(find.text(':'), findsNothing);
|
|
});
|
|
|
|
test('CupertinoDatePicker selectableDayPredicate parameter validation', () async {
|
|
expect(() => CupertinoDatePicker(onDateTimeChanged: (DateTime _) {}), returnsNormally);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
initialDateTime: DateTime(2025),
|
|
onDateTimeChanged: (DateTime _) {},
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.year == 2025;
|
|
},
|
|
),
|
|
returnsNormally,
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
onDateTimeChanged: (DateTime _) {},
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.year == 2025;
|
|
},
|
|
),
|
|
returnsNormally,
|
|
);
|
|
|
|
expect(
|
|
() => CupertinoDatePicker(
|
|
initialDateTime: DateTime(2025, 7, 4),
|
|
onDateTimeChanged: (DateTime _) {},
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.month == 6;
|
|
},
|
|
),
|
|
throwsA(
|
|
isA<AssertionError>().having(
|
|
(AssertionError e) => e.message ?? 'Unknown error',
|
|
'message',
|
|
contains('must satisfy provided selectableDayPredicate.'),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
|
|
testWidgets('DatePicker with workdays predicate test case', (WidgetTester tester) async {
|
|
// Set initial date time to a work day.
|
|
final initialDateTime = DateTime(2025, 6, 13);
|
|
var selectedDate = initialDateTime;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: initialDateTime,
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.weekday >= DateTime.monday && date.weekday <= DateTime.friday;
|
|
},
|
|
onDateTimeChanged: (DateTime dateTime) {
|
|
selectedDate = dateTime;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Scrolling to Saturday should trigger automatic scroll to the next workday (Monday).
|
|
await tester.drag(find.text('Sat Jun 14'), const Offset(0.0, -100.0));
|
|
expect(selectedDate, DateTime(2025, 6, 16));
|
|
});
|
|
|
|
testWidgets('DatePicker with weekend predicate test case', (WidgetTester tester) async {
|
|
// Set initial date time to a weekend day.
|
|
final initialDateTime = DateTime(2025, 6, 14);
|
|
var selectedDate = initialDateTime;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: initialDateTime,
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.weekday == DateTime.saturday || date.weekday == DateTime.sunday;
|
|
},
|
|
onDateTimeChanged: (DateTime dateTime) {
|
|
selectedDate = dateTime;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Pressing on the friday day item should trigger automatic scroll back to
|
|
// saturday.
|
|
await tester.press(find.text('Fri Jun 13'));
|
|
await tester.pump();
|
|
|
|
expect(selectedDate, DateTime(2025, 6, 14));
|
|
});
|
|
|
|
testWidgets('DatePicker with custom predicate test case', (WidgetTester tester) async {
|
|
// Set initial date time to a work day.
|
|
final initialDateTime = DateTime(2025, 6, 16);
|
|
var selectedDate = initialDateTime;
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: CupertinoDatePicker(
|
|
initialDateTime: initialDateTime,
|
|
selectableDayPredicate: (DateTime date) {
|
|
return date.day >= 16;
|
|
},
|
|
onDateTimeChanged: (DateTime dateTime) {
|
|
selectedDate = dateTime;
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.drag(find.text('Sun Jun 15'), const Offset(0.0, 64.0));
|
|
await tester.pump();
|
|
|
|
expect(selectedDate, initialDateTime);
|
|
});
|
|
|
|
// Regression test for https://github.com/flutter/flutter/issues/161773
|
|
testWidgets('CupertinoDatePicker date value baseline alignment', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox(
|
|
width: 400,
|
|
height: 400,
|
|
child: CupertinoDatePicker(
|
|
mode: CupertinoDatePickerMode.date,
|
|
onDateTimeChanged: (_) {},
|
|
initialDateTime: DateTime(2025, 2, 14),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
Offset lastOffset = tester.getTopLeft(find.text('November'));
|
|
expect(tester.getTopLeft(find.text('11')).dy, lastOffset.dy);
|
|
|
|
lastOffset = tester.getTopLeft(find.text('11'));
|
|
expect(tester.getTopLeft(find.text('2022')).dy, lastOffset.dy);
|
|
});
|
|
|
|
testWidgets('CupertinoTimerPicker does not crash at zero area', (WidgetTester tester) async {
|
|
await tester.pumpWidget(
|
|
CupertinoApp(
|
|
home: Center(
|
|
child: SizedBox.shrink(child: CupertinoTimerPicker(onTimerDurationChanged: (_) {})),
|
|
),
|
|
),
|
|
);
|
|
expect(tester.getSize(find.byType(CupertinoTimerPicker)), Size.zero);
|
|
});
|
|
}
|
|
|
|
Widget _buildPicker({
|
|
FixedExtentScrollController? controller,
|
|
required ValueChanged<int> onSelectedItemChanged,
|
|
}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: CupertinoPicker(
|
|
scrollController: controller,
|
|
itemExtent: 100.0,
|
|
onSelectedItemChanged: onSelectedItemChanged,
|
|
children: List<Widget>.generate(100, (int index) {
|
|
return Center(child: SizedBox(width: 400.0, height: 100.0, child: Text(index.toString())));
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
double getColumnWidth(String text, TextStyle textStyle, BuildContext context) {
|
|
return TextPainter.computeMaxIntrinsicWidth(
|
|
text: TextSpan(text: text, style: textStyle),
|
|
textDirection: Directionality.of(context),
|
|
);
|
|
}
|