1 #include "wallettests.h"
3 #include "qt/bitcoinamountfield.h"
4 #include "qt/callback.h"
5 #include "qt/optionsmodel.h"
6 #include "qt/platformstyle.h"
7 #include "qt/qvalidatedlineedit.h"
8 #include "qt/sendcoinsdialog.h"
9 #include "qt/sendcoinsentry.h"
10 #include "qt/transactiontablemodel.h"
11 #include "qt/transactionview.h"
12 #include "qt/walletmodel.h"
13 #include "test/test_bitcoin.h"
14 #include "validation.h"
15 #include "wallet/wallet.h"
17 #include <QAbstractButton>
19 #include <QApplication>
21 #include <QPushButton>
23 #include <QVBoxLayout>
27 //! Press "Ok" button in message box dialog.
28 void ConfirmMessage(QString
* text
= nullptr)
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();
42 //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
43 void ConfirmSend(QString
* text
= nullptr, bool cancel
= false)
45 QTimer::singleShot(0, makeCallback([text
, cancel
](Callback
* callback
) {
46 for (QWidget
* widget
: QApplication::topLevelWidgets()) {
47 if (widget
->inherits("SendConfirmationDialog")) {
48 SendConfirmationDialog
* dialog
= qobject_cast
<SendConfirmationDialog
*>(widget
);
49 if (text
) *text
= dialog
->text();
50 QAbstractButton
* button
= dialog
->button(cancel
? QMessageBox::Cancel
: QMessageBox::Yes
);
51 button
->setEnabled(true);
59 //! Send coins to address and return txid.
60 uint256
SendCoins(CWallet
& wallet
, SendCoinsDialog
& sendCoinsDialog
, const CTxDestination
& address
, CAmount amount
, bool rbf
)
62 QVBoxLayout
* entries
= sendCoinsDialog
.findChild
<QVBoxLayout
*>("entries");
63 SendCoinsEntry
* entry
= qobject_cast
<SendCoinsEntry
*>(entries
->itemAt(0)->widget());
64 entry
->findChild
<QValidatedLineEdit
*>("payTo")->setText(QString::fromStdString(EncodeDestination(address
)));
65 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
);
71 boost::signals2::scoped_connection
c(wallet
.NotifyTransactionChanged
.connect([&txid
](CWallet
*, const uint256
& hash
, ChangeType status
) {
72 if (status
== CT_NEW
) txid
= hash
;
75 QMetaObject::invokeMethod(&sendCoinsDialog
, "on_sendButton_clicked");
79 //! Find index of txid in transaction list.
80 QModelIndex
FindTx(const QAbstractItemModel
& model
, const uint256
& txid
)
82 QString hash
= QString::fromStdString(txid
.ToString());
83 int rows
= model
.rowCount({});
84 for (int row
= 0; row
< rows
; ++row
) {
85 QModelIndex index
= model
.index(row
, 0, {});
86 if (model
.data(index
, TransactionTableModel::TxHashRole
) == hash
) {
93 //! Request context menu (call method that is public in qt5, but protected in qt4).
94 void RequestContextMenu(QWidget
* widget
)
96 class Qt4Hack
: public QWidget
99 using QWidget::customContextMenuRequested
;
101 static_cast<Qt4Hack
*>(widget
)->customContextMenuRequested({});
104 //! Invoke bumpfee on txid and check results.
105 void BumpFee(TransactionView
& view
, const uint256
& txid
, bool expectDisabled
, std::string expectError
, bool cancel
)
107 QTableView
* table
= view
.findChild
<QTableView
*>("transactionView");
108 QModelIndex index
= FindTx(*table
->selectionModel()->model(), txid
);
109 QVERIFY2(index
.isValid(), "Could not find BumpFee txid");
111 // Select row in table, invoke context menu, and make sure bumpfee action is
112 // enabled or disabled as expected.
113 QAction
* action
= view
.findChild
<QAction
*>("bumpFeeAction");
114 table
->selectionModel()->select(index
, QItemSelectionModel::ClearAndSelect
| QItemSelectionModel::Rows
);
115 action
->setEnabled(expectDisabled
);
116 RequestContextMenu(table
);
117 QCOMPARE(action
->isEnabled(), !expectDisabled
);
119 action
->setEnabled(true);
121 if (expectError
.empty()) {
122 ConfirmSend(&text
, cancel
);
124 ConfirmMessage(&text
);
127 QVERIFY(text
.indexOf(QString::fromStdString(expectError
)) != -1);
130 //! Simple qt wallet tests.
132 // Test widgets can be debugged interactively calling show() on them and
133 // manually running the event loop, e.g.:
135 // sendCoinsDialog.show();
136 // QEventLoop().exec();
138 // This also requires overriding the default minimal Qt platform:
140 // src/qt/test/test_bitcoin-qt -platform xcb # Linux
141 // src/qt/test/test_bitcoin-qt -platform windows # Windows
142 // src/qt/test/test_bitcoin-qt -platform cocoa # macOS
145 // Set up wallet and chain with 105 blocks (5 mature blocks for spending).
146 TestChain100Setup test
;
147 for (int i
= 0; i
< 5; ++i
) {
148 test
.CreateAndProcessBlock({}, GetScriptForRawPubKey(test
.coinbaseKey
.GetPubKey()));
151 std::unique_ptr
<CWalletDBWrapper
> dbw(new CWalletDBWrapper(&bitdb
, "wallet_test.dat"));
152 CWallet
wallet(std::move(dbw
));
154 wallet
.LoadWallet(firstRun
);
156 LOCK(wallet
.cs_wallet
);
157 wallet
.SetAddressBook(test
.coinbaseKey
.GetPubKey().GetID(), "", "receive");
158 wallet
.AddKeyPubKey(test
.coinbaseKey
, test
.coinbaseKey
.GetPubKey());
160 wallet
.ScanForWalletTransactions(chainActive
.Genesis(), true);
161 wallet
.SetBroadcastTransactions(true);
163 // Create widgets for sending coins and listing transactions.
164 std::unique_ptr
<const PlatformStyle
> platformStyle(PlatformStyle::instantiate("other"));
165 SendCoinsDialog
sendCoinsDialog(platformStyle
.get());
166 TransactionView
transactionView(platformStyle
.get());
167 OptionsModel optionsModel
;
168 WalletModel
walletModel(platformStyle
.get(), &wallet
, &optionsModel
);
169 sendCoinsDialog
.setModel(&walletModel
);
170 transactionView
.setModel(&walletModel
);
172 // Send two transactions, and verify they are added to transaction list.
173 TransactionTableModel
* transactionTableModel
= walletModel
.getTransactionTableModel();
174 QCOMPARE(transactionTableModel
->rowCount({}), 105);
175 uint256 txid1
= SendCoins(wallet
, sendCoinsDialog
, CKeyID(), 5 * COIN
, false /* rbf */);
176 uint256 txid2
= SendCoins(wallet
, sendCoinsDialog
, CKeyID(), 10 * COIN
, true /* rbf */);
177 QCOMPARE(transactionTableModel
->rowCount({}), 107);
178 QVERIFY(FindTx(*transactionTableModel
, txid1
).isValid());
179 QVERIFY(FindTx(*transactionTableModel
, txid2
).isValid());
181 // Call bumpfee. Test disabled, canceled, enabled, then failing cases.
182 BumpFee(transactionView
, txid1
, true /* expect disabled */, "not BIP 125 replaceable" /* expected error */, false /* cancel */);
183 BumpFee(transactionView
, txid2
, false /* expect disabled */, {} /* expected error */, true /* cancel */);
184 BumpFee(transactionView
, txid2
, false /* expect disabled */, {} /* expected error */, false /* cancel */);
185 BumpFee(transactionView
, txid2
, true /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
193 void WalletTests::walletTests()