eric7/Unittest/UTTestResultsTree.py

branch
unittest
changeset 9062
7f27bf3b50c3
parent 9059
e7fd342f8bfc
child 9063
f1d7dd7ae471
diff -r 22dab1be7953 -r 7f27bf3b50c3 eric7/Unittest/UTTestResultsTree.py
--- a/eric7/Unittest/UTTestResultsTree.py	Thu May 12 09:00:35 2022 +0200
+++ b/eric7/Unittest/UTTestResultsTree.py	Fri May 13 17:23:21 2022 +0200
@@ -8,11 +8,23 @@
 data.
 """
 
+import contextlib
+import copy
+import locale
+from operator import attrgetter
+
 from PyQt6.QtCore import (
     pyqtSignal, pyqtSlot, Qt, QAbstractItemModel, QCoreApplication, QModelIndex
 )
+from PyQt6.QtGui import QBrush, QColor
 from PyQt6.QtWidgets import QTreeView
 
+from EricWidgets.EricApplication import ericApp
+
+import Preferences
+
+from .Interfaces.UTExecutorBase import ResultCategory
+
 TopLevelId = 2 ** 32 - 1
 
 
@@ -27,6 +39,11 @@
         QCoreApplication.translate("TestResultsModel", "Duration (ms)"),
     ]
     
+    StatusColumn = 0
+    NameColumn = 1
+    MessageColumn = 2
+    DurationColumn = 3
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -36,8 +53,107 @@
         """
         super().__init__(parent)
         
+        if ericApp().usesDarkPalette():
+            self.__backgroundColors = {
+                ResultCategory.RUNNING: None,
+                ResultCategory.FAIL: QBrush(QColor("#880000")),
+                ResultCategory.OK: QBrush(QColor("#005500")),
+                ResultCategory.SKIP: QBrush(QColor("#3f3f3f")),
+                ResultCategory.PENDING: QBrush(QColor("#004768")),
+            }
+        else:
+            self.__backgroundColors = {
+                ResultCategory.RUNNING: None,
+                ResultCategory.FAIL: QBrush(QColor("#ff8080")),
+                ResultCategory.OK: QBrush(QColor("#c1ffba")),
+                ResultCategory.SKIP: QBrush(QColor("#c5c5c5")),
+                ResultCategory.PENDING: QBrush(QColor("#6fbaff")),
+            }
+        
         self.__testResults = []
     
+    def index(self, row, column, parent=QModelIndex()):
+        """
+        Public method to generate an index for the given row and column to
+        identify the item.
+        
+        @param row row for the index
+        @type int
+        @param column column for the index
+        @type int
+        @param parent index of the parent item (defaults to QModelIndex())
+        @type QModelIndex (optional)
+        @return index for the item
+        @rtype QModelIndex
+        """
+        if not self.hasIndex(row, column, parent):  # check bounds etc.
+            return QModelIndex()
+        
+        if not parent.isValid():
+            # top level item
+            return self.createIndex(row, column, TopLevelId)
+        else:
+            testResultIndex = parent.row()
+            return self.createIndex(row, column, testResultIndex)
+    
+    def data(self, index, role):
+        """
+        Public method to get the data for the various columns and roles.
+        
+        @param index index of the data to be returned
+        @type QModelIndex
+        @param role role designating the data to return
+        @type Qt.ItemDataRole
+        @return requested data item
+        @rtype Any
+        """
+        if not index.isValid():
+            return None
+        
+        row = index.row()
+        column = index.column()
+        idx = index.internalId()
+        
+        if role == Qt.ItemDataRole.DisplayRole:
+            if idx != TopLevelId:
+                if bool(self.__testResults[idx].extra):
+                    return self.__testResults[idx].extra[index.row()]
+                else:
+                    return None
+            elif column == TestResultsModel.StatusColumn:
+                return self.__testResults[row].status
+            elif column == TestResultsModel.NameColumn:
+                return self.__testResults[row].name
+            elif column == TestResultsModel.MessageColumn:
+                return self.__testResults[row].message
+            elif column == TestResultsModel.DurationColumn:
+                duration = self.__testResults[row].duration
+                return (
+                    ''
+                    if duration is None else
+                    locale.format_string("%.2f", duration, grouping=True)
+                )
+        elif role == Qt.ItemDataRole.ToolTipRole:
+            if idx == TopLevelId and column == TestResultsModel.NameColumn:
+                return self.testresults[row].name
+        elif role == Qt.ItemDataRole.FontRole:
+            if idx != TopLevelId:
+                return Preferences.getEditorOtherFonts("MonospacedFont")
+        elif role == Qt.ItemDataRole.BackgroundRole:
+            if idx == TopLevelId:
+                testResult = self.__testResults[row]
+                with contextlib.suppress(KeyError):
+                    return self.__backgroundColors[testResult.category]
+        elif role == Qt.ItemDataRole.TextAlignmentRole:
+            if idx == TopLevelId and column == TestResultsModel.DurationColumn:
+                return Qt.AlignmentFlag.AlignRight
+        elif role == Qt.ItemDataRole.UserRole:      # __IGNORE_WARNING_Y102__
+            if idx == TopLevelId:
+                testresult = self.testresults[row]
+                return (testresult.filename, testresult.lineno)
+        
+        return None
+    
     def headerData(self, section, orientation,
                    role=Qt.ItemDataRole.DisplayRole):
         """
@@ -60,6 +176,24 @@
         else:
             return None
     
+    def parent(self, index):
+        """
+        Public method to get the parent of the item pointed to by index.
+        
+        @param index index of the item
+        @type QModelIndex
+        @return index of the parent item
+        @rtype QModelIndex
+        """
+        if not index.isValid():
+            return QModelIndex()
+        
+        idx = index.internalId()
+        if idx == TopLevelId:
+            return QModelIndex()
+        else:
+            return self.index(idx, 0)
+    
     def rowCount(self, parent=QModelIndex()):
         """
         Public method to get the number of row for a given parent index.
@@ -72,7 +206,11 @@
         if not parent.isValid():
             return len(self.__testResults)
         
-        if parent.internalId() == TopLevelId and parent.column() == 0:
+        if (
+            parent.internalId() == TopLevelId and
+            parent.column() == 0 and
+            self.__testResults[parent.row()].extra is not None
+        ):
             return len(self.__testResults[parent.row()].extra)
         
         return 0
@@ -98,6 +236,100 @@
         self.beginResetModel()
         self.__testResults.clear()
         self.endResetModel()
+    
+    def sort(self, column, order):
+        """
+        Public method to sort the model data by column in order.
+        
+        @param column sort column number
+        @type int
+        @param order sort order
+        @type Qt.SortOrder
+        """             # __IGNORE_WARNING_D234r__
+        def durationKey(result):
+            """
+            Function to generate a key for duration sorting
+            
+            @param result result object
+            @type UTTestResult
+            @return sort key
+            @rtype float
+            """
+            return result.duration or -1.0
+
+        self.beginResetModel()
+        reverse = order == Qt.SortOrder.DescendingOrder
+        if column == TestResultsModel.StatusColumn:
+            self.__testResults.sort(key=attrgetter('category', 'status'),
+                                    reverse=reverse)
+        elif column == TestResultsModel.NameColumn:
+            self.__testResults.sort(key=attrgetter('name'), reverse=reverse)
+        elif column == TestResultsModel.MessageColumn:
+            self.__testResults.sort(key=attrgetter('message'), reverse=reverse)
+        elif column == TestResultsModel.DurationColumn:
+            self.__testResults.sort(key=durationKey, reverse=reverse)
+        self.endResetModel()
+    
+    def getTestResults(self):
+        """
+        Public method to get the list of test results managed by the model.
+        
+        @return list of test results managed by the model
+        @rtype list of UTTestResult
+        """
+        return copy.deepcopy(self.__testResults)
+    
+    def setTestResults(self, testResults):
+        """
+        Public method to set the list of test results of the model.
+        
+        @param testResults test results to be managed by the model
+        @type list of UTTestResult
+        """
+        self.beginResetModel()
+        self.__testResults = copy.deepcopy(testResults)
+        self.endResetModel()
+    
+    def addTestResults(self, testResults):
+        """
+        Public method to add test results to the ones already managed by the
+        model.
+        
+        @param testResults test results to be added to the model
+        @type list of UTTestResult
+        """
+        firstRow = len(self.__testResults)
+        lastRow = firstRow + len(testResults) - 1
+        self.beginInsertRows(QModelIndex(), firstRow, lastRow)
+        self.__testResults.extend(testResults)
+        self.endInsertRows()
+    
+    def updateTestResults(self, testResults):
+        """
+        Public method to update the data of managed test result items.
+        
+        @param testResults test results to be updated
+        @type list of UTTestResult
+        """
+        minIndex = None
+        maxIndex = None
+        
+        for testResult in testResults:
+            for (index, currentResult) in enumerate(self.__testResults):
+                if currentResult.id == testResult.id:
+                    self.__testResults[index] = testResult
+                    if minIndex is None:
+                        minIndex = index
+                        maxIndex = index
+                    else:
+                        minIndex = min(minIndex, index)
+                        maxIndex = max(maxIndex, index)
+        
+        if minIndex is not None:
+            self.dataChanged.emit(
+                self.index(minIndex, 0),
+                self.index(maxIndex, len(TestResultsModel.Headers) - 1)
+            )
 
 
 class TestResultsTreeView(QTreeView):
@@ -132,6 +364,51 @@
         self.header().sortIndicatorChanged.connect(
             lambda column, order: self.header().setSortIndicatorShown(True))
     
+    def reset(self):
+        """
+        Public method to reset the internal state of the view.
+        """
+        super().reset()
+        
+        self.resizeColumns()
+        self.spanFirstColumn(0, self.model().rowCount() - 1)
+    
+    def rowsInserted(self, parent, startRow, endRow):
+        """
+        Public method called when rows are inserted.
+        
+        @param parent model index of the parent item
+        @type QModelIndex
+        @param startRow first row been inserted
+        @type int
+        @param endRow last row been inserted
+        @type int
+        """
+        super().rowsInserted(parent, startRow, endRow)
+        
+        self.resizeColumns()
+        self.spanFirstColumn(startRow, endRow)
+    
+    def dataChanged(self, topLeft, bottomRight, roles=[]):
+        """
+        Public method called when the model data has changed.
+        
+        @param topLeft index of the top left element
+        @type QModelIndex
+        @param bottomRight index of the bottom right element
+        @type QModelIndex
+        @param roles list of roles changed (defaults to [])
+        @type list of Qt.ItemDataRole (optional)
+        """
+        super().dataChanged(topLeft, bottomRight, roles)
+        
+        self.resizeColumns()
+        while topLeft.parent().isValid():
+            topLeft = topLeft.parent()
+        while bottomRight.parent().isValid():
+            bottomRight = bottomRight.parent()
+        self.spanFirstColumn(topLeft.row(), bottomRight.row())
+    
     @pyqtSlot(QModelIndex)
     def __gotoTestDefinition(self, index):
         """
@@ -140,8 +417,33 @@
         @param index index for the double-clicked item
         @type QModelIndex
         """
-        # TODO: not implemented yet
+        # TODO: not implemented yet (__gotoTestDefinition)
         pass
+    
+    def resizeColumns(self):
+        """
+        Public method to resize the columns to their contents.
+        """
+        for column in range(self.model().columnCount()):
+            self.resizeColumnToContents(column)
+    
+    def spanFirstColumn(self, startRow, endRow):
+        """
+        Public method to make the first column span the row for second level
+        items.
+        
+        These items contain the test results.
+        
+        @param startRow index of the first row to span
+        @type QModelIndex
+        @param endRow index of the last row (including) to span
+        @type QModelIndex
+        """
+        model = self.model()
+        for row in range(startRow, endRow + 1):
+            index = model.index(row, 0)
+            for i in range(model.rowCount(index)):
+                self.setFirstColumnSpanned(i, index, True)
 
 #
-# eflag: noqa = M822
+# eflag: noqa = M821, M822

eric ide

mercurial