src/eric7/Testing/TestingWidget.py

branch
eric7-maintenance
changeset 10460
3b34efa2857c
parent 10349
df7edc29cbfb
parent 10453
16235de22ee7
child 10694
f46c1e224e8a
diff -r 411df92e881f -r 3b34efa2857c src/eric7/Testing/TestingWidget.py
--- a/src/eric7/Testing/TestingWidget.py	Sun Dec 03 14:54:00 2023 +0100
+++ b/src/eric7/Testing/TestingWidget.py	Mon Jan 01 11:10:45 2024 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-# Copyright (c) 2022 - 2023 Detlev Offenbach <detlev@die-offenbachs.de>
+# Copyright (c) 2022 - 2024 Detlev Offenbach <detlev@die-offenbachs.de>
 #
 
 """
@@ -12,8 +12,15 @@
 import locale
 import os
 
-from PyQt6.QtCore import QCoreApplication, QEvent, Qt, pyqtSignal, pyqtSlot
-from PyQt6.QtWidgets import QAbstractButton, QComboBox, QDialogButtonBox, QWidget
+from PyQt6.QtCore import QCoreApplication, QEvent, QPoint, Qt, pyqtSignal, pyqtSlot
+from PyQt6.QtWidgets import (
+    QAbstractButton,
+    QComboBox,
+    QDialogButtonBox,
+    QMenu,
+    QTreeWidgetItem,
+    QWidget,
+)
 
 from eric7 import Preferences
 from eric7.DataViews.PyCoverageDialog import PyCoverageDialog
@@ -33,7 +40,11 @@
 from .Interfaces import Frameworks
 from .Interfaces.TestExecutorBase import TestConfig, TestResult, TestResultCategory
 from .Interfaces.TestFrameworkRegistry import TestFrameworkRegistry
-from .TestResultsTree import TestResultsModel, TestResultsTreeView
+from .TestResultsTree import (
+    TestResultsFilterModel,
+    TestResultsModel,
+    TestResultsTreeView,
+)
 from .Ui_TestingWidget import Ui_TestingWidget
 
 
@@ -45,6 +56,7 @@
     IDLE = 0  # idle, no test were run yet
     RUNNING = 1  # test run being performed
     STOPPED = 2  # test run finished
+    DISCOVERY = 3  # discovery of tests being performed
 
 
 class TestingWidget(QWidget, Ui_TestingWidget):
@@ -59,6 +71,11 @@
     testFile = pyqtSignal(str, int, bool)
     testRunStopped = pyqtSignal()
 
+    TestCaseNameRole = Qt.ItemDataRole.UserRole
+    TestCaseFileRole = Qt.ItemDataRole.UserRole + 1
+    TestCaseLinenoRole = Qt.ItemDataRole.UserRole + 2
+    TestCaseIdRole = Qt.ItemDataRole.UserRole + 3
+
     def __init__(self, testfile=None, parent=None):
         """
         Constructor
@@ -73,8 +90,10 @@
 
         self.__resultsModel = TestResultsModel(self)
         self.__resultsModel.summary.connect(self.__setStatusLabel)
+        self.__resultFilterModel = TestResultsFilterModel(self)
+        self.__resultFilterModel.setSourceModel(self.__resultsModel)
         self.__resultsTree = TestResultsTreeView(self)
-        self.__resultsTree.setModel(self.__resultsModel)
+        self.__resultsTree.setModel(self.__resultFilterModel)
         self.__resultsTree.goto.connect(self.__showSource)
         self.resultsGroupBox.layout().addWidget(self.__resultsTree)
 
@@ -99,6 +118,8 @@
         )
         self.testComboBox.lineEdit().setClearButtonEnabled(True)
 
+        self.__allFilter = self.tr("<all>")
+
         # create some more dialog buttons for orchestration
         self.__showLogButton = self.buttonBox.addButton(
             self.tr("Show Output..."), QDialogButtonBox.ButtonRole.ActionRole
@@ -128,11 +149,22 @@
             )
         )
 
+        self.__discoverButton = self.buttonBox.addButton(
+            self.tr("Discover"), QDialogButtonBox.ButtonRole.ActionRole
+        )
+        self.__discoverButton.setToolTip(self.tr("Discover Tests"))
+        self.__discoverButton.setWhatsThis(
+            self.tr(
+                """<b>Discover Tests</b>"""
+                """<p>This button starts a discovery of available tests.</p>"""
+            )
+        )
+
         self.__startButton = self.buttonBox.addButton(
             self.tr("Start"), QDialogButtonBox.ButtonRole.ActionRole
         )
 
