text: // Copyright (c) 2015-2020 The Bitcoin Core developers
  // Distributed under the MIT software license, see the accompanying
  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  
  #include <qt/test/wallettests.h>
  #include <qt/test/util.h>
  
  #include <interfaces/chain.h>
  #include <interfaces/node.h>
  #include <qt/bitcoinamountfield.h>
  #include <qt/clientmodel.h>
  #include <qt/optionsmodel.h>
  #include <qt/platformstyle.h>
  #include <qt/qvalidatedlineedit.h>
  #include <qt/sendcoinsdialog.h>
  #include <qt/sendcoinsentry.h>
  #include <qt/transactiontablemodel.h>
  #include <qt/transactionview.h>
  #include <qt/walletmodel.h>
  #include <key_io.h>
  #include <test/util/setup_common.h>
  #include <validation.h>
  #include <wallet/wallet.h>
  #include <qt/overviewpage.h>
  #include <qt/receivecoinsdialog.h>
  #include <qt/recentrequeststablemodel.h>
  #include <qt/receiverequestdialog.h>
  
  #include <memory>
  
  #include <QAbstractButton>
  #include <QAction>
  #include <QApplication>
  #include <QCheckBox>
  #include <QPushButton>
  #include <QTimer>
  #include <QVBoxLayout>
  #include <QTextEdit>
  #include <QListView>
  #include <QDialogButtonBox>
  
  namespace
  {
  //! Press "Yes" or "Cancel" buttons in modal send confirmation dialog.
  void ConfirmSend(QString* text = nullptr, bool cancel = false)
  {
      QTimer::singleShot(0, [text, cancel]() {
          for (QWidget* widget : QApplication::topLevelWidgets()) {
              if (widget->inherits("SendConfirmationDialog")) {
                  SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
                  if (text) *text = dialog->text();
                  QAbstractButton* button = dialog->button(cancel ? QMessageBox::Cancel : QMessageBox::Yes);
                  button->setEnabled(true);
                  button->click();
              }
          }
      });
  }
  
  //! Send coins to address and return txid.
  uint256 SendCoins(CWallet& wallet, SendCoinsDialog& sendCoinsDialog, const CTxDestination& address, CAmount amount, bool rbf)
  {
      QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>("entries");
      SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
      entry->findChild<QValidatedLineEdit*>("payTo")->setText(QString::fromStdString(EncodeDestination(address)));
      entry->findChild<BitcoinAmountField*>("payAmount")->setValue(amount);
      sendCoinsDialog.findChild<QFrame*>("frameFee")
          ->findChild<QFrame*>("frameFeeSelection")
          ->findChild<QCheckBox*>("optInRBF")
          ->setCheckState(rbf ? Qt::Checked : Qt::Unchecked);
      uint256 txid;
      boost::signals2::scoped_connection c(wallet.NotifyTransactionChanged.connect([&txid](CWallet*, const uint256& hash, ChangeType status) {
          if (status == CT_NEW) txid = hash;
      }));
      ConfirmSend();
      bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog, "on_sendButton_clicked");
      assert(invoked);
      return txid;
  }
  
  //! Find index of txid in transaction list.
  QModelIndex FindTx(const QAbstractItemModel& model, const uint256& txid)
  {
      QString hash = QString::fromStdString(txid.ToString());
      int rows = model.rowCount({});
      for (int row = 0; row < rows; ++row) {
          QModelIndex index = model.index(row, 0, {});
          if (model.data(index, TransactionTableModel::TxHashRole) == hash) {
              return index;
          }
      }
      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.
  //
  // Test widgets can be debugged interactively calling show() on them and
  // manually running the event loop, e.g.:
  //
  //     sendCoinsDialog.show();
  //     QEventLoop().exec();
  //
  // This also requires overriding the default minimal Qt platform:
  //
  //     QT_QPA_PLATFORM=xcb     src/qt/test/test_bitcoin-qt  # Linux
  //     QT_QPA_PLATFORM=windows src/qt/test/test_bitcoin-qt  # Windows
  //     QT_QPA_PLATFORM=cocoa   src/qt/test/test_bitcoin-qt  # macOS
  void TestGUI(interfaces::Node& node)
  {
      // Set up wallet and chain with 105 blocks (5 mature blocks for spending).
      TestChain100Setup test;
      for (int i = 0; i < 5; ++i) {
          test.CreateAndProcessBlock({}, GetScriptForRawPubKey(test.coinbaseKey.GetPubKey()));
      }
      node.setContext(&test.m_node);
      std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(node.context()->chain.get(), "", CreateMockWalletDatabase());
      bool firstRun;
      wallet->LoadWallet(firstRun);
      {
          auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan();
          LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore);
          wallet->SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), wallet->m_default_address_type), "", "receive");
          spk_man->AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey());
          wallet->SetLastBlockProcessed(105, ::ChainActive().Tip()->GetBlockHash());
      }
      {
          WalletRescanReserver reserver(*wallet);
          reserver.reserve();
          CWallet::ScanResult result = wallet->ScanForWalletTransactions(Params().GetConsensus().hashGenesisBlock, 0 /* block height */, {} /* max height */, reserver, true /* fUpdate */);
          QCOMPARE(result.status, CWallet::ScanResult::SUCCESS);
          QCOMPARE(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash());
          QVERIFY(result.last_failed_block.IsNull());
      }
      wallet->SetBroadcastTransactions(true);
  
      // Create widgets for sending coins and listing transactions.
      std::unique_ptr<const PlatformStyle> platformStyle(PlatformStyle::instantiate("other"));
      SendCoinsDialog sendCoinsDialog(platformStyle.get());
      TransactionView transactionView(platformStyle.get());
      OptionsModel optionsModel;
      ClientModel clientModel(node, &optionsModel);
      AddWallet(wallet);
      WalletModel walletModel(interfaces::MakeWallet(wallet), clientModel, platformStyle.get());
      RemoveWallet(wallet, nullopt);
      sendCoinsDialog.setModel(&walletModel);
      transactionView.setModel(&walletModel);
  
      {
          // Check balance in send dialog
          QLabel* balanceLabel = sendCoinsDialog.findChild<QLabel*>("labelBalance");
          QString balanceText = balanceLabel->text();
          int unit = walletModel.getOptionsModel()->getDisplayUnit();
          CAmount balance = walletModel.wallet().getBalance();
          QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
          QCOMPARE(balanceText, balanceComparison);
      }
  
      // Send two transactions, and verify they are added to transaction list.
      TransactionTableModel* transactionTableModel = walletModel.getTransactionTableModel();
      QCOMPARE(transactionTableModel->rowCount({}), 105);
      uint256 txid1 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 5 * COIN, false /* rbf */);
      uint256 txid2 = SendCoins(*wallet.get(), sendCoinsDialog, PKHash(), 10 * COIN, true /* rbf */);
      QCOMPARE(transactionTableModel->rowCount({}), 107);
      QVERIFY(FindTx(*transactionTableModel, txid1).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, true /* expect disabled */, "already bumped" /* expected error */, false /* cancel */);
  
      // Check current balance on OverviewPage
      OverviewPage overviewPage(platformStyle.get());
      overviewPage.setWalletModel(&walletModel);
      QLabel* balanceLabel = overviewPage.findChild<QLabel*>("labelBalance");
      QString balanceText = balanceLabel->text().trimmed();
      int unit = walletModel.getOptionsModel()->getDisplayUnit();
      CAmount balance = walletModel.wallet().getBalance();
      QString balanceComparison = BitcoinUnits::formatWithUnit(unit, balance, false, BitcoinUnits::SeparatorStyle::ALWAYS);
      QCOMPARE(balanceText, balanceComparison);
  
      // Check Request Payment button
      ReceiveCoinsDialog receiveCoinsDialog(platformStyle.get());
      receiveCoinsDialog.setModel(&walletModel);
      RecentRequestsTableModel* requestTableModel = walletModel.getRecentRequestsTableModel();
  
      // Label input
      QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>("reqLabel");
      labelInput->setText("TEST_LABEL_1");
  
      // Amount input
      BitcoinAmountField* amountInput = receiveCoinsDialog.findChild<BitcoinAmountField*>("reqAmount");
      amountInput->setValue(1);
  
      // Message input
      QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>("reqMessage");
      messageInput->setText("TEST_MESSAGE_1");
      int initialRowCount = requestTableModel->rowCount({});
      QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>("receiveButton");
      requestPaymentButton->click();
      for (QWidget* widget : QApplication::topLevelWidgets()) {
          if (widget->inherits("ReceiveRequestDialog")) {
              ReceiveRequestDialog* receiveRequestDialog = qobject_cast<ReceiveRequestDialog*>(widget);
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("payment_header")->text(), QString("Payment information"));
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("uri_tag")->text(), QString("URI:"));
              QString uri = receiveRequestDialog->QObject::findChild<QLabel*>("uri_content")->text();
              QCOMPARE(uri.count("doichain:"), 2);
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("address_tag")->text(), QString("Address:"));
  
              QCOMPARE(uri.count("amount=0.00000001"), 2);
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("amount_tag")->text(), QString("Amount:"));
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("amount_content")->text(), QString("0.00000001 ") + QString::fromStdString(CURRENCY_UNIT));
  
              QCOMPARE(uri.count("label=TEST_LABEL_1"), 2);
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("label_tag")->text(), QString("Label:"));
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("label_content")->text(), QString("TEST_LABEL_1"));
  
              QCOMPARE(uri.count("message=TEST_MESSAGE_1"), 2);
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("message_tag")->text(), QString("Message:"));
              QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>("message_content")->text(), QString("TEST_MESSAGE_1"));
          }
      }
  
      // Clear button
      QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>("clearButton");
      clearButton->click();
      QCOMPARE(labelInput->text(), QString(""));
      QCOMPARE(amountInput->value(), CAmount(0));
      QCOMPARE(messageInput->text(), QString(""));
  
      // Check addition to history
      int currentRowCount = requestTableModel->rowCount({});
      QCOMPARE(currentRowCount, initialRowCount+1);
  
      // Check Remove button
      QTableView* table = receiveCoinsDialog.findChild<QTableView*>("recentRequestsView");
      table->selectRow(currentRowCount-1);
      QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>("removeRequestButton");
      removeRequestButton->click();
      QCOMPARE(requestTableModel->rowCount({}), currentRowCount-1);
  }
  
  } // namespace
  
  void WalletTests::walletTests()
  {
  #ifdef Q_OS_MAC
      if (QApplication::platformName() == "minimal") {
          // Disable for mac on "minimal" platform to avoid crashes inside the Qt
          // framework when it tries to look up unimplemented cocoa functions,
          // and fails to handle returned nulls
          // (https://bugreports.qt.io/browse/QTBUG-49686).
          QWARN("Skipping WalletTests on mac build with 'minimal' platform set due to Qt bugs. To run AppTests, invoke "
                "with 'QT_QPA_PLATFORM=cocoa test_bitcoin-qt' on mac, or else use a linux or windows build.");
          return;
      }
  #endif
      TestGUI(m_node);
  }
  