// Copyright 2016 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_test/flutter_test.dart'; class _TimePickerLauncher extends StatelessWidget { const _TimePickerLauncher({ Key key, this.onChanged, this.locale }) : super(key: key); final ValueChanged onChanged; final Locale locale; @override Widget build(BuildContext context) { return MaterialApp( locale: locale, supportedLocales: [locale], localizationsDelegates: GlobalMaterialLocalizations.delegates, 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) )); } ); } ) ) ) ); } } Future startPicker( WidgetTester tester, ValueChanged onChanged, { Locale locale = const Locale('en', 'US'), }) async { await tester.pumpWidget(_TimePickerLauncher(onChanged: onChanged, locale: locale,)); await tester.tap(find.text('X')); await tester.pumpAndSettle(const Duration(seconds: 1)); return tester.getCenter(find.byKey(const Key('time-picker-dial'))); } Future 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() { testWidgets('can localize the header in all known formats', (WidgetTester tester) async { // 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 Map> locales = >{ const Locale('en', 'US'): const ['hour', 'string :', 'minute', 'period'], //'h:mm a' const Locale('en', 'GB'): const ['hour', 'string :', 'minute'], //'HH:mm' const Locale('es', 'ES'): const ['hour', 'string :', 'minute'], //'H:mm' const Locale('fr', 'CA'): const ['hour', 'string h', 'minute'], //'HH \'h\' mm' const Locale('zh', 'ZH'): const ['period', 'hour', 'string :', 'minute'], //'ah:mm' }; for (Locale locale in locales.keys) { final Offset center = await startPicker(tester, (TimeOfDay time) { }, locale: locale); final List actual = []; tester.element(find.byType(CustomMultiChildLayout)).visitChildren((Element child) { final LayoutId layout = child.widget; final String fragmentType = '${layout.child.runtimeType}'; final dynamic widget = layout.child; if (fragmentType == '_MinuteControl') { actual.add('minute'); } else if (fragmentType == '_DayPeriodControl') { actual.add('period'); } else if (fragmentType == '_HourControl') { actual.add('hour'); } else if (fragmentType == '_StringFragment') { actual.add('string ${widget.value}'); } else { fail('Unsupported fragment type: $fragmentType'); } }); expect(actual, locales[locale]); await tester.tapAt(Offset(center.dx, center.dy - 50.0)); await finishPicker(tester); } }); testWidgets('uses single-ring 12-hour dial for h hour format', (WidgetTester tester) async { // Tap along the segment stretching from the center to the edge at // 12:00 AM position. Because there's only one ring, no matter where you // tap the time will be the same. See the 24-hour dial test that behaves // differently. for (int i = 1; i < 10; i++) { TimeOfDay result; final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }); 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('uses two-ring 24-hour dial for H and HH hour formats', (WidgetTester tester) async { const List locales = [ Locale('en', 'GB'), // HH Locale('es', 'ES'), // H ]; for (Locale locale in locales) { // Tap along the segment stretching from the center to the edge at // 12:00 AM position. There are two rings. At ~70% mark, the ring // switches between inner ring and outer ring. for (int i = 1; i < 10; i++) { TimeOfDay result; final Offset center = await startPicker(tester, (TimeOfDay time) { result = time; }, locale: locale); 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(TimeOfDay(hour: i < 7 ? 12 : 0, minute: 0))); } } }); const List labels12To11 = ['12', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11']; const List labels12To11TwoDigit = ['12', '01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11']; const List labels00To23 = ['00', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23']; Future mediaQueryBoilerplate(WidgetTester tester, bool alwaysUse24HourFormat) async { await tester.pumpWidget( Localizations( locale: const Locale('en', 'US'), delegates: const >[ GlobalMaterialLocalizations.delegate, DefaultWidgetsLocalizations.delegate, ], child: MediaQuery( data: MediaQueryData(alwaysUse24HourFormat: alwaysUse24HourFormat), child: Material( child: Directionality( textDirection: TextDirection.ltr, child: Navigator( onGenerateRoute: (RouteSettings settings) { return MaterialPageRoute(builder: (BuildContext context) { return FlatButton( 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('respects MediaQueryData.alwaysUse24HourFormat == false', (WidgetTester tester) async { await mediaQueryBoilerplate(tester, false); final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey('time-picker-dial'))); final dynamic dialPainter = dialPaint.painter; final List primaryOuterLabels = dialPainter.primaryOuterLabels; expect(primaryOuterLabels.map((dynamic tp) => tp.painter.text.text), labels12To11); expect(dialPainter.primaryInnerLabels, null); final List secondaryOuterLabels = dialPainter.secondaryOuterLabels; expect(secondaryOuterLabels.map((dynamic tp) => tp.painter.text.text), labels12To11); expect(dialPainter.secondaryInnerLabels, null); }); testWidgets('respects MediaQueryData.alwaysUse24HourFormat == true', (WidgetTester tester) async { await mediaQueryBoilerplate(tester, true); final CustomPaint dialPaint = tester.widget(find.byKey(const ValueKey('time-picker-dial'))); final dynamic dialPainter = dialPaint.painter; final List primaryOuterLabels = dialPainter.primaryOuterLabels; expect(primaryOuterLabels.map((dynamic tp) => tp.painter.text.text), labels00To23); final List primaryInnerLabels = dialPainter.primaryInnerLabels; expect(primaryInnerLabels.map((dynamic tp) => tp.painter.text.text), labels12To11TwoDigit); final List secondaryOuterLabels = dialPainter.secondaryOuterLabels; expect(secondaryOuterLabels.map((dynamic tp) => tp.painter.text.text), labels00To23); final List secondaryInnerLabels = dialPainter.secondaryInnerLabels; expect(secondaryInnerLabels.map((dynamic tp) => tp.painter.text.text), labels12To11TwoDigit); }); }