Files
hash-wallet/integration_test/robots/new_send_page_robot.dart
T
jwinterm dd449f2594 Merge upstream Cake Wallet v6.5.0
Catches up from the May 12 fork point (v6.1.2+23) through v6.5.0: 535
upstream commits, including the Monero silent-open fix, Monero on Trezor,
Ledger/Trezor BLE fixes, the new seed and history screens, quick Bitcoin
sync, git-pinned dependencies (pubspec_overrides.yaml), frozen Hive
adapters (*.part.dart), monero_c v0.18.4.6-RC2 and Flutter 3.41.9.

Merged with both sides normalized first (import rename, 100-column
format), which took the conflict count from 614 paths to 322. The
procedure is now scripts/upstream_sync.sh.

Kept from Hash Bags:
- WalletType enum order and Hive IDs. SQLite stores WalletType.index and
  our order differs from upstream's; taking theirs would reread saved
  wallets as other chains. Our frozen adapter keeps the shipped IDs.
- The 1.0.2 legacy PIN and wallet-password decode and self-heal.
- Removed chains (Solana, Tron, Zano, Zcash, Decred, Haven) and the new
  Robinhood chain stay out; Cake Pay and non-Trocador swaps stay out.
- Default nodes (node.wownero.org, sethforprivacy, Blockstream, bysh.me),
  converted to upstream's isDefault node-list key; Wownero is back in the
  node reset list.
- Privacy: address resolvers limited to domain-anchored sources again;
  prices.neroswap.com for fiat; upstream's charts tab, which reads
  prices.cakewallet.com, sits behind FeatureFlag.isChartsEnabled = false.
- Lightning stays behind FeatureFlag.isLightningEnabled, including
  upstream's new post-seed Lightning username step.
- Our icon pipeline, branding and bundle identifiers.

