From 0dd6e3c33a65ee8aa6b52a25d32d3aeac7316ea5 Mon Sep 17 00:00:00 2001
From: Inso <insomniak.fr@gmail.com>
Date: Thu, 21 Jan 2016 08:21:07 +0100
Subject: [PATCH] Add context menu to identities tab

---
 res/ui/identities_tab.ui                      |  11 ++
 src/sakia/gui/community_view.py               |   4 +-
 src/sakia/gui/identities_tab.py               | 177 +++++-------------
 src/sakia/gui/transactions_tab.py             |   2 +-
 src/sakia/gui/widgets/context_menu.py         |  60 +++---
 src/sakia/tests/unit/gui/test_context_menu.py |  67 +++++++
 .../tests/unit/gui/test_transactions_tab.py   |  68 -------
 7 files changed, 162 insertions(+), 227 deletions(-)
 create mode 100644 src/sakia/tests/unit/gui/test_context_menu.py
 delete mode 100644 src/sakia/tests/unit/gui/test_transactions_tab.py

diff --git a/res/ui/identities_tab.ui b/res/ui/identities_tab.ui
index 5f3f2e07..0dab4a2d 100644
--- a/res/ui/identities_tab.ui
+++ b/res/ui/identities_tab.ui
@@ -63,8 +63,19 @@
      </attribute>
     </widget>
    </item>
+   <item>
+    <widget class="Busy" name="busy" native="true"/>
+   </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>Busy</class>
+   <extends>QWidget</extends>
+   <header>sakia.gui.widgets.busy</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
  <resources/>
  <connections/>
 </ui>
diff --git a/src/sakia/gui/community_view.py b/src/sakia/gui/community_view.py
index 2da8ca31..e953759d 100644
--- a/src/sakia/gui/community_view.py
+++ b/src/sakia/gui/community_view.py
@@ -85,7 +85,7 @@ class CommunityWidget(QWidget, Ui_CommunityWidget):
                          QIcon(':/icons/wot_icon'),
                          self.tr(CommunityWidget._tab_wot_label))
 
