import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:sxchnge_theme/sxchnge_theme.dart'; import 'package:sxchnge_app/kyc/birth.dart'; import 'package:sxchnge_app/generated/i18n/app_localizations.dart'; void main() { final day = find.byKey(const ValueKey('birth-day')); final year = find.byKey(const ValueKey('birth-year')); final month = find.byType(DropdownButtonFormField); Future mount( WidgetTester t, ValueNotifier value, { Locale locale = const Locale('en'), bool enabled = true, }) async { await t.pumpWidget( MaterialApp( theme: SxchngeTheme.light(), locale: locale, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, home: Scaffold( body: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ValueListenableBuilder( valueListenable: value, builder: (_, date, _) => BirthField( value: date, enabled: enabled, onChanged: (date) => value.value = date, ), ), ), ), ), ); await t.pumpAndSettle(); } Future select(WidgetTester t, String name) async { await t.ensureVisible(month); await t.tap(month); await t.pumpAndSettle(); await t.tap(find.text(name).last); await t.pumpAndSettle(); } testWidgets('enters and corrects a birthday without ISO syntax', (t) async { final value = ValueNotifier(''); addTearDown(value.dispose); await mount(t, value); expect(find.text('Date of birth'), findsOneWidget); await t.enterText(day, '23'); await select(t, 'April'); expect(value.value, ''); await t.enterText(year, '1990'); await t.pumpAndSettle(); expect(value.value, '1990-04-23'); await t.enterText(day, '9'); await t.pumpAndSettle(); expect(value.value, '1990-04-09'); await t.enterText(year, '19'); await t.pumpAndSettle(); expect(value.value, ''); // A provider rebuild must retain the other components of an incomplete date. expect(t.widget(day).controller!.text, '9'); expect(find.text('April'), findsOneWidget); await t.enterText(year, '1985'); await t.pumpAndSettle(); expect(value.value, '1985-04-09'); }); testWidgets('leap dates, impossible days and future dates recover inline', ( t, ) async { final value = ValueNotifier('2000-02-29'); addTearDown(value.dispose); await mount(t, value); expect(find.text('February'), findsOneWidget); await t.enterText(year, '2001'); await t.pumpAndSettle(); expect(value.value, ''); expect(find.text('Enter a valid date of birth.'), findsOneWidget); expect(t.widget(day).controller!.text, '29'); await t.enterText(year, '2004'); await t.pumpAndSettle(); expect(value.value, '2004-02-29'); expect(find.text('Enter a valid date of birth.'), findsNothing); await t.enterText(day, '31'); await select(t, 'April'); expect(value.value, ''); expect(find.text('Enter a valid date of birth.'), findsOneWidget); await t.enterText(day, '30'); await t.enterText(year, '${DateTime.now().year + 1}'); await t.pumpAndSettle(); expect(value.value, ''); expect(find.text('Date of birth cannot be in the future.'), findsOneWidget); await t.enterText(year, '1990'); await t.pumpAndSettle(); expect(value.value, '1990-04-30'); await t.enterText(day, '0'); await t.pumpAndSettle(); expect(value.value, ''); expect(find.text('Enter a valid date of birth.'), findsOneWidget); await t.enterText(day, '1'); await t.enterText(year, '0000'); await t.pumpAndSettle(); expect(value.value, ''); expect(find.text('Enter a valid date of birth.'), findsOneWidget); }); testWidgets( 'prefill, external replacement, locale change and clear synchronize', (t) async { final value = ValueNotifier('1990-04-23'); addTearDown(value.dispose); await mount(t, value); expect(t.widget(year).controller!.text, '1990'); value.value = '1985-12-09'; await t.pumpAndSettle(); expect(t.widget(day).controller!.text, '09'); expect(t.widget(year).controller!.text, '1985'); expect(find.text('December'), findsOneWidget); await mount(t, value, locale: const Locale('ru')); expect(find.text('Дата рождения'), findsOneWidget); expect(find.text('декабрь'), findsOneWidget); expect(value.value, '1985-12-09'); await select(t, 'апрель'); expect(value.value, '1985-04-09'); value.value = ''; await t.pumpAndSettle(); expect(t.widget(day).controller!.text, ''); expect(t.widget(year).controller!.text, ''); expect(find.text('апрель'), findsNothing); }, ); testWidgets('digits and length limits keep each component usable', (t) async { final value = ValueNotifier('1990-04-23'); addTearDown(value.dispose); await mount(t, value); await t.enterText(day, ''); await t.enterText(day, 'a123'); await t.enterText(year, ''); await t.enterText(year, 'b19856'); await t.pumpAndSettle(); expect(t.widget(day).controller!.text, '12'); expect(t.widget(year).controller!.text, '1985'); expect(value.value, '1985-04-12'); }); testWidgets('busy state disables every birthday control', (t) async { final value = ValueNotifier('1990-04-23'); addTearDown(value.dispose); await mount(t, value, enabled: false); expect(t.widget(day).enabled, isFalse); expect(t.widget(year).enabled, isFalse); expect(t.widget>(month).onChanged, isNull); expect(value.value, '1990-04-23'); }); testWidgets('keyboard can traverse, choose month, cancel and edit year', ( t, ) async { final value = ValueNotifier('1990-04-23'); addTearDown(value.dispose); await mount(t, value); await t.sendKeyEvent(LogicalKeyboardKey.tab); await t.pump(); expect( t .widget( find.descendant(of: day, matching: find.byType(EditableText)), ) .focusNode .hasFocus, isTrue, ); await t.sendKeyEvent(LogicalKeyboardKey.tab); await t.sendKeyEvent(LogicalKeyboardKey.enter); await t.pumpAndSettle(); await t.sendKeyEvent(LogicalKeyboardKey.arrowDown); await t.sendKeyEvent(LogicalKeyboardKey.enter); await t.pumpAndSettle(); expect(value.value, '1990-05-23'); await t.sendKeyEvent(LogicalKeyboardKey.enter); await t.pumpAndSettle(); await t.sendKeyEvent(LogicalKeyboardKey.escape); await t.pumpAndSettle(); expect(value.value, '1990-05-23'); await t.sendKeyEvent(LogicalKeyboardKey.tab); await t.pump(); expect( t .widget( find.descendant(of: year, matching: find.byType(EditableText)), ) .focusNode .hasFocus, isTrue, ); }); testWidgets( 'Russian controls and errors fit 320px with large text and labeled targets', (t) async { t.view.physicalSize = const Size(320, 900); t.view.devicePixelRatio = 1; t.platformDispatcher.textScaleFactorTestValue = 1.6; addTearDown(t.view.resetPhysicalSize); addTearDown(t.view.resetDevicePixelRatio); addTearDown(t.platformDispatcher.clearTextScaleFactorTestValue); final semantics = t.ensureSemantics(); final value = ValueNotifier('1990-09-23'); addTearDown(value.dispose); await mount(t, value, locale: const Locale('ru')); expect(find.text('сентябрь'), findsOneWidget); expect(t, meetsGuideline(labeledTapTargetGuideline)); expect(t, meetsGuideline(androidTapTargetGuideline)); await t.enterText(day, '31'); await t.pumpAndSettle(); expect(find.text('Укажите корректную дату рождения.'), findsOneWidget); await select(t, 'март'); expect(value.value, '1990-03-31'); expect(t.takeException(), isNull); semantics.dispose(); }, ); }