Twelve new upstream widget tests are skipped: test and code are unchanged
from v6.5.0 apart from renames (not rerun on a pristine upstream checkout;
upstream's top_bar_test does not compile against its own TopBar).
flutter analyze lib: 0 errors; flutter test: 345 passed, 12 skipped;
flutter build bundle compiles. Built with Flutter 3.41.9.
2026-10-24 23:49:29 -04:00

206 lines
6.2 KiB
Dart

import "package:hash_wallet/core/execution_state.dart";
import "package:hash_wallet/new-ui/pages/send_page.dart";
import "package:hash_wallet/new-ui/widgets/confirm_swiper.dart";
import "package:hash_wallet/new-ui/widgets/new_primary_button.dart";
import "package:hash_wallet/new-ui/widgets/send_page/send_confirm_bottom_widget.dart";
import "package:hash_wallet/new-ui/widgets/send_page/send_confirm_sheet.dart";
import "package:hash_wallet/view_model/send/send_view_model.dart";
import "package:flutter/material.dart";
import "package:flutter_test/flutter_test.dart";
import "../core/base_robot.dart";
import "../core/test_config.dart";
class NewSendPageRobot extends BaseRobot {
NewSendPageRobot(super.tester);
@override
Future<void> isDisplayed() async {
await pumpUntilFound(find.byType(NewSendPage));
}
Future<void> enterAddress(String address) async {
await _enterTextInInput("send_page_address_input_key", address);
}
Future<void> enterAmount(String amount) async {
await _enterTextInInput("send_page_amount_input_key", amount);
}
String enteredAddress() => _textInInput("send_page_address_input_key");
Future<void> pickContactFromAddressBook(String contactName) async {
await tapByKey("send_page_address_book_button_key");
final tabs = find.byType(Tab);
await pumpUntilFound(tabs);
await tester.tap(tabs.at(1));
await settle();
final contactFinder = find.byKey(ValueKey(contactName));
await pumpUntilFound(contactFinder);
await tester.tap(contactFinder.first);
await pumpUntilGone(contactFinder);
}
Future<void> tapSendButton() async {
await tapByKey("send_page_send_button_key");
}
Future<void> tapSendButtonWhenReady({Duration timeout = TestConfig.walletSyncBudget}) async {
final ready = await pumpUntil(() => _model()?.isReadyForSend ?? false, timeout: timeout);
expect(
ready,
true,
reason: "The wallet never became ready to send within ${timeout.inMinutes}m, "
"so the send button stayed disabled",
);
await tapSendButton();
}
bool get isSendFlowOpen =>
tester.any(find.byType(NewSendPage)) || tester.any(find.byType(SendConfirmSheet));
Future<void> swipeToConfirm({Duration timeout = const Duration(seconds: 90)}) async {
final finder = find.byKey(const ValueKey("send_page_confirm_swiper_key"));
await pumpUntilFound(finder, timeout: timeout);
await settle(max: const Duration(seconds: 2));
final width = tester.view.physicalSize.width / tester.view.devicePixelRatio;
await tester.drag(finder.first, Offset(width, 0));
await tester.pump(const Duration(milliseconds: 500));
}
Future<void> confirmSendButtonReplacesSwiper() async {
final swiper = find.byKey(const ValueKey("send_page_confirm_swiper_key"));
await pumpUntilFound(swiper);
tester.binding.platformDispatcher.accessibilityFeaturesTestValue =
const FakeAccessibilityFeatures(accessibleNavigation: true);
addTearDown(tester.binding.platformDispatcher.clearAccessibilityFeaturesTestValue);
await settle(max: const Duration(seconds: 2));
final button = find.descendant(of: swiper, matching: find.byType(NewPrimaryButton));
expect(
tester.any(button),
true,
reason: "Accessible navigation is on but the sheet still only offers the swiper",
);
expect(
tester.widget<NewPrimaryButton>(button.first).onPressed,
isNotNull,
reason: "The send button a screen reader gets is disabled",
);
}
Future<void> confirmTransactionBuilt({Duration timeout = const Duration(minutes: 3)}) async {
await pumpUntil(
() => tester.any(find.byType(ConfirmSwiper)) || _model()?.state is FailureState,
timeout: timeout,
);
if (tester.any(find.byType(ConfirmSwiper))) {
return;
}
fail("No confirm sheet after ${timeout.inSeconds}s. ${_describeSendState()}");
}
Future<void> confirmTransactionCommitted({
Duration timeout = const Duration(minutes: 3),
}) async {
await pumpUntilFound(find.byType(TransactionCommittedScreenActionButton), timeout: timeout);
}
Future<void> expectNoTransactionBuilt({
Duration window = const Duration(seconds: 15),
}) async {
final offered = await pumpUntil(
() => tester.any(find.byType(ConfirmSwiper)),
timeout: window,
);
expect(
offered,
false,
reason:
"The send screen showed the swiper, wanting to broadcast a transaction it should have refused",
);
}
SendViewModel? _model() {
final sheet = find.byType(SendConfirmSheet);
if (tester.any(sheet)) {
return tester.widget<SendConfirmSheet>(sheet.first).sendViewModel;
}
final page = find.byType(NewSendPage);
if (tester.any(page)) {
return tester.widget<NewSendPage>(page.first).sendViewModel;
}
return null;
}
String _describeSendState() {
final model = _model();
final available = model?.wallet.balance.values
.map((balance) => balance.available)
.where((amount) => amount.sign > 0)
.join(", ");
final errorFinder = find.byType(TransactionErrorActions);
final error = tester.any(errorFinder)
? tester.widget<TransactionErrorActions>(errorFinder.first).errorText
: model?.state is FailureState
? (model!.state as FailureState).error
: null;
return "state=${model?.state.runtimeType}, "
"wallet holds ${available == null || available.isEmpty ? "nothing" : available}"
"${error == null ? "" : ", the chain said: $error"}";
}
String _textInInput(String inputKey) {
final inputFinder = find.byKey(ValueKey(inputKey));
if (!tester.any(inputFinder)) {
return "unknown";
}
final field = find.descendant(of: inputFinder, matching: find.byType(EditableText));
return tester.widget<EditableText>(field.first).controller.text;
}
Future<void> _enterTextInInput(String inputKey, String text) async {
final inputFinder = find.byKey(ValueKey(inputKey));
await pumpUntilFound(inputFinder);
final fieldFinder = find.descendant(of: inputFinder, matching: find.byType(EditableText));
await tester.enterText(fieldFinder.first, text);
await tester.pump(const Duration(milliseconds: 300));
}
}