-        self.__startButton.setToolTip(self.tr("Start the selected testsuite"))
+        self.__startButton.setToolTip(self.tr("Start the selected test suite"))
         self.__startButton.setWhatsThis(
             self.tr("""<b>Start Test</b><p>This button starts the test run.</p>""")
         )
@@ -171,7 +203,6 @@
             self.__project = ericApp().getObject("Project")
             self.__project.projectOpened.connect(self.__projectOpened)
             self.__project.projectClosed.connect(self.__projectClosed)
-            self.__projectEnvironmentMarker = self.tr("<project>")
         except KeyError:
             # we were called as a standalone application
             from eric7.VirtualEnv.VirtualenvManager import (  # __IGNORE_WARNING_I101__
@@ -191,7 +222,9 @@
             ericApp().registerObject("VirtualEnvManager", self.__venvManager)
 
             self.__project = None
-            self.__projectEnvironmentMarker = ""
+
+            self.debuggerCheckBox.setChecked(False)
+            self.debuggerCheckBox.setVisible(False)
 
         self.__discoverHistory = []
         self.__fileHistory = []
@@ -206,9 +239,18 @@
         self.__editors = []
         self.__testExecutor = None
         self.__recentLog = ""
+        self.__projectString = ""
 
         self.__markersWindow = None
 
+        self.__discoveryListContextMenu = QMenu(self.discoveryList)
+        self.__discoveryListContextMenu.addAction(
+            self.tr("Collapse All"), self.discoveryList.collapseAll
+        )
+        self.__discoveryListContextMenu.addAction(
+            self.tr("Expand All"), self.discoveryList.expandAll
+        )
+
         # connect some signals
         self.discoveryPicker.editTextChanged.connect(self.__resetResults)
         self.testsuitePicker.editTextChanged.connect(self.__resetResults)
@@ -248,7 +290,10 @@
         @return path of the interpreter executable
         @rtype str
         """
-        if self.__project and venvName == self.__projectEnvironmentMarker:
+        if (
+            self.__project
+            and venvName == ericApp().getObject("DebugUI").getProjectEnvironmentString()
+        ):
             return self.__project.getProjectInterpreter()
         else:
             return self.__venvManager.getVirtualenvInterpreter(venvName)
@@ -264,7 +309,10 @@
         self.venvComboBox.clear()
         self.venvComboBox.addItem("")
         if self.__project and self.__project.isOpen():
-            self.venvComboBox.addItem(self.__projectEnvironmentMarker)
+            venvName = ericApp().getObject("DebugUI").getProjectEnvironmentString()
+            if venvName:
+                self.venvComboBox.addItem(venvName)
+                self.__projectString = venvName
         self.venvComboBox.addItems(sorted(self.__venvManager.getVirtualenvNames()))
         self.venvComboBox.setCurrentText(currentText)
 
@@ -333,15 +381,16 @@
         @param item item to be inserted
         @type str
         """
-        # prepend the given directory to the discovery picker
-        if item is None:
-            item = ""
-        if item in history:
-            history.remove(item)
-        history.insert(0, item)
-        widget.clear()
-        widget.addItems(history)
-        widget.setEditText(item)
+        if history and item != history[0]:
+            # prepend the given directory to the given widget
+            if item is None:
+                item = ""
+            if item in history:
+                history.remove(item)
+            history.insert(0, item)
+            widget.clear()
+            widget.addItems(history)
+            widget.setEditText(item)
 
     @pyqtSlot(str)
     def __insertDiscovery(self, start):
@@ -496,6 +545,18 @@
         """
         failedAvailable = bool(self.__resultsModel.getFailedTests())
 
+        # Discover button
+        if self.__mode in (TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED):
+            self.__discoverButton.setEnabled(
+                bool(self.venvComboBox.currentText())
+                and bool(self.frameworkComboBox.currentText())
+                and self.discoverCheckBox.isChecked()
+                and bool(self.discoveryPicker.currentText())
+            )
+        else:
+            self.__discoverButton.setEnabled(False)
+            self.__discoverButton.setDefault(False)
+
         # Start button
         if self.__mode in (TestingWidgetModes.IDLE, TestingWidgetModes.STOPPED):
             self.__startButton.setEnabled(
@@ -569,6 +630,21 @@
         self.progressGroupBox.hide()
         self.tabWidget.setCurrentIndex(0)
 
+        self.raise_()
+        self.activateWindow()
+
+    @pyqtSlot()
+    def __setDiscoverMode(self):
+        """
+        Private slot to switch the widget to test discovery mode.
+        """
+        self.__mode = TestingWidgetModes.DISCOVERY
+
+        self.__totalCount = 0
+
+        self.tabWidget.setCurrentIndex(0)
+        self.__updateButtonBoxButtons()
+
     @pyqtSlot()
     def __setRunningMode(self):
         """
@@ -624,6 +700,18 @@
 
         self.__resetResults()
 
+        self.discoveryList.clear()
+
+    @pyqtSlot(str)
+    def on_discoveryPicker_editTextChanged(self, txt):
+        """
+        Private slot to handle a change of the discovery start directory.
+
+        @param txt new discovery start directory
+        @type str
+        """
+        self.discoveryList.clear()
+
     @pyqtSlot()
     def on_testsuitePicker_aboutToShowPathPickerDialog(self):
         """
@@ -664,13 +752,15 @@
         @param button button that was clicked
         @type QAbstractButton
         """
+        if button == self.__discoverButton:
+            self.__discoverTests()
         if button == self.__startButton:
-            self.startTests()
+            self.startTests(debug=self.debuggerCheckBox.isChecked())
             self.__saveRecent()
         elif button == self.__stopButton:
             self.__stopTests()
         elif button == self.__startFailedButton:
-            self.startTests(failedOnly=True)
+            self.startTests(failedOnly=True, debug=self.debuggerCheckBox.isChecked())
         elif button == self.__showCoverageButton:
             self.__showCoverageDialog()
         elif button == self.__showLogButton:
@@ -685,6 +775,8 @@
         @type int
         """
         self.__populateTestFrameworkComboBox()
+        self.discoveryList.clear()
+
         self.__updateButtonBoxButtons()
 
         self.versionsButton.setEnabled(bool(self.venvComboBox.currentText()))
@@ -703,6 +795,7 @@
         self.__updateCoverage()
         self.__updateMarkerSupport()
         self.__updatePatternSupport()
+        self.discoveryList.clear()
 
     @pyqtSlot()
     def __updateCoverage(self):
@@ -805,7 +898,7 @@
             headerText = self.tr("<h3>Versions of Frameworks and their Plugins</h3>")
             versionsText = ""
             interpreter = self.__determineInterpreter(venvName)
-            for framework in sorted(self.__frameworkRegistry.getFrameworks().keys()):
+            for framework in sorted(self.__frameworkRegistry.getFrameworks()):
                 executor = self.__frameworkRegistry.createExecutor(framework, self)
                 versions = executor.getVersions(interpreter)
                 if versions:
@@ -832,14 +925,59 @@
             )
 
     @pyqtSlot()
-    def startTests(self, failedOnly=False):
+    def __discoverTests(self):
+        """
+        Private slot to discover tests but don't execute them.
+        """
+        if self.__mode in (TestingWidgetModes.RUNNING, TestingWidgetModes.DISCOVERY):
+            return
+
+        self.__recentLog = ""
+
+        environment = self.venvComboBox.currentText()
+        framework = self.frameworkComboBox.currentText()
+
+        discoveryStart = self.discoveryPicker.currentText()
+        if discoveryStart:
+            self.__insertDiscovery(discoveryStart)
+
+        self.sbLabel.setText(self.tr("Discovering Tests"))
+        QCoreApplication.processEvents()
+
+        interpreter = self.__determineInterpreter(environment)
+        config = TestConfig(
+            interpreter=interpreter,
+            discover=True,
+            discoveryStart=discoveryStart,
+            discoverOnly=True,
+            testNamePattern=self.testNamePatternEdit.text(),
+            testMarkerExpression=self.markerExpressionEdit.text(),
+            failFast=self.failfastCheckBox.isChecked(),
+        )
+
+        self.__testExecutor = self.__frameworkRegistry.createExecutor(framework, self)
+        self.__testExecutor.collected.connect(self.__testsDiscovered)
+        self.__testExecutor.collectError.connect(self.__testDiscoveryError)
+        self.__testExecutor.testFinished.connect(self.__testDiscoveryProcessFinished)
+        self.__testExecutor.discoveryAboutToBeStarted.connect(
+            self.__testDiscoveryAboutToBeStarted
+        )
+
+        self.__setDiscoverMode()
+        self.__testExecutor.discover(config, [])
+
+    @pyqtSlot()
+    def startTests(self, failedOnly=False, debug=False):
         """
         Public slot to start the test run.
 
-        @param failedOnly flag indicating to run only failed tests
-        @type bool
+        @param failedOnly flag indicating to run only failed tests (defaults to False)
+        @type bool (optional)
+        @param debug flag indicating to start the test run with debugger support
+            (defaults to False)
+        @type bool (optional)
         """
-        if self.__mode == TestingWidgetModes.RUNNING:
+        if self.__mode in (TestingWidgetModes.RUNNING, TestingWidgetModes.DISCOVERY):
             return
 
         self.__recentLog = ""
@@ -876,10 +1014,22 @@
         else:
             coverageFile = ""
         interpreter = self.__determineInterpreter(self.__recentEnvironment)
+
+        testCases = self.__selectedTestCases()
+        if not testCases and self.discoveryList.topLevelItemCount() > 0:
+            ok = EricMessageBox.yesNo(
+                self,
+                self.tr("Running Tests"),
+                self.tr("No test case has been selected. Shall all test cases be run?"),
+            )
+            if not ok:
+                return
+
         config = TestConfig(
             interpreter=interpreter,
             discover=discover,
             discoveryStart=discoveryStart,
+            testCases=testCases,
             testFilename=testFileName,
             testName=testName,
             testNamePattern=self.testNamePatternEdit.text(),
@@ -889,6 +1039,7 @@
             collectCoverage=self.coverageCheckBox.isChecked(),
             eraseCoverage=self.coverageEraseCheckBox.isChecked(),
             coverageFile=coverageFile,
+            venvName=self.__recentEnvironment,
         )
 
         self.__testExecutor = self.__frameworkRegistry.createExecutor(
@@ -907,7 +1058,10 @@
         )
 
         self.__setRunningMode()
-        self.__testExecutor.start(config, [])
+        if debug:
+            self.__testExecutor.startDebug(config, [], ericApp().getObject("DebugUI"))
+        else:
+            self.__testExecutor.start(config, [])
 
     @pyqtSlot()
     def __stopTests(self):
@@ -922,8 +1076,9 @@
         Private slot handling the 'collected' signal of the executor.
 
         @param testNames list of tuples containing the test id, the test name
-            and a description of collected tests
-        @type list of tuple of (str, str, str)
+            a description, the file name, the line number and the test path as a list
+            of collected tests
+        @type list of tuple of (str, str, str, str, int, list)
         """
         testResults = [
             TestResult(
@@ -932,8 +1087,10 @@
                 name=name,
                 id=id,
                 message=desc,
+                filename=filename,
+                lineno=lineno,
             )
-            for id, name, desc in testNames
+            for id, name, desc, filename, lineno, _ in testNames
         ]
         self.__resultsModel.addTestResults(testResults)
         self.__resultsTree.resizeColumns()
@@ -1029,6 +1186,7 @@
         self.__testExecutor = None
 
         self.__adjustPendingState()
+        self.__updateStatusFilterComboBox()
 
     @pyqtSlot(int, float)
     def __testRunFinished(self, noTests, duration):
@@ -1064,6 +1222,7 @@
         executor.
         """
         self.__resultsModel.clear()
+        self.statusFilterComboBox.clear()
 
     def __adjustPendingState(self):
         """
@@ -1136,8 +1295,20 @@
         """
         Private slot to handle a project being opened.
         """
-        self.venvComboBox.insertItem(1, self.__projectEnvironmentMarker)
-        self.venvComboBox.setCurrentIndex(1)
+        self.__projectString = (
+            ericApp().getObject("DebugUI").getProjectEnvironmentString()
+        )
+
+        if self.__projectString:
+            # 1a. remove old project venv entries
+            while (row := self.venvComboBox.findText(self.__projectString)) != -1:
+                self.venvComboBox.removeItem(row)
+
+            # 1b. add a new project venv entry
+            self.venvComboBox.insertItem(1, self.__projectString)
+            self.venvComboBox.setCurrentIndex(1)
+
+        # 2. set some other project related stuff
         self.frameworkComboBox.setCurrentText(
             self.__project.getProjectTestingFramework()
         )
@@ -1148,11 +1319,18 @@
         """
         Private slot to handle a project being closed.
         """
-        self.venvComboBox.removeItem(1)  # <project> is always at index 1
-        self.venvComboBox.setCurrentText("")
+        if self.__projectString:
+            while (row := self.venvComboBox.findText(self.__projectString)) != -1:
+                self.venvComboBox.removeItem(row)
+
+            self.venvComboBox.setCurrentText("")
+
         self.frameworkComboBox.setCurrentText("")
         self.__insertDiscovery("")
 
+        # clear latest log assuming it was for a project test run
+        self.__recentLog = ""
+
     @pyqtSlot(str, int)
     def __showSource(self, filename, lineno):
         """
@@ -1168,6 +1346,7 @@
             self.testFile.emit(filename, lineno, True)
         else:
             self.__openEditor(filename, lineno)
+            self.__resultsTree.resizeColumns()
 
     def __openEditor(self, filename, linenumber=1):
         """
@@ -1202,6 +1381,213 @@
             with contextlib.suppress(RuntimeError):
                 editor.close()
 
+    @pyqtSlot(str)
+    def on_statusFilterComboBox_currentTextChanged(self, status):
+        """
+        Private slot handling the selection of a status for items to be shown.
+
+        @param status selected status
+        @type str
+        """
+        if status == self.__allFilter:
+            status = ""
+
+        self.__resultFilterModel.setStatusFilterString(status)
+
+        if not self.__project:
+            # running in standalone mode
+            self.__resultsTree.resizeColumns()
+
+    def __updateStatusFilterComboBox(self):
+        """
+        Private method to update the status filter dialog box.
+        """
+        statusFilters = self.__resultsModel.getStatusFilterList()
+        self.statusFilterComboBox.clear()
+        self.statusFilterComboBox.addItem(self.__allFilter)
+        self.statusFilterComboBox.addItems(sorted(statusFilters))
+
+    ############################################################################
+    ## Methods below are handling the discovery only mode.
+    ############################################################################
+
+    def __findDiscoveryItem(self, modulePath):
+        """
+        Private method to find an item given the module path.
+
+        @param modulePath path of the module in dotted notation
+        @type str
+        @return reference to the item or None
+        @rtype QTreeWidgetItem or None
+        """
+        itm = self.discoveryList.topLevelItem(0)
+        while itm is not None:
+            if itm.data(0, TestingWidget.TestCaseNameRole) == modulePath:
+                return itm
+
+            itm = self.discoveryList.itemBelow(itm)
+
+        return None
+
+    @pyqtSlot(list)
+    def __testsDiscovered(self, testNames):
+        """
+        Private slot handling the 'collected' signal of the executor in discovery
+        mode.
+
+        @param testNames list of tuples containing the test id, the test name
+            a description, the file name, the line number and the test path as a list
+            of collected tests
+        @type list of tuple of (str, str, str, str, int, list)
+        """
+        for tid, _name, _desc, filename, lineno, testPath in testNames:
+            parent = None
+            for index in range(1, len(testPath) + 1):
+                modulePath = ".".join(testPath[:index])
+                itm = self.__findDiscoveryItem(modulePath)
+                if itm is not None:
+                    parent = itm
+                else:
+                    if parent is None:
+                        itm = QTreeWidgetItem(self.discoveryList, [testPath[index - 1]])
+                    else:
+                        itm = QTreeWidgetItem(parent, [testPath[index - 1]])
+                        parent.setExpanded(True)
+                    itm.setFlags(itm.flags() | Qt.ItemFlag.ItemIsUserCheckable)
+                    itm.setCheckState(0, Qt.CheckState.Unchecked)
+                    itm.setData(0, TestingWidget.TestCaseNameRole, modulePath)
+                    itm.setData(0, TestingWidget.TestCaseLinenoRole, 0)
+                    if os.path.splitext(os.path.basename(filename))[0] == itm.text(0):
+                        itm.setData(0, TestingWidget.TestCaseFileRole, filename)
+                    elif parent:
+                        fn = parent.data(0, TestingWidget.TestCaseFileRole)
+                        if fn:
+                            itm.setData(0, TestingWidget.TestCaseFileRole, fn)
+                    parent = itm
+
+            if parent:
+                parent.setData(0, TestingWidget.TestCaseLinenoRole, lineno)
+                parent.setData(0, TestingWidget.TestCaseIdRole, tid)
+
+        self.__totalCount += len(testNames)
+
+        self.sbLabel.setText(self.tr("Discovered %n Test(s)", "", self.__totalCount))
+
+    def __testDiscoveryError(self, errors):
+        """
+        Private slot handling the 'collectError' signal of the executor.
+
+        @param errors list of tuples containing the test name and a description
+            of the error
+        @type list of tuple of (str, str)
+        """
+        for _testFile, error in errors:
+            EricMessageBox.critical(
+                self,
+                self.tr("Discovery Error"),
+                self.tr(
+                    "<p>There was an error while discovering tests in <b>{0}</b>.</p>"
+                    "<p>{1}</p>"
+                ).format(
+                    self.discoveryPicker.currentText(),
+                    "<br/>".join(error.splitlines()),
+                ),
+            )
+        self.sbLabel.clear()
+
+    def __testDiscoveryProcessFinished(self, results, output):  # noqa: U100
+        """
+        Private slot to handle the 'testFinished' signal of the executor in
+        discovery mode.
+
+        @param results list of test result objects (if not sent via the
+            'testResult' signal)
+        @type list of TestResult
+        @param output string containing the test process output (if any)
+        @type str
+        """
+        self.__recentLog = output
+        self.discoveryList.sortItems(0, Qt.SortOrder.AscendingOrder)
+
+        self.__setIdleMode()
+
+    def __testDiscoveryAboutToBeStarted(self):
+        """
+        Private slot to handle the 'testDiscoveryAboutToBeStarted' signal of the
+        executor.
+        """
+        self.discoveryList.clear()
+
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_discoveryList_itemChanged(self, item, column):
+        """
+        Private slot handling the user checking or unchecking an item.
+
+        @param item reference to the item
+        @type QTreeWidgetItem
+        @param column changed column
+        @type int
+        """
+        if column == 0:
+            for index in range(item.childCount()):
+                item.child(index).setCheckState(0, item.checkState(0))
+
+    @pyqtSlot(QTreeWidgetItem, int)
+    def on_discoveryList_itemActivated(self, item, column):
+        """
+        Private slot handling the user activating an item.
+
+        @param item reference to the item
+        @type QTreeWidgetItem
+        @param column column of the double click
+        @type int
+        """
+        if item:
+            filename = item.data(0, TestingWidget.TestCaseFileRole)
+            if filename:
+                self.__showSource(
+                    filename, item.data(0, TestingWidget.TestCaseLinenoRole) + 1
+                )
+
+    def __selectedTestCases(self, parent=None):
+        """
+        Private method to assemble the list of selected test cases and suites.
+
+        @param parent reference to the parent item
+        @type QTreeWidgetItem
+        @return list of selected test cases
+        @rtype list of str
+        """
+        selectedTests = []
+        itemsList = (
+            [
+                # top level
+                self.discoveryList.topLevelItem(index)
+                for index in range(self.discoveryList.topLevelItemCount())
+            ]
+            if parent is None
+            else [parent.child(index) for index in range(parent.childCount())]
+        )
+
+        for itm in itemsList:
+            if itm.checkState(0) == Qt.CheckState.Checked and itm.childCount() == 0:
+                selectedTests.append(itm.data(0, TestingWidget.TestCaseIdRole))
+            if itm.childCount():
+                # recursively check children
+                selectedTests.extend(self.__selectedTestCases(itm))
+
+        return selectedTests
+
+    @pyqtSlot(QPoint)
+    def on_discoveryList_customContextMenuRequested(self, pos):
+        """
+        Private slot to show the context menu of the dicovery list.
+
+        @param pos the position of the mouse pointer
+        @type QPoint
+        """
+        self.__discoveryListContextMenu.exec(self.discoveryList.mapToGlobal(pos))
+
 
 class TestingWindow(EricMainWindow):
     """
@@ -1235,9 +1621,12 @@
         """
         Public method to filter events.
 
-        @param obj reference to the object the event is meant for (QObject)
-        @param event reference to the event object (QEvent)
-        @return flag indicating, whether the event was handled (boolean)
+        @param obj reference to the object the event is meant for
+        @type QObject
+        @param event reference to the event object
+        @type QEvent
+        @return flag indicating, whether the event was handled
+        @rtype bool
         """
         if event.type() == QEvent.Type.Close:
             QCoreApplication.exit(0)

eric ide

mercurial