Skip to content
Bitcoin Roots

v31.1-roots.1 · Commit 6: [PATCH 06/17] feat(wallet): preserve Roots signing and spend controls on v31

src/qt/test/wallettests.cpp

Before: src/qt/test/wallettests.cpp · After: src/qt/test/wallettests.cpp

File metadata
index 19b25650b7886ceb4e12dad37fe246107ab42c04..41b00b2313b920956b7434f1174ac9dadb1d87c9 100644

Download original patch · Release notes

Diff rows 1–400 of 588. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. The two line-number columns refer to before and after.

src/qt/test/wallettests.cpp CHANGED
@@ -5,13 +5,17 @@
5
5
  #include <qt/test/wallettests.h>
6
6
  #include <qt/test/util.h>
7
7
 
8
+ #include <util/rbf.h>
8
9
  #include <wallet/coincontrol.h>
9
10
  #include <interfaces/chain.h>
10
11
  #include <interfaces/node.h>
11
12
  #include <key_io.h>
13
+ #include <logging.h>
14
+ #include <policy/feerate.h>
12
15
  #include <qt/bitcoinamountfield.h>
13
16
  #include <qt/bitcoinunits.h>
14
17
  #include <qt/clientmodel.h>
18
+ #include <qt/coincontroldialog.h>
15
19
  #include <qt/optionsmodel.h>
16
20
  #include <qt/overviewpage.h>
17
21
  #include <qt/platformstyle.h>
@@ -21,16 +25,23 @@
21
25
  #include <qt/recentrequeststablemodel.h>
22
26
  #include <qt/sendcoinsdialog.h>
23
27
  #include <qt/sendcoinsentry.h>
28
+ #include <qt/sweepdialog.h>
24
29
  #include <qt/transactiontablemodel.h>
25
30
  #include <qt/transactionview.h>
26
31
  #include <qt/walletmodel.h>
27
32
  #include <script/solver.h>
33
+ #include <support/lockedpool.h>
28
34
  #include <test/util/setup_common.h>
29
35
  #include <validation.h>
30
36
  #include <wallet/test/util.h>
31
37
  #include <wallet/wallet.h>
32
38
 
33
39
  #include <chrono>
40
+ #include <functional>
41
+ #include <future>
42
+ #include <thread>
43
+ #include <initializer_list>
44
+ #include <list>
34
45
  #include <memory>
35
46
 
36
47
  #include <QAbstractButton>
@@ -38,9 +49,19 @@
38
49
  #include <QApplication>
39
50
  #include <QCheckBox>
40
51
  #include <QClipboard>
52
+ #include <QFont>
53
+ #include <QDir>
54
+ #include <QLabel>
55
+ #include <QLineEdit>
41
56
  #include <QObject>
42
57
  #include <QPushButton>
58
+ #include <QRadioButton>
59
+ #include <QSettings>
60
+ #include <QScrollBar>
61
+ #include <QScreen>
62
+ #include <QTest>
43
63
  #include <QTimer>
64
+ #include <QTreeWidget>
44
65
  #include <QVBoxLayout>
45
66
  #include <QTextEdit>
46
67
  #include <QListView>
@@ -56,8 +77,55 @@ using wallet::WalletContext;
56
77
  using wallet::WalletDescriptor;
57
78
  using wallet::WalletRescanReserver;
58
79
 
80
+ struct SweepDialogTestAccess
81
+ {
82
+ static SecureString& Cache(SweepDialog& dialog) { return dialog.m_private_key_cache; }
83
+ static void ClearCache(SweepDialog& dialog) { dialog.clearPrivateKeyCache(); }
84
+ };
85
+
59
86
  namespace
60
87
  {
88
+ class LogCapture
89
+ {
90
+ public:
91
+ LogCapture()
92
+ : m_connection{LogInstance().PushBackCallback([this](const std::string& message) { output += message; })}
93
+ {
94
+ }
95
+
96
+ ~LogCapture() { LogInstance().DeleteCallback(m_connection); }
97
+
98
+ std::string output;
99
+
100
+ private:
101
+ std::list<std::function<void(const std::string&)>>::iterator m_connection;
102
+ };
103
+
104
+ //! Optional evidence uses the actual wallet widgets and disposable regtest fixture.
105
+ bool CaptureWalletWidget(QWidget& widget, const QString& name)
106
+ {
107
+ const QString directory{qEnvironmentVariable("ROOTS_WALLET_CAPTURE_DIR")};
108
+ if (directory.isEmpty()) return true;
109
+ const auto dimensions{qEnvironmentVariable("ROOTS_WALLET_CAPTURE_SIZE", "1280x800").split('x')};
110
+ if (dimensions.size() != 2) return false;
111
+ const QSize size{dimensions[0].toInt(), dimensions[1].toInt()};
112
+ // Native confirmation boxes choose their own size. Capture the actual
113
+ // screen at the requested resolution, rather than forcing their layout.
114
+ if (qobject_cast<QMessageBox*>(&widget)) {
115
+ widget.show();
116
+ qApp->processEvents();
117
+ widget.move(std::max(0, (size.width() - widget.width()) / 2), std::max(0, (size.height() - widget.height()) / 2));
118
+ QTest::qWait(50);
119
+ const auto screenshot{widget.screen()->grabWindow(0)};
120
+ return screenshot.size() == size && QDir().mkpath(directory) && screenshot.save(directory + '/' + name + ".png");
121
+ }
122
+ widget.resize(size);
123
+ widget.show();
124
+ qApp->processEvents();
125
+ QTest::qWait(50);
126
+ return widget.size() == size && QDir().mkpath(directory) && widget.grab().save(directory + '/' + name + ".png");
127
+ }
128
+
61
129
  //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
62
130
  void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
63
131
  {
@@ -66,9 +134,11 @@ void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_ty
66
134
  if (widget->inherits("SendConfirmationDialog")) {
67
135
  SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
68
136
  if (text) *text = dialog->text();
137
+ const bool captured{CaptureWalletWidget(*dialog, dialog->text().contains("This transaction signals") ? "send-confirmation-rbf" : "send-confirmation-no-rbf")};
69
138
  QAbstractButton* button = dialog->button(confirm_type);
70
139
  button->setEnabled(true);
71
140
  button->click();
141
+ QVERIFY(captured);
72
142
  }
73
143
  }
74
144
  });
@@ -76,8 +146,9 @@ void ConfirmSend(QString* text = nullptr, QMessageBox::StandardButton confirm_ty
76
146
 
77
147
  //! Send coins to address and return txid.
78
148
  Txid SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf,
79
- QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
149
+ QMessageBox::StandardButton confirm_type = QMessageBox::Yes, QString* confirmation = nullptr)
80
150
  {
151
+ qApp->processEvents(); // Deliver preceding transaction notifications before preparing another send.
81
152
  QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
82
153
  SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
83
154
  entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(EncodeDestination(address)));
@@ -90,7 +161,7 @@ Txid SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDesti
90
161
  boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](const Txid& hash, ChangeType status) {
91
162
  if (status == CT_NEW) txid = hash;
92
163
  }));
93
- ConfirmSend(/*text=*/nullptr, confirm_type);
164
+ ConfirmSend(confirmation, confirm_type);
94
165
  bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "sendButtonClicked", Q_ARG(bool, false));
95
166
  assert(invoked);
96
167
  return txid;
@@ -266,11 +337,152 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet)
266
337
  {
267
338
  // Create widgets for sending coins and listing transactions.
268
339
  std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
340
+ wallet->m_signal_rbf = false;
269
341
  MiniGUI mini_gui(node, platformStyle.get());
270
342
  mini_gui.initModelForWallet(node, wallet, platformStyle.get());
271
343
  WalletModel& walletModel = *mini_gui.walletModel;
272
344
  SendCoinsDialog& sendCoinsDialog = mini_gui.sendCoinsDialog;
273
345
  TransactionView& transactionView = mini_gui.transactionView;
346
+ qApp->processEvents(); // Drain the fixture's queued RemoveWallet notification.
347
+
348
+ const QString sweep_sentinel{"sweep-ui-secret-sentinel"};
349
+ LogCapture sweep_logs;
350
+
351
+ // Verify the guarded sweep dialog contract and asynchronous error path.
352
+ SweepDialog sweep_dialog(&walletModel);
353
+ auto& sweep_cache{SweepDialogTestAccess::Cache(sweep_dialog)};
354
+ // Disposal must release locked storage while the dialog stays alive;
355
+ // observe allocator accounting rather than dereferencing freed memory.
356
+ const auto locked_bytes_before{LockedPoolManager::Instance().stats().used};
357
+ sweep_cache.assign(52, 's');
358
+ QVERIFY(LockedPoolManager::Instance().stats().used > locked_bytes_before);
359
+ SweepDialogTestAccess::ClearCache(sweep_dialog);
360
+ QVERIFY(sweep_cache.empty());
361
+ QCOMPARE(LockedPoolManager::Instance().stats().used, locked_bytes_before);
362
+ sweep_cache.assign(4, 's');
363
+ SweepDialogTestAccess::ClearCache(sweep_dialog);
364
+ QVERIFY(sweep_cache.empty());
365
+ QCOMPARE(LockedPoolManager::Instance().stats().used, locked_bytes_before);
366
+ QLineEdit* sweep_key = sweep_dialog.findChild<QLineEdit*>("sweepPrivateKey");
367
+ QLineEdit* sweep_destination = sweep_dialog.findChild<QLineEdit*>("sweepDestination");
368
+ QPushButton* sweep_preview = sweep_dialog.findChild<QPushButton*>("sweepPreview");
369
+ QPushButton* sweep_broadcast = sweep_dialog.findChild<QPushButton*>("sweepBroadcast");
370
+ QCheckBox* sweep_reveal = sweep_dialog.findChild<QCheckBox*>("sweepReveal");
371
+ QLabel* sweep_result = sweep_dialog.findChild<QLabel*>("sweepResult");
372
+ QVERIFY(sweep_key && sweep_destination && sweep_preview && sweep_broadcast && sweep_reveal && sweep_result);
373
+ QCOMPARE(sweep_key->echoMode(), QLineEdit::Password);
374
+ QCOMPARE(sweep_key->contextMenuPolicy(), Qt::NoContextMenu);
375
+ QCOMPARE(sweep_key->accessibleName(), "Private key");
376
+ QVERIFY(sweep_key->accessibleDescription().contains("Copy, cut"));
377
+ QCOMPARE(sweep_reveal->accessibleName(), "Show private key");
378
+ QVERIFY(sweep_reveal->accessibleDescription().contains("Copy and cut"));
379
+ QCOMPARE(sweep_destination->accessibleName(), "Destination address");
380
+ QVERIFY(sweep_destination->accessibleDescription().contains("selected wallet"));
381
+ QVERIFY(sweep_destination->accessibleDescription().contains("Watch-only"));
382
+ QCOMPARE(sweep_result->accessibleName(), "Sweep preview");
383
+ QVERIFY(sweep_result->accessibleDescription().contains("Non-secret"));
384
+ QCOMPARE(sweep_preview->accessibleName(), "Preview sweep");
385
+ QVERIFY(sweep_preview->accessibleDescription().contains("Replace-By-Fee"));
386
+ QCOMPARE(sweep_broadcast->accessibleName(), "Broadcast sweep");
387
+ QVERIFY(sweep_broadcast->accessibleDescription().contains("fees changed"));
388
+ sweep_dialog.show();
389
+ sweep_key->setFocus();
390
+ QTRY_COMPARE(qApp->focusWidget(), static_cast<QWidget*>(sweep_key));
391
+ QTest::keyClick(sweep_key, Qt::Key_Tab);
392
+ QTRY_COMPARE(qApp->focusWidget(), static_cast<QWidget*>(sweep_reveal));
393
+ QTest::keyClick(sweep_reveal, Qt::Key_Tab);
394
+ QTRY_COMPARE(qApp->focusWidget(), static_cast<QWidget*>(sweep_destination));
395
+ QTest::keyClick(sweep_destination, Qt::Key_Tab);
396
+ QTRY_COMPARE(qApp->focusWidget(), static_cast<QWidget*>(sweep_preview));
397
+ sweep_reveal->setChecked(true);
398
+ QCOMPARE(sweep_key->echoMode(), QLineEdit::Normal);
399
+ sweep_reveal->setChecked(false);
400
+ QCOMPARE(sweep_key->echoMode(), QLineEdit::Password);
401
+ QVERIFY(!sweep_broadcast->isEnabled());
402
+ auto sweep_dest{walletModel.wallet().getNewDestination(OutputType::BECH32, "")};
403
+ QVERIFY(sweep_dest);
404
+ sweep_destination->setText(QString::fromStdString(EncodeDestination(*sweep_dest)));
405
+ sweep_key->setText(sweep_sentinel);
406
+ QVERIFY(QMetaObject::invokeMethod(&sweep_dialog, "preview"));
407
+ QVERIFY(!sweep_key->isEnabled());
408
+ QVERIFY(sweep_key->text().isEmpty());
409
+ QTRY_VERIFY(sweep_key->isEnabled());
410
+ QVERIFY(sweep_cache.empty());
411
+ QCOMPARE(sweep_cache.capacity(), SecureString{}.capacity());
412
+ QVERIFY(!sweep_result->text().contains(sweep_sentinel));
413
+ QVERIFY(!QString::fromStdString(sweep_logs.output).contains(sweep_sentinel));
414
+
415
+ sweep_key->setText("not-a-private-key");
416
+ QVERIFY(QMetaObject::invokeMethod(&sweep_dialog, "preview"));
417
+ QVERIFY(!sweep_key->isEnabled());
418
+ QVERIFY(sweep_key->text().isEmpty());
419
+ // A competing wallet update must discard the transient key and preview
420
+ // immediately, rather than relying on broadcast-time stale detection.
421
+ walletModel.updateTransaction();
422
+ QVERIFY(sweep_cache.empty());
423
+ QCOMPARE(sweep_cache.capacity(), SecureString{}.capacity());
424
+ QVERIFY(sweep_key->isEnabled());
425
+ QVERIFY(sweep_key->text().isEmpty());
426
+ QVERIFY(!sweep_broadcast->isEnabled());
427
+ QVERIFY(sweep_result->text().contains("fresh preview"));
428
+ QVERIFY(CaptureWalletWidget(sweep_dialog, "sweep-cancelled"));
429
+ QTRY_VERIFY_WITH_TIMEOUT(sweep_key->isEnabled(), 5000);
430
+ QVERIFY(!sweep_result->text().contains("not-a-private-key"));
431
+ QVERIFY(!sweep_broadcast->isEnabled());
432
+ sweep_dialog.reject();
433
+ QVERIFY(sweep_key->text().isEmpty());
434
+ QVERIFY(sweep_cache.empty());
435
+ QCOMPARE(sweep_cache.capacity(), SecureString{}.capacity());
436
+
437
+ const auto settings_contain_sentinel = [&] {
438
+ QSettings settings;
439
+ for (const QString& key : settings.allKeys()) {
440
+ if (key.contains(sweep_sentinel) || settings.value(key).toString().contains(sweep_sentinel)) return true;
441
+ }
442
+ return false;
443
+ };
444
+ const auto widgets_contain_sentinel = [&] {
445
+ for (QWidget* widget : qApp->allWidgets()) {
446
+ if (auto* line_edit = qobject_cast<QLineEdit*>(widget); line_edit && line_edit->text().contains(sweep_sentinel)) return true;
447
+ if (auto* label = qobject_cast<QLabel*>(widget); label && label->text().contains(sweep_sentinel)) return true;
448
+ if (auto* button = qobject_cast<QAbstractButton*>(widget); button && button->text().contains(sweep_sentinel)) return true;
449
+ }
450
+ return false;
451
+ };
452
+ QVERIFY(!settings_contain_sentinel());
453
+ QVERIFY(!widgets_contain_sentinel());
454
+ for (int i = 0; i < 3; ++i) {
455
+ auto dialog = std::make_unique<SweepDialog>(&walletModel);
456
+ QLineEdit* key = dialog->findChild<QLineEdit*>("sweepPrivateKey");
457
+ QVERIFY(key);
458
+ key->setText(sweep_sentinel);
459
+ dialog->reject();
460
+ QVERIFY(key->text().isEmpty());
461
+ dialog.reset();
462
+ QVERIFY(!widgets_contain_sentinel());
463
+ }
464
+ QVERIFY(!settings_contain_sentinel());
465
+
466
+ const auto verify_layout = [&](const QFont& font, const QSize& size) {
467
+ SweepDialog dialog(&walletModel);
468
+ dialog.setFont(font);
469
+ dialog.resize(size);
470
+ dialog.show();
471
+ QTRY_VERIFY(dialog.isVisible());
472
+ QVERIFY(dialog.sizeHint().width() <= size.width());
473
+ QVERIFY(dialog.sizeHint().height() <= size.height());
474
+ for (QWidget* widget : std::initializer_list<QWidget*>{dialog.findChild<QLineEdit*>("sweepPrivateKey"), dialog.findChild<QLineEdit*>("sweepDestination"),
475
+ dialog.findChild<QCheckBox*>("sweepReveal"), dialog.findChild<QPushButton*>("sweepPreview"),
476
+ dialog.findChild<QPushButton*>("sweepBroadcast"), dialog.findChild<QLabel*>("sweepResult")}) {
477
+ QVERIFY(widget && widget->isVisible());
478
+ QVERIFY(dialog.contentsRect().contains(widget->mapTo(&dialog, widget->rect().center())));
479
+ }
480
+ dialog.reject();
481
+ };
482
+ verify_layout(sweep_dialog.font(), QSize{1024, 700});
483
+ QFont large_font{sweep_dialog.font()};
484
+ large_font.setPointSize(24);
485
+ verify_layout(large_font, QSize{1600, 1000});
274
486
 
275
487
  // Update walletModel cached balance which will trigger an update for the 'labelBalance' QLabel.
276
488
  walletModel.pollBalanceChanged();
@@ -280,16 +492,157 @@ void TestGUI(interfaces::Node& node, const std::shared_ptr<CWallet>& wallet)
280
492
  // Check 'UseAvailableBalance' functionality
281
493
  VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
282
494
 
495
+ auto* entries_for_capture{sendCoinsDialog.findChild<QVBoxLayout*>("entries")};
496
+ auto* recipient_for_capture{qobject_cast<SendCoinsEntry*>(entries_for_capture->itemAt(0)->widget())};
497
+ recipient_for_capture->findChild<QCheckBox*>("checkboxSubtractFeeFromAmount")->setChecked(false);
498
+ recipient_for_capture->findChild<BitcoinAmountField*>("payAmount")->setValue(COIN);
499
+ sendCoinsDialog.getCoinControl()->m_feerate = CFeeRate{1000};
500
+
501
+ // The coin control view presents wallet-owned facts and keeps filtering a
502
+ // display-only operation: it must not alter the selected outpoints.
503
+ const auto capture_coins{walletModel.wallet().listCoins()};
504
+ QVERIFY(!capture_coins.empty());
505
+ const COutPoint locked_output{std::get<0>(capture_coins.begin()->second.front())};
506
+ walletModel.wallet().lockCoin(locked_output, false);
507
+ CoinControlDialog coin_control_dialog(*sendCoinsDialog.getCoinControl(), &walletModel, platformStyle.get());
508
+ QTreeWidget* coin_tree = coin_control_dialog.findChild<QTreeWidget*>("treeWidget");
509
+ QLineEdit* coin_filter = coin_control_dialog.findChild<QLineEdit*>("lineEditFilter");
510
+ QLabel* available = coin_control_dialog.findChild<QLabel*>("labelAvailable");
511
+ QLabel* fee_rate = coin_control_dialog.findChild<QLabel*>("labelCoinControlFeeRate");
512
+ QLabel* rbf_summary = coin_control_dialog.findChild<QLabel*>("labelCoinControlRbf");
513
+ QLabel* selection_notice = coin_control_dialog.findChild<QLabel*>("labelSelectionNotice");
514
+ QVERIFY(coin_tree && coin_filter && available && fee_rate && rbf_summary && selection_notice);
515
+ QCOMPARE(coin_tree->headerItem()->text(6), "Status");
516
+ QCOMPARE(coin_tree->headerItem()->text(7), "Effective value");
517
+ QCOMPARE(coin_tree->headerItem()->text(8), "Input bytes");
518
+ QVERIFY(available->text().contains("eligible"));
519
+ // Populate an eligible selection while retaining the separate locked row.
520
+ for (int i = 0; i < coin_tree->topLevelItemCount(); ++i) {
521
+ auto* parent{coin_tree->topLevelItem(i)};
522
+ if (parent->childCount() == 0 && !parent->isDisabled()) { parent->setCheckState(0, Qt::Checked); break; }
523
+ for (int j = 0; j < parent->childCount(); ++j) {
524
+ auto* item{parent->child(j)};
525
+ if (!item->isDisabled()) { item->setCheckState(0, Qt::Checked); break; }
526
+ }
527
+ }
528
+ coin_tree->expandAll();
529
+ QVERIFY(CaptureWalletWidget(coin_control_dialog, "coin-control"));
530
+ coin_tree->horizontalScrollBar()->setValue(coin_tree->horizontalScrollBar()->maximum());
531
+ QVERIFY(CaptureWalletWidget(coin_control_dialog, "coin-control-details"));
532
+ coin_tree->horizontalScrollBar()->setValue(0);
533
+ QVERIFY(rbf_summary->text().contains("Replace-By-Fee"));
534
+ sendCoinsDialog.getCoinControl()->m_signal_bip125_rbf = false;
535
+ CoinControlDialog::updateLabels(*sendCoinsDialog.getCoinControl(), &walletModel, &coin_control_dialog);
536
+ QVERIFY(rbf_summary->text().startsWith("Does not signal"));
537
+ QCOMPARE(coin_filter->accessibleName(), "Filter coin selection");
538
+ QVERIFY(coin_tree->accessibleDescription().contains("Status column"));
539
+ const auto selected_before_filter = sendCoinsDialog.getCoinControl()->ListSelected();
540
+ coin_filter->setText("not-a-wallet-coin");
541
+ qApp->processEvents();
542
+ for (int row = 0; row < coin_tree->topLevelItemCount(); ++row) QVERIFY(coin_tree->topLevelItem(row)->isHidden());
543
+ QVERIFY(sendCoinsDialog.getCoinControl()->ListSelected() == selected_before_filter);
544
+ coin_filter->clear();
545
+ QVERIFY(coin_tree->focusPolicy() != Qt::NoFocus);
546
+ walletModel.updateTransaction(); // Invalidate even when the balance is unchanged.
547
+ QVERIFY(!sendCoinsDialog.getCoinControl()->HasSelected());
548
+ QVERIFY(!selection_notice->isHidden());
549
+ QVERIFY(selection_notice->text().contains("cleared"));
550
+ coin_control_dialog.reject();
551
+ walletModel.wallet().unlockCoin(locked_output);
552
+
553
+ // Effective value must use the active coin-control fee rate, not fall back
554
+ // to face value. Check the formatted value, its numeric sorting key, and
555
+ // the item sorting implementation independently of localized labels.
556
+ const CFeeRate effective_fee_rate{1000};
557
+ const auto coin_groups = walletModel.wallet().listCoins();
558
+ QVERIFY(!coin_groups.empty());
559
+ const COutPoint effective_outpoint{std::get<0>(coin_groups.begin()->second.front())};
560
+ const auto effective_coins = walletModel.wallet().getCoins({effective_outpoint}, effective_fee_rate);
561
+ QCOMPARE(effective_coins.size(), 1U);
562
+ const auto& effective_coin = effective_coins.front();
563
+ QVERIFY(effective_coin.effective_value);
564
+ QVERIFY(*effective_coin.effective_value < effective_coin.txout.nValue);
565
+
566
+ wallet::CCoinControl effective_control;
567
+ effective_control.m_feerate = effective_fee_rate;
568
+ CoinControlDialog effective_dialog(effective_control, &walletModel, platformStyle.get());
569
+ QRadioButton* list_mode = effective_dialog.findChild<QRadioButton*>("radioListMode");
570
+ QTreeWidget* effective_tree = effective_dialog.findChild<QTreeWidget*>("treeWidget");
571
+ QVERIFY(list_mode && effective_tree);
572
+ list_mode->setChecked(true);
573
+ qApp->processEvents();
574
+ QTreeWidgetItem* effective_item{nullptr};
575
+ for (int row = 0; row < effective_tree->topLevelItemCount(); ++row) {
576
+ QTreeWidgetItem* item = effective_tree->topLevelItem(row);
577
+ if (item->data(3, Qt::UserRole).toString() == QString::fromStdString(effective_outpoint.hash.GetHex()) &&
578
+ item->data(3, Qt::UserRole + 1).toUInt() == effective_outpoint.n) {
579
+ effective_item = item;
580
+ break;
581
+ }
582
+ }
583
+ QVERIFY(effective_item);
584
+ QCOMPARE(effective_item->text(7), BitcoinUnits::format(walletModel.getOptionsModel()->getDisplayUnit(), *effective_coin.effective_value));
585
+ QCOMPARE(effective_item->data(7, Qt::UserRole).toLongLong(), qlonglong{*effective_coin.effective_value});
586
+
587
+ QTreeWidget effective_sort_tree;
588
+ effective_sort_tree.setColumnCount(9);
589
+ auto* larger_effective = new CCoinControlWidgetItem(&effective_sort_tree);
590
+ larger_effective->setText(7, "0.00000001");
591
+ larger_effective->setData(7, Qt::UserRole, qlonglong{*effective_coin.effective_value + 1});
592
+ auto* smaller_effective = new CCoinControlWidgetItem(&effective_sort_tree);
593
+ smaller_effective->setText(7, "9.99999999");
594
+ smaller_effective->setData(7, Qt::UserRole, qlonglong{*effective_coin.effective_value});
595
+ effective_sort_tree.sortItems(7, Qt::AscendingOrder);
596
+ QCOMPARE(effective_sort_tree.topLevelItem(0)->data(7, Qt::UserRole).toLongLong(), qlonglong{*effective_coin.effective_value});