mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
831 lines
30 KiB
Dart
831 lines
30 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.
|
|
|
|
// @dart = 2.8
|
|
|
|
@TestOn('!chrome') // entire file needs triage.
|
|
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import '../widgets/semantics_tester.dart';
|
|
import 'feedback_tester.dart';
|
|
|
|
final Finder _hourControl = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
|
|
final Finder _minuteControl = find.byWidgetPredicate((Widget widget) => '${widget.runtimeType}' == '_MinuteControl');
|
|
final Finder _timePickerDialog = find.byWidgetPredicate((Widget widget) => '${widget.runtimeType}' == '_TimePickerDialog');
|
|
|
|
class _TimePickerLauncher extends StatelessWidget {
|
|
const _TimePickerLauncher({
|
|
Key key,
|
|
this.onChanged,
|
|
this.locale,
|
|
this.entryMode = TimePickerEntryMode.dial,
|
|
}) : super(key: key);
|
|
|
|
final ValueChanged<TimeOfDay> onChanged;
|
|
final Locale locale;
|
|
final TimePickerEntryMode entryMode;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
locale: locale,
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
child: const Text('X'),
|
|
onPressed: () async {
|
|
onChanged(await showTimePicker(
|
|
context: context,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 0),
|
|
initialEntryMode: entryMode,
|
|
));
|
|
},
|
|
);
|
|
}
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<Offset> startPicker(
|
|
WidgetTester tester,
|
|
ValueChanged<TimeOfDay> onChanged, {
|
|
TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
|
|
}) async {
|
|
await tester.pumpWidget(_TimePickerLauncher(onChanged: onChanged, locale: const Locale('en', 'US'), entryMode: entryMode));
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
return entryMode == TimePickerEntryMode.dial ? tester.getCenter(find.byKey(const ValueKey<String>('time-picker-dial'))) : null;
|
|
}
|
|
|
|
Future<void> finishPicker(WidgetTester tester) async {
|
|
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(RaisedButton)));
|
|
await tester.tap(find.text(materialLocalizations.okButtonLabel));
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
}
|
|
|
|
void main() {
|
|
group('Time picker - Dial', () {
|
|
_tests();
|
|
});
|
|
|
|
group('Time picker - Input', () {
|
|
_testsInput();
|
|
});
|
|
}
|
|
|
|
void _tests() {
|
|
testWidgets('tap-select an hour', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
await tester.tapAt(Offset(center.dx, center.dy - 50.0)); // 12:00 AM
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
|
|
|
|
center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
await tester.tapAt(Offset(center.dx + 50.0, center.dy));
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 3, minute: 0)));
|
|
|
|
center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 6, minute: 0)));
|
|
|
|
center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
|
|
await tester.tapAt(Offset(center.dx - 50, center.dy));
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 9, minute: 0)));
|
|
});
|
|
|
|
testWidgets('drag-select an hour', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
final Offset hour0 = Offset(center.dx, center.dy - 50.0); // 12:00 AM
|
|
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
|
|
final Offset hour6 = Offset(center.dx, center.dy + 50.0);
|
|
final Offset hour9 = Offset(center.dx - 50.0, center.dy);
|
|
|
|
TestGesture gesture;
|
|
|
|
gesture = await tester.startGesture(hour3);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(result.hour, 0);
|
|
|
|
expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
|
|
gesture = await tester.startGesture(hour0);
|
|
await gesture.moveBy(hour3 - hour0);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(result.hour, 3);
|
|
|
|
expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
|
|
gesture = await tester.startGesture(hour3);
|
|
await gesture.moveBy(hour6 - hour3);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(result.hour, equals(6));
|
|
|
|
expect(await startPicker(tester, (TimeOfDay time) { result = time; }), equals(center));
|
|
gesture = await tester.startGesture(hour6);
|
|
await gesture.moveBy(hour9 - hour6);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(result.hour, equals(9));
|
|
});
|
|
|
|
testWidgets('tap-select switches from hour to minute', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
|
|
final Offset min45 = Offset(center.dx - 50.0, center.dy); // 45 mins (or 9:00 hours)
|
|
|
|
await tester.tapAt(hour6);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
await tester.tapAt(min45);
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 6, minute: 45)));
|
|
});
|
|
|
|
testWidgets('drag-select switches from hour to minute', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
|
|
final Offset hour6 = Offset(center.dx, center.dy + 50.0);
|
|
final Offset hour9 = Offset(center.dx - 50.0, center.dy);
|
|
|
|
TestGesture gesture = await tester.startGesture(hour6);
|
|
await gesture.moveBy(hour9 - hour6);
|
|
await gesture.up();
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
gesture = await tester.startGesture(hour6);
|
|
await gesture.moveBy(hour3 - hour6);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 9, minute: 15)));
|
|
});
|
|
|
|
testWidgets('tap-select rounds down to nearest 5 minute increment', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
|
|
final Offset min46 = Offset(center.dx - 50.0, center.dy - 5); // 46 mins
|
|
|
|
await tester.tapAt(hour6);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
await tester.tapAt(min46);
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 6, minute: 45)));
|
|
});
|
|
|
|
testWidgets('tap-select rounds up to nearest 5 minute increment', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; });
|
|
final Offset hour6 = Offset(center.dx, center.dy + 50.0); // 6:00
|
|
final Offset min48 = Offset(center.dx - 50.0, center.dy - 15); // 48 mins
|
|
|
|
await tester.tapAt(hour6);
|
|
await tester.pump(const Duration(milliseconds: 50));
|
|
await tester.tapAt(min48);
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 6, minute: 50)));
|
|
});
|
|
|
|
group('haptic feedback', () {
|
|
const Duration kFastFeedbackInterval = Duration(milliseconds: 10);
|
|
const Duration kSlowFeedbackInterval = Duration(milliseconds: 200);
|
|
FeedbackTester feedback;
|
|
|
|
setUp(() {
|
|
feedback = FeedbackTester();
|
|
});
|
|
|
|
tearDown(() {
|
|
feedback?.dispose();
|
|
});
|
|
|
|
testWidgets('tap-select vibrates once', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 1);
|
|
});
|
|
|
|
testWidgets('quick successive tap-selects vibrate once', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
|
|
await tester.pump(kFastFeedbackInterval);
|
|
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 1);
|
|
});
|
|
|
|
testWidgets('slow successive tap-selects vibrate once per tap', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
|
|
await tester.pump(kSlowFeedbackInterval);
|
|
await tester.tapAt(Offset(center.dx, center.dy + 50.0));
|
|
await tester.pump(kSlowFeedbackInterval);
|
|
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 3);
|
|
});
|
|
|
|
testWidgets('drag-select vibrates once', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
|
|
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
|
|
|
|
final TestGesture gesture = await tester.startGesture(hour3);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 1);
|
|
});
|
|
|
|
testWidgets('quick drag-select vibrates once', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
|
|
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
|
|
|
|
final TestGesture gesture = await tester.startGesture(hour3);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await tester.pump(kFastFeedbackInterval);
|
|
await gesture.moveBy(hour3 - hour0);
|
|
await tester.pump(kFastFeedbackInterval);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 1);
|
|
});
|
|
|
|
testWidgets('slow drag-select vibrates once', (WidgetTester tester) async {
|
|
final Offset center = await startPicker(tester, (TimeOfDay time) { });
|
|
final Offset hour0 = Offset(center.dx, center.dy - 50.0);
|
|
final Offset hour3 = Offset(center.dx + 50.0, center.dy);
|
|
|
|
final TestGesture gesture = await tester.startGesture(hour3);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await tester.pump(kSlowFeedbackInterval);
|
|
await gesture.moveBy(hour3 - hour0);
|
|
await tester.pump(kSlowFeedbackInterval);
|
|
await gesture.moveBy(hour0 - hour3);
|
|
await gesture.up();
|
|
await finishPicker(tester);
|
|
expect(feedback.hapticCount, 3);
|
|
});
|
|
});
|
|
|
|
const List<String> labels12To11 = <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
|
|
const List<String> labels00To22 = <String>['00', '02', '04', '06', '08', '10', '12', '14', '16', '18', '20', '22'];
|
|
|
|
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
|
|
await mediaQueryBoilerplate(tester, false);
|
|
|
|
final CustomPaint dialPaint = tester.widget(findDialPaint);
|
|
final dynamic dialPainter = dialPaint.painter;
|
|
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
|
|
expect(primaryLabels.map<String>((dynamic tp) => tp.painter.text.text as String), labels12To11);
|
|
|
|
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
|
expect(secondaryLabels.map<String>((dynamic tp) => tp.painter.text.text as String), labels12To11);
|
|
});
|
|
|
|
testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
|
|
await mediaQueryBoilerplate(tester, true);
|
|
|
|
final CustomPaint dialPaint = tester.widget(findDialPaint);
|
|
final dynamic dialPainter = dialPaint.painter;
|
|
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
|
|
expect(primaryLabels.map<String>((dynamic tp) => tp.painter.text.text as String), labels00To22);
|
|
|
|
final List<dynamic> secondaryLabels = dialPainter.secondaryLabels as List<dynamic>;
|
|
expect(secondaryLabels.map<String>((dynamic tp) => tp.painter.text.text as String), labels00To22);
|
|
});
|
|
|
|
testWidgets('provides semantics information for AM/PM indicator', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
await mediaQueryBoilerplate(tester, false);
|
|
|
|
expect(semantics, includesNodeWith(label: 'AM', actions: <SemanticsAction>[SemanticsAction.tap]));
|
|
expect(semantics, includesNodeWith(label: 'PM', actions: <SemanticsAction>[SemanticsAction.tap]));
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('provides semantics information for header and footer', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
await mediaQueryBoilerplate(tester, true);
|
|
|
|
expect(semantics, isNot(includesNodeWith(label: ':')));
|
|
expect(semantics.nodesWith(value: '00'), hasLength(1),
|
|
reason: '00 appears once in the header');
|
|
expect(semantics.nodesWith(value: '07'), hasLength(1),
|
|
reason: '07 appears once in the header');
|
|
expect(semantics, includesNodeWith(label: 'CANCEL'));
|
|
expect(semantics, includesNodeWith(label: 'OK'));
|
|
|
|
// In 24-hour mode we don't have AM/PM control.
|
|
expect(semantics, isNot(includesNodeWith(label: 'AM')));
|
|
expect(semantics, isNot(includesNodeWith(label: 'PM')));
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('can increment and decrement hours', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
|
|
Future<void> actAndExpect({ String initialValue, SemanticsAction action, String finalValue }) async {
|
|
final SemanticsNode elevenHours = semantics.nodesWith(
|
|
value: initialValue,
|
|
ancestor: tester.renderObject(_hourControl).debugSemantics,
|
|
).single;
|
|
tester.binding.pipelineOwner.semanticsOwner.performAction(elevenHours.id, action);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.descendant(of: _hourControl, matching: find.text(finalValue)),
|
|
findsOneWidget,
|
|
);
|
|
}
|
|
|
|
// 12-hour format
|
|
await mediaQueryBoilerplate(tester, false, initialTime: const TimeOfDay(hour: 11, minute: 0));
|
|
await actAndExpect(
|
|
initialValue: '11',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '12',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '12',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '1',
|
|
);
|
|
|
|
// Ensure we preserve day period as we roll over.
|
|
final dynamic pickerState = tester.state(_timePickerDialog);
|
|
expect(pickerState.selectedTime, const TimeOfDay(hour: 1, minute: 0));
|
|
|
|
await actAndExpect(
|
|
initialValue: '1',
|
|
action: SemanticsAction.decrease,
|
|
finalValue: '12',
|
|
);
|
|
await tester.pumpWidget(Container()); // clear old boilerplate
|
|
|
|
// 24-hour format
|
|
await mediaQueryBoilerplate(tester, true, initialTime: const TimeOfDay(hour: 23, minute: 0));
|
|
await actAndExpect(
|
|
initialValue: '23',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '00',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '00',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '01',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '01',
|
|
action: SemanticsAction.decrease,
|
|
finalValue: '00',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '00',
|
|
action: SemanticsAction.decrease,
|
|
finalValue: '23',
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('can increment and decrement minutes', (WidgetTester tester) async {
|
|
final SemanticsTester semantics = SemanticsTester(tester);
|
|
|
|
Future<void> actAndExpect({ String initialValue, SemanticsAction action, String finalValue }) async {
|
|
final SemanticsNode elevenHours = semantics.nodesWith(
|
|
value: initialValue,
|
|
ancestor: tester.renderObject(_minuteControl).debugSemantics,
|
|
).single;
|
|
tester.binding.pipelineOwner.semanticsOwner.performAction(elevenHours.id, action);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
find.descendant(of: _minuteControl, matching: find.text(finalValue)),
|
|
findsOneWidget,
|
|
);
|
|
}
|
|
|
|
await mediaQueryBoilerplate(tester, false, initialTime: const TimeOfDay(hour: 11, minute: 58));
|
|
await actAndExpect(
|
|
initialValue: '58',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '59',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '59',
|
|
action: SemanticsAction.increase,
|
|
finalValue: '00',
|
|
);
|
|
|
|
// Ensure we preserve hour period as we roll over.
|
|
final dynamic pickerState = tester.state(_timePickerDialog);
|
|
expect(pickerState.selectedTime, const TimeOfDay(hour: 11, minute: 0));
|
|
|
|
await actAndExpect(
|
|
initialValue: '00',
|
|
action: SemanticsAction.decrease,
|
|
finalValue: '59',
|
|
);
|
|
await actAndExpect(
|
|
initialValue: '59',
|
|
action: SemanticsAction.decrease,
|
|
finalValue: '58',
|
|
);
|
|
|
|
semantics.dispose();
|
|
});
|
|
|
|
testWidgets('header touch regions are large enough', (WidgetTester tester) async {
|
|
// Ensure picker is displayed in portrait mode.
|
|
tester.binding.window.physicalSizeTestValue = const Size(400, 800);
|
|
tester.binding.window.devicePixelRatioTestValue = 1;
|
|
await mediaQueryBoilerplate(tester, false);
|
|
|
|
final Size dayPeriodControlSize = tester.getSize(find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl'));
|
|
expect(dayPeriodControlSize.width, greaterThanOrEqualTo(48.0));
|
|
// Height should be double the minimum size to account for both AM/PM stacked.
|
|
expect(dayPeriodControlSize.height, greaterThanOrEqualTo(48.0 * 2));
|
|
|
|
final Size hourSize = tester.getSize(find.ancestor(
|
|
of: find.text('7'),
|
|
matching: find.byType(InkWell),
|
|
));
|
|
expect(hourSize.width, greaterThanOrEqualTo(48.0));
|
|
expect(hourSize.height, greaterThanOrEqualTo(48.0));
|
|
|
|
final Size minuteSize = tester.getSize(find.ancestor(
|
|
of: find.text('00'),
|
|
matching: find.byType(InkWell),
|
|
));
|
|
expect(minuteSize.width, greaterThanOrEqualTo(48.0));
|
|
expect(minuteSize.height, greaterThanOrEqualTo(48.0));
|
|
|
|
tester.binding.window.physicalSizeTestValue = null;
|
|
tester.binding.window.devicePixelRatioTestValue = null;
|
|
});
|
|
|
|
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: () {
|
|
showTimePicker(
|
|
context: context,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 0),
|
|
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);
|
|
});
|
|
|
|
testWidgets('uses root navigator by default', (WidgetTester tester) async {
|
|
final PickerObserver rootObserver = PickerObserver();
|
|
final PickerObserver nestedObserver = PickerObserver();
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorObservers: <NavigatorObserver>[rootObserver],
|
|
home: Navigator(
|
|
observers: <NavigatorObserver>[nestedObserver],
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<dynamic>(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
onPressed: () {
|
|
showTimePicker(
|
|
context: context,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 0),
|
|
);
|
|
},
|
|
child: const Text('Show Picker'),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
));
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(RaisedButton));
|
|
|
|
expect(rootObserver.pickerCount, 1);
|
|
expect(nestedObserver.pickerCount, 0);
|
|
});
|
|
|
|
testWidgets('uses nested navigator if useRootNavigator is false', (WidgetTester tester) async {
|
|
final PickerObserver rootObserver = PickerObserver();
|
|
final PickerObserver nestedObserver = PickerObserver();
|
|
|
|
await tester.pumpWidget(MaterialApp(
|
|
navigatorObservers: <NavigatorObserver>[rootObserver],
|
|
home: Navigator(
|
|
observers: <NavigatorObserver>[nestedObserver],
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<dynamic>(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
onPressed: () {
|
|
showTimePicker(
|
|
context: context,
|
|
useRootNavigator: false,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 0),
|
|
);
|
|
},
|
|
child: const Text('Show Picker'),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
),
|
|
));
|
|
|
|
// Open the dialog.
|
|
await tester.tap(find.byType(RaisedButton));
|
|
|
|
expect(rootObserver.pickerCount, 0);
|
|
expect(nestedObserver.pickerCount, 1);
|
|
});
|
|
|
|
testWidgets('optional text parameters are utilized', (WidgetTester tester) async {
|
|
const String cancelText = 'Custom Cancel';
|
|
const String confirmText = 'Custom OK';
|
|
const String helperText = 'Custom Help';
|
|
await tester.pumpWidget(MaterialApp(
|
|
home: Material(
|
|
child: Center(
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return RaisedButton(
|
|
child: const Text('X'),
|
|
onPressed: () async {
|
|
await showTimePicker(
|
|
context: context,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 0),
|
|
cancelText: cancelText,
|
|
confirmText: confirmText,
|
|
helpText: helperText,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
),
|
|
),
|
|
)
|
|
));
|
|
|
|
// Open the picker.
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle(const Duration(seconds: 1));
|
|
|
|
expect(find.text(cancelText), findsOneWidget);
|
|
expect(find.text(confirmText), findsOneWidget);
|
|
expect(find.text(helperText), findsOneWidget);
|
|
});
|
|
|
|
// TODO(rami-a): Re-enable and fix test.
|
|
testWidgets('text scale affects certain elements and not others',
|
|
(WidgetTester tester) async {
|
|
await mediaQueryBoilerplate(
|
|
tester,
|
|
false,
|
|
textScaleFactor: 1.0,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 41),
|
|
);
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final double minutesDisplayHeight = tester.getSize(find.text('41')).height;
|
|
final double amHeight = tester.getSize(find.text('AM')).height;
|
|
|
|
await tester.tap(find.text('OK')); // dismiss the dialog
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that the time display is not affected by text scale.
|
|
await mediaQueryBoilerplate(
|
|
tester,
|
|
false,
|
|
textScaleFactor: 2.0,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 41),
|
|
);
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
|
|
final double amHeight2x = tester.getSize(find.text('AM')).height;
|
|
expect(tester.getSize(find.text('41')).height, equals(minutesDisplayHeight));
|
|
expect(amHeight2x, greaterThanOrEqualTo(amHeight * 2));
|
|
|
|
await tester.tap(find.text('OK')); // dismiss the dialog
|
|
await tester.pumpAndSettle();
|
|
|
|
// Verify that text scale for AM/PM is at most 2x.
|
|
await mediaQueryBoilerplate(
|
|
tester,
|
|
false,
|
|
textScaleFactor: 3.0,
|
|
initialTime: const TimeOfDay(hour: 7, minute: 41),
|
|
);
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
|
|
expect(tester.getSize(find.text('41')).height, equals(minutesDisplayHeight));
|
|
expect(tester.getSize(find.text('AM')).height, equals(amHeight2x));
|
|
});
|
|
}
|
|
|
|
void _testsInput() {
|
|
testWidgets('Initial entry mode is used', (WidgetTester tester) async {
|
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
|
|
expect(find.byType(TextField), findsNWidgets(2));
|
|
});
|
|
|
|
testWidgets('Initial time is the default', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 7, minute: 0)));
|
|
});
|
|
|
|
testWidgets('Help text is used - Input', (WidgetTester tester) async {
|
|
const String helpText = 'help';
|
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input, helpText: helpText);
|
|
expect(find.text(helpText), findsOneWidget);
|
|
});
|
|
|
|
testWidgets('Can toggle to dial entry mode', (WidgetTester tester) async {
|
|
await mediaQueryBoilerplate(tester, true, entryMode: TimePickerEntryMode.input);
|
|
await tester.tap(find.byIcon(Icons.access_time));
|
|
await tester.pumpAndSettle();
|
|
expect(find.byType(TextField), findsNothing);
|
|
});
|
|
|
|
|
|
testWidgets('Entered text returns time', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
|
|
await tester.enterText(find.byType(TextField).first, '9');
|
|
await tester.enterText(find.byType(TextField).last, '12');
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 9, minute: 12)));
|
|
});
|
|
|
|
testWidgets('Toggle to dial mode keeps selected time', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
|
|
await tester.enterText(find.byType(TextField).first, '8');
|
|
await tester.enterText(find.byType(TextField).last, '15');
|
|
await tester.tap(find.byIcon(Icons.access_time));
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 8, minute: 15)));
|
|
});
|
|
|
|
testWidgets('Invalid text prevents dismissing', (WidgetTester tester) async {
|
|
TimeOfDay result;
|
|
await startPicker(tester, (TimeOfDay time) { result = time; }, entryMode: TimePickerEntryMode.input);
|
|
|
|
// Invalid hour.
|
|
await tester.enterText(find.byType(TextField).first, '88');
|
|
await tester.enterText(find.byType(TextField).last, '15');
|
|
await finishPicker(tester);
|
|
expect(result, null);
|
|
|
|
// Invalid minute.
|
|
await tester.enterText(find.byType(TextField).first, '8');
|
|
await tester.enterText(find.byType(TextField).last, '150');
|
|
await finishPicker(tester);
|
|
expect(result, null);
|
|
|
|
await tester.enterText(find.byType(TextField).first, '8');
|
|
await tester.enterText(find.byType(TextField).last, '15');
|
|
await finishPicker(tester);
|
|
expect(result, equals(const TimeOfDay(hour: 8, minute: 15)));
|
|
});
|
|
}
|
|
|
|
final Finder findDialPaint = find.descendant(
|
|
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_Dial'),
|
|
matching: find.byWidgetPredicate((Widget w) => w is CustomPaint),
|
|
);
|
|
|
|
class PickerObserver extends NavigatorObserver {
|
|
int pickerCount = 0;
|
|
|
|
@override
|
|
void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
|
|
if (route.toString().contains('_DialogRoute')) {
|
|
pickerCount++;
|
|
}
|
|
super.didPush(route, previousRoute);
|
|
}
|
|
}
|
|
|
|
Future<void> mediaQueryBoilerplate(
|
|
WidgetTester tester,
|
|
bool alwaysUse24HourFormat, {
|
|
TimeOfDay initialTime = const TimeOfDay(hour: 7, minute: 0),
|
|
double textScaleFactor = 1.0,
|
|
TimePickerEntryMode entryMode = TimePickerEntryMode.dial,
|
|
String helpText,
|
|
}) async {
|
|
await tester.pumpWidget(
|
|
Localizations(
|
|
locale: const Locale('en', 'US'),
|
|
delegates: const <LocalizationsDelegate<dynamic>>[
|
|
DefaultMaterialLocalizations.delegate,
|
|
DefaultWidgetsLocalizations.delegate,
|
|
],
|
|
child: MediaQuery(
|
|
data: MediaQueryData(
|
|
alwaysUse24HourFormat: alwaysUse24HourFormat,
|
|
textScaleFactor: textScaleFactor,
|
|
),
|
|
child: Material(
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Navigator(
|
|
onGenerateRoute: (RouteSettings settings) {
|
|
return MaterialPageRoute<void>(builder: (BuildContext context) {
|
|
return FlatButton(
|
|
onPressed: () {
|
|
showTimePicker(
|
|
context: context,
|
|
initialTime: initialTime,
|
|
initialEntryMode: entryMode,
|
|
helpText: helpText,
|
|
);
|
|
},
|
|
child: const Text('X'),
|
|
);
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
await tester.tap(find.text('X'));
|
|
await tester.pumpAndSettle();
|
|
}
|