blob: 573a05488745850fea163164bc495a6a9f2352a6 [file] [log] [blame]
// 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.
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Material2 - can localize the header in all known formats - portrait', (WidgetTester tester) async {
// Ensure picker is displayed in portrait mode.
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteControl');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: false);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
}
});
testWidgets('Material3 - can localize the header in all known formats - portrait', (WidgetTester tester) async {
// Ensure picker is displayed in portrait mode.
tester.view.physicalSize = const Size(400, 800);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteControl');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: true);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
}
});
testWidgets('Material2 - can localize the header in all known formats - landscape', (WidgetTester tester) async {
// Ensure picker is displayed in landscape mode.
tester.view.physicalSize = const Size(800, 400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteControl');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: false);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
final double dayPeriodTopOffset = tester.getTopLeft(dayPeriodControlFinder).dy;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(hourLeftOffset, dayPeriodLeftOffset);
expect(hourTopOffset, lessThan(dayPeriodTopOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
final double dayPeriodTopOffset = tester.getTopLeft(dayPeriodControlFinder).dy;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(hourLeftOffset, dayPeriodLeftOffset);
expect(hourTopOffset, greaterThan(dayPeriodTopOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
}
});
testWidgets('Material3 - can localize the header in all known formats - landscape', (WidgetTester tester) async {
// Ensure picker is displayed in landscape mode.
tester.view.physicalSize = const Size(800, 400);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourControl');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteControl');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
final Offset center = await startPicker(tester, (TimeOfDay? time) { }, locale: locale, useMaterial3: true);
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double hourTopOffset = tester.getTopLeft(hourControlFinder).dy;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
final double dayPeriodTopOffset = tester.getTopLeft(dayPeriodControlFinder).dy;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(hourLeftOffset, dayPeriodLeftOffset);
expect(hourTopOffset, lessThan(dayPeriodTopOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
final double dayPeriodTopOffset = tester.getTopLeft(dayPeriodControlFinder).dy;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(hourLeftOffset, dayPeriodLeftOffset);
expect(hourTopOffset, greaterThan(dayPeriodTopOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await tester.tapAt(Offset(center.dx, center.dy - 50.0));
await finishPicker(tester);
}
});
testWidgets('Material2 - can localize input mode in all known formats', (WidgetTester tester) async {
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
await tester.pumpWidget(_TimePickerLauncher(onChanged: (TimeOfDay? time) { }, locale: locale, entryMode: TimePickerEntryMode.input, useMaterial3 : false));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await finishPicker(tester);
expect(tester.takeException(), isNot(throwsFlutterError));
}
});
testWidgets('Material3 - can localize input mode in all known formats', (WidgetTester tester) async {
final Finder hourControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_HourTextField');
final Finder minuteControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_MinuteTextField');
final Finder dayPeriodControlFinder = find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_DayPeriodControl');
final Finder timeSelectorSeparatorFinder = find.descendant(
of: find.byWidgetPredicate((Widget w) => '${w.runtimeType}' == '_TimeSelectorSeparator'),
matching: find.byType(Text),
).first;
// TODO(yjbanov): also test `HH.mm` (in_ID), `a h:mm` (ko_KR) and `HH:mm น.` (th_TH) when we have .arb files for them
final List<Locale> locales = <Locale>[
const Locale('en', 'US'), //'h:mm a'
const Locale('en', 'GB'), //'HH:mm'
const Locale('es', 'ES'), //'H:mm'
const Locale('fr', 'CA'), //'HH \'h\' mm'
const Locale('zh', 'ZH'), //'ah:mm'
const Locale('fa', 'IR'), //'H:mm' but RTL
];
for (final Locale locale in locales) {
await tester.pumpWidget(_TimePickerLauncher(onChanged: (TimeOfDay? time) { }, locale: locale, entryMode: TimePickerEntryMode.input, useMaterial3 : true));
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
final Text stringFragmentText = tester.widget(timeSelectorSeparatorFinder);
final double hourLeftOffset = tester.getTopLeft(hourControlFinder).dx;
final double minuteLeftOffset = tester.getTopLeft(minuteControlFinder).dx;
final double stringFragmentLeftOffset = tester.getTopLeft(timeSelectorSeparatorFinder).dx;
if (locale == const Locale('en', 'US')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(minuteLeftOffset, lessThan(dayPeriodLeftOffset));
} else if (locale == const Locale('en', 'GB')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('es', 'ES')) {
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('fr', 'CA')) {
expect(stringFragmentText.data, 'h');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
} else if (locale == const Locale('zh', 'ZH')) {
final double dayPeriodLeftOffset = tester.getTopLeft(dayPeriodControlFinder).dx;
expect(stringFragmentText.data, ':');
expect(dayPeriodLeftOffset, lessThan(hourLeftOffset));
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
} else if (locale == const Locale('fa', 'IR')) {
// Even though this is an RTL locale, the hours and minutes positions should remain the same.
expect(stringFragmentText.data, ':');
expect(hourLeftOffset, lessThan(stringFragmentLeftOffset));
expect(stringFragmentLeftOffset, lessThan(minuteLeftOffset));
expect(dayPeriodControlFinder, findsNothing);
}
await finishPicker(tester);
expect(tester.takeException(), isNot(throwsFlutterError));
}
});
testWidgets('Material2 uses single-ring 24-hour dial for all locales', (WidgetTester tester) async {
const List<Locale> locales = <Locale>[
Locale('en', 'US'), // h
Locale('en', 'GB'), // HH
Locale('es', 'ES'), // H
];
for (final Locale locale in locales) {
// Tap along the segment stretching from the center to the edge at
// 12:00 AM position. Because there's only one ring, in the M2
// DatePicker no matter where you tap the time will be the same.
for (int i = 1; i < 10; i++) {
TimeOfDay? result;
final Offset center = await startPicker(tester, (TimeOfDay? time) { result = time; }, locale: locale, useMaterial3: false);
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
final double dy = (size.height / 2.0 / 10) * i;
await tester.tapAt(Offset(center.dx, center.dy - dy));
await finishPicker(tester);
expect(result, equals(const TimeOfDay(hour: 0, minute: 0)));
}
}
});
testWidgets('Material3 uses a double-ring 24-hour dial for 24 hour locales', (WidgetTester tester) async {
Future<void> testLocale(Locale locale, int startFactor, int endFactor, TimeOfDay expectedTime) async {
// For locales that display 24 hour time, factors 1-5 put the tap on the
// inner ring's "12" (the inner ring goes from 12-23). Otherwise the offset
// should land on the outer ring's "00".
for (int factor = startFactor; factor < endFactor; factor += 1) {
TimeOfDay? result;
final Offset center = await startPicker(tester, (TimeOfDay? time) { result = time; }, locale: locale, useMaterial3: true);
final Size size = tester.getSize(find.byKey(const Key('time-picker-dial')));
final double dy = (size.height / 2.0 / 10) * factor;
await tester.tapAt(Offset(center.dx, center.dy - dy));
await finishPicker(tester);
expect(result, equals(expectedTime), reason: 'Failed for locale=$locale with factor=$factor');
}
}
await testLocale(const Locale('en', 'US'), 1, 10, const TimeOfDay(hour: 0, minute: 0)); // 12 hour
await testLocale(const Locale('en', 'ES'), 1, 10, const TimeOfDay(hour: 0, minute: 0)); // 12 hour
await testLocale(const Locale('en', 'GB'), 1, 5, const TimeOfDay(hour: 12, minute: 0)); // 24 hour, inner ring
await testLocale(const Locale('en', 'GB'), 6, 10, const TimeOfDay(hour: 0, minute: 0)); // 24 hour, outer ring
});
const List<String> labels12To11 = <String>['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'];
const List<String> labels00To22TwoDigit = <String>['00', '02', '04', '06', '08', '10', '12', '14', '16', '18', '20', '22']; // Material 2
const List<String> labels00To23TwoDigit = <String>[ // Material 3
'00', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23'];
Future<void> mediaQueryBoilerplate(WidgetTester tester, {required bool alwaysUse24HourFormat, required bool useMaterial3}) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: useMaterial3),
builder: (BuildContext context, Widget? child) {
return MediaQuery(
data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat),
child: child!,
);
},
home: Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Navigator(
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute<void>(builder: (BuildContext context) {
return TextButton(
onPressed: () {
showTimePicker(context: context, initialTime: const TimeOfDay(hour: 7, minute: 0));
},
child: const Text('X'),
);
});
},
),
),
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
}
testWidgets('Material2 respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: false, useMaterial3: false);
final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial')));
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels12To11,
);
// ignore: avoid_dynamic_calls
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels12To11,
);
});
testWidgets('Material3 respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: false, useMaterial3: true);
final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial')));
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels12To11,
);
// ignore: avoid_dynamic_calls
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels12To11,
);
});
testWidgets('Material3 respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: true, useMaterial3: true);
final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial')));
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels00To23TwoDigit,
);
// ignore: avoid_dynamic_calls
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels00To23TwoDigit,
);
});
testWidgets('Material2 respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async {
await mediaQueryBoilerplate(tester, alwaysUse24HourFormat: true, useMaterial3: false);
final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey<String>('time-picker-dial')));
final dynamic dialPainter = dialPaint.painter;
// ignore: avoid_dynamic_calls
final List<dynamic> primaryLabels = dialPainter.primaryLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
primaryLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels00To22TwoDigit,
);
// ignore: avoid_dynamic_calls
final List<dynamic> selectedLabels = dialPainter.selectedLabels as List<dynamic>;
expect(
// ignore: avoid_dynamic_calls
selectedLabels.map<String>((dynamic tp) => ((tp.painter as TextPainter).text! as TextSpan).text!),
labels00To22TwoDigit,
);
});
}
class _TimePickerLauncher extends StatelessWidget {
const _TimePickerLauncher({
this.onChanged,
required this.locale,
this.entryMode = TimePickerEntryMode.dial,
this.useMaterial3,
});
final ValueChanged<TimeOfDay?>? onChanged;
final Locale locale;
final TimePickerEntryMode entryMode;
final bool? useMaterial3;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(useMaterial3: useMaterial3),
locale: locale,
supportedLocales: <Locale>[locale],
localizationsDelegates: GlobalMaterialLocalizations.delegates,
home: Material(
child: Center(
child: Builder(
builder: (BuildContext context) {
return ElevatedButton(
child: const Text('X'),
onPressed: () async {
onChanged?.call(await showTimePicker(
context: context,
initialEntryMode: entryMode,
initialTime: const TimeOfDay(hour: 7, minute: 0),
));
},
);
}
),
),
),
);
}
}
Future<Offset> startPicker(
WidgetTester tester,
ValueChanged<TimeOfDay?> onChanged, {
Locale locale = const Locale('en', 'US'),
bool? useMaterial3,
}) async {
await tester.pumpWidget(
_TimePickerLauncher(
onChanged: onChanged,
locale: locale,
useMaterial3: useMaterial3,
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(const Duration(seconds: 1));
return tester.getCenter(find.byKey(const Key('time-picker-dial')));
}
Future<void> finishPicker(WidgetTester tester) async {
final MaterialLocalizations materialLocalizations = MaterialLocalizations.of(tester.element(find.byType(ElevatedButton)));
await tester.tap(find.text(materialLocalizations.okButtonLabel));
await tester.pumpAndSettle(const Duration(seconds: 1));
}