Files
sxchnge/frontend/app/test/destinations_test.dart

246 lines
8.9 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
);
},
);
}