Use dedicated birthday entry controls (#67)
This commit is contained in:
1 parent
f1ff40d072
commit
4605a197ad
7 files changed
+551
-13
No files matched your search
@@ -0,0 +1,236 @@
|
||||
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<int>);
|
||||
|
||||
Future<void> mount(
|
||||
WidgetTester t,
|
||||
ValueNotifier<String> 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<String>(
|
||||
valueListenable: value,
|
||||
builder: (_, date, _) => BirthField(
|
||||
value: date,
|
||||
enabled: enabled,
|
||||
onChanged: (date) => value.value = date,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await t.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> 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<TextFormField>(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<TextFormField>(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<TextFormField>(year).controller!.text, '1990');
|
||||
value.value = '1985-12-09';
|
||||
await t.pumpAndSettle();
|
||||
expect(t.widget<TextFormField>(day).controller!.text, '09');
|
||||
expect(t.widget<TextFormField>(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<TextFormField>(day).controller!.text, '');
|
||||
expect(t.widget<TextFormField>(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<TextFormField>(day).controller!.text, '12');
|
||||
expect(t.widget<TextFormField>(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<TextFormField>(day).enabled, isFalse);
|
||||
expect(t.widget<TextFormField>(year).enabled, isFalse);
|
||||
expect(t.widget<DropdownButtonFormField<int>>(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<EditableText>(
|
||||
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<EditableText>(
|
||||
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();
|
||||
},
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user