From eaec82ca8b13183737d901e7494397dab2106930 Mon Sep 17 00:00:00 2001 From: Rebelo Alexis Date: Fri, 28 Jun 2024 15:44:39 +0200 Subject: [PATCH 1/2] Added test for CustomPlotSelectionWindow --- .../app/view/CustomPlotSelectionWindow.py | 22 +- src/silx/app/view/test/test_plotselection.py | 234 ++++++++++++++++++ src/silx/conftest.py | 43 ++++ src/silx/gui/conftest.py | 42 +--- 4 files changed, 295 insertions(+), 46 deletions(-) create mode 100644 src/silx/app/view/test/test_plotselection.py diff --git a/src/silx/app/view/CustomPlotSelectionWindow.py b/src/silx/app/view/CustomPlotSelectionWindow.py index c339871cce..3edee7c9cd 100644 --- a/src/silx/app/view/CustomPlotSelectionWindow.py +++ b/src/silx/app/view/CustomPlotSelectionWindow.py @@ -35,6 +35,8 @@ import silx.io.url import silx.io.utils from silx.gui.hdf5 import _utils +import weakref + # Custom role for highlighting the drop zones _DROP_HIGHLIGHT_ROLE = qt.Qt.UserRole + 1 @@ -120,7 +122,7 @@ def __init__(self, plot, parent=None): self._yParent.setEditable(False) root.appendRow(self._yParent) - self._plot1D = plot + self._plot1D = weakref.proxy(plot) self._index = 0 self._xDataset = None @@ -332,6 +334,8 @@ def __init__(self, model, parent=None): def _createIconWidget(self, row: int, parentItem: qt.QStandardItem): """Create the icon widget for a row in the model""" fileItem = parentItem.child(row, 1) + if fileItem is None: + return iconItem = parentItem.child(row, 0) curve = fileItem.data(qt.Qt.UserRole) if curve is None: @@ -599,16 +603,16 @@ def __init__(self, parent=None): super().__init__(parent) self.setWindowTitle("Plot selection") - plot1D = _DropPlot1D() - model = _FileListModel(plot1D) + self._plot1D = _DropPlot1D() + model = _FileListModel(self._plot1D) self._treeView = _DropTreeView(model, self) - plot1D.setTreeView(self._treeView) + self._plot1D.setTreeView(self._treeView) centralWidget = qt.QSplitter() centralWidget.addWidget(self._treeView) - centralWidget.addWidget(plot1D) + centralWidget.addWidget(self._plot1D) centralWidget.setCollapsible(0, False) centralWidget.setCollapsible(1, False) @@ -620,6 +624,14 @@ def __init__(self, parent=None): toolbar.addClearAction(self._treeView) self.addToolBar(qt.Qt.TopToolBarArea, toolbar) + def getPlot1D(self) -> _DropPlot1D: + """Return the plot widget.""" + return self._plot1D + + def getTreeView(self) -> _DropTreeView: + """Return the tree view widget.""" + return self._treeView + def showEvent(self, event): super().showEvent(event) self.sigVisibilityChanged.emit(True) diff --git a/src/silx/app/view/test/test_plotselection.py b/src/silx/app/view/test/test_plotselection.py new file mode 100644 index 0000000000..c8734514de --- /dev/null +++ b/src/silx/app/view/test/test_plotselection.py @@ -0,0 +1,234 @@ +import pytest +from silx.gui import qt +from silx.io.dictdump import dicttoh5 +import silx.io +from silx.app.view.CustomPlotSelectionWindow import CustomPlotSelectionWindow + + +@pytest.fixture +def setupMimeData(tmpdir): + # Create HDF5 file with 1D dataset in tmpdir + filename = str(tmpdir.join("test.h5")) + dicttoh5({"x": [1, 2, 3], "y1": [1, 2], "y2": [4, 3, 2, 1], "y3": []}, filename) + + # Create mime data + mime_data = qt.QMimeData() + url = silx.io.url.DataUrl(file_path=filename, data_path="/x") + mime_data.setData("application/x-silx-uri", url.path().encode()) + + return mime_data + + +@pytest.fixture +def setupInvalidMimeData(tmpdir): + # Create HDF5 file with 2D dataset in tmpdir + filename = str(tmpdir.join("test.h5")) + dicttoh5({"x": [[1, 2], [3, 4]], "y": [[1, 2], [3, 4]]}, filename) + + # Create mime data + mime_data = qt.QMimeData() + url = silx.io.url.DataUrl(file_path=filename, data_path="/x") + mime_data.setData("application/x-silx-uri", url.path().encode()) + + return mime_data + + +def testDrop(qapp, setupMimeData, qWidgetFactory): + window = qWidgetFactory(CustomPlotSelectionWindow) + + mime_data = setupMimeData + + # Create drag enter event + dragEnterEvent = qt.QDragEnterEvent( + qt.QPoint(0, 0), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, qt.Qt.NoModifier + ) + window.getPlot1D().dragEnterEvent(dragEnterEvent) + qapp.processEvents() + + # Create drag move event + dragMoveEvent = qt.QDragMoveEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dragMoveEvent(dragMoveEvent) + qapp.processEvents() + + # Create drop event + dropEvent = qt.QDropEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dropEvent(dropEvent) + qapp.processEvents() + + # Verify the drop by checking if the data is in the model + model = window.getTreeView().model() + assert model.rowCount() == 2 + + x_item = model.getXParent() + y_item = model.getYParent().child(0, 1) + + assert x_item is not None + assert y_item is not None + + +def testRemoveDataset(setupMimeData, qapp, qWidgetFactory): + window = qWidgetFactory(CustomPlotSelectionWindow) + + mime_data = setupMimeData + + # Create drop event for X dataset + dragEnterEvent = qt.QDragEnterEvent( + qt.QPoint(0, 0), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, qt.Qt.NoModifier + ) + window.getPlot1D().dragEnterEvent(dragEnterEvent) + qapp.processEvents() + + # Create drop event for Y1 dataset + dropEvent = qt.QDropEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dropEvent(dropEvent) + qapp.processEvents() + + model = window.getTreeView().model() + assert model.getYParent().rowCount() == 2 + + # Remove the dataset + window.getTreeView()._removeFile(model.getYParent().child(0, 2), model.getYParent()) + qapp.processEvents() + + assert model.getYParent().rowCount() == 1 + + +def testResetModel(setupMimeData, qapp, qWidgetFactory): + window = qWidgetFactory(CustomPlotSelectionWindow) + + mime_data = setupMimeData + + # Create drop event for X dataset + dragEnterEvent = qt.QDragEnterEvent( + qt.QPoint(0, 0), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, qt.Qt.NoModifier + ) + window.getPlot1D().dragEnterEvent(dragEnterEvent) + qapp.processEvents() + + # Create drop event for dataset + dropEvent = qt.QDropEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dropEvent(dropEvent) + qapp.processEvents() + + model = window.getTreeView().model() + assert model.getYParent().rowCount() == 2 + + # Reset the model + model.clearAll() + qapp.processEvents() + + assert model.getXParent().rowCount() == 0 + assert model.getYParent().rowCount() == 1 + + +def testMultipleDrop(qapp, setupMimeData, qWidgetFactory, qapp_utils): + window = qWidgetFactory(CustomPlotSelectionWindow) + + mime_data = setupMimeData + + # Create drag enter event + dragEnterEvent = qt.QDragEnterEvent( + qt.QPoint(0, 0), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, qt.Qt.NoModifier + ) + + # Create drag move event + dragMoveEvent = qt.QDragMoveEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + + # Create drop event + dropEvent = qt.QDropEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + + for _ in range(2): + window.getPlot1D().dragEnterEvent(dragEnterEvent) + qapp.processEvents() + window.getPlot1D().dragMoveEvent(dragMoveEvent) + qapp.processEvents() + window.getPlot1D().dropEvent(dropEvent) + qapp.processEvents() + + # Verify the drop by checking if the data is in the model + model = window.getTreeView().model() + assert model.rowCount() == 2 + print(model.rowCount()) + + x_item = model.getXParent() + y_item1 = model.getYParent().child(0, 1) + y_item2 = model.getYParent().child(1, 1) + + assert x_item is not None + assert y_item1 is not None + assert y_item2 is not None + + +def testDropInvalid(qapp, setupInvalidMimeData, qWidgetFactory, qapp_utils): + window = qWidgetFactory(CustomPlotSelectionWindow) + + mime_data = setupInvalidMimeData + + # Create drag enter event + dragEnterEvent = qt.QDragEnterEvent( + qt.QPoint(0, 0), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, qt.Qt.NoModifier + ) + window.getPlot1D().dragEnterEvent(dragEnterEvent) + qapp.processEvents() + + # Create drag move event + dragMoveEvent = qt.QDragMoveEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dragMoveEvent(dragMoveEvent) + qapp.processEvents() + + # Create drop event + dropEvent = qt.QDropEvent( + qt.QPoint(50, 50), + qt.Qt.CopyAction, + mime_data, + qt.Qt.LeftButton, + qt.Qt.NoModifier, + ) + window.getPlot1D().dropEvent(dropEvent) + qapp.processEvents() + + # Verify the drop by checking if the data is in the model + model = window.getTreeView().model() + assert model.rowCount() == 2 diff --git a/src/silx/conftest.py b/src/silx/conftest.py index 9b43f5d67b..4304d163d4 100644 --- a/src/silx/conftest.py +++ b/src/silx/conftest.py @@ -157,6 +157,49 @@ def qapp_utils(qapp): utils.tearDownClass() +@pytest.fixture +def qWidgetFactory(qapp, qapp_utils): + """QWidget factory as fixture + + This fixture provides a function taking a QWidget subclass as argument + which returns an instance of this QWidget making sure it is shown first + and destroyed once the test is done. + """ + from silx.gui import qt + from silx.gui.qt.inspect import isValid + + widgets = [] + + def createWidget(cls, *args, **kwargs): + widget = cls(*args, **kwargs) + widget.setAttribute(qt.Qt.WA_DeleteOnClose) + widget.show() + qapp_utils.qWaitForWindowExposed(widget) + widgets.append(widget) + + return widget + + yield createWidget + + qapp.processEvents() + + for widget in widgets: + if isValid(widget): + widget.close() + qapp.processEvents() + + # Wait some time for all widgets to be deleted + for _ in range(10): + validWidgets = [widget for widget in widgets if isValid(widget)] + if validWidgets: + qapp_utils.qWait(10) + + validWidgets = [widget for widget in widgets if isValid(widget)] + assert not validWidgets, f"Some widgets were not destroyed: {validWidgets}" + + del widgets + + @pytest.fixture def tmp_h5py_file(): with BytesIO() as buffer: diff --git a/src/silx/gui/conftest.py b/src/silx/gui/conftest.py index 2e9cf0d12f..91635110b7 100644 --- a/src/silx/gui/conftest.py +++ b/src/silx/gui/conftest.py @@ -1,47 +1,7 @@ import pytest -from silx.gui import qt -from silx.gui.qt.inspect import isValid - - @pytest.fixture(autouse=True) def auto_qapp(qapp): pass + - -@pytest.fixture -def qWidgetFactory(qapp, qapp_utils): - """QWidget factory as fixture - - This fixture provides a function taking a QWidget subclass as argument - which returns an instance of this QWidget making sure it is shown first - and destroyed once the test is done. - """ - widgets = [] - - def createWidget(cls, *args, **kwargs): - widget = cls(*args, **kwargs) - widget.setAttribute(qt.Qt.WA_DeleteOnClose) - widget.show() - qapp_utils.qWaitForWindowExposed(widget) - widgets.append(widget) - - return widget - - yield createWidget - - qapp.processEvents() - - for widget in widgets: - if isValid(widget): - widget.close() - qapp.processEvents() - - # Wait some time for all widgets to be deleted - for _ in range(10): - validWidgets = [widget for widget in widgets if isValid(widget)] - if validWidgets: - qapp_utils.qWait(10) - - validWidgets = [widget for widget in widgets if isValid(widget)] - assert not validWidgets, f"Some widgets were not destroyed: {validWidgets}" From 4b5696f898d3ee6f2716e551281ebb5cc6d0dffc Mon Sep 17 00:00:00 2001 From: Rebelo Alexis Date: Mon, 1 Jul 2024 14:59:58 +0200 Subject: [PATCH 2/2] Added PR suggestions --- src/silx/app/view/test/test_plotselection.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/silx/app/view/test/test_plotselection.py b/src/silx/app/view/test/test_plotselection.py index c8734514de..9745170ae3 100644 --- a/src/silx/app/view/test/test_plotselection.py +++ b/src/silx/app/view/test/test_plotselection.py @@ -6,7 +6,7 @@ @pytest.fixture -def setupMimeData(tmpdir): +def silxMimeData(tmpdir): # Create HDF5 file with 1D dataset in tmpdir filename = str(tmpdir.join("test.h5")) dicttoh5({"x": [1, 2, 3], "y1": [1, 2], "y2": [4, 3, 2, 1], "y3": []}, filename) @@ -20,7 +20,7 @@ def setupMimeData(tmpdir): @pytest.fixture -def setupInvalidMimeData(tmpdir): +def invalidSilxMimeData(tmpdir): # Create HDF5 file with 2D dataset in tmpdir filename = str(tmpdir.join("test.h5")) dicttoh5({"x": [[1, 2], [3, 4]], "y": [[1, 2], [3, 4]]}, filename) @@ -33,10 +33,10 @@ def setupInvalidMimeData(tmpdir): return mime_data -def testDrop(qapp, setupMimeData, qWidgetFactory): +def testDrop(qapp, silxMimeData, qWidgetFactory): window = qWidgetFactory(CustomPlotSelectionWindow) - mime_data = setupMimeData + mime_data = silxMimeData # Create drag enter event dragEnterEvent = qt.QDragEnterEvent( @@ -58,7 +58,7 @@ def testDrop(qapp, setupMimeData, qWidgetFactory): # Create drop event dropEvent = qt.QDropEvent( - qt.QPoint(50, 50), + qt.QPointF(50, 50), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, @@ -78,10 +78,10 @@ def testDrop(qapp, setupMimeData, qWidgetFactory): assert y_item is not None -def testRemoveDataset(setupMimeData, qapp, qWidgetFactory): +def testRemoveDataset(silxMimeData, qapp, qWidgetFactory): window = qWidgetFactory(CustomPlotSelectionWindow) - mime_data = setupMimeData + mime_data = silxMimeData # Create drop event for X dataset dragEnterEvent = qt.QDragEnterEvent( @@ -92,7 +92,7 @@ def testRemoveDataset(setupMimeData, qapp, qWidgetFactory): # Create drop event for Y1 dataset dropEvent = qt.QDropEvent( - qt.QPoint(50, 50), + qt.QPointF(50, 50), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, @@ -111,10 +111,10 @@ def testRemoveDataset(setupMimeData, qapp, qWidgetFactory): assert model.getYParent().rowCount() == 1 -def testResetModel(setupMimeData, qapp, qWidgetFactory): +def testResetModel(silxMimeData, qapp, qWidgetFactory): window = qWidgetFactory(CustomPlotSelectionWindow) - mime_data = setupMimeData + mime_data = silxMimeData # Create drop event for X dataset dragEnterEvent = qt.QDragEnterEvent( @@ -125,7 +125,7 @@ def testResetModel(setupMimeData, qapp, qWidgetFactory): # Create drop event for dataset dropEvent = qt.QDropEvent( - qt.QPoint(50, 50), + qt.QPointF(50, 50), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, @@ -145,10 +145,10 @@ def testResetModel(setupMimeData, qapp, qWidgetFactory): assert model.getYParent().rowCount() == 1 -def testMultipleDrop(qapp, setupMimeData, qWidgetFactory, qapp_utils): +def testMultipleDrop(qapp, silxMimeData, qWidgetFactory, qapp_utils): window = qWidgetFactory(CustomPlotSelectionWindow) - mime_data = setupMimeData + mime_data = silxMimeData # Create drag enter event dragEnterEvent = qt.QDragEnterEvent( @@ -166,7 +166,7 @@ def testMultipleDrop(qapp, setupMimeData, qWidgetFactory, qapp_utils): # Create drop event dropEvent = qt.QDropEvent( - qt.QPoint(50, 50), + qt.QPointF(50, 50), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton, @@ -195,10 +195,10 @@ def testMultipleDrop(qapp, setupMimeData, qWidgetFactory, qapp_utils): assert y_item2 is not None -def testDropInvalid(qapp, setupInvalidMimeData, qWidgetFactory, qapp_utils): +def testDropInvalid(qapp, invalidSilxMimeData, qWidgetFactory, qapp_utils): window = qWidgetFactory(CustomPlotSelectionWindow) - mime_data = setupInvalidMimeData + mime_data = invalidSilxMimeData # Create drag enter event dragEnterEvent = qt.QDragEnterEvent( @@ -220,7 +220,7 @@ def testDropInvalid(qapp, setupInvalidMimeData, qWidgetFactory, qapp_utils): # Create drop event dropEvent = qt.QDropEvent( - qt.QPoint(50, 50), + qt.QPointF(50, 50), qt.Qt.CopyAction, mime_data, qt.Qt.LeftButton,