Skip to content

Commit 5749a48

Browse files
committed
Add Qt tests for wallet spends & bumpfee
A few code changes were needed to accompany the test: * Adding setObjectName() calls for a few Qt controls to make them easily accessible from the test. * Calling contextMenu->popup() instead of contextMenu->exec() to open the transaction list context menu without blocking the test thread. * Opening the context menu at the contextualMenu event point rather than the cursor position (this change was not strictly needed to make the test work, but is more correct). * Updating the bumped transaction row with showTransaction=true instead of false. This is needed to prevent the bumped tx from being hidden, so the last part of the test which attempts to bump the bumped tx can work. (Technically this change is a more general bugfix not limited to the testing environment, but the bug doesn't happen outside of the testing environment because in the full Qt client, a queued NotifyTransactionChanged notification causes the row to be updated twice, first with showTransaction=false, then immediately after with showTransaction=true.)
1 parent 7e96ecf commit 5749a48

File tree

2 files changed

+76
-13
lines changed

2 files changed

+76
-13
lines changed

src/qt/test/wallettests.cpp

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,46 @@
88
#include "qt/sendcoinsdialog.h"
99
#include "qt/sendcoinsentry.h"
1010
#include "qt/transactiontablemodel.h"
11+
#include "qt/transactionview.h"
1112
#include "qt/walletmodel.h"
1213
#include "test/test_bitcoin.h"
1314
#include "validation.h"
1415
#include "wallet/wallet.h"
1516

1617
#include <QAbstractButton>
18+
#include <QAction>
1719
#include <QApplication>
20+
#include <QCheckBox>
21+
#include <QPushButton>
1822
#include <QTimer>
1923
#include <QVBoxLayout>
2024

2125
namespace
2226
{
23-
//! Press "Yes" button in modal send confirmation dialog.
24-
void ConfirmSend()
27+
//! Press "Ok" button in message box dialog.
28+
void ConfirmMessage(QString* text = nullptr)
2529
{
26-
QTimer::singleShot(0, makeCallback([](Callback* callback) {
30+
QTimer::singleShot(0, makeCallback([text](Callback* callback) {
31+
for (QWidget* widget : QApplication::topLevelWidgets()) {
32+
if (widget->inherits("QMessageBox")) {
33+
QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget);
34+
if (text) *text = messageBox->text();
35+
messageBox->defaultButton()->click();
36+
}
37+
}
38+
delete callback;
39+
}), SLOT(call()));
40+
}
41+
42+
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
43+
void ConfirmSend(QString* text = nullptr, bool cancel = false)
44+
{
45+
QTimer::singleShot(0, makeCallback([text, cancel](Callback* callback) {
2746
for (QWidget* widget : QApplication::topLevelWidgets()) {
2847
if (widget->inherits("SendConfirmationDialog")) {
2948
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
30-
QAbstractButton* button = dialog->button(QMessageBox::Yes);
49+
if (text) *text = dialog->text();
50+
QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
3151
button->setEnabled(true);
3252
button->click();
3353
}
@@ -37,12 +57,16 @@ void ConfirmSend()
3757
}
3858

3959
//! Send coins to address and return txid.
40-
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CBitcoinAddress& address, CAmount amount)
60+
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CBitcoinAddress& address, CAmount amount, bool rbf)
4161
{
4262
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
4363
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
4464
entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(address.ToString()));
4565
entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
66+
sendCoinsDialog.findChild<QFrame*>("frameFee")
67+
->findChild<QFrame*>("frameFeeSelection")
68+
->findChild<QCheckBox*>("optInRBF")
69+
->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
4670
uint256 txid;
4771
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
4872
if (status == CT_NEW) txid = hash;
@@ -66,6 +90,32 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
6690
return {};
6791
}
6892

93+
//! Invoke bumpfee on txid and check results.
94+
void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, std::string expectError, bool cancel)
95+
{
96+
QTableView* table = view.findChild<QTableView*>("transactionView");
97+
QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
98+
QVERIFY2(index.isValid(), "Could not find BumpFee txid");
99+
100+
// Select row in table, invoke context menu, and make sure bumpfee action is
101+
// enabled or disabled as expected.
102+
QAction* action = view.findChild<QAction*>("bumpFeeAction");
103+
table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
104+
action->setEnabled(expectDisabled);
105+
table->customContextMenuRequested({});
106+
QCOMPARE(action->isEnabled(), !expectDisabled);
107+
108+
action->setEnabled(true);
109+
QString text;
110+
if (expectError.empty()) {
111+
ConfirmSend(&text, cancel);
112+
} else {
113+
ConfirmMessage(&text);
114+
}
115+
action->trigger();
116+
QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
117+
}
118+
69119
//! Simple qt wallet tests.
70120
//
71121
// Test widgets can be debugged interactively calling show() on them and
@@ -81,9 +131,11 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
81131
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
82132
void TestSendCoins()
83133
{
84-
// Set up wallet and chain with 101 blocks (1 mature block for spending).
134+
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
85135
TestChain100Setup test;
86-
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
136+
for (int i = 0; i < 5; ++i) {
137+
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
138+
}
87139
bitdb.MakeMock();
88140
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
89141
CWallet wallet(std::move(dbw));
@@ -100,19 +152,27 @@ void TestSendCoins()
100152
// Create widgets for sending coins and listing transactions.
101153
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
102154
SendCoinsDialog sendCoinsDialog(platformStyle.get());
155+
TransactionView transactionView(platformStyle.get());
103156
OptionsModel optionsModel;
104157
WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel);
105158
sendCoinsDialog.setModel(&walletModel);
159+
transactionView.setModel(&walletModel);
106160

107161
// Send two transactions, and verify they are added to transaction list.
108162
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
109-
QCOMPARE(transactionTableModel->rowCount({}), 101);
110-
uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 5 * COIN);
111-
uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 10 * COIN);
112-
QCOMPARE(transactionTableModel->rowCount({}), 103);
163+
QCOMPARE(transactionTableModel->rowCount({}), 105);
164+
uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 5 * COIN, false /* rbf */);
165+
uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 10 * COIN, true /* rbf */);
166+
QCOMPARE(transactionTableModel->rowCount({}), 107);
113167
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
114168
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
115169

170+
// Call bumpfee. Test disabled, canceled, enabled, then failing cases.
171+
BumpFee(transactionView, txid1, true /* expect disabled */, "not BIP 125 replaceable" /* expected error */, false /* cancel */);
172+
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, true /* cancel */);
173+
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, false /* cancel */);
174+
BumpFee(transactionView, txid2, false /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
175+
116176
bitdb.Flush(true);
117177
bitdb.Reset();
118178
}

src/qt/transactionview.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,10 +136,12 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
136136
view->installEventFilter(this);
137137

138138
transactionView = view;
139+
transactionView->setObjectName("transactionView");
139140

140141
// Actions
141142
abandonAction = new QAction(tr("Abandon transaction"), this);
142143
bumpFeeAction = new QAction(tr("Increase transaction fee"), this);
144+
bumpFeeAction->setObjectName("bumpFeeAction");
143145
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
144146
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
145147
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
@@ -150,6 +152,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
150152
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
151153

152154
contextMenu = new QMenu(this);
155+
contextMenu->setObjectName("contextMenu");
153156
contextMenu->addAction(copyAddressAction);
154157
contextMenu->addAction(copyLabelAction);
155158
contextMenu->addAction(copyAmountAction);
@@ -380,7 +383,7 @@ void TransactionView::contextualMenu(const QPoint &point)
380383

381384
if(index.isValid())
382385
{
383-
contextMenu->exec(QCursor::pos());
386+
contextMenu->popup(transactionView->viewport()->mapToGlobal(point));
384387
}
385388
}
386389

@@ -416,7 +419,7 @@ void TransactionView::bumpFee()
416419
// Bump tx fee over the walletModel
417420
if (model->bumpFee(hash)) {
418421
// Update the table
419-
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
422+
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, true);
420423
}
421424
}
422425

0 commit comments

Comments
 (0)