-        self.tabs.addTab(self.tab_identities,
+        self.tabs.addTab(self.tab_identities.widget,
                          QIcon(':/icons/members_icon'),
                          self.tr(CommunityWidget._tab_identities_label))
 
@@ -414,7 +414,7 @@ The process to join back the community later will have to be done again.""")
         self.tabs.setTabText(self.tabs.indexOf(self.tab_network), self.tr(CommunityWidget._tab_network_label))
         self.tabs.setTabText(self.tabs.indexOf(self.tab_informations), self.tr(CommunityWidget._tab_informations_label))
         self.tabs.setTabText(self.tabs.indexOf(self.tab_history.widget), self.tr(CommunityWidget._tab_history_label))
-        self.tabs.setTabText(self.tabs.indexOf(self.tab_identities), self.tr(CommunityWidget._tab_identities_label))
+        self.tabs.setTabText(self.tabs.indexOf(self.tab_identities.widget), self.tr(CommunityWidget._tab_identities_label))
         self.action_publish_uid.setText(self.tr(CommunityWidget._action_publish_uid_text))
         self.action_revoke_uid.setText(self.tr(CommunityWidget._action_revoke_uid_text))
         self.action_showinfo.setText(self.tr(CommunityWidget._action_showinfo_text))
diff --git a/src/sakia/gui/identities_tab.py b/src/sakia/gui/identities_tab.py
index a84857fe..2a623466 100644
--- a/src/sakia/gui/identities_tab.py
+++ b/src/sakia/gui/identities_tab.py
@@ -4,10 +4,9 @@ Created on 2 févr. 2014
 @author: inso
 """
 
-import asyncio
 import logging
 
-from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QT_TRANSLATE_NOOP
+from PyQt5.QtCore import Qt, pyqtSignal, QEvent, QT_TRANSLATE_NOOP, QObject
 from PyQt5.QtGui import QCursor
 from PyQt5.QtWidgets import QWidget, QAction, QMenu, QDialog, \
                             QAbstractItemView
@@ -15,17 +14,14 @@ from ucoinpy.api import bma
 
 from ..models.identities import IdentitiesFilterProxyModel, IdentitiesTableModel
 from ..gen_resources.identities_tab_uic import Ui_IdentitiesTab
-from .contact import ConfigureContactDialog
-from .member import MemberDialog
-from .transfer import TransferMoneyDialog
 from sakia.gui.widgets.busy import Busy
-from .certification import CertificationDialog
 from ..core.registry import Identity, BlockchainState
 from ..tools.exceptions import NoPeerAvailable
 from ..tools.decorators import asyncify, once_at_a_time, cancel_once_task
+from .widgets import ContextMenu
 
 
-class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
+class IdentitiesTabWidget(QObject):
 
     """
     classdocs
@@ -36,41 +32,45 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
     _direct_connections_text = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Search direct certifications")
     _search_placeholder = QT_TRANSLATE_NOOP("IdentitiesTabWidget", "Research a pubkey, an uid...")
 
-    def __init__(self, app):
+    def __init__(self, app, account=None, community=None, password_asker=None,
+                 widget=QWidget, view=Ui_IdentitiesTab):
         """
         Init
-        :param sakia.core.account.Account account: Account instance
-        :param sakia.core.community.Community community: Community instance
-        :param sakia.gui.password_asker.PasswordAskerDialog password_asker: Password asker dialog
-        :return:
+
+        :param sakia.core.app.Application app: Application instance
+        :param sakia.core.Account account: The account displayed in the widget
+        :param sakia.core.Community community: The community displayed in the widget
+        :param sakia.gui.Password_Asker: password_asker: The widget to ask for passwords
+        :param class widget: The class of the PyQt5 widget used for this tab
+        :param class view: The class of the UI View for this tab
         """
         super().__init__()
+        self.widget = widget()
+        self.ui = view()
+        self.ui.setupUi(self.widget)
+
         self.app = app
-        self.community = None
-        self.account = None
-        self.password_asker = None
+        self.community = community
+        self.account = account
+        self.password_asker = password_asker
 
         self.direct_connections = QAction(self.tr(IdentitiesTabWidget._direct_connections_text), self)
-        self.setupUi(self)
-        self.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
+        self.ui.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
 
         identities_model = IdentitiesTableModel()
         proxy = IdentitiesFilterProxyModel()
         proxy.setSourceModel(identities_model)
-        self.table_identities.setModel(proxy)
-        self.table_identities.setSelectionBehavior(QAbstractItemView.SelectRows)
-        self.table_identities.customContextMenuRequested.connect(self.identity_context_menu)
-        self.table_identities.sortByColumn(0, Qt.AscendingOrder)
-        self.table_identities.resizeColumnsToContents()
-        identities_model.modelAboutToBeReset.connect(lambda: self.table_identities.setEnabled(False))
-        identities_model.modelReset.connect(lambda: self.table_identities.setEnabled(True))
+        self.ui.table_identities.setModel(proxy)
+        self.ui.table_identities.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.ui.table_identities.customContextMenuRequested.connect(self.identity_context_menu)
+        self.ui.table_identities.sortByColumn(0, Qt.AscendingOrder)
+        self.ui.table_identities.resizeColumnsToContents()
+        identities_model.modelAboutToBeReset.connect(lambda: self.ui.table_identities.setEnabled(False))
+        identities_model.modelReset.connect(lambda: self.ui.table_identities.setEnabled(True))
 
         self.direct_connections.triggered.connect(self._async_search_direct_connections)
-        self.button_search.addAction(self.direct_connections)
-        self.button_search.clicked.connect(self._async_execute_search_text)
-
-        self.busy = Busy(self.table_identities)
-        self.busy.hide()
+        self.ui.button_search.addAction(self.direct_connections)
+        self.ui.button_search.clicked.connect(self._async_execute_search_text)
 
     def cancel_once_tasks(self):
         cancel_once_task(self, self.identity_context_menu)
@@ -88,14 +88,14 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
     def change_community(self, community):
         self.cancel_once_tasks()
         self.community = community
-        self.table_identities.model().change_community(community)
+        self.ui.table_identities.model().change_community(community)
         self._async_search_direct_connections()
 
     @once_at_a_time
     @asyncify
     async def identity_context_menu(self, point):
-        index = self.table_identities.indexAt(point)
-        model = self.table_identities.model()
+        index = self.ui.table_identities.indexAt(point)
+        model = self.ui.table_identities.model()
         if index.isValid() and index.row() < model.rowCount():
             source_index = model.mapToSource(index)
             pubkey_col = model.sourceModel().columns_ids.index('pubkey')
@@ -103,102 +103,19 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
                                                    pubkey_col)
             pubkey = model.sourceModel().data(pubkey_index, Qt.DisplayRole)
             identity = await self.app.identities_registry.future_find(pubkey, self.community)
-            menu = QMenu(self)
-
-            informations = QAction(self.tr("Informations"), self)
-            informations.triggered.connect(self.menu_informations)
-            informations.setData(identity)
-            add_contact = QAction(self.tr("Add as contact"), self)
-            add_contact.triggered.connect(self.menu_add_as_contact)
-            add_contact.setData(identity)
-
-            send_money = QAction(self.tr("Send money"), self)
-            send_money.triggered.connect(self.menu_send_money)
-            send_money.setData(identity)
-
-            certify = QAction(self.tr("Certify identity"), self)
-            certify.triggered.connect(self.menu_certify_member)
-            certify.setData(identity)
-
-            view_wot = QAction(self.tr("View in Web of Trust"), self)
-            view_wot.triggered.connect(self.view_wot)
-            view_wot.setData(identity)
-
-            copy_pubkey = QAction(self.tr("Copy pubkey"), self)
-            copy_pubkey.triggered.connect(self.copy_identity_pubkey)
-            copy_pubkey.setData(identity)
-
-            menu.addAction(informations)
-            menu.addAction(add_contact)
-            menu.addAction(send_money)
-            menu.addAction(certify)
-            menu.addAction(view_wot)
-            menu.addAction(copy_pubkey)
+            menu = ContextMenu.from_data(self.widget, self.app, self.account, self.community, self.password_asker,
+                                         (identity,))
 
             # Show the context menu.
-            menu.popup(QCursor.pos())
-
-    def menu_informations(self):
-        person = self.sender().data()
-        self.identity_informations(person)
-
-    def menu_add_as_contact(self):
-        person = self.sender().data()
-        self.add_identity_as_contact({'name': person.uid,
-                                    'pubkey': person.pubkey})
-
-    def menu_send_money(self):
-        person = self.sender().data()
-        self.send_money_to_identity(person)
-
-    def menu_certify_member(self):
-        person = self.sender().data()
-        self.certify_identity(person)
-
-    def identity_informations(self, person):
-        dialog = MemberDialog(self.app, self.account, self.community, person)
-        dialog.exec_()
-
-    def add_identity_as_contact(self, person):
-        dialog = ConfigureContactDialog(self.account, self.window(), person)
-        result = dialog.exec_()
-        if result == QDialog.Accepted:
-            self.window().refresh_contacts()
-
-    @asyncify
-    async def send_money_to_identity(self, identity):
-        result = await TransferMoneyDialog.send_money_to_identity(self.app, self.account, self.password_asker,
-                                                            self.community, identity)
-        if result == QDialog.Accepted:
-            self.money_sent.emit()
-
-    @asyncify
-    async def certify_identity(self, identity):
-        await CertificationDialog.certify_identity(self.app, self.account, self.password_asker,
-                                             self.community, identity)
-
-    def copy_identity_pubkey(self):
-        """
-        Copy the identity pubkey to the clipboard
-
-        :param sakia.core.registry.Identity identity: The identity
-        """
-        identity = self.sender().data()
-        cb = self.app.qapp.clipboard()
-        cb.clear(mode=cb.Clipboard)
-        cb.setText(identity.pubkey, mode=cb.Clipboard)
-
-    def view_wot(self):
-        identity = self.sender().data()
-        self.view_in_wot.emit(identity)
+            menu.qmenu.popup(QCursor.pos())
 
     @once_at_a_time
     @asyncify
     async def _async_execute_search_text(self, checked):
         cancel_once_task(self, self._async_search_direct_connections)
 
-        self.busy.show()
-        text = self.edit_textsearch.text()
+        self.ui.busy.show()
+        text = self.ui.edit_textsearch.text()
         if len(text) < 2:
             return
         try:
@@ -212,13 +129,13 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
                                                          BlockchainState.BUFFERED)
                     identities.append(identity)
 
-            self.edit_textsearch.clear()
-            self.edit_textsearch.setPlaceholderText(text)
+            self.ui.edit_textsearch.clear()
+            self.ui.edit_textsearch.setPlaceholderText(text)
             await self.refresh_identities(identities)
         except ValueError as e:
             logging.debug(str(e))
         finally:
-            self.busy.hide()
+            self.ui.busy.hide()
 
     @once_at_a_time
     @asyncify
@@ -230,9 +147,9 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
 
         if self.account and self.community:
             try:
-                self.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
+                self.ui.edit_textsearch.setPlaceholderText(self.tr(IdentitiesTabWidget._search_placeholder))
                 await self.refresh_identities([])
-                self.busy.show()
+                self.ui.busy.show()
                 self_identity = await self.account.identity(self.community)
                 account_connections = []
                 certs_of = await self_identity.unique_valid_certifiers_of(self.app.identities_registry, self.community)
@@ -245,25 +162,25 @@ class IdentitiesTabWidget(QWidget, Ui_IdentitiesTab):
                 certified_by = [p for p in account_connections
                           if p.pubkey not in [i.pubkey for i in certifiers_of]]
                 identities = certifiers_of + certified_by
-                self.busy.hide()
+                self.ui.busy.hide()
                 await self.refresh_identities(identities)
             except NoPeerAvailable:
-                self.busy.hide()
+                self.ui.busy.hide()
 
     async def refresh_identities(self, identities):
         """
         Refresh the table with specified identities.
         If no identities is passed, use the account connections.
         """
-        await self.table_identities.model().sourceModel().refresh_identities(identities)
-        self.table_identities.resizeColumnsToContents()
+        await self.ui.table_identities.model().sourceModel().refresh_identities(identities)
+        self.ui.table_identities.resizeColumnsToContents()
 
     def retranslateUi(self, widget):
         self.direct_connections.setText(self.tr(IdentitiesTabWidget._direct_connections_text))
         super().retranslateUi(self)
 
     def resizeEvent(self, event):
-        self.busy.resize(event.size())
+        self.ui.busy.resize(event.size())
         super().resizeEvent(event)
 
     def changeEvent(self, event):
diff --git a/src/sakia/gui/transactions_tab.py b/src/sakia/gui/transactions_tab.py
index 833f7f35..cd70abe5 100644
--- a/src/sakia/gui/transactions_tab.py
+++ b/src/sakia/gui/transactions_tab.py
@@ -184,7 +184,7 @@ class TransactionsTabWidget(QObject):
                                          (identity, transfer))
 
             # Show the context menu.
-            menu.popup(QCursor.pos())
+            menu.qmenu.popup(QCursor.pos())
 
     def dates_changed(self):
         logging.debug("Changed dates")
diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py
index fa836167..8cb327cc 100644
--- a/src/sakia/gui/widgets/context_menu.py
+++ b/src/sakia/gui/widgets/context_menu.py
@@ -1,4 +1,5 @@
 from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
+from PyQt5.QtCore import QObject, pyqtSignal
 from ucoinpy.documents import Block
 from ..member import MemberDialog
 from ..contact import ConfigureContactDialog
@@ -9,16 +10,19 @@ from ...core.transfer import Transfer, TransferState
 from ...core.registry import Identity
 
 
-class ContextMenu(QMenu):
-    def __init__(self, parent, app, account, community, password_asker):
+class ContextMenu(QObject):
+    view_identity_in_wot = pyqtSignal(object)
+
+    def __init__(self, qmenu, app, account, community, password_asker):
         """
-        :param PyQt5.QtWidgets.QWidget: the parent widget
+        :param PyQt5.QtWidgets.QMenu: the qmenu widget
         :param sakia.core.Application app: Application instance
         :param sakia.core.Account account: The current account instance
         :param sakia.core.Community community: The community instance
         :param sakia.gui.PasswordAsker password_asker: The password dialog
         """
-        super().__init__(parent)
+        super().__init__()
+        self.qmenu = qmenu
         self._app = app
         self._community = community
         self._account = account
@@ -30,27 +34,31 @@ class ContextMenu(QMenu):
         :param ContextMenu menu: the qmenu to add actions to
         :param Identity identity: the identity
         """
-        menu.addSeparator().setText(menu.tr("Identity"))
+        menu.qmenu.addSeparator().setText(menu.qmenu.tr("Identity"))
 
-        informations = QAction(menu.tr("Informations"), menu.parent())
+        informations = QAction(menu.qmenu.tr("Informations"), menu.qmenu.parent())
         informations.triggered.connect(lambda checked, i=identity: menu.informations(i))
-        menu.addAction(informations)
+        menu.qmenu.addAction(informations)
 
-        add_as_contact = QAction(menu.tr("Add as contact"), menu.parent())
+        add_as_contact = QAction(menu.qmenu.tr("Add as contact"), menu.qmenu.parent())
         add_as_contact.triggered.connect(lambda checked,i=identity: menu.add_as_contact(i))
-        menu.addAction(add_as_contact)
+        menu.qmenu.addAction(add_as_contact)
 
-        send_money = QAction(menu.tr("Send money"), menu.parent())
+        send_money = QAction(menu.qmenu.tr("Send money"), menu.qmenu.parent())
         send_money.triggered.connect(lambda checked, i=identity: menu.send_money(i))
-        menu.addAction(send_money)
+        menu.qmenu.addAction(send_money)
+
+        certify = QAction(menu.tr("Certify identity"), menu.qmenu.parent())
+        certify.triggered.connect(lambda checked, i=identity: menu.certify_identity(i))
+        menu.qmenu.addAction(certify)
 
-        view_wot = QAction(menu.tr("View in Web of Trust"), menu.parent())
+        view_wot = QAction(menu.qmenu.tr("View in Web of Trust"), menu.qmenu.parent())
         view_wot.triggered.connect(lambda checked, i=identity: menu.view_wot(i))
-        menu.addAction(view_wot)
+        menu.qmenu.addAction(view_wot)
 
-        copy_pubkey = QAction(menu.tr("Copy pubkey to clipboard"), menu.parent())
+        copy_pubkey = QAction(menu.qmenu.tr("Copy pubkey to clipboard"), menu.qmenu.parent())
         copy_pubkey.triggered.connect(lambda checked, i=identity: ContextMenu.copy_pubkey_to_clipboard(i))
-        menu.addAction(copy_pubkey)
+        menu.qmenu.addAction(copy_pubkey)
 
     @staticmethod
     def _add_transfers_actions(menu, transfer):
@@ -58,25 +66,25 @@ class ContextMenu(QMenu):
         :param ContextMenu menu: the qmenu to add actions to
         :param Transfer transfer: the transfer
         """
-        menu.addSeparator().setText(menu.tr("Transfer"))
+        menu.qmenu.addSeparator().setText(menu.qmenu.tr("Transfer"))
         if transfer.state in (TransferState.REFUSED, TransferState.TO_SEND):
-            send_back = QAction(menu.tr("Send again"), menu.parent())
+            send_back = QAction(menu.qmenu.tr("Send again"), menu.qmenu.parent())
             send_back.triggered.connect(lambda checked, tr=transfer: menu.send_again(tr))
-            menu.addAction(send_back)
+            menu.qmenu.addAction(send_back)
 
-            cancel = QAction(menu.tr("Cancel"), menu.parent())
+            cancel = QAction(menu.qmenu.tr("Cancel"), menu.qmenu.parent())
             cancel.triggered.connect(lambda checked, tr=transfer: menu.cancel_transfer(tr))
-            menu.addAction(cancel)
+            menu.qmenu.addAction(cancel)
 
         if menu._app.preferences['expert_mode']:
-            copy_doc = QAction(menu.tr("Copy raw transaction to clipboard"), menu.parent())
+            copy_doc = QAction(menu.qmenu.tr("Copy raw transaction to clipboard"), menu.qmenu.parent())
             copy_doc.triggered.connect(lambda checked, tx=transfer: menu.copy_transaction_to_clipboard(tx))
-            menu.addAction(copy_doc)
+            menu.qmenu.addAction(copy_doc)
 
-            copy_doc = QAction(menu.tr("Copy transaction block to clipboard"), menu.parent())
+            copy_doc = QAction(menu.qmenu.tr("Copy transaction block to clipboard"), menu.qmenu.parent())
             copy_doc.triggered.connect(lambda checked, number=transfer.blockid.number:
                                        menu.copy_block_to_clipboard(number))
-            menu.addAction(copy_doc)
+            menu.qmenu.addAction(copy_doc)
 
 
     @classmethod
@@ -94,7 +102,7 @@ class ContextMenu(QMenu):
         :param tuple data: a tuple of data to add to the menu
         :rtype: ContextMenu
         """
-        menu = cls(parent, app, account, community, password_asker)
+        menu = cls(QMenu(parent), app, account, community, password_asker)
         build_actions = {
             Identity: ContextMenu._add_identity_actions,
             Transfer: ContextMenu._add_transfers_actions,
@@ -129,7 +137,7 @@ class ContextMenu(QMenu):
         #self.ui.table_history.model().sourceModel().refresh_transfers()
 
     def view_wot(self, identity):
-        self._app.view_identity_in_wot.emit(identity)
+        self.view_identity_in_wot.emit(identity)
 
     @asyncify
     async def certify_identity(self, identity):
diff --git a/src/sakia/tests/unit/gui/test_context_menu.py b/src/sakia/tests/unit/gui/test_context_menu.py
new file mode 100644
index 00000000..2e3fa7bb
--- /dev/null
+++ b/src/sakia/tests/unit/gui/test_context_menu.py
@@ -0,0 +1,67 @@
+import unittest
+from unittest.mock import patch, MagicMock, Mock
+from asynctest.mock import CoroutineMock
+from asynctest.mock import MagicMock as AsyncMagicMock
+from PyQt5.QtCore import QLocale, pyqtSignal
+from sakia.tests import QuamashTest
+from sakia.tests.mocks.bma import nice_blockchain
+from sakia.gui.widgets import ContextMenu
+
+
+class TestContextMenu(unittest.TestCase, QuamashTest):
+    def setUp(self):
+        self.setUpQuamash()
+        QLocale.setDefault(QLocale("en_GB"))
+
+        self.identity = Mock(specs='sakia.core.registry.Identity')
+        self.identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
+        self.identity.uid = "A"
+
+        self.app = MagicMock(specs='sakia.core.Application')
+        self.account = MagicMock(specs='sakia.core.Account')
+        self.community = MagicMock(specs='sakia.core.Community')
+        self.password_asker = MagicMock(specs='sakia.gui.password_asker.PasswordAsker')
+
+    def tearDown(self):
+        self.tearDownQuamash()
+
+    @patch('PyQt5.QtWidgets.QMenu', create=True)
+    def test_view_in_wot(self, qmenu):
+        wot_refreshed = False
+
+        def refresh_wot(identity):
+            nonlocal wot_refreshed
+            self.assertEqual(identity, self.identity)
+            wot_refreshed = True
+
+        async def exec_test():
+            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
+            context_menu.view_identity_in_wot.connect(refresh_wot)
+            context_menu.view_wot(self.identity)
+
+        self.lp.run_until_complete(exec_test())
+        self.assertTrue(wot_refreshed)
+
+    @patch('PyQt5.QtWidgets.QMenu', create=True)
+    def test_copy_pubkey_to_clipboard(self, qmenu):
+        app = Mock('sakia.core.Application')
+        async def exec_test():
+            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
+            context_menu.copy_pubkey_to_clipboard(self.identity)
+        self.lp.run_until_complete(exec_test())
+        self.assertEqual(self.qapplication.clipboard().text(), "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
+
+    @patch('PyQt5.QtWidgets.QMenu', create=True)
+    def test_copy_block_to_clipboard(self, qmenu):
+        self.community.get_block = CoroutineMock(side_effect=lambda n: nice_blockchain.bma_blockchain_current if n == 15 \
+                                                    else nice_blockchain.bma_blockchain_0)
+        self.qapplication.clipboard().clear()
+        async def exec_test():
+            context_menu = ContextMenu(qmenu, self.app, self.account, self.community, self.password_asker)
+            context_menu.community = self.community
+            context_menu.copy_block_to_clipboard(15)
+
+        self.lp.run_until_complete(exec_test())
+        raw_block = "{0}{1}\n".format(nice_blockchain.bma_blockchain_current["raw"],
+                                      nice_blockchain.bma_blockchain_current["signature"])
+        self.assertEqual(self.qapplication.clipboard().text(), raw_block)
diff --git a/src/sakia/tests/unit/gui/test_transactions_tab.py b/src/sakia/tests/unit/gui/test_transactions_tab.py
deleted file mode 100644
index 9efecc70..00000000
--- a/src/sakia/tests/unit/gui/test_transactions_tab.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import unittest
-from unittest.mock import patch, MagicMock, Mock
-from asynctest.mock import CoroutineMock
-from asynctest.mock import MagicMock as AsyncMagicMock
-from PyQt5.QtCore import QLocale, pyqtSignal
-from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-from sakia.gui.transactions_tab import TransactionsTabWidget, Ui_transactionsTabWidget, QWidget
-
-
-class TestTransactionTab(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-
-        self.identity = Mock(specs='core.registry.Identity')
-        self.identity.pubkey = "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk"
-        self.identity.uid = "A"
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    @patch('sakia.gen_resources.transactions_tab_uic.Ui_transactionsTabWidget', create=True)
-    @patch('PyQt5.QtWidgets.QWidget', create=True)
-    def test_view_in_wot(self, widget, view):
-        wot_refreshed = False
-
-        def refresh_wot(identity):
-            nonlocal wot_refreshed
-            self.assertEqual(identity, self.identity)
-            wot_refreshed = True
-
-        app = Mock('sakia.core.Application')
-        async def exec_test():
-            transaction_tab = TransactionsTabWidget(app, widget, view)
-            transaction_tab.view_in_wot.connect(refresh_wot)
-            transaction_tab.view_wot(self.identity)
-
-        self.lp.run_until_complete(exec_test())
-        self.assertTrue(wot_refreshed)
-
-    @patch('sakia.gen_resources.transactions_tab_uic.Ui_transactionsTabWidget', create=True)
-    @patch('PyQt5.QtWidgets.QWidget', create=True)
-    def copy_pubkey_to_clipboard(self, widget, view):
-        app = Mock('sakia.core.Application')
-        async def exec_test():
-            transaction_tab = TransactionsTabWidget(app, widget, view)
-            transaction_tab.copy_pubkey_to_clipboard(self.identity)
-        self.lp.run_until_complete(exec_test())
-        self.assertEqual(self.qapplication.clipboard().text(), "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk")
-
-    @patch('sakia.gen_resources.transactions_tab_uic.Ui_transactionsTabWidget', create=True)
-    @patch('PyQt5.QtWidgets.QWidget', create=True)
-    def test_copy_block_to_clipboard(self, widget, view):
-        app = AsyncMagicMock('sakia.core.Application')
-        community = Mock('sakia.core.Community')
-        community.get_block = CoroutineMock(side_effect=lambda n: nice_blockchain.bma_blockchain_current if n == 15 \
-                                                    else nice_blockchain.bma_blockchain_0)
-        self.qapplication.clipboard().clear()
-        async def exec_test():
-            transaction_tab = TransactionsTabWidget(app, widget, view)
-            transaction_tab.community = community
-            transaction_tab.copy_block_to_clipboard(15)
-
-        self.lp.run_until_complete(exec_test())
-        raw_block = "{0}{1}\n".format(nice_blockchain.bma_blockchain_current["raw"],
-                                      nice_blockchain.bma_blockchain_current["signature"])
-        self.assertEqual(self.qapplication.clipboard().text(), raw_block)
-- 
GitLab