246 lines
8.9 KiB
Dart
246 lines
8.9 KiB
Dart
import 'package:flutter/material.dart';
|
||
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
|
||
import 'package:sxchnge_analytics/sxchnge_analytics.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> click(WidgetTester t, Finder finder) async {
|
||
await t.ensureVisible(finder);
|
||
await t.pumpAndSettle();
|
||
await t.tap(finder);
|
||
await t.pumpAndSettle();
|
||
}
|
||
|
||
Future<DummyWalletService> open(
|
||
WidgetTester t, {
|
||
String path = '/settings/payout-methods',
|
||
String locale = 'en',
|
||
DummyDestinationService? destinations,
|
||
DummyAnalyticsService? analytics,
|
||
}) async {
|
||
t.platformDispatcher.defaultRouteNameTestValue = path;
|
||
addTearDown(t.platformDispatcher.clearDefaultRouteNameTestValue);
|
||
final wallet = DummyWalletService(
|
||
DummyIdentityService(delay: Duration.zero),
|
||
delay: Duration.zero,
|
||
destinations: destinations,
|
||
initialLocale: locale,
|
||
settlement: Duration.zero,
|
||
);
|
||
await t.pumpWidget(
|
||
SxchngeApp(
|
||
walletService: wallet,
|
||
analyticsService: analytics,
|
||
localeProvider: LocaleProvider(initialLocale: Locale(locale)),
|
||
),
|
||
);
|
||
await t.pumpAndSettle();
|
||
addTearDown(() => t.pumpWidget(const SizedBox.shrink()));
|
||
return wallet;
|
||
}
|
||
|
||
Future<void> cryptoForm(WidgetTester t, {bool payout = false}) async {
|
||
await click(
|
||
t,
|
||
find.text(payout ? 'Use a new destination' : 'Add payout method').first,
|
||
);
|
||
await click(t, find.byType(DropdownButtonFormField<DestinationType>));
|
||
await click(t, find.text('Crypto address').last);
|
||
await click(t, find.byKey(const ValueKey('asset-null')));
|
||
await click(t, find.text('ETH').last);
|
||
await click(t, find.byType(DropdownButtonFormField<String>).last);
|
||
await click(t, find.text('Ethereum').last);
|
||
await t.enterText(
|
||
find.byKey(const ValueKey('destination-address')),
|
||
testDestination.address,
|
||
);
|
||
await t.pump();
|
||
}
|
||
|
||
void main() {
|
||
testWidgets('Settings saves without nickname and exposes renamed method', (
|
||
t,
|
||
) async {
|
||
final wallet = await open(t);
|
||
await cryptoForm(t);
|
||
expect(find.text('Save for future payouts'), findsNothing);
|
||
await click(t, find.byKey(const ValueKey('destination-continue')));
|
||
expect((await wallet.destinations.list()).length, 1);
|
||
expect(find.textContaining('ETH · Ethereum'), findsOneWidget);
|
||
await click(t, find.textContaining('ETH · Ethereum'));
|
||
await t.enterText(
|
||
find.byKey(const ValueKey('destination-nickname')),
|
||
'My own wallet',
|
||
);
|
||
await click(t, find.text('Rename'));
|
||
expect((await wallet.destinations.list()).single.nickname, 'My own wallet');
|
||
await click(t, find.text('Remove'));
|
||
expect(find.textContaining('Submitted payouts'), findsOneWidget);
|
||
await click(t, find.widgetWithText(FilledButton, 'Remove'));
|
||
expect(await wallet.destinations.list(), isEmpty);
|
||
expect(t.takeException(), isNull);
|
||
});
|
||
testWidgets('new one-time payout keeps saving unchecked and never persists', (
|
||
t,
|
||
) async {
|
||
final wallet = await open(t, path: '/payout');
|
||
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
||
await click(t, find.byKey(const ValueKey('choose-payout-destination')));
|
||
await cryptoForm(t, payout: true);
|
||
expect(
|
||
t.widget<CheckboxListTile>(find.byType(CheckboxListTile)).value,
|
||
isFalse,
|
||
);
|
||
expect(find.byKey(const ValueKey('destination-nickname')), findsNothing);
|
||
await click(t, find.byKey(const ValueKey('destination-continue')));
|
||
await click(t, find.byKey(const ValueKey('transfer-review')));
|
||
expect(find.textContaining(testDestination.address), findsOneWidget);
|
||
await click(t, find.byKey(const ValueKey('transfer-confirm')));
|
||
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
||
expect(await wallet.destinations.list(), isEmpty);
|
||
});
|
||
testWidgets(
|
||
'optional saving failure permits explicit one-time continuation',
|
||
(t) async {
|
||
final service = DummyDestinationService();
|
||
final wallet = await open(t, path: '/payout', destinations: service);
|
||
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
||
await click(t, find.byKey(const ValueKey('choose-payout-destination')));
|
||
await cryptoForm(t, payout: true);
|
||
await click(t, find.text('Save for future payouts'));
|
||
service.nextSaveError = DestinationIssue.conflict;
|
||
await click(t, find.byKey(const ValueKey('destination-continue')));
|
||
expect(find.textContaining('Couldn’t save'), findsOneWidget);
|
||
await click(t, find.text('Continue without saving'));
|
||
await click(t, find.byKey(const ValueKey('transfer-review')));
|
||
await click(t, find.byKey(const ValueKey('transfer-confirm')));
|
||
expect(find.byKey(const ValueKey('operation-completed')), findsOneWidget);
|
||
expect(await wallet.destinations.list(), isEmpty);
|
||
},
|
||
);
|
||
testWidgets(
|
||
'saved destination is never selected automatically and removal preserves amount',
|
||
(t) async {
|
||
final service = DummyDestinationService();
|
||
await seedDestination(service);
|
||
final wallet = await open(t, path: '/payout', destinations: service);
|
||
await t.enterText(find.byKey(const ValueKey('transfer-amount')), '5');
|
||
expect(
|
||
t
|
||
.widget<FilledButton>(find.byKey(const ValueKey('transfer-review')))
|
||
.onPressed,
|
||
isNull,
|
||
);
|
||
await selectPayout(t);
|
||
await click(t, find.byKey(const ValueKey('transfer-review')));
|
||
final d = (await service.list()).single;
|
||
await service.remove(d.id, d.metadataRevision, d.executionRevision);
|
||
await click(t, find.byKey(const ValueKey('transfer-confirm')));
|
||
expect(find.textContaining('Payout terms changed'), findsOneWidget);
|
||
expect(find.text('5'), findsOneWidget);
|
||
expect((await t.runAsync(wallet.fetch))!.operations, isEmpty);
|
||
},
|
||
);
|
||
for (final locale in ['en', 'ru']) {
|
||
testWidgets('all method forms fit $locale at 320px with large 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, locale: locale);
|
||
await click(
|
||
t,
|
||
find
|
||
.text(
|
||
locale == 'en' ? 'Add payout method' : 'Добавить способ вывода',
|
||
)
|
||
.first,
|
||
);
|
||
for (final name
|
||
in locale == 'en'
|
||
? ['Crypto address', 'Bank card', 'Russian bank account']
|
||
: [
|
||
'Криптоадрес',
|
||
'Банковская карта',
|
||
'Российский банковский счёт',
|
||
]) {
|
||
await click(t, find.byType(DropdownButtonFormField<DestinationType>));
|
||
await click(t, find.text(name).last);
|
||
expect(t.takeException(), isNull);
|
||
}
|
||
});
|
||
}
|
||
testWidgets('semantic save outcomes contain no destination fields', (
|
||
t,
|
||
) async {
|
||
final analytics = DummyAnalyticsService();
|
||
await open(t, analytics: analytics);
|
||
await cryptoForm(t);
|
||
await click(t, find.byKey(const ValueKey('destination-continue')));
|
||
await t.pumpAndSettle();
|
||
final events = analytics.events
|
||
.where((e) => e.kind == JourneyKind.destination)
|
||
.toList();
|
||
expect(
|
||
events.any(
|
||
(e) =>
|
||
e.step == JourneyStep.destinationSave &&
|
||
e.signal == JourneySignal.simulated,
|
||
),
|
||
isTrue,
|
||
);
|
||
expect(
|
||
events.map((e) => e.properties.toString()).join(),
|
||
isNot(contains(testDestination.address)),
|
||
);
|
||
});
|
||
|
||
testWidgets(
|
||
'binding recovery uses secure capture and preserves the saved card',
|
||
(t) async {
|
||
final service = DummyDestinationService();
|
||
final created = await service.create(
|
||
const CardDestination(
|
||
capture: 'demo-card-recover',
|
||
brand: '',
|
||
last4: '',
|
||
expiryMonth: 0,
|
||
expiryYear: 0,
|
||
),
|
||
'Own card',
|
||
'creation-key-000001',
|
||
);
|
||
await open(t, destinations: service);
|
||
await click(t, find.text('Own card'));
|
||
await click(t, find.text('Update authorization'));
|
||
expect(find.byKey(const ValueKey('destination-nickname')), findsNothing);
|
||
await click(t, find.text('Demo card •••• 4242'));
|
||
await click(t, find.byKey(const ValueKey('destination-continue')));
|
||
final saved = await service.get(created.destinationId);
|
||
expect(saved.executionRevision, 2);
|
||
expect(saved.nickname, 'Own card');
|
||
expect((await service.list()).length, 1);
|
||
expect(
|
||
(await service.assess(
|
||
saved.input,
|
||
const PayoutContext('eur'),
|
||
)).eligible,
|
||
isTrue,
|
||
);
|
||
},
|
||
);
|
||
}
|