218 lines
8.4 KiB
Dart
218 lines
8.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import 'package:sxchnge_kyc/sxchnge_kyc.dart';
|
|
|
|
import 'package:sxchnge_wallet/sxchnge_wallet.dart';
|
|
|
|
import 'package:sxchnge_app/main.dart';
|
|
import 'package:sxchnge_app/providers/locale.dart';
|
|
|
|
import 'destination_helpers.dart';
|
|
|
|
Future<void> tap(WidgetTester t, Finder f) async {
|
|
await t.ensureVisible(f);
|
|
await t.tap(f);
|
|
await t.pumpAndSettle();
|
|
}
|
|
|
|
Future<DummyWalletService> open(
|
|
WidgetTester t, {
|
|
String path = '/accounts',
|
|
String locale = 'en',
|
|
Duration settlement = Duration.zero,
|
|
}) async {
|
|
t.platformDispatcher.defaultRouteNameTestValue = path;
|
|
addTearDown(t.platformDispatcher.clearDefaultRouteNameTestValue);
|
|
final identity = DummyIdentityService(delay: Duration.zero);
|
|
final service = DummyWalletService(
|
|
identity,
|
|
delay: Duration.zero,
|
|
settlement: settlement,
|
|
initialLocale: locale,
|
|
);
|
|
await seedDestination(service.destinations);
|
|
await t.pumpWidget(
|
|
SxchngeApp(
|
|
identityService: identity,
|
|
walletService: service,
|
|
localeProvider: LocaleProvider(initialLocale: Locale(locale)),
|
|
),
|
|
);
|
|
await t.pumpAndSettle();
|
|
addTearDown(() => t.pumpWidget(const SizedBox.shrink()));
|
|
return service;
|
|
}
|
|
|
|
void main() {
|
|
testWidgets('available-only accounts expose one set of actions', (t) async {
|
|
await open(t);
|
|
expect(find.text('Reserved'), findsNothing);
|
|
expect(find.text('Held'), findsNothing);
|
|
expect(find.byKey(const ValueKey('exchange-eur')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('exchange-usd')), findsNothing);
|
|
await tap(t, find.byKey(const ValueKey('account-usd')));
|
|
expect(find.byKey(const ValueKey('topUp-usd')), findsOneWidget);
|
|
expect(find.byKey(const ValueKey('exchange-eur')), findsNothing);
|
|
});
|
|
testWidgets('top-up exchange payout end-to-end and history', (t) async {
|
|
final s = await open(t);
|
|
await tap(t, find.byKey(const ValueKey('topUp-eur')));
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '10');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(find.text('You pay'), findsOneWidget);
|
|
await tap(t, find.byKey(const ValueKey('transfer-confirm')));
|
|
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
|
expect(
|
|
find.text(
|
|
'Your account has been credited. The money is available to use.',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
await tap(t, find.text('Exchange').last);
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '20');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(find.text('Leaves your account'), findsOneWidget);
|
|
expect(find.text('Fee'), findsOneWidget);
|
|
await tap(t, find.byKey(const ValueKey('transfer-confirm')));
|
|
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
|
await tap(t, find.text('Pay out').last);
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
|
await selectPayout(t);
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(find.text('Recipient receives'), findsOneWidget);
|
|
await tap(t, find.byKey(const ValueKey('transfer-confirm')));
|
|
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
|
final data = (await t.runAsync(() => s.fetch()))!;
|
|
expect(data.operations.length, 3);
|
|
expect(data.accounts.first.available, 6490);
|
|
expect(data.accounts[1].available, 1600);
|
|
});
|
|
testWidgets(
|
|
'processing payout explains reduced available balance then failed refund',
|
|
(t) async {
|
|
final s = await open(
|
|
t,
|
|
path: '/payout',
|
|
settlement: const Duration(milliseconds: 100),
|
|
);
|
|
s.failNextOperation = true;
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '10');
|
|
await selectPayout(t);
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
await tap(t, find.byKey(const ValueKey('transfer-confirm')));
|
|
// Settlement is service-owned; refresh after the simulated processing period.
|
|
await t.runAsync(
|
|
() => Future<void>.delayed(const Duration(milliseconds: 150)),
|
|
);
|
|
await t.pump(const Duration(seconds: 3));
|
|
await t.pumpAndSettle();
|
|
expect(find.byKey(const ValueKey('operation-failed')), findsOneWidget);
|
|
expect(
|
|
find.text(
|
|
'The transaction did not complete. The amount and fee are available in your account again.',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(
|
|
((await t.runAsync(() => s.fetch()))!).accounts.first.available,
|
|
7500,
|
|
);
|
|
},
|
|
);
|
|
testWidgets(
|
|
'settings saves account language and hides page language selectors',
|
|
(t) async {
|
|
final s = await open(t, path: '/settings');
|
|
expect(find.byKey(const ValueKey('locale-selector')), findsNothing);
|
|
await tap(t, find.byKey(const ValueKey('account-language-en')));
|
|
await tap(t, find.text('Русский').last);
|
|
expect(((await t.runAsync(() => s.profile()))!).locale, 'ru');
|
|
expect(find.text('Язык аккаунта'), findsWidgets);
|
|
},
|
|
);
|
|
testWidgets(
|
|
'language save failure retains session choice and provides retry',
|
|
(t) async {
|
|
final s = await open(t, path: '/settings');
|
|
s.nextError = WalletError.unavailable;
|
|
await tap(t, find.byKey(const ValueKey('account-language-en')));
|
|
await tap(t, find.text('Русский').last);
|
|
expect(
|
|
find.text(
|
|
'Изменено для текущей сессии, но не сохранено в аккаунте. Повторите попытку ниже.',
|
|
),
|
|
findsOneWidget,
|
|
);
|
|
expect(((await t.runAsync(() => s.profile()))!).locale, 'en');
|
|
await tap(t, find.text('Обновить').last);
|
|
expect(((await t.runAsync(() => s.profile()))!).locale, 'ru');
|
|
},
|
|
);
|
|
testWidgets(
|
|
'insufficient funds preserves entered amount and can be corrected',
|
|
(t) async {
|
|
await open(t, path: '/exchange');
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '900');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(find.byKey(const ValueKey('transfer-error')), findsOneWidget);
|
|
expect(find.text('900'), findsOneWidget);
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(find.byKey(const ValueKey('transfer-confirm')), findsOneWidget);
|
|
},
|
|
);
|
|
testWidgets('uncertain submission checks outcome instead of sending twice', (
|
|
t,
|
|
) async {
|
|
final s = await open(t, path: '/exchange');
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
s.loseNextResponse = true;
|
|
await tap(t, find.byKey(const ValueKey('transfer-confirm')));
|
|
expect(find.text('Check outcome'), findsOneWidget);
|
|
await tap(t, find.text('Check outcome'));
|
|
expect(((await t.runAsync(() => s.fetch()))!).operations.length, 1);
|
|
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
|
});
|
|
for (final path in [
|
|
'/accounts',
|
|
'/exchange',
|
|
'/payout',
|
|
'/activity',
|
|
'/limits',
|
|
'/settings',
|
|
]) {
|
|
testWidgets('$path fits Russian at 320px with larger text', (t) async {
|
|
t.view.physicalSize = const Size(320, 1100);
|
|
t.view.devicePixelRatio = 1;
|
|
t.platformDispatcher.textScaleFactorTestValue = 1.6;
|
|
addTearDown(t.view.resetPhysicalSize);
|
|
addTearDown(t.view.resetDevicePixelRatio);
|
|
addTearDown(t.platformDispatcher.clearTextScaleFactorTestValue);
|
|
await open(t, path: path, locale: 'ru');
|
|
expect(t.takeException(), null);
|
|
if (path == '/exchange') {
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
|
await tap(t, find.byKey(const ValueKey('transfer-review')));
|
|
expect(t.takeException(), null);
|
|
}
|
|
});
|
|
}
|
|
testWidgets('leaving a draft requires a clear discard choice', (t) async {
|
|
await open(t, path: '/exchange');
|
|
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
|
final router = GoRouter.of(
|
|
t.element(find.byKey(const ValueKey('transfer-amount'))),
|
|
);
|
|
router.go('/accounts');
|
|
await t.pumpAndSettle();
|
|
expect(find.text('Leave this transaction?'), findsOneWidget);
|
|
await tap(t, find.text('Keep editing'));
|
|
expect(find.text('5'), findsOneWidget);
|
|
});
|
|
}
|