date_picker_test.dart 12.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7
// 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';
8
import 'package:intl/intl.dart' as intl;
9 10

void main() {
11 12 13
  late DateTime firstDate;
  late DateTime lastDate;
  late DateTime initialDate;
14 15

  setUp(() {
16
    firstDate = DateTime(2001);
17 18
    lastDate = DateTime(2031, DateTime.december, 31);
    initialDate = DateTime(2016, DateTime.january, 15);
19 20
  });

21
  group(CalendarDatePicker, () {
22
    final intl.NumberFormat arabicNumbers = intl.NumberFormat('0', 'ar');
23 24 25 26 27
    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'],
28
        'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'),
29 30 31 32 33
        'expectedMonthYearHeader': 'September 2017',
      },
      // Tests a different first day of week.
      const Locale('ru', 'RU'): <String, dynamic>{
        'textDirection': TextDirection.ltr,
34
        'expectedDaysOfWeek': <String>['В', 'П', 'В', 'С', 'Ч', 'П', 'С',],
35
        'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'),
36 37
        'expectedMonthYearHeader': 'сентябрь 2017 г.',
      },
38 39 40
      const Locale('ro', 'RO'): <String, dynamic>{
        'textDirection': TextDirection.ltr,
        'expectedDaysOfWeek': <String>['D', 'L', 'M', 'M', 'J', 'V', 'S'],
41
        'expectedDaysOfMonth': List<String>.generate(30, (int i) => '${i + 1}'),
42 43
        'expectedMonthYearHeader': 'septembrie 2017',
      },
44 45 46 47
      // Tests RTL.
      const Locale('ar', 'AR'): <String, dynamic>{
        'textDirection': TextDirection.rtl,
        'expectedDaysOfWeek': <String>['ح', 'ن', 'ث', 'ر', 'خ', 'ج', 'س'],
48
        'expectedDaysOfMonth': List<String>.generate(30, (int i) => arabicNumbers.format(i + 1)),
49
        'expectedMonthYearHeader': 'سبتمبر ٢٠١٧',
50 51 52
      },
    };

53
    for (final Locale locale in testLocales.keys) {
54
      testWidgets('shows dates for $locale', (WidgetTester tester) async {
55 56 57 58
        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;
59
        final DateTime baseDate = DateTime(2017, 9, 27);
60

61 62
        await _pumpBoilerplate(tester, CalendarDatePicker(
          initialDate: baseDate,
63 64
          firstDate: baseDate.subtract(const Duration(days: 90)),
          lastDate: baseDate.add(const Duration(days: 90)),
65
          onDateChanged: (DateTime newValue) {},
66 67 68 69
        ), locale: locale, textDirection: textDirection);

        expect(find.text(expectedMonthYearHeader), findsOneWidget);

70
        for (final String dayOfWeek in expectedDaysOfWeek) {
71
          expect(find.text(dayOfWeek), findsWidgets);
72
        }
73

74
        Offset? previousCellOffset;
75
        for (final String dayOfMonth in expectedDaysOfMonth) {
76 77 78 79 80 81 82 83 84 85 86 87 88 89
          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;
90
        }
91 92 93 94 95
      });
    }
  });

  testWidgets('locale parameter overrides ambient locale', (WidgetTester tester) async {
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
    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'),
              );
            },
          ),
122
        ),
123 124 125 126
      );
    }

    Element getPicker() => tester.element(find.byType(CalendarDatePicker));
127

128
    await tester.pumpWidget(buildFrame(true));
129
    await tester.tap(find.text('X'));
130
    await tester.pumpAndSettle();
131 132

    expect(
133
      Localizations.localeOf(getPicker()),
134 135
      const Locale('fr', 'CA'),
    );
136 137 138 139 140 141 142 143 144 145 146 147 148 149
    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();
150 151

    expect(
152 153 154 155 156
      Localizations.localeOf(getPicker()),
      const Locale('fr', 'CA'),
    );
    expect(
      Directionality.of(getPicker()),
157 158 159 160
      TextDirection.ltr,
    );

    await tester.tap(find.text('ANNULER'));
161

162 163 164
  });

  testWidgets('textDirection parameter overrides ambient textDirection', (WidgetTester tester) async {
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
    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'),
              );
            },
          ),
186
        ),
187 188 189 190
      );
    }

    Element getPicker() => tester.element(find.byType(CalendarDatePicker));
191

192
    await tester.pumpWidget(buildFrame(true));
193
    await tester.tap(find.text('X'));
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
    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();
210 211

    expect(
212
      Directionality.of(getPicker()),
213 214 215 216 217 218
      TextDirection.rtl,
    );

    await tester.tap(find.text('CANCEL'));
  });

Josh Soref's avatar
Josh Soref committed
219
  testWidgets('textDirection parameter takes precedence over locale parameter', (WidgetTester tester) async {
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
    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'),
              );
            },
          ),
247
        ),
248 249 250 251
      );
    }

    Element getPicker() => tester.element(find.byType(CalendarDatePicker));
252

253
    await tester.pumpWidget(buildFrame(true));
254 255 256 257
    await tester.tap(find.text('X'));
    await tester.pumpAndSettle(const Duration(seconds: 1));

    expect(
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
      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()),
279 280 281 282
      const Locale('fr', 'CA'),
    );

    expect(
283
      Directionality.of(getPicker()),
284 285 286 287 288
      TextDirection.rtl,
    );

    await tester.tap(find.text('ANNULER'));
  });
289

290
  group("locale fonts don't overflow layout", () {
291 292 293 294 295 296 297
    // 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);

298
    Future<void> showPicker(WidgetTester tester, Locale locale, Size size) async {
299 300 301
      tester.view.physicalSize = size;
      tester.view.devicePixelRatio = 1.0;
      addTearDown(tester.view.reset);
302 303 304 305 306 307 308
      await tester.pumpWidget(
        MaterialApp(
          home: Builder(
            builder: (BuildContext context) {
              return Localizations(
                locale: locale,
                delegates: GlobalMaterialLocalizations.delegates,
309
                child: TextButton(
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
                  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 {
331
      await showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizePortrait);
332 333 334 335
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - landscape - Chinese', (WidgetTester tester) async {
336
      await showPicker(tester, const Locale('zh', 'CN'), kCommonScreenSizeLandscape);
337 338 339 340
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - portrait - Japanese', (WidgetTester tester) async {
341
      await showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizePortrait);
342 343 344 345
      expect(tester.takeException(), isNull);
    });

    testWidgets('common screen size - landscape - Japanese', (WidgetTester tester) async {
346
      await showPicker(tester, const Locale('ja', 'JA'), kCommonScreenSizeLandscape);
347 348 349 350
      expect(tester.takeException(), isNull);
    });
  });

351 352
}

353
Future<void> _pumpBoilerplate(
354 355 356
  WidgetTester tester,
  Widget child, {
  Locale locale = const Locale('en', 'US'),
357
  TextDirection textDirection = TextDirection.ltr,
358
}) async {
359 360 361 362 363 364 365 366 367 368
  await tester.pumpWidget(MaterialApp(
    home: Directionality(
      textDirection: TextDirection.ltr,
      child: Localizations(
        locale: locale,
        delegates: GlobalMaterialLocalizations.delegates,
        child: Material(
          child: child,
        ),
      ),
369 370 371
    ),
  ));
}