mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
CW-1468 Home Screen Top Bar Layout (#3576)
* Improve homescreen layout and account access * Add homescreen layout regression tests * address feedback
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
import "package:cake_wallet/core/auth_service.dart";
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/locales/locale.dart";
|
||||
import "package:cake_wallet/new-ui/pages/account_customizer.dart";
|
||||
import "package:cake_wallet/new-ui/pages/settings_page.dart";
|
||||
import "package:cake_wallet/routes.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/balance_view_model.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cake_wallet/view_model/monero_account_list/account_list_item.dart";
|
||||
import "package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart";
|
||||
import "package:cw_core/balance.dart";
|
||||
import "package:cw_core/transaction_history.dart";
|
||||
import "package:cw_core/transaction_info.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:cw_core/wallet_type.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:mocktail/mocktail.dart";
|
||||
|
||||
class MockBalanceViewModel extends Mock implements BalanceViewModel {}
|
||||
|
||||
class MockDashboardViewModel extends Mock implements DashboardViewModel {}
|
||||
|
||||
class _MockAccountListViewModel extends Mock implements MoneroAccountListViewModel {}
|
||||
|
||||
class _MockAuthService extends Mock implements AuthService {}
|
||||
|
||||
class _MockWallet extends Mock
|
||||
implements WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> {}
|
||||
|
||||
void main() {
|
||||
late MockBalanceViewModel balanceViewModel;
|
||||
late MockDashboardViewModel dashboardViewModel;
|
||||
|
||||
setUpAll(() => S.delegate.load(const Locale("en")));
|
||||
|
||||
setUp(() {
|
||||
balanceViewModel = MockBalanceViewModel();
|
||||
dashboardViewModel = MockDashboardViewModel();
|
||||
when(() => dashboardViewModel.balanceViewModel).thenReturn(balanceViewModel);
|
||||
});
|
||||
|
||||
SettingsListItem accountsItem() => SettingsSectionData.walletSettings.items.singleWhere(
|
||||
(item) => item.route == Routes.accountCustomizer,
|
||||
);
|
||||
|
||||
test("Accounts is the first Wallet Settings item and routes with the dashboard", () {
|
||||
final item = accountsItem();
|
||||
|
||||
expect(SettingsSectionData.walletSettings.items.first, same(item));
|
||||
expect(item.iconPath, "assets/new-ui/settings_row_icons/accounts.svg");
|
||||
expect(item.title, S.current.accounts);
|
||||
expect(item.route, Routes.accountCustomizer);
|
||||
expect(item.routeArgs, isNull);
|
||||
expect(item.routeArgsBuilder, isNotNull);
|
||||
expect(item.routeArgsBuilder!(dashboardViewModel), same(dashboardViewModel));
|
||||
});
|
||||
|
||||
test("Accounts visibility follows account support", () {
|
||||
final item = accountsItem();
|
||||
|
||||
when(() => balanceViewModel.hasAccounts).thenReturn(true);
|
||||
expect(item.condition(dashboardViewModel), isTrue);
|
||||
|
||||
when(() => balanceViewModel.hasAccounts).thenReturn(false);
|
||||
expect(item.condition(dashboardViewModel), isFalse);
|
||||
});
|
||||
|
||||
testWidgets("Accounts opens through the settings modal with the active dashboard",
|
||||
(tester) async {
|
||||
final accountListViewModel = _MockAccountListViewModel();
|
||||
final authService = _MockAuthService();
|
||||
final wallet = _MockWallet();
|
||||
DashboardViewModel? routedDashboardViewModel;
|
||||
|
||||
when(() => balanceViewModel.hasAccounts).thenReturn(true);
|
||||
when(() => dashboardViewModel.hasLightning).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasWalletConnect).thenReturn(false);
|
||||
when(() => dashboardViewModel.wallet).thenReturn(wallet);
|
||||
when(() => dashboardViewModel.loadCardDesigns()).thenAnswer((_) async {});
|
||||
when(() => wallet.type).thenReturn(WalletType.monero);
|
||||
when(() => wallet.hardwareWalletType).thenReturn(null);
|
||||
when(() => accountListViewModel.accounts).thenReturn(const []);
|
||||
when(() => accountListViewModel.selected).thenReturn(
|
||||
AccountListItem(label: "Primary", id: 0, isSelected: true),
|
||||
);
|
||||
|
||||
getIt.pushNewScope(scopeName: "settings-accounts-route-test");
|
||||
getIt.registerFactoryParam<AccountCustomizer, DashboardViewModel, void>(
|
||||
(routedViewModel, _) {
|
||||
routedDashboardViewModel = routedViewModel;
|
||||
return AccountCustomizer(
|
||||
accountListViewModel: accountListViewModel,
|
||||
dashboardViewModel: routedViewModel,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: NewSettingsPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
authService: authService,
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text(S.current.accounts));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(routedDashboardViewModel, same(dashboardViewModel));
|
||||
expect(find.byType(AccountCustomizer), findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
} finally {
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
await tester.pump();
|
||||
await getIt.popScope();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/entities/sync_status_display_mode.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/locales/locale.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/store/settings_store.dart";
|
||||
import "package:cake_wallet/themes/core/theme_store.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cw_core/sync_status.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:mobx/mobx.dart" show Observable;
|
||||
import "package:mocktail/mocktail.dart";
|
||||
|
||||
class _ObservableDashboardViewModel extends Mock implements DashboardViewModel {
|
||||
_ObservableDashboardViewModel(SyncStatus status) : _status = Observable(status);
|
||||
|
||||
final Observable<SyncStatus> _status;
|
||||
|
||||
@override
|
||||
SyncStatus get status => _status.value;
|
||||
}
|
||||
|
||||
class _MockSettingsStore extends Mock implements SettingsStore {}
|
||||
|
||||
class _UnknownSyncStatus extends SyncStatus {
|
||||
const _UnknownSyncStatus();
|
||||
|
||||
@override
|
||||
double progress() => 0;
|
||||
}
|
||||
|
||||
void main() {
|
||||
late bool registeredThemeStore;
|
||||
|
||||
setUpAll(() {
|
||||
registeredThemeStore = !getIt.isRegistered<ThemeStore>();
|
||||
if (registeredThemeStore) {
|
||||
getIt.registerSingleton(ThemeStore());
|
||||
}
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
if (registeredThemeStore) {
|
||||
await getIt.unregister<ThemeStore>();
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets("unknown sync-heavy statuses render a localized fallback", (tester) async {
|
||||
const status = _UnknownSyncStatus();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(status);
|
||||
final settingsStore = _MockSettingsStore();
|
||||
|
||||
when(() => dashboardViewModel.settingsStore).thenReturn(settingsStore);
|
||||
when(() => dashboardViewModel.isTorEnabled).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasMweb).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasSilentPayments).thenReturn(false);
|
||||
when(() => settingsStore.syncStatusDisplayMode)
|
||||
.thenReturn(SyncStatusDisplayMode.blocksRemaining);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: SyncBar(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
isSyncHeavy: true,
|
||||
showSyncedMessage: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(S.current.synchronizing), findsOneWidget);
|
||||
expect(find.bySemanticsLabel(S.current.synchronizing), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets("active Silent Payments syncing shows integer progress and its icon in a 40px pill",
|
||||
(tester) async {
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.24));
|
||||
final settingsStore = _MockSettingsStore();
|
||||
|
||||
when(() => dashboardViewModel.settingsStore).thenReturn(settingsStore);
|
||||
when(() => dashboardViewModel.isTorEnabled).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasMweb).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasSilentPayments).thenReturn(true);
|
||||
when(() => dashboardViewModel.silentPaymentsScanningActive).thenReturn(true);
|
||||
when(() => settingsStore.syncStatusDisplayMode)
|
||||
.thenReturn(SyncStatusDisplayMode.blocksRemaining);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 210,
|
||||
height: 36,
|
||||
child: SyncBar(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
isSyncHeavy: true,
|
||||
showSyncedMessage: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final progressText = tester.widget<Text>(find.text("24%"));
|
||||
final pill = find.descendant(
|
||||
of: find.byType(SyncBar),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is Container &&
|
||||
widget.constraints?.minHeight == 40 &&
|
||||
widget.constraints?.maxHeight == 40,
|
||||
),
|
||||
);
|
||||
final silentPaymentsIcon = tester.widget<CakeImageWidget>(
|
||||
find.byWidgetPredicate(
|
||||
(widget) => widget is CakeImageWidget && widget.imageUrl == "assets/new-ui/silent_sync.svg",
|
||||
),
|
||||
);
|
||||
|
||||
final warningOutlineColor = getIt<ThemeStore>().currentTheme.customColors.warningOutlineColor;
|
||||
|
||||
expect(progressText.style?.color, warningOutlineColor);
|
||||
expect(find.text("·"), findsOneWidget);
|
||||
expect(find.text(S.current.Blocks_remaining("100")), findsOneWidget);
|
||||
expect(tester.getSize(pill).height, 40);
|
||||
expect(Size(silentPaymentsIcon.width!, silentPaymentsIcon.height!), const Size(16, 16));
|
||||
expect(
|
||||
silentPaymentsIcon.colorFilter,
|
||||
ColorFilter.mode(warningOutlineColor, BlendMode.srcIn),
|
||||
);
|
||||
expect(tester.getSemantics(find.text("24%")).value, "24%");
|
||||
});
|
||||
|
||||
testWidgets("Silent Payments progress requires support, active scanning, and syncing",
|
||||
(tester) async {
|
||||
final testCases = <({bool hasSilentPayments, bool scanningActive, SyncStatus status})>[
|
||||
(
|
||||
hasSilentPayments: false,
|
||||
scanningActive: true,
|
||||
status: SyncingSyncStatus(100, 0.24),
|
||||
),
|
||||
(
|
||||
hasSilentPayments: true,
|
||||
scanningActive: false,
|
||||
status: SyncingSyncStatus(100, 0.24),
|
||||
),
|
||||
(
|
||||
hasSilentPayments: true,
|
||||
scanningActive: true,
|
||||
status: AttemptingScanSyncStatus(),
|
||||
),
|
||||
];
|
||||
|
||||
for (final testCase in testCases) {
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(testCase.status);
|
||||
final settingsStore = _MockSettingsStore();
|
||||
|
||||
when(() => dashboardViewModel.settingsStore).thenReturn(settingsStore);
|
||||
when(() => dashboardViewModel.isTorEnabled).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasMweb).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasSilentPayments).thenReturn(testCase.hasSilentPayments);
|
||||
when(() => dashboardViewModel.silentPaymentsScanningActive)
|
||||
.thenReturn(testCase.scanningActive);
|
||||
when(() => settingsStore.syncStatusDisplayMode)
|
||||
.thenReturn(SyncStatusDisplayMode.blocksRemaining);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: Scaffold(
|
||||
body: SyncBar(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
isSyncHeavy: true,
|
||||
showSyncedMessage: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text("24%"), findsNothing, reason: "$testCase");
|
||||
expect(find.text("0%"), findsNothing, reason: "$testCase");
|
||||
expect(find.text("·"), findsNothing, reason: "$testCase");
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
import "package:cake_wallet/di.dart";
|
||||
import "package:cake_wallet/entities/sync_status_display_mode.dart";
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/locales/locale.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/lightning_switcher.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/sync_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/top_bar_widget/top_bar.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cake_wallet/store/settings_store.dart";
|
||||
import "package:cake_wallet/themes/core/theme_store.dart";
|
||||
import "package:cake_wallet/view_model/dashboard/dashboard_view_model.dart";
|
||||
import "package:cw_core/balance.dart";
|
||||
import "package:cw_core/crypto_currency.dart";
|
||||
import "package:cw_core/sync_status.dart";
|
||||
import "package:cw_core/transaction_history.dart";
|
||||
import "package:cw_core/transaction_info.dart";
|
||||
import "package:cw_core/wallet_base.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
import "package:mobx/mobx.dart" show Observable, runInAction;
|
||||
import "package:mocktail/mocktail.dart";
|
||||
|
||||
class _MockWallet extends Mock
|
||||
implements WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> {}
|
||||
|
||||
class _ObservableDashboardViewModel extends Mock implements DashboardViewModel {
|
||||
_ObservableDashboardViewModel(SyncStatus initialStatus)
|
||||
: _status = Observable<SyncStatus>(initialStatus);
|
||||
|
||||
final Observable<SyncStatus> _status;
|
||||
|
||||
@override
|
||||
SyncStatus get status => _status.value;
|
||||
|
||||
void setStatus(SyncStatus status) => runInAction(() => _status.value = status);
|
||||
}
|
||||
|
||||
class _MockSettingsStore extends Mock implements SettingsStore {}
|
||||
|
||||
class _UnknownSyncStatus extends SyncStatus {
|
||||
const _UnknownSyncStatus();
|
||||
|
||||
@override
|
||||
double progress() => 0;
|
||||
}
|
||||
|
||||
void _stubDashboardViewModel(
|
||||
_ObservableDashboardViewModel dashboardViewModel,
|
||||
_MockWallet wallet,
|
||||
String walletName, {
|
||||
bool hasLightning = true,
|
||||
bool isSyncHeavy = false,
|
||||
bool hasSilentPayments = false,
|
||||
bool silentPaymentsScanningActive = false,
|
||||
}) {
|
||||
when(() => dashboardViewModel.hasLightning).thenReturn(hasLightning);
|
||||
when(() => dashboardViewModel.isSyncHeavy).thenReturn(isSyncHeavy);
|
||||
when(() => dashboardViewModel.isTorEnabled).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasMweb).thenReturn(false);
|
||||
when(() => dashboardViewModel.hasSilentPayments).thenReturn(hasSilentPayments);
|
||||
when(() => dashboardViewModel.silentPaymentsScanningActive)
|
||||
.thenReturn(silentPaymentsScanningActive);
|
||||
when(() => dashboardViewModel.wallet).thenReturn(wallet);
|
||||
when(() => wallet.name).thenReturn(walletName);
|
||||
when(() => wallet.hardwareWalletType).thenReturn(null);
|
||||
final settingsStore = _MockSettingsStore();
|
||||
when(() => dashboardViewModel.settingsStore).thenReturn(settingsStore);
|
||||
when(() => settingsStore.syncStatusDisplayMode).thenReturn(SyncStatusDisplayMode.blocksRemaining);
|
||||
}
|
||||
|
||||
Widget _buildTopBar(DashboardViewModel dashboardViewModel) => TopBar(
|
||||
lightningMode: false,
|
||||
onLightningSwitchPress: () {},
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
onSettingsButtonPress: () {},
|
||||
);
|
||||
|
||||
Widget _buildTestApp(Widget child) => MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: Scaffold(body: child),
|
||||
);
|
||||
|
||||
void main() {
|
||||
late bool registeredThemeStore;
|
||||
|
||||
setUpAll(() {
|
||||
registeredThemeStore = !getIt.isRegistered<ThemeStore>();
|
||||
if (registeredThemeStore) {
|
||||
getIt.registerSingleton(ThemeStore());
|
||||
}
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
if (registeredThemeStore) {
|
||||
await getIt.unregister<ThemeStore>();
|
||||
}
|
||||
});
|
||||
|
||||
for (final isSyncHeavy in [false, true]) {
|
||||
testWidgets(
|
||||
"keeps the name hidden until Synced expires, then restores it (heavy: $isSyncHeavy)",
|
||||
(tester) async {
|
||||
const walletName = "My Bitcoin Wallet";
|
||||
final wallet = _MockWallet();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.5));
|
||||
|
||||
_stubDashboardViewModel(dashboardViewModel, wallet, walletName, isSyncHeavy: isSyncHeavy);
|
||||
|
||||
await tester.pumpWidget(_buildTestApp(_buildTopBar(dashboardViewModel)));
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(walletName), findsNothing);
|
||||
expect(find.byType(SyncBar), findsOneWidget);
|
||||
|
||||
dashboardViewModel.setStatus(SyncedSyncStatus());
|
||||
await tester.pump();
|
||||
|
||||
expect(find.text(walletName), findsNothing);
|
||||
expect(find.byIcon(Icons.check), findsOneWidget);
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isTrue);
|
||||
|
||||
await tester.pump(const Duration(seconds: 3) - const Duration(milliseconds: 1));
|
||||
|
||||
expect(find.text(walletName), findsNothing);
|
||||
expect(find.byIcon(Icons.check), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 1));
|
||||
|
||||
expect(find.text(walletName), findsOneWidget);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
expect(find.byType(SyncBar), findsNothing);
|
||||
|
||||
dashboardViewModel.setStatus(SyncingSyncStatus(100, 0.5));
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
expect(find.text(walletName), findsNothing);
|
||||
});
|
||||
}
|
||||
|
||||
testWidgets("replaces the wallet name for progress and failure statuses in all wallets",
|
||||
(tester) async {
|
||||
final statuses = <SyncStatus>[
|
||||
SyncingSyncStatus(100, 0.5),
|
||||
const NotConnectedSyncStatus(),
|
||||
SyncronizingSyncStatus(),
|
||||
AttemptingSyncStatus(),
|
||||
StartingScanSyncStatus(0),
|
||||
AttemptingScanSyncStatus(),
|
||||
SyncedTipSyncStatus(100),
|
||||
ProcessingSyncStatus(),
|
||||
ConnectingSyncStatus(),
|
||||
ConnectedSyncStatus(),
|
||||
FailedSyncStatus(),
|
||||
LostConnectionSyncStatus(),
|
||||
TimedOutSyncStatus(),
|
||||
UnsupportedSyncStatus(),
|
||||
const _UnknownSyncStatus(),
|
||||
SyncedSyncStatus(),
|
||||
];
|
||||
|
||||
for (final isSyncHeavy in [false, true]) {
|
||||
for (final status in statuses) {
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(status);
|
||||
_stubDashboardViewModel(
|
||||
dashboardViewModel,
|
||||
_MockWallet(),
|
||||
"Wallet",
|
||||
isSyncHeavy: isSyncHeavy,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(_buildTestApp(_buildTopBar(dashboardViewModel)));
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
|
||||
final showsName = status.runtimeType == SyncedSyncStatus ||
|
||||
(status is _UnknownSyncStatus && !isSyncHeavy);
|
||||
expect(
|
||||
find.text("Wallet"),
|
||||
showsName ? findsOneWidget : findsNothing,
|
||||
reason: "$status, sync-heavy: $isSyncHeavy",
|
||||
);
|
||||
expect(find.byType(SyncBar), showsName ? findsNothing : findsOneWidget);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets("rebinds to a replacement view model and cancels its stale Synced timer",
|
||||
(tester) async {
|
||||
const firstWalletName = "First Wallet";
|
||||
const secondWalletName = "Second Wallet";
|
||||
final firstWallet = _MockWallet();
|
||||
final secondWallet = _MockWallet();
|
||||
final firstViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.5));
|
||||
final secondViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.5));
|
||||
DashboardViewModel activeViewModel = firstViewModel;
|
||||
late StateSetter setHarnessState;
|
||||
|
||||
_stubDashboardViewModel(firstViewModel, firstWallet, firstWalletName);
|
||||
_stubDashboardViewModel(secondViewModel, secondWallet, secondWalletName);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
StatefulBuilder(
|
||||
builder: (context, setState) {
|
||||
setHarnessState = setState;
|
||||
return _buildTopBar(activeViewModel);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
firstViewModel.setStatus(SyncedSyncStatus());
|
||||
await tester.pump();
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isTrue);
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
setHarnessState(() => activeViewModel = secondViewModel);
|
||||
await tester.pump();
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isFalse);
|
||||
|
||||
firstViewModel.setStatus(SyncingSyncStatus(50, 0.75));
|
||||
await tester.pump();
|
||||
firstViewModel.setStatus(SyncedSyncStatus());
|
||||
await tester.pump();
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isFalse);
|
||||
|
||||
secondViewModel.setStatus(SyncedSyncStatus());
|
||||
await tester.pump();
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isTrue);
|
||||
|
||||
await tester.pump(const Duration(seconds: 2));
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isTrue);
|
||||
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
await tester.pump(const Duration(milliseconds: 150));
|
||||
expect(find.text(secondWalletName), findsOneWidget);
|
||||
expect(find.byType(SyncBar), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets("cancels the Synced timer when disposed", (tester) async {
|
||||
final wallet = _MockWallet();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.5));
|
||||
|
||||
_stubDashboardViewModel(dashboardViewModel, wallet, "Wallet");
|
||||
|
||||
await tester.pumpWidget(_buildTestApp(_buildTopBar(dashboardViewModel)));
|
||||
await tester.pump();
|
||||
|
||||
dashboardViewModel.setStatus(SyncedSyncStatus());
|
||||
await tester.pump();
|
||||
expect(tester.widget<SyncBar>(find.byType(SyncBar)).showSyncedMessage, isTrue);
|
||||
|
||||
await tester.pumpWidget(const SizedBox.shrink());
|
||||
|
||||
// Flutter reports any timer that survives disposal as pending when this test ends.
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets("uses natural control widths in the synced Bitcoin header", (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(376, 200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final wallet = _MockWallet();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncedSyncStatus());
|
||||
|
||||
_stubDashboardViewModel(dashboardViewModel, wallet, "My Bitcoin Wallet");
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: _buildTopBar(dashboardViewModel),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final switcher = find.byType(LightningSwitcher);
|
||||
final walletInfo = find.byType(WalletInfoBar);
|
||||
final settingsButton = find.byType(ModernButton);
|
||||
final switcherIcons = find.descendant(
|
||||
of: switcher,
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is CakeImageWidget &&
|
||||
widget.imageUrl?.startsWith("assets/new-ui/switcher-") == true,
|
||||
),
|
||||
);
|
||||
|
||||
expect(tester.getSize(switcher), const Size(63, 36));
|
||||
expect(switcherIcons, findsNWidgets(2));
|
||||
for (final icon in tester.widgetList<CakeImageWidget>(switcherIcons)) {
|
||||
expect(Size(icon.width!, icon.height!), const Size(27, 27));
|
||||
}
|
||||
expect(tester.getSize(walletInfo).width, 217);
|
||||
expect(tester.getRect(settingsButton).right, 358);
|
||||
});
|
||||
|
||||
testWidgets("fits the syncing pill between the controls at different screen widths",
|
||||
(tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(376, 200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final wallet = _MockWallet();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncingSyncStatus(100, 0.24));
|
||||
|
||||
_stubDashboardViewModel(
|
||||
dashboardViewModel,
|
||||
wallet,
|
||||
"My Bitcoin Wallet",
|
||||
isSyncHeavy: true,
|
||||
hasSilentPayments: true,
|
||||
silentPaymentsScanningActive: true,
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: _buildTopBar(dashboardViewModel),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
final pill = find.descendant(
|
||||
of: find.byType(SyncBar),
|
||||
matching: find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is Container &&
|
||||
widget.constraints?.minHeight == 40 &&
|
||||
widget.constraints?.maxHeight == 40,
|
||||
),
|
||||
);
|
||||
|
||||
for (final width in [320.0, 376.0, 600.0]) {
|
||||
await tester.binding.setSurfaceSize(Size(width, 200));
|
||||
await tester.pump();
|
||||
|
||||
final switcherRect = tester.getRect(find.byType(LightningSwitcher));
|
||||
final syncBarRect = tester.getRect(find.byType(SyncBar));
|
||||
final settingsRect = tester.getRect(find.byType(ModernButton));
|
||||
final pillRect = tester.getRect(pill);
|
||||
|
||||
expect(switcherRect.left, 18);
|
||||
expect(settingsRect.right, width - 18);
|
||||
expect(syncBarRect.height, 36);
|
||||
expect(pillRect.height, 40);
|
||||
expect(pillRect.left - switcherRect.right, closeTo(12, 0.001));
|
||||
expect(settingsRect.left - pillRect.right, closeTo(12, 0.001));
|
||||
expect(pillRect.center.dy, closeTo(syncBarRect.center.dy, 0.001));
|
||||
expect(tester.takeException(), isNull);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets("keeps the non-Lightning header geometry unchanged", (tester) async {
|
||||
await tester.binding.setSurfaceSize(const Size(376, 200));
|
||||
addTearDown(() => tester.binding.setSurfaceSize(null));
|
||||
|
||||
final wallet = _MockWallet();
|
||||
final dashboardViewModel = _ObservableDashboardViewModel(SyncedSyncStatus());
|
||||
|
||||
_stubDashboardViewModel(
|
||||
dashboardViewModel,
|
||||
wallet,
|
||||
"My Monero Wallet",
|
||||
hasLightning: false,
|
||||
);
|
||||
when(() => wallet.currency).thenReturn(CryptoCurrency.xmr);
|
||||
|
||||
await tester.pumpWidget(
|
||||
_buildTestApp(
|
||||
Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: _buildTopBar(dashboardViewModel),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(tester.getSize(find.byType(WalletInfoBar)).width, 244);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import "package:cake_wallet/generated/i18n.dart";
|
||||
import "package:cake_wallet/locales/locale.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/coins_page/wallet_info.dart";
|
||||
import "package:cake_wallet/new-ui/widgets/modern_button.dart";
|
||||
import "package:cake_wallet/src/widgets/cake_image_widget.dart";
|
||||
import "package:cw_core/wallet_info.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_test/flutter_test.dart";
|
||||
|
||||
void main() {
|
||||
testWidgets("keeps a hardware wallet name on one ellipsized line", (tester) async {
|
||||
const walletName =
|
||||
"My exceptionally long Ledger hardware wallet name that cannot fit in the header";
|
||||
final semanticsHandle = tester.ensureSemantics();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: localizationDelegates,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
home: const Scaffold(
|
||||
body: Center(
|
||||
child: SizedBox(
|
||||
width: 160,
|
||||
child: WalletInfoBar(
|
||||
name: walletName,
|
||||
hardwareWalletType: HardwareWalletType.ledger,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final name = tester.widget<Text>(find.text(walletName));
|
||||
expect(name.maxLines, 1);
|
||||
expect(name.softWrap, isFalse);
|
||||
expect(name.overflow, TextOverflow.ellipsis);
|
||||
expect(name.style?.fontSize, 16);
|
||||
|
||||
final hardwareIcon = tester.widget<CakeImageWidget>(
|
||||
find.byKey(const ValueKey("hardware_wallet_icon")),
|
||||
);
|
||||
expect(hardwareIcon.imageUrl, "assets/new-ui/hardware_wallets/device_ledger_nano_x.svg");
|
||||
expect(hardwareIcon.width, 24);
|
||||
expect(hardwareIcon.height, 24);
|
||||
expect(
|
||||
tester.getRect(find.text(walletName)).left -
|
||||
tester.getRect(find.byKey(const ValueKey("hardware_wallet_icon"))).right,
|
||||
4,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.bySemanticsLabel("$walletName, ${S.current.hardware_wallet}"),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(ModernButton), findsNothing);
|
||||
expect(find.bySemanticsLabel(S.current.wallet_accounts), findsNothing);
|
||||
expect(tester.takeException(), isNull);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user