Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62df9eac5f | ||
|
|
4b22f6ce12 | ||
|
|
b5c4772168 | ||
|
|
464bd2d565 | ||
|
|
f1ff40d072 | ||
|
|
417e6d96b6 |
No files matched your search
@@ -302,6 +302,33 @@ that contradicts displayed balances or usage without explaining the fixtures.
|
||||
- Keep unrelated accounts/history accessible during a pending operation. Do not
|
||||
block the whole product behind an exchange spinner.
|
||||
|
||||
## Empty currency accounts (task #66)
|
||||
|
||||
The shared empty-account message used on Accounts and transfer screens explains
|
||||
that no currency accounts have been set up and directs the customer to support
|
||||
to request setup. English and Russian no longer suggest that refreshing creates
|
||||
accounts or imply that automatic provisioning is underway. This follows the
|
||||
existing support guidance for missing funding and payout setup. The application
|
||||
has no configured support contact channel; the message therefore provides text
|
||||
guidance without a nonfunctional contact button or invented contact details.
|
||||
|
||||
UX review: the copy names the missing resource and the next step, retains the
|
||||
existing wrapping text and accessible reading order, and does not alter loading,
|
||||
error or populated-state behavior. Existing manual refresh remains available for
|
||||
accounts provisioned externally. This copy-only change adds no controls or
|
||||
journey outcomes and requires no new analytics events. Existing transaction
|
||||
journey signals and their production measurement limitations below still apply;
|
||||
support resolution and external account provisioning are not measured in-app.
|
||||
|
||||
Validation: `flutter gen-l10n`, `flutter analyze` in `frontend/app`,
|
||||
`make test-frontend` (217 tests), and `git diff --check` passed. No Dart source
|
||||
changed, so Dart formatting was not applicable. Existing responsive workspace
|
||||
tests cover Russian at 320px with enlarged text; this copy-only change does not
|
||||
add a dedicated empty-state rendering or native screen-reader test. No security
|
||||
scan was requested. Delivery: [task #66](https://git.sendico.io/tech/sxchnge/issues/66),
|
||||
branch `wallet-66`, [PR #68](https://git.sendico.io/tech/sxchnge/pulls/68).
|
||||
Merge and deployment remain separate.
|
||||
|
||||
## Journey measurement plan
|
||||
|
||||
Reuse the typed analytics abstraction and maintain a separate journey per active
|
||||
|
||||
@@ -167,3 +167,47 @@ This is a display correction within the existing KYC journey, so no additional
|
||||
analytics events or financial data collection are introduced. Tests cover mapping
|
||||
without inferring remaining, unused/exhausted/over-cap/zero-cap ratios and EN/RU
|
||||
usage display at 320px with 1.6 text scaling.
|
||||
|
||||
## Residence country selection — task #64
|
||||
|
||||
Task [#64](https://git.sendico.io/tech/sxchnge/issues/64), branch `kyc-64`.
|
||||
Delivery: [PR #65](https://git.sendico.io/tech/sxchnge/pulls/65).
|
||||
The profile now shows a country name and opens a searchable country list instead
|
||||
of asking customers to type a two-letter code. Names follow the English/Russian
|
||||
app locale. Search accepts localized or English names and two-letter codes,
|
||||
ignoring case and surrounding whitespace. Selection updates only
|
||||
`IdentityProfile.residenceCountry` with the country code; the existing provider,
|
||||
service, mapper and transport contracts remain unchanged.
|
||||
|
||||
The `country_picker` dependency supplies bundled country data, code lookup and
|
||||
translations through `CountryService` and `CountryLocalizations`. Flutter Material
|
||||
controls provide the search interface, explicit no-results recovery, minimum
|
||||
touch targets, keyboard selection, a selected-state checkmark, Back and Escape.
|
||||
The package's built-in list omits explicit no-results feedback and uses shorter
|
||||
row targets, so its data/localizations are reused with standard Material controls.
|
||||
There is no remote lookup, copied country table or extra application provider.
|
||||
Cancelling preserves the current profile, prefilled codes display names, busy
|
||||
state disables opening the selector, and the search controller is disposed with
|
||||
its dialog. The list is a data catalogue; backend eligibility remains authoritative.
|
||||
|
||||
This change stays within the existing KYC preparation/submission journey and its
|
||||
measurement definitions above. No selector events, queries, names or country
|
||||
codes are added to telemetry. Existing acceptance/failure/retry/abandonment
|
||||
signals and documented server-side review measurement gaps still apply.
|
||||
|
||||
Focused widget checks cover name/code search, code-only updates, reselection,
|
||||
cancel, no-results recovery, clearing search, saved values, locale changes,
|
||||
Russian names, disabled state, keyboard activation/selection/Escape and labeled
|
||||
touch targets. A long country name fits at 320px with 1.6 text scale. Existing
|
||||
EN/RU verification journeys now select a country and assert the profile code
|
||||
before continuing through document submission. Native screen-reader and device
|
||||
keyboard testing remain release checks; widget semantics checks do not establish
|
||||
those device outcomes.
|
||||
|
||||
Validation: five changed Dart files passed the format check; `flutter analyze`
|
||||
in `frontend/app` reported no issues; `make test-frontend` passed all 222 tests;
|
||||
and `flutter build web` passed, including its Wasm compatibility dry run. Browser
|
||||
smoke testing on the local demo opened the selector, searched for Poland and
|
||||
confirmed that the profile displays Poland after selection. Focused and full
|
||||
journey tests verify the stored `PL` code. Generated localization/build outputs
|
||||
remain ignored and uncommitted.
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
|
||||
import '../generated/i18n/app_localizations.dart';
|
||||
|
||||
class CountryField extends StatelessWidget {
|
||||
const CountryField({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
final String value;
|
||||
final ValueChanged<String> onChanged;
|
||||
final bool enabled;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
final country = CountryService().findByCode(value);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(l.kycCountry, style: Theme.of(context).textTheme.labelLarge),
|
||||
const SizedBox(height: 8),
|
||||
Semantics(
|
||||
label: l.kycCountry,
|
||||
child: OutlinedButton(
|
||||
onPressed: !enabled
|
||||
? null
|
||||
: () async {
|
||||
final code = await showDialog<String>(
|
||||
context: context,
|
||||
builder: (_) => _CountrySearch(selected: value),
|
||||
);
|
||||
if (context.mounted && code != null) onChanged(code);
|
||||
},
|
||||
style: OutlinedButton.styleFrom(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
country == null
|
||||
? l.kycCountrySelect
|
||||
: _name(context, country),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Icon(Icons.expand_more),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _name(BuildContext context, Country country) =>
|
||||
country.getTranslatedName(context) ?? country.name;
|
||||
|
||||
class _CountrySearch extends StatefulWidget {
|
||||
const _CountrySearch({required this.selected});
|
||||
|
||||
final String selected;
|
||||
|
||||
@override
|
||||
State<_CountrySearch> createState() => _CountrySearchState();
|
||||
}
|
||||
|
||||
class _CountrySearchState extends State<_CountrySearch> {
|
||||
final _query = TextEditingController();
|
||||
final _countries = {
|
||||
for (final country in CountryService().getAll())
|
||||
country.countryCode: country,
|
||||
}.values.toList();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_query.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l = AppLocalizations.of(context)!;
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
onKeyEvent: (_, event) {
|
||||
if (event is KeyDownEvent &&
|
||||
event.logicalKey == LogicalKeyboardKey.escape) {
|
||||
Navigator.of(context).pop();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
},
|
||||
child: Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
leading: BackButton(onPressed: () => Navigator.of(context).pop()),
|
||||
title: TextField(
|
||||
key: const ValueKey('country-search'),
|
||||
controller: _query,
|
||||
autofocus: true,
|
||||
decoration: InputDecoration(
|
||||
labelText: l.kycCountrySearch,
|
||||
prefixIcon: const Icon(Icons.search),
|
||||
),
|
||||
onChanged: (_) => setState(() {}),
|
||||
),
|
||||
toolbarHeight: 96,
|
||||
actions: [
|
||||
if (_query.text.isNotEmpty)
|
||||
IconButton(
|
||||
tooltip: l.kycCountryClearSearch,
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () => setState(_query.clear),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _list(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _list(BuildContext context) {
|
||||
final search = _query.text.trim().toLowerCase();
|
||||
final countries = _countries.where((country) {
|
||||
return _name(context, country).toLowerCase().contains(search) ||
|
||||
country.name.toLowerCase().contains(search) ||
|
||||
country.countryCode.toLowerCase() == search;
|
||||
}).toList()..sort((a, b) => _name(context, a).compareTo(_name(context, b)));
|
||||
if (countries.isEmpty) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Text(AppLocalizations.of(context)!.kycCountryEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: countries.length,
|
||||
itemBuilder: (context, index) {
|
||||
final country = countries[index];
|
||||
final isSelected = country.countryCode == widget.selected;
|
||||
return ListTile(
|
||||
title: Text(_name(context, country)),
|
||||
selected: isSelected,
|
||||
trailing: isSelected ? const Icon(Icons.check) : null,
|
||||
onTap: () => Navigator.of(context).pop(country.countryCode),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:sxchnge_kyc/sxchnge_kyc.dart';
|
||||
|
||||
import 'country.dart';
|
||||
|
||||
import '../generated/i18n/app_localizations.dart';
|
||||
|
||||
class ProfileForm extends StatelessWidget {
|
||||
@@ -48,17 +50,11 @@ class ProfileForm extends StatelessWidget {
|
||||
onChanged: (value) => update(birth: value.trim()),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextFormField(
|
||||
CountryField(
|
||||
key: const ValueKey('kyc-country'),
|
||||
initialValue: p.profile.residenceCountry,
|
||||
value: p.profile.residenceCountry,
|
||||
enabled: !p.busy,
|
||||
maxLength: 2,
|
||||
textCapitalization: TextCapitalization.characters,
|
||||
decoration: InputDecoration(
|
||||
labelText: l.kycCountry,
|
||||
helperText: l.kycCountryHelp,
|
||||
),
|
||||
onChanged: (value) => update(country: value.trim().toUpperCase()),
|
||||
onChanged: (value) => update(country: value),
|
||||
),
|
||||
if (!p.profile.isComplete) Text(l.kycProfileRequired),
|
||||
],
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
"wRecent": "Recent activity",
|
||||
"wSeeAll": "See all",
|
||||
"wEmptyActivity": "Your next move starts here. Your transactions will appear below.",
|
||||
"wEmptyAccounts": "Your accounts are not ready yet. Try refreshing.",
|
||||
"wEmptyAccounts": "No currency accounts have been set up for you yet. Contact support to request account setup.",
|
||||
"wRefresh": "Refresh",
|
||||
"wSource": "From account",
|
||||
"wDestination": "To account",
|
||||
@@ -271,8 +271,11 @@
|
||||
"kycBirth": "Date of birth",
|
||||
"kycBirthHelp": "YYYY-MM-DD, for example 1990-04-23",
|
||||
"kycCountry": "Country of residence",
|
||||
"kycCountryHelp": "Two-letter country code, for example PL or DE",
|
||||
"kycProfileRequired": "Enter your full name, a valid date of birth and a two-letter country code to continue.",
|
||||
"kycCountrySelect": "Select a country",
|
||||
"kycCountrySearch": "Search countries",
|
||||
"kycCountryEmpty": "No countries found. Try another name or country code.",
|
||||
"kycCountryClearSearch": "Clear search",
|
||||
"kycProfileRequired": "Enter your full name, a valid date of birth and select your country of residence to continue.",
|
||||
"kycContinue": "Continue to my account",
|
||||
"kycOperationMinimum": "Per operation minimum",
|
||||
"kycOperationMaximum": "Per operation maximum",
|
||||
|
||||
@@ -160,7 +160,7 @@
|
||||
"wRecent": "Последние операции",
|
||||
"wSeeAll": "Смотреть все",
|
||||
"wEmptyActivity": "Ваш следующий шаг начинается здесь. Здесь появятся ваши операции.",
|
||||
"wEmptyAccounts": "Ваши счета ещё не готовы. Попробуйте обновить страницу.",
|
||||
"wEmptyAccounts": "У вас ещё нет валютных счетов. Обратитесь в поддержку за помощью с их открытием.",
|
||||
"wRefresh": "Обновить",
|
||||
"wSource": "Со счёта",
|
||||
"wDestination": "На счёт",
|
||||
@@ -271,8 +271,11 @@
|
||||
"kycBirth": "Дата рождения",
|
||||
"kycBirthHelp": "ГГГГ-ММ-ДД, например 1990-04-23",
|
||||
"kycCountry": "Страна проживания",
|
||||
"kycCountryHelp": "Код страны из двух букв, например PL или DE",
|
||||
"kycProfileRequired": "Укажите полное имя, корректную дату рождения и код страны из двух букв.",
|
||||
"kycCountrySelect": "Выберите страну",
|
||||
"kycCountrySearch": "Поиск страны",
|
||||
"kycCountryEmpty": "Страны не найдены. Попробуйте другое название или код страны.",
|
||||
"kycCountryClearSearch": "Очистить поиск",
|
||||
"kycProfileRequired": "Укажите полное имя, корректную дату рождения и выберите страну проживания.",
|
||||
"kycContinue": "Перейти в личный кабинет",
|
||||
"kycOperationMinimum": "Минимум на операцию",
|
||||
"kycOperationMaximum": "Максимум на операцию",
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flutter_web_plugins/url_strategy.dart';
|
||||
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:sxchnge_analytics/sxchnge_analytics.dart';
|
||||
@@ -195,7 +197,10 @@ class _SxchngeAppState extends State<SxchngeApp> {
|
||||
debugShowCheckedModeBanner: false,
|
||||
theme: SxchngeTheme.light(),
|
||||
locale: locale.locale,
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
localizationsDelegates: const [
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
CountryLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
routerConfig: _router,
|
||||
),
|
||||
|
||||
@@ -49,6 +49,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
country_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: country_picker
|
||||
sha256: "8ef721183bd7e0a6017c413693cdc68051daceef5790f5420c3b4378b5c69115"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.28"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -525,6 +533,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
universal_io:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: universal_io
|
||||
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -27,6 +27,7 @@ dependencies:
|
||||
shared_preferences: ^2.5.3
|
||||
cupertino_icons: ^1.0.8
|
||||
go_router: ^18.0.1
|
||||
country_picker: ^2.0.28
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:country_picker/country_picker.dart';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:sxchnge_theme/sxchnge_theme.dart';
|
||||
|
||||
import 'package:sxchnge_app/kyc/country.dart';
|
||||
|
||||
import 'package:sxchnge_app/generated/i18n/app_localizations.dart';
|
||||
|
||||
void main() {
|
||||
final search = find.byKey(const ValueKey('country-search'));
|
||||
final button = find.byType(OutlinedButton);
|
||||
|
||||
Future<void> mount(
|
||||
WidgetTester tester,
|
||||
ValueNotifier<String> value, {
|
||||
Locale locale = const Locale('en'),
|
||||
bool enabled = true,
|
||||
}) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: SxchngeTheme.light(),
|
||||
locale: locale,
|
||||
localizationsDelegates: const [
|
||||
...AppLocalizations.localizationsDelegates,
|
||||
CountryLocalizations.delegate,
|
||||
],
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: ValueListenableBuilder<String>(
|
||||
valueListenable: value,
|
||||
builder: (_, code, _) => CountryField(
|
||||
value: code,
|
||||
enabled: enabled,
|
||||
onChanged: (code) => value.value = code,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> open(WidgetTester tester) async {
|
||||
await tester.tap(button);
|
||||
await tester.pumpAndSettle();
|
||||
}
|
||||
|
||||
testWidgets(
|
||||
'name search stores code, reselection and cancel preserve value',
|
||||
(tester) async {
|
||||
final value = ValueNotifier('');
|
||||
addTearDown(value.dispose);
|
||||
await mount(tester, value);
|
||||
expect(find.text('Select a country'), findsOneWidget);
|
||||
await open(tester);
|
||||
await tester.enterText(search, 'polAND');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Poland'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value.value, 'PL');
|
||||
expect(find.text('Poland'), findsOneWidget);
|
||||
expect(find.text('0/2'), findsNothing);
|
||||
|
||||
await open(tester);
|
||||
await tester.enterText(search, ' de ');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Germany'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value.value, 'DE');
|
||||
expect(find.text('Germany'), findsOneWidget);
|
||||
|
||||
await open(tester);
|
||||
await tester.enterText(search, 'poland');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byType(BackButton));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value.value, 'DE');
|
||||
expect(find.text('Germany'), findsOneWidget);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('no-results state recovers through clear and Escape cancels', (
|
||||
tester,
|
||||
) async {
|
||||
final value = ValueNotifier('PL');
|
||||
addTearDown(value.dispose);
|
||||
await mount(tester, value);
|
||||
await open(tester);
|
||||
await tester.enterText(search, 'no-such-country');
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('No countries found'), findsOneWidget);
|
||||
expect(find.byType(ListTile), findsNothing);
|
||||
expect(value.value, 'PL');
|
||||
await tester.tap(find.byTooltip('Clear search'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.textContaining('No countries found'), findsNothing);
|
||||
expect(find.byType(ListTile), findsWidgets);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
await tester.pumpAndSettle();
|
||||
expect(search, findsNothing);
|
||||
expect(value.value, 'PL');
|
||||
});
|
||||
|
||||
testWidgets('saved code follows locale and Russian names are searchable', (
|
||||
tester,
|
||||
) async {
|
||||
final value = ValueNotifier('PL');
|
||||
addTearDown(value.dispose);
|
||||
await mount(tester, value);
|
||||
expect(find.text('Poland'), findsOneWidget);
|
||||
await mount(tester, value, locale: const Locale('ru'));
|
||||
expect(find.text('Польша'), findsOneWidget);
|
||||
expect(value.value, 'PL');
|
||||
await open(tester);
|
||||
await tester.enterText(search, 'Германия');
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.byType(ListTile));
|
||||
await tester.pumpAndSettle();
|
||||
expect(value.value, 'DE');
|
||||
expect(find.text('Германия'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('busy field cannot open or change selection', (tester) async {
|
||||
final value = ValueNotifier('PL');
|
||||
addTearDown(value.dispose);
|
||||
await mount(tester, value, enabled: false);
|
||||
expect(tester.widget<OutlinedButton>(button).onPressed, isNull);
|
||||
await tester.tap(button);
|
||||
await tester.pumpAndSettle();
|
||||
expect(search, findsNothing);
|
||||
expect(value.value, 'PL');
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'keyboard and labeled touch targets work at 320px with large text',
|
||||
(tester) async {
|
||||
tester.view.physicalSize = const Size(320, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
tester.platformDispatcher.textScaleFactorTestValue = 1.6;
|
||||
addTearDown(tester.view.resetPhysicalSize);
|
||||
addTearDown(tester.view.resetDevicePixelRatio);
|
||||
addTearDown(tester.platformDispatcher.clearTextScaleFactorTestValue);
|
||||
final semantics = tester.ensureSemantics();
|
||||
final value = ValueNotifier('GS');
|
||||
addTearDown(value.dispose);
|
||||
await mount(tester, value);
|
||||
expect(tester, meetsGuideline(labeledTapTargetGuideline));
|
||||
expect(tester, meetsGuideline(androidTapTargetGuideline));
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
expect(search, findsOneWidget);
|
||||
await tester.enterText(search, 'GS');
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.widget<ListTile>(find.byType(ListTile)).selected, isTrue);
|
||||
expect(tester, meetsGuideline(labeledTapTargetGuideline));
|
||||
expect(tester, meetsGuideline(androidTapTargetGuideline));
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
||||
await tester.sendKeyEvent(LogicalKeyboardKey.enter);
|
||||
await tester.pumpAndSettle();
|
||||
expect(search, findsNothing);
|
||||
expect(value.value, 'GS');
|
||||
expect(tester.takeException(), isNull);
|
||||
semantics.dispose();
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import 'package:sxchnge_analytics/sxchnge_analytics.dart';
|
||||
|
||||
import 'package:sxchnge_kyc/sxchnge_kyc.dart';
|
||||
|
||||
import 'package:sxchnge_app/kyc/country.dart';
|
||||
import 'package:sxchnge_app/main.dart';
|
||||
import 'package:sxchnge_app/providers/locale.dart';
|
||||
|
||||
@@ -69,7 +70,20 @@ void main() {
|
||||
await tap(t, find.byKey(const ValueKey('kyc-start')));
|
||||
await t.enterText(find.byKey(const ValueKey('kyc-name')), 'Example Person');
|
||||
await t.enterText(find.byKey(const ValueKey('kyc-birth')), '1990-04-23');
|
||||
await t.enterText(find.byKey(const ValueKey('kyc-country')), 'PL');
|
||||
await tap(
|
||||
t,
|
||||
find.descendant(
|
||||
of: find.byKey(const ValueKey('kyc-country')),
|
||||
matching: find.byType(OutlinedButton),
|
||||
),
|
||||
);
|
||||
await t.enterText(find.byKey(const ValueKey('country-search')), 'PL');
|
||||
await t.pumpAndSettle();
|
||||
await tap(t, find.byType(ListTile));
|
||||
expect(
|
||||
t.widget<CountryField>(find.byKey(const ValueKey('kyc-country'))).value,
|
||||
'PL',
|
||||
);
|
||||
await t.pumpAndSettle();
|
||||
await tap(t, find.byKey(const ValueKey('pick-passport')));
|
||||
await tap(t, find.byKey(const ValueKey('kyc-next')));
|
||||
|
||||
Reference in new issue
Block a user