| // 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'; |
| import 'package:intl/intl.dart' as intl; |
| |
| void main() { |
| late DateTime firstDate; |
| late DateTime lastDate; |
| late DateTime initialDate; |
| |
| setUp(() { |
| firstDate = DateTime(2001); |
| lastDate = DateTime(2031, DateTime.december, 31); |
| initialDate = DateTime(2016, DateTime.january, 15); |
| }); |
| |
| group(CalendarDatePicker, () { |
| final intl.NumberFormat arabicNumbers = intl.NumberFormat('0', 'ar'); |
| final Map<Locale, Map<String, dynamic>> testLocales = <Locale, Map<String, dynamic>>{ |
| // Tests the default. |
| const Locale('en', 'US'): <String, dynamic>{ |
| 'textDirection': TextDirection.ltr, |
| 'expectedDaysOfWeek': <String>['S', 'M', 'T', 'W', 'T', 'F', 'S'], |
| 'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'), |
| 'expectedMonthYearHeader': 'September 2017', |
| }, |
| // Tests a different first day of week. |
| const Locale('ru', 'RU'): <String, dynamic>{ |
| 'textDirection': TextDirection.ltr, |
| 'expectedDaysOfWeek': <String>['В', 'П', 'В', 'С', 'Ч', 'П', 'С',], |
| 'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'), |
| 'expectedMonthYearHeader': 'сентябрь 2017 г.', |
| }, |
| const Locale('ro', 'RO'): <String, dynamic>{ |
| 'textDirection': TextDirection.ltr, |
| 'expectedDaysOfWeek': <String>['D', 'L', 'M', 'M', 'J', 'V', 'S'], |
| 'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'), |
| 'expectedMonthYearHeader': 'septembrie 2017', |
| }, |
| // Tests RTL. |
| const Locale('ar', 'AR'): <String, dynamic>{ |
| 'textDirection': TextDirection.rtl, |
| 'expectedDaysOfWeek': <String>['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'], |
| 'expectedDaysOfMonth': List<String>.generate(30, (int i) => arabicNumbers.format(i + 1)), |
| 'expectedMonthYearHeader': 'سبتمبر ٢٠١٧', |
| }, |
| }; |
| |
| for (final Locale locale in testLocales.keys) { |
| testWidgets('shows dates for $locale', (WidgetTester tester) async { |
| final List<String> expectedDaysOfWeek = testLocales[locale]!['expectedDaysOfWeek'] as List<String>; |
| final List<String> expectedDaysOfMonth = testLocales[locale]!['expectedDaysOfMonth'] as List<String>; |
| final String expectedMonthYearHeader = testLocales[locale]!['expectedMonthYearHeader'] as String; |
| final TextDirection textDirection = testLocales[locale]!['textDirection'] as TextDirection; |
| final DateTime baseDate = DateTime(2017, 9, 27); |
| |
| await _pumpBoilerplate(tester, CalendarDatePicker( |
| initialDate: baseDate, |
| firstDate: baseDate.subtract(const Duration(days: 90)), |
| lastDate: baseDate.add(const Duration(days: 90)), |
| onDateChanged: (DateTime newValue) {}, |
| ), locale: locale, textDirection: textDirection); |
| |
| expect(find.text(expectedMonthYearHeader), findsOneWidget); |
| |
| for (final String dayOfWeek in expectedDaysOfWeek) { |
| expect(find.text(dayOfWeek), findsWidgets); |
| } |
| |
| Offset? previousCellOffset; |
| for (final String dayOfMonth in expectedDaysOfMonth) { |
| final Finder dayCell = find.descendant(of: find.byType(GridView), matching: find.text(dayOfMonth)); |
| expect(dayCell, findsOneWidget); |
| |
| // Check that cells are correctly positioned relative to each other, |
| // taking text direction into account. |
| final Offset offset = tester.getCenter(dayCell); |
| if (previousCellOffset != null) { |
| if (textDirection == TextDirection.ltr) { |
| expect(offset.dx > previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true); |
| } else { |
| expect(offset.dx < previousCellOffset.dx && offset.dy == previousCellOffset.dy || offset.dy > previousCellOffset.dy, true); |
| } |
| } |
| previousCellOffset = offset; |
| } |
| }); |
| } |
| }); |
| |
| testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async { |
| Widget buildFrame(bool useMaterial3) { |
| return MaterialApp( |
| theme: ThemeData(useMaterial3: useMaterial3), |
| locale: const Locale('en', 'US'), |
| supportedLocales: const <Locale>[ |
| Locale('en', 'US'), |
| Locale('fr', 'CA'), |
| ], |
| localizationsDelegates: GlobalMaterialLocalizations.delegates, |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return TextButton( |
| onPressed: () async { |
| await showDatePicker( |
| context: context, |
| initialDate: initialDate, |
| firstDate: firstDate, |
| lastDate: lastDate, |
| locale: const Locale('fr', 'CA'), |
| ); |
| }, |
| child: const Text('X'), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Element getPicker() => tester.element(find.byType(CalendarDatePicker)); |
| |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect( |
| Localizations.localeOf(getPicker()), |
| const Locale('fr', 'CA'), |
| ); |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.ltr, |
| ); |
| |
| await tester.tap(find.text('Annuler')); |
| |
| // The tests below are only relevant for Material 2. Once Material 2 |
| // support is deprecated and the APIs are removed, these tests |
| // can be deleted. |
| |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect( |
| Localizations.localeOf(getPicker()), |
| const Locale('fr', 'CA'), |
| ); |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.ltr, |
| ); |
| |
| await tester.tap(find.text('ANNULER')); |
| |
| }); |
| |
| testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async { |
| Widget buildFrame(bool useMaterial3) { |
| return MaterialApp( |
| theme: ThemeData(useMaterial3: useMaterial3), |
| locale: const Locale('en', 'US'), |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return TextButton( |
| onPressed: () async { |
| await showDatePicker( |
| context: context, |
| initialDate: initialDate, |
| firstDate: firstDate, |
| lastDate: lastDate, |
| textDirection: TextDirection.rtl, |
| ); |
| }, |
| child: const Text('X'), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Element getPicker() => tester.element(find.byType(CalendarDatePicker)); |
| |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.rtl, |
| ); |
| |
| await tester.tap(find.text('Cancel')); |
| |
| // The tests below are only relevant for Material 2. Once Material 2 |
| // support is deprecated and the APIs are removed, these tests |
| // can be deleted. |
| |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.rtl, |
| ); |
| |
| await tester.tap(find.text('CANCEL')); |
| }); |
| |
| testWidgets('textDirection parameter takes precedence over locale parameter', (WidgetTester tester) async { |
| Widget buildFrame(bool useMaterial3) { |
| return MaterialApp( |
| theme: ThemeData(useMaterial3: useMaterial3), |
| locale: const Locale('en', 'US'), |
| supportedLocales: const <Locale>[ |
| Locale('en', 'US'), |
| Locale('fr', 'CA'), |
| ], |
| localizationsDelegates: GlobalMaterialLocalizations.delegates, |
| home: Material( |
| child: Builder( |
| builder: (BuildContext context) { |
| return TextButton( |
| onPressed: () async { |
| await showDatePicker( |
| context: context, |
| initialDate: initialDate, |
| firstDate: firstDate, |
| lastDate: lastDate, |
| locale: const Locale('fr', 'CA'), |
| textDirection: TextDirection.rtl, |
| ); |
| }, |
| child: const Text('X'), |
| ); |
| }, |
| ), |
| ), |
| ); |
| } |
| |
| Element getPicker() => tester.element(find.byType(CalendarDatePicker)); |
| |
| await tester.pumpWidget(buildFrame(true)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(const Duration(seconds: 1)); |
| |
| expect( |
| Localizations.localeOf(getPicker()), |
| const Locale('fr', 'CA'), |
| ); |
| |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.rtl, |
| ); |
| |
| await tester.tap(find.text('Annuler')); |
| |
| // The tests below are only relevant for Material 2. Once Material 2 |
| // support is deprecated and the APIs are removed, these tests |
| // can be deleted. |
| |
| await tester.pumpWidget(buildFrame(false)); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| |
| expect( |
| Localizations.localeOf(getPicker()), |
| const Locale('fr', 'CA'), |
| ); |
| |
| expect( |
| Directionality.of(getPicker()), |
| TextDirection.rtl, |
| ); |
| |
| await tester.tap(find.text('ANNULER')); |
| }); |
| |
| group("locale fonts don't overflow layout", () { |
| // Test screen layouts in various locales to ensure the fonts used |
| // don't overflow the layout |
| |
| // Common screen size roughly based on a Pixel 1 |
| const Size kCommonScreenSizePortrait = Size(1070, 1770); |
| const Size kCommonScreenSizeLandscape = Size(1770, 1070); |
| |
| Future<void> showPicker(WidgetTester tester, Locale locale, Size size) async { |
| tester.view.physicalSize = size; |
| tester.view.devicePixelRatio = 1.0; |
| addTearDown(tester.view.reset); |
| await tester.pumpWidget( |
| MaterialApp( |
| home: Builder( |
| builder: (BuildContext context) { |
| return Localizations( |
| locale: locale, |
| delegates: GlobalMaterialLocalizations.delegates, |
| child: TextButton( |
| child: const Text('X'), |
| onPressed: () { |
| showDatePicker( |
| context: context, |
| initialDate: initialDate, |
| firstDate: firstDate, |
| lastDate: lastDate, |
| ); |
| }, |
| ), |
| ); |
| }, |
| ), |
| ) |
| ); |
| await tester.tap(find.text('X')); |
| await tester.pumpAndSettle(); |
| } |
| |
| // Regression test for https://github.com/flutter/flutter/issues/20171 |
| testWidgets('common screen size - portrait - Chinese', (WidgetTester tester) async { |
| await showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizePortrait); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - landscape - Chinese', (WidgetTester tester) async { |
| await showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizeLandscape); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - portrait - Japanese', (WidgetTester tester) async { |
| await showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizePortrait); |
| expect(tester.takeException(), isNull); |
| }); |
| |
| testWidgets('common screen size - landscape - Japanese', (WidgetTester tester) async { |
| await showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizeLandscape); |
| expect(tester.takeException(), isNull); |
| }); |
| }); |
| |
| } |
| |
| Future<void> _pumpBoilerplate( |
| WidgetTester tester, |
| Widget child, { |
| Locale locale = const Locale('en', 'US'), |
| TextDirection textDirection = TextDirection.ltr, |
| }) async { |
| await tester.pumpWidget(MaterialApp( |
| home: Directionality( |
| textDirection: TextDirection.ltr, |
| child: Localizations( |
| locale: locale, |
| delegates: GlobalMaterialLocalizations.delegates, |
| child: Material( |
| child: child, |
| ), |
| ), |
| ), |
| )); |
| } |