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.)
This commit is contained in:
parent
7e96ecf075
commit
5749a48821
2 changed files with 76 additions and 13 deletions
|
@ -8,26 +8,46 @@
|
||||||
#include "qt/sendcoinsdialog.h"
|
#include "qt/sendcoinsdialog.h"
|
||||||
#include "qt/sendcoinsentry.h"
|
#include "qt/sendcoinsentry.h"
|
||||||
#include "qt/transactiontablemodel.h"
|
#include "qt/transactiontablemodel.h"
|
||||||
|
#include "qt/transactionview.h"
|
||||||
#include "qt/walletmodel.h"
|
#include "qt/walletmodel.h"
|
||||||
#include "test/test_bitcoin.h"
|
#include "test/test_bitcoin.h"
|
||||||
#include "validation.h"
|
#include "validation.h"
|
||||||
#include "wallet/wallet.h"
|
#include "wallet/wallet.h"
|
||||||
|
|
||||||
#include <QAbstractButton>
|
#include <QAbstractButton>
|
||||||
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include <QPushButton>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
//! Press "Yes" button in modal send confirmation dialog.
|
//! Press "Ok" button in message box dialog.
|
||||||
void ConfirmSend()
|
void ConfirmMessage(QString* text = nullptr)
|
||||||
{
|
{
|
||||||
QTimer::singleShot(0, makeCallback([](Callback* callback) {
|
QTimer::singleShot(0, makeCallback([text](Callback* callback) {
|
||||||
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
||||||
|
if (widget->inherits("QMessageBox")) {
|
||||||
|
QMessageBox* messageBox = qobject_cast<QMessageBox*>(widget);
|
||||||
|
if (text) *text = messageBox->text();
|
||||||
|
messageBox->defaultButton()->click();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete callback;
|
||||||
|
}), SLOT(call()));
|
||||||
|
}
|
||||||
|
|
||||||
|
//! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
|
||||||
|
void ConfirmSend(QString* text = nullptr, bool cancel = false)
|
||||||
|
{
|
||||||
|
QTimer::singleShot(0, makeCallback([text, cancel](Callback* callback) {
|
||||||
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
for (QWidget* widget : QApplication::topLevelWidgets()) {
|
||||||
if (widget->inherits("SendConfirmationDialog")) {
|
if (widget->inherits("SendConfirmationDialog")) {
|
||||||
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
|
||||||
QAbstractButton* button = dialog->button(QMessageBox::Yes);
|
if (text) *text = dialog->text();
|
||||||
|
QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
|
||||||
button->setEnabled(true);
|
button->setEnabled(true);
|
||||||
button->click();
|
button->click();
|
||||||
}
|
}
|
||||||
|
@ -37,12 +57,16 @@ void ConfirmSend()
|
||||||
}
|
}
|
||||||
|
|
||||||
//! Send coins to address and return txid.
|
//! Send coins to address and return txid.
|
||||||
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CBitcoinAddress& address, CAmount amount)
|
uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CBitcoinAddress& address, CAmount amount, bool rbf)
|
||||||
{
|
{
|
||||||
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
|
QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
|
||||||
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
|
SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
|
||||||
entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(address.ToString()));
|
entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(address.ToString()));
|
||||||
entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
|
entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
|
||||||
|
sendCoinsDialog.findChild<QFrame*>("frameFee")
|
||||||
|
->findChild<QFrame*>("frameFeeSelection")
|
||||||
|
->findChild<QCheckBox*>("optInRBF")
|
||||||
|
->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
|
||||||
uint256 txid;
|
uint256 txid;
|
||||||
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
|
boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
|
||||||
if (status == CT_NEW) txid = hash;
|
if (status == CT_NEW) txid = hash;
|
||||||
|
@ -66,6 +90,32 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! Invoke bumpfee on txid and check results.
|
||||||
|
void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, std::string expectError, bool cancel)
|
||||||
|
{
|
||||||
|
QTableView* table = view.findChild<QTableView*>("transactionView");
|
||||||
|
QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
|
||||||
|
QVERIFY2(index.isValid(), "Could not find BumpFee txid");
|
||||||
|
|
||||||
|
// Select row in table, invoke context menu, and make sure bumpfee action is
|
||||||
|
// enabled or disabled as expected.
|
||||||
|
QAction* action = view.findChild<QAction*>("bumpFeeAction");
|
||||||
|
table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
|
||||||
|
action->setEnabled(expectDisabled);
|
||||||
|
table->customContextMenuRequested({});
|
||||||
|
QCOMPARE(action->isEnabled(), !expectDisabled);
|
||||||
|
|
||||||
|
action->setEnabled(true);
|
||||||
|
QString text;
|
||||||
|
if (expectError.empty()) {
|
||||||
|
ConfirmSend(&text, cancel);
|
||||||
|
} else {
|
||||||
|
ConfirmMessage(&text);
|
||||||
|
}
|
||||||
|
action->trigger();
|
||||||
|
QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
|
||||||
|
}
|
||||||
|
|
||||||
//! Simple qt wallet tests.
|
//! Simple qt wallet tests.
|
||||||
//
|
//
|
||||||
// Test widgets can be debugged interactively calling show() on them and
|
// Test widgets can be debugged interactively calling show() on them and
|
||||||
|
@ -81,9 +131,11 @@ QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
|
||||||
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
|
// src/qt/test/test_bitcoin-qt -platform cocoa # macOS
|
||||||
void TestSendCoins()
|
void TestSendCoins()
|
||||||
{
|
{
|
||||||
// Set up wallet and chain with 101 blocks (1 mature block for spending).
|
// Set up wallet and chain with 105 blocks (5 mature blocks for spending).
|
||||||
TestChain100Setup test;
|
TestChain100Setup test;
|
||||||
|
for (int i = 0; i < 5; ++i) {
|
||||||
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
|
||||||
|
}
|
||||||
bitdb.MakeMock();
|
bitdb.MakeMock();
|
||||||
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
|
std::unique_ptr<CWalletDBWrapper> dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
|
||||||
CWallet wallet(std::move(dbw));
|
CWallet wallet(std::move(dbw));
|
||||||
|
@ -100,19 +152,27 @@ void TestSendCoins()
|
||||||
// Create widgets for sending coins and listing transactions.
|
// Create widgets for sending coins and listing transactions.
|
||||||
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
|
std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
|
||||||
SendCoinsDialog sendCoinsDialog(platformStyle.get());
|
SendCoinsDialog sendCoinsDialog(platformStyle.get());
|
||||||
|
TransactionView transactionView(platformStyle.get());
|
||||||
OptionsModel optionsModel;
|
OptionsModel optionsModel;
|
||||||
WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel);
|
WalletModel walletModel(platformStyle.get(), &wallet, &optionsModel);
|
||||||
sendCoinsDialog.setModel(&walletModel);
|
sendCoinsDialog.setModel(&walletModel);
|
||||||
|
transactionView.setModel(&walletModel);
|
||||||
|
|
||||||
// Send two transactions, and verify they are added to transaction list.
|
// Send two transactions, and verify they are added to transaction list.
|
||||||
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
|
TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
|
||||||
QCOMPARE(transactionTableModel->rowCount({}), 101);
|
QCOMPARE(transactionTableModel->rowCount({}), 105);
|
||||||
uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 5 * COIN);
|
uint256 txid1 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 5 * COIN, false /* rbf */);
|
||||||
uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 10 * COIN);
|
uint256 txid2 = SendCoins(wallet, sendCoinsDialog, CBitcoinAddress(CKeyID()), 10 * COIN, true /* rbf */);
|
||||||
QCOMPARE(transactionTableModel->rowCount({}), 103);
|
QCOMPARE(transactionTableModel->rowCount({}), 107);
|
||||||
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
|
QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
|
||||||
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
|
QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
|
||||||
|
|
||||||
|
// Call bumpfee. Test disabled, canceled, enabled, then failing cases.
|
||||||
|
BumpFee(transactionView, txid1, true /* expect disabled */, "not BIP 125 replaceable" /* expected error */, false /* cancel */);
|
||||||
|
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, true /* cancel */);
|
||||||
|
BumpFee(transactionView, txid2, false /* expect disabled */, {} /* expected error */, false /* cancel */);
|
||||||
|
BumpFee(transactionView, txid2, false /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
|
||||||
|
|
||||||
bitdb.Flush(true);
|
bitdb.Flush(true);
|
||||||
bitdb.Reset();
|
bitdb.Reset();
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,10 +136,12 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
|
||||||
view->installEventFilter(this);
|
view->installEventFilter(this);
|
||||||
|
|
||||||
transactionView = view;
|
transactionView = view;
|
||||||
|
transactionView->setObjectName("transactionView");
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
abandonAction = new QAction(tr("Abandon transaction"), this);
|
abandonAction = new QAction(tr("Abandon transaction"), this);
|
||||||
bumpFeeAction = new QAction(tr("Increase transaction fee"), this);
|
bumpFeeAction = new QAction(tr("Increase transaction fee"), this);
|
||||||
|
bumpFeeAction->setObjectName("bumpFeeAction");
|
||||||
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
|
QAction *copyAddressAction = new QAction(tr("Copy address"), this);
|
||||||
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
|
QAction *copyLabelAction = new QAction(tr("Copy label"), this);
|
||||||
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
|
QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
|
||||||
|
@ -150,6 +152,7 @@ TransactionView::TransactionView(const PlatformStyle *platformStyle, QWidget *pa
|
||||||
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
|
QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);
|
||||||
|
|
||||||
contextMenu = new QMenu(this);
|
contextMenu = new QMenu(this);
|
||||||
|
contextMenu->setObjectName("contextMenu");
|
||||||
contextMenu->addAction(copyAddressAction);
|
contextMenu->addAction(copyAddressAction);
|
||||||
contextMenu->addAction(copyLabelAction);
|
contextMenu->addAction(copyLabelAction);
|
||||||
contextMenu->addAction(copyAmountAction);
|
contextMenu->addAction(copyAmountAction);
|
||||||
|
@ -380,7 +383,7 @@ void TransactionView::contextualMenu(const QPoint &point)
|
||||||
|
|
||||||
if(index.isValid())
|
if(index.isValid())
|
||||||
{
|
{
|
||||||
contextMenu->exec(QCursor::pos());
|
contextMenu->popup(transactionView->viewport()->mapToGlobal(point));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,7 +419,7 @@ void TransactionView::bumpFee()
|
||||||
// Bump tx fee over the walletModel
|
// Bump tx fee over the walletModel
|
||||||
if (model->bumpFee(hash)) {
|
if (model->bumpFee(hash)) {
|
||||||
// Update the table
|
// Update the table
|
||||||
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, false);
|
model->getTransactionTableModel()->updateTransaction(hashQStr, CT_UPDATED, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue