diff --git a/.github/workflows/coveralls.yml b/.github/workflows/coveralls.yml index 7201d9d37..cf90dac68 100644 --- a/.github/workflows/coveralls.yml +++ b/.github/workflows/coveralls.yml @@ -16,7 +16,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: "3.9" + python-version: "3.12" - name: Install packages shell: bash @@ -39,7 +39,7 @@ jobs: export DISPLAY=:0 - name: Run coverage - run: tox -e py39 + run: tox -e py312 - name: Stop Xvfb if: matrix.os == 'ubuntu-latest' diff --git a/src/asammdf/gui/widgets/mdi_area.py b/src/asammdf/gui/widgets/mdi_area.py index 4ae78a504..c88e1f9f4 100644 --- a/src/asammdf/gui/widgets/mdi_area.py +++ b/src/asammdf/gui/widgets/mdi_area.py @@ -4644,7 +4644,7 @@ def verify_bookmarks(self, bookmarks, plot): "You have modified bookmarks.\n\n" "Do you want to save the changes in the measurement file?\n" "", ) - if result == MessageBox.No: + if result == MessageBox.StandardButton.No: return _password = self.mdf._password diff --git a/test/asammdf/gui/dialogs/test_FunctionsManagerDialog.py b/test/asammdf/gui/dialogs/test_FunctionsManagerDialog.py index c13b28382..579ce53b4 100644 --- a/test/asammdf/gui/dialogs/test_FunctionsManagerDialog.py +++ b/test/asammdf/gui/dialogs/test_FunctionsManagerDialog.py @@ -312,6 +312,7 @@ def test_PushButton_StoreFunctionChanges_0(self): """ # Events: QtTest.QTest.mouseClick(self.fm.widget.add_btn, QtCore.Qt.MouseButton.LeftButton) + self.mouseClick_WidgetItem(self.fm.widget.functions_list.item(0)) source = inspect.getsource(maximum) self.fm.widget.function_definition.clear() diff --git a/test/asammdf/gui/resources/ASAP2_Demo_V171.mf4 b/test/asammdf/gui/resources/ASAP2_Demo_V171.mf4 index 779dc2528..40ff0ce3c 100644 Binary files a/test/asammdf/gui/resources/ASAP2_Demo_V171.mf4 and b/test/asammdf/gui/resources/ASAP2_Demo_V171.mf4 differ diff --git a/test/asammdf/gui/test_base.py b/test/asammdf/gui/test_base.py index bed333a3e..7d3de28d1 100644 --- a/test/asammdf/gui/test_base.py +++ b/test/asammdf/gui/test_base.py @@ -22,6 +22,7 @@ class DragAndDrop import pyqtgraph from PySide6 import QtCore, QtGui, QtTest, QtWidgets +from asammdf import mdf from asammdf.gui.utils import excepthook @@ -53,10 +54,7 @@ class TestBase(unittest.TestCase): resource = os.path.normpath(os.path.join(os.path.dirname(__file__), "resources")) test_workspace = os.path.join(os.path.dirname(__file__), "test_workspace") screenshots = os.path.join(os.path.dirname(__file__).split("test")[0], "screenshots") - # os.path.dirname(__file__).split("test")[0], f"screenshots_{sys.platform}_{platform.python_version()}" - # ) - # save_ss_here = os.path.normpath(os.path.join(screenshots, sys.platform, platform.python_version())) - # save_ss_here = os.path.normpath(os.path.join(platform_path, platform.python_version().replace(".", "_"))) + patchers = [] # MockClass ErrorDialog mc_ErrorDialog = None @@ -102,10 +100,6 @@ def setUp(self) -> None: shutil.rmtree(self.test_workspace) if not os.path.exists(self.screenshots): os.makedirs(self.screenshots) - # if not os.path.exists(self.platform_path): - # os.makedirs(self.platform_path) - # if not os.path.exists(self.save_ss_here): - # os.makedirs(self.save_ss_here) os.makedirs(self.test_workspace) self.mc_ErrorDialog.reset_mock() @@ -126,8 +120,29 @@ def tearDownClass(cls): def tearDown(self): self.processEvents() + path_ = os.path.join(self.screenshots, self.id().split("gui.")[-1].rsplit(".", 1)[0]) + if not os.path.exists(path_): + os.makedirs(path_) + + w = getattr(self, "widget", None) + if w: + w.grab().save(os.path.join(path_, f"{self.id().split('.')[-1]}.png")) + self.destroy(w) + + self.mc_ErrorDialog.reset_mock() + if self.test_workspace and pathlib.Path(self.test_workspace).exists(): - shutil.rmtree(self.test_workspace) + try: + shutil.rmtree(self.test_workspace) + except PermissionError as e: + self.destroy(w) + print(e) + + @staticmethod + def destroy(w): + w.close() + w.destroy() + w.deleteLater() def mouseClick_RadioButton(self, qitem): QtTest.QTest.mouseClick( @@ -218,29 +233,17 @@ class Pixmap: COLOR_CURSOR = "#e69138" @staticmethod - def is_black(pixmap): + def is_black(pixmap) -> bool: """ Excepting cursor """ - cursor_x = None - cursor_y = None - cursor_color = None image = pixmap.toImage() for y in range(image.height()): for x in range(image.width()): color = QtGui.QColor(image.pixel(x, y)) - if color.name() != Pixmap.COLOR_BACKGROUND: - if not cursor_x and not cursor_y and not cursor_color: - cursor_x = x - cursor_y = y + 1 - cursor_color = color - continue - elif cursor_x == x and cursor_y == y and cursor_color == color: - cursor_y += 1 - continue - else: - return False + if color.name() not in (Pixmap.COLOR_BACKGROUND, Pixmap.COLOR_CURSOR): + return False return True @staticmethod @@ -454,3 +457,20 @@ def search_y_of_signal_in_column(pixmap_column, signal_color): line = y break return line + + +class OpenMDF: + def __init__(self, file_path): + self.mdf = None + self._file_path = file_path + self._process_bus_logging = ("process_bus_logging", True) + + def __enter__(self): + self.mdf = mdf.MDF(self._file_path, process_bus_logging=self._process_bus_logging) + return self.mdf + + def __exit__(self, exc_type, exc_val, exc_tb): + for exc in (exc_type, exc_val, exc_tb): + if exc is not None: + raise exc + self.mdf.close() diff --git a/test/asammdf/gui/widgets/Shortcuts/test_FileWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_FileWidget_Shortcuts.py index 43442ddab..8a961f907 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_FileWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_FileWidget_Shortcuts.py @@ -37,12 +37,6 @@ def setUp(self): # Get shortcuts self.assertIsNotNone(self.load_shortcuts_from_json_file(self.widget)) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_search_and_select_channels_shortcut(self): """ Test Scope: diff --git a/test/asammdf/gui/widgets/Shortcuts/test_NumericWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_NumericWidget_Shortcuts.py index 3f048838d..3e6093bc8 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_NumericWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_NumericWidget_Shortcuts.py @@ -41,12 +41,6 @@ def setUp(self): # get shortcuts self.assertIsNotNone(self.load_shortcuts_from_json_file(self.table_view)) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_delete_shortcut(self): """ Test Scope: @@ -251,12 +245,6 @@ def setUp(self): # get shortcuts self.assertIsNotNone(self.load_shortcuts_from_json_file(self.numeric)) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_ascii__bin__hex__physical_shortcuts(self): """ Test Scope: diff --git a/test/asammdf/gui/widgets/Shortcuts/test_PlotGraphicsWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_PlotGraphicsWidget_Shortcuts.py index fccee77e3..7e5d9d7a0 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_PlotGraphicsWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_PlotGraphicsWidget_Shortcuts.py @@ -70,15 +70,6 @@ def setUp(self): self.assertIsNotNone(self.load_shortcuts_from_json_file(self.pg)) self.processEvents() - def tearDown(self): - """ - Destroy widget if it still exists - """ - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_lock_unlock_range_shortcut(self): """ Test Scope: @@ -110,7 +101,7 @@ def test_lock_unlock_range_shortcut(self): # Press Key 'Y' for range selection QTest.keySequence(self.pg, QKeySequence(self.shortcuts["toggle_range"])) - self.processEvents(timeout=0.01) + self.processEvents(timeout=0.1) # Save PixMap of Range plot range_pixmap = self.pg.grab() @@ -355,6 +346,21 @@ def test_fit__stack_shortcuts(self): Additional Evaluation - Evaluate that all signals are continuous on plot """ + + def continuous(ch): + extremes = Pixmap.search_signal_extremes_by_ax(self.pg.grab(), signal_color=ch.color.name(), ax="x") + for x in range(self.pg.height() - 1): + column = self.pg.grab(QRect(x, 0, 1, self.pg.height())) + if x < extremes[0] - 1: + self.assertTrue(Pixmap.is_black(column), f"column {x} for channel {ch.name} is not black") + elif extremes[0] <= x <= extremes[1]: + self.assertTrue( + Pixmap.has_color(column, ch.color.name()), + f"column {x} doesn't have color of channel {ch.name}", + ) + elif x > extremes[1] + 1: + self.assertTrue(Pixmap.is_black(column), f"column {x} for channel {ch.name} is not black") + self.pg.cursor1.color = "#000000" self.add_channels([35, 36, 37]) @@ -368,7 +374,7 @@ def test_fit__stack_shortcuts(self): # Evaluate with self.subTest("test_stack_all_shortcut"): # First 2 lines - self.assertTrue(Pixmap.is_black(self.pg.grab(QRect(0, 0, self.pg.width(), 2)))) + self.assertTrue(Pixmap.is_black(self.pg.grab(QRect(0, 0, self.pg.width(), 1)))) # Top pixmap = self.pg.grab(QRect(0, 0, self.pg.width(), int(self.pg.height() / 3))) self.assertTrue(Pixmap.has_color(pixmap, channel_35.color.name())) @@ -532,46 +538,15 @@ def test_fit__stack_shortcuts(self): # search if all channels are fitted into extremes self.mouseDClick_WidgetItem(channel_35) - extremes = Pixmap.search_signal_extremes_by_ax(self.pg.grab(), signal_color=channel_35.color.name(), ax="x") - for x in range(self.pg.height() - 1): - column = self.pg.grab(QRect(x, 0, 1, self.pg.height())) - if x < extremes[0] - 1: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") - elif extremes[0] <= x <= extremes[1]: - self.assertTrue( - Pixmap.has_color(column, channel_35.color.name()), - f"column {x} doesn't have color of channel 35", - ) - else: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") + continuous(channel_35) self.mouseDClick_WidgetItem(channel_35) self.mouseDClick_WidgetItem(channel_36) - for x in range(self.pg.height() - 1): - column = self.pg.grab(QRect(x, 0, 1, self.pg.height())) - if x < extremes[0] - 1: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") - elif extremes[0] <= x <= extremes[1]: - self.assertTrue( - Pixmap.has_color(column, channel_36.color.name()), - f"column {x} doesn't have color of channel 36", - ) - else: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") + continuous(channel_36) - self.mouseDClick_WidgetItem(channel_37) self.mouseDClick_WidgetItem(channel_36) - for x in range(self.pg.height() - 1): - column = self.pg.grab(QRect(x, 0, 1, self.pg.height())) - if x < extremes[0] - 1: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") - elif extremes[0] + 1 <= x <= extremes[1] - 1: - self.assertTrue( - Pixmap.has_color(column, channel_37.color.name()), - f"column {x} doesn't have color of channel 37", - ) - else: - self.assertTrue(Pixmap.is_black(column), f"column {x} is not black") + self.mouseDClick_WidgetItem(channel_37) + continuous(channel_37) def test_grid_shortcut(self): """ @@ -729,6 +704,9 @@ def get_expected_result(step, is_x_axis: bool): return top - delta * step, bottom + delta * step # Setup + if self.plot.lock_btn.isFlat(): + QTest.mouseClick(self.plot.lock_btn, Qt.MouseButton.LeftButton) + y_step = 0.165 x_step = 0.25 @@ -966,6 +944,8 @@ def test_move_cursor_left__right_shortcuts(self): - Evaluate values from selected channel value and cursor info, it must be equal to the expected values. """ # Setup + if self.plot.selected_channel_value_btn.isFlat(): + QTest.mouseClick(self.plot.selected_channel_value_btn, Qt.MouseButton.LeftButton) self.add_channels([37]) ch = self.channels[0] # Number of times that specific key will be pressed @@ -1063,6 +1043,8 @@ def test_shift_channels_shortcut(self): - Evaluate that first signal is shifted down & left after pressing combination "Shift+Down" & "Shift+Left" - Evaluate that second signal is shifted up & right after pressing combination "Shift+Up" & "Shift+Right" """ + if self.plot.lock_btn.isFlat(): + QTest.mouseClick(self.plot.lock_btn, Qt.MouseButton.LeftButton) self.add_channels([36, 37]) channel_36 = self.channels[0] channel_37 = self.channels[1] diff --git a/test/asammdf/gui/widgets/Shortcuts/test_PlotWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_PlotWidget_Shortcuts.py index bc65be232..1a5bb06aa 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_PlotWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_PlotWidget_Shortcuts.py @@ -6,6 +6,7 @@ from PySide6.QtTest import QTest from PySide6.QtWidgets import QMessageBox +from asammdf.gui.utils import COLORS from test.asammdf.gui.test_base import Pixmap from test.asammdf.gui.widgets.test_BasePlotWidget import TestPlotWidget @@ -61,11 +62,8 @@ def setUp(self): def tearDown(self): # Ensure that at the end, button "No" is pressed for MessageBox question window with mock.patch("asammdf.gui.widgets.mdi_area.MessageBox.question") as mo_question: - mo_question.return_value = QMessageBox.No + mo_question.return_value = QMessageBox.StandardButton.No super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() def test_statistics_shortcut(self): """ @@ -315,15 +313,44 @@ def test_insert_bookmark_shortcut(self): - Evaluate that bookmarks are displayed after pressing "Ctrl+I" and the message of the last bookmark is the first element of the returned list of mock object """ + QTest.mouseClick( + self.plot.plot.viewport(), Qt.MouseButton.LeftButton, pos=self.plot.plot.viewport().geometry().center() + ) + self.processEvents() + timestamp = self.plot.plot.cursor1.value() # mock for bookmark with mock.patch("asammdf.gui.widgets.plot.QtWidgets.QInputDialog.getMultiLineText") as mo_getMultiLineText: mo_getMultiLineText.return_value = [self.id(), True] # Press "Ctrl+I" QTest.keySequence(self.plot, QKeySequence(self.shortcuts["insert_bookmark"])) - # Evaluate + mo_getMultiLineText.assert_called() + + # Destroy current widget + with mock.patch("asammdf.gui.widgets.mdi_area.MessageBox.question") as mo_question: + mo_question.return_value = QMessageBox.StandardButton.Yes + self.destroy(self.widget) + + # Open file with new bookmark in new File Widget + self.setUpFileWidget(measurement_file=self.measurement_file, default=True) + self.processEvents(0.1) + self.widget.channel_view.setCurrentText("Natural sort") + # Select channels -> Press PushButton "Create Window" -> "Plot" + self.create_window(window_type="Plot") + self.assertEqual(len(self.widget.mdi_area.subWindowList()), 1) + self.plot = self.widget.mdi_area.subWindowList()[0].widget() + self.processEvents(0.1) + + pg_colors = Pixmap.color_names_exclude_defaults(self.plot.plot.viewport().grab()) + bookmarks_colors = COLORS[: len(COLORS) - len(self.plot.plot.bookmarks) - 1 : -1] + + # Evaluate self.assertTrue(self.plot.show_bookmarks) self.assertEqual(self.plot.plot.bookmarks[len(self.plot.plot.bookmarks) - 1].message, self.id()) + self.assertEqual(self.plot.plot.bookmarks[len(self.plot.plot.bookmarks) - 1].value(), timestamp) + for bookmark, color in zip(self.plot.plot.bookmarks, bookmarks_colors): + self.assertEqual(bookmark.color, color) + self.assertIn(color, pg_colors) def test_toggle_bookmarks_shortcut(self): """ diff --git a/test/asammdf/gui/widgets/Shortcuts/test_Tabular_BaseWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_Tabular_BaseWidget_Shortcuts.py index 2aea5bca1..c2a99045a 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_Tabular_BaseWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_Tabular_BaseWidget_Shortcuts.py @@ -44,12 +44,6 @@ def setUp(self): self.assertIsNotNone(self.load_shortcuts_from_json_file(self.dtw)) self.processEvents(0.01) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_set_color_range_shortcut(self): """ Test Scope: @@ -187,12 +181,6 @@ def setUp(self): self.assertIsNotNone(self.load_shortcuts_from_json_file(self.tabular)) self.processEvents(0.01) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_bin__hex__physical_shortcuts(self): """ Test scope: @@ -359,12 +347,6 @@ def setUp(self): self.assertIsNotNone(self.load_shortcuts_from_json_file(self.dfw)) self.processEvents(0.01) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_copy_shortcut(self): """ Test scope: diff --git a/test/asammdf/gui/widgets/Shortcuts/test_TreeWidget_Shortcuts.py b/test/asammdf/gui/widgets/Shortcuts/test_TreeWidget_Shortcuts.py index 9ade5b93a..7b02d5de1 100644 --- a/test/asammdf/gui/widgets/Shortcuts/test_TreeWidget_Shortcuts.py +++ b/test/asammdf/gui/widgets/Shortcuts/test_TreeWidget_Shortcuts.py @@ -34,12 +34,6 @@ def setUp(self): # get shortcuts self.assertIsNotNone(self.load_shortcuts_from_json_file(self.tw)) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_toggle_select_channel_shortcut(self): """ Test scope: @@ -116,12 +110,6 @@ def setUp(self): # get shortcuts self.assertIsNotNone(self.load_shortcuts_from_json_file(self.ctw)) - def tearDown(self): - super().tearDown() - if self.widget: - self.widget.destroy() - self.widget.deleteLater() - def test_delete_shortcut(self): """ Test Scope: diff --git a/test/asammdf/gui/widgets/file/test_FileWidget_TabModifyAndExport.py b/test/asammdf/gui/widgets/file/test_FileWidget_TabModifyAndExport.py index 0fa9c5545..199d20859 100644 --- a/test/asammdf/gui/widgets/file/test_FileWidget_TabModifyAndExport.py +++ b/test/asammdf/gui/widgets/file/test_FileWidget_TabModifyAndExport.py @@ -6,6 +6,7 @@ from PySide6 import QtCore, QtTest, QtWidgets +from test.asammdf.gui.test_base import OpenMDF from test.asammdf.gui.widgets.test_BaseFileWidget import TestFileWidget # Note: If it's possible and make sense, use self.subTests @@ -39,15 +40,10 @@ def test_PushButton_ScrambleTexts(self): scrambled_filepath = pathlib.Path(self.test_workspace, "ASAP2_Demo_V171.scrambled.mf4") # Wait for Thread to finish time.sleep(0.1) - # TearDown Current Widget. - self.widget.close() - self.widget.destroy() - self.widget.deleteLater() - self.setUpFileWidget(measurement_file=scrambled_filepath, default=True) - scrambled_channels = list(self.widget.mdf.channels_db) - result = filter(lambda c: c in scrambled_channels, channels) - self.assertFalse(any(result)) + with OpenMDF(scrambled_filepath) as mdf_file: + result = filter(lambda c: c in mdf_file.channels_db, channels) + self.assertFalse(any(result)) def test_ExportMDF(self): """ @@ -122,13 +118,6 @@ def test_ExportMDF(self): self.assertTrue(saved_file.exists()) # TearDown Widget - self.widget.close() - self.widget.destroy() - self.widget.deleteLater() - self.processEvents() - - self.setUpFileWidget(measurement_file=saved_file, default=True) - - channels = list(self.widget.mdf.channels_db) - selected_channels.append("time") - self.assertListEqual(sorted(selected_channels), sorted(channels)) + with OpenMDF(saved_file) as mdf_file: + selected_channels.append("time") + self.assertListEqual(sorted(selected_channels), sorted(mdf_file.channels_db)) diff --git a/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py b/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py index fe855154c..6b35b410a 100644 --- a/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py +++ b/test/asammdf/gui/widgets/plot/test_PlotWidget_ContextMenu.py @@ -203,7 +203,7 @@ def test_Action_CopyNamesAndValues(self): clipboard = QtWidgets.QApplication.instance().clipboard().text() pattern_name = re.escape(self.plot_channel_a.text(self.Column.NAME)) pattern_unit = re.escape(self.plot_channel_a.text(self.Column.UNIT)) - self.assertRegex(clipboard, expected_regex=f"{pattern_name}, t = \d+[.]?\d+s, \d+[.]?\d+{pattern_unit}") + self.assertRegex(clipboard, expected_regex=f"{pattern_name}, t = \\d+[.]?\\d+s, \\d+[.]?\\d+{pattern_unit}") with self.subTest("2Channels"): self.plot_channel_a.setSelected(True) @@ -216,7 +216,7 @@ def test_Action_CopyNamesAndValues(self): for channel in (self.plot_channel_a, self.plot_channel_b): pattern_name = re.escape(channel.text(self.Column.NAME)) pattern_unit = re.escape(channel.text(self.Column.UNIT)) - expected_regex.append(f"{pattern_name}, t = \d+[.]?\d+s, \d+[.]?\d+{pattern_unit}") + expected_regex.append(f"{pattern_name}, t = \\d+[.]?\\d+s, \\d+[.]?\\d+{pattern_unit}") self.assertRegex(clipboard, "\\n".join(expected_regex)) @@ -447,7 +447,7 @@ def test_Menu_ShowHide_Action_ShowDisabledItems(self): QtTest.QTest.keyClick(self.plot.channel_selection, QtCore.Qt.Key.Key_Space) # Evaluate self.assertTrue(self.plot_channel_a.isHidden()) - self.context_menu(action_text="Show disabled items") + self.context_menu(action_text="Hide disabled items") self.assertFalse(self.plot_channel_a.isHidden()) with self.subTest("DisableByClick"): @@ -464,7 +464,7 @@ def test_Menu_ShowHide_Action_ShowDisabledItems(self): ) # Evaluate self.assertTrue(self.plot_channel_b.isHidden()) - self.context_menu(action_text="Show disabled items") + self.context_menu(action_text="Hide disabled items") self.assertFalse(self.plot_channel_b.isHidden()) def test_Menu_ShowHide_Action_HideMissingItems(self): @@ -517,7 +517,7 @@ def test_Menu_ShowHide_Action_ShowMissingItems(self): self.assertTrue(plot_channel.isHidden()) # Events - self.context_menu(action_text="Show missing items") + self.context_menu(action_text="Hide missing items") # Evaluate self.assertFalse(plot_channel.isHidden()) @@ -584,7 +584,7 @@ def test_Menu_ShowHide_Action_UnfilterComputedChannels(self): iterator += 1 # Events - self.context_menu(action_text="Un-filter computed channels") + self.context_menu(action_text="Filter only computed channels") # Evaluate iterator = QtWidgets.QTreeWidgetItemIterator(self.plot.channel_selection) diff --git a/test/asammdf/gui/widgets/plot/test_PlotWidget_PushButtons.py b/test/asammdf/gui/widgets/plot/test_PlotWidget_PushButtons.py index ac656ddab..1a7a09e8b 100644 --- a/test/asammdf/gui/widgets/plot/test_PlotWidget_PushButtons.py +++ b/test/asammdf/gui/widgets/plot/test_PlotWidget_PushButtons.py @@ -1,6 +1,12 @@ #!/usr/bin/env python +import shutil +from unittest import mock + from PySide6 import QtCore, QtTest +from PySide6.QtCore import Qt +from PySide6.QtWidgets import QPushButton as QBtn, QTreeWidgetItemIterator, QMessageBox +from asammdf.gui.utils import COLORS, BLUE from test.asammdf.gui.test_base import Pixmap from test.asammdf.gui.widgets.test_BasePlotWidget import TestPlotWidget @@ -12,8 +18,9 @@ def setUp(self): self.channel_0_name = "ASAM_[14].M.MATRIX_DIM_16.UBYTE.IDENTICAL" self.channel_1_name = "ASAM_[15].M.MATRIX_DIM_16.UBYTE.IDENTICAL" - # Event + # Open test file in File Widget self.setUpFileWidget(measurement_file=self.measurement_file, default=True) + # Switch ComboBox to "Natural sort" self.widget.channel_view.setCurrentText("Natural sort") # Press PushButton "Create Window" @@ -25,6 +32,14 @@ def setUp(self): if not self.plot.hide_axes_btn.isFlat(): QtTest.QTest.mouseClick(self.plot.hide_axes_btn, QtCore.Qt.MouseButton.LeftButton) + # Press PushButton "Hide bookmarks" + if not self.plot.bookmark_btn.isFlat(): + QtTest.QTest.mouseClick(self.plot.bookmark_btn, QtCore.Qt.MouseButton.LeftButton) + + if not self.plot.focused_mode_btn.isFlat(): + QtTest.QTest.mouseClick(self.plot.focused_mode_btn, QtCore.Qt.MouseButton.LeftButton) + + self.processEvents(0.1) # Save PixMap of clear plot clear_pixmap = self.plot.plot.viewport().grab() self.assertTrue(Pixmap.is_black(clear_pixmap)) @@ -309,3 +324,412 @@ def test_Plot_ChannelSelection_PushButton_RegionDelta(self): # Ensure that delta char is not present on channel values even if button is pressed self.assertNotIn("Δ", self.plot_tree_channel_0.text(self.Column.VALUE)) self.assertNotIn("Δ", self.plot_tree_channel_1.text(self.Column.VALUE)) + + def test_Plot_ChannelSelection_PushButton_ToggleBookmarks(self): + """ + Precondition + - + + Events: + - Display 1 signal on plot + - Press 3 times `Toggle bookmarks` button + + Evaluate: + - Evaluate that bookmarks are not displayed before pressing `Toggle bookmarks` button + - Evaluate that bookmarks are displayed after pressing `Toggle bookmarks` button first time + - Evaluate that bookmarks are not displayed after pressing `Toggle bookmarks` button second time + """ + # Hide channels + iterator = QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item.checkState(0) == QtCore.Qt.CheckState.Checked: + item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) + iterator += 1 + + # Press `Toggle bookmarks` button + QtTest.QTest.mouseClick(self.plot.bookmark_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents(0.1) + + # Get expected colors + pg_colors = Pixmap.color_names_exclude_defaults(self.plot.plot.viewport().grab()) + bookmarks_colors = COLORS[: len(COLORS) - len(self.plot.plot.bookmarks) - 1 : -1] + + # Evaluate + self.assertTrue(self.plot.show_bookmarks) + for color in bookmarks_colors: + self.assertIn(color, pg_colors) + self.assertIn(BLUE, pg_colors) + + # Press `Toggle bookmarks` button + QtTest.QTest.mouseClick(self.plot.bookmark_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + # Evaluate + self.assertFalse(self.plot.show_bookmarks) + self.assertTrue(Pixmap.is_black(self.plot.plot.viewport().grab())) + + def test_Plot_ChannelSelection_PushButton_HideAxes(self): + """ + Events: + - Display 2 signal on plot + - Press 3 times `Show axes` button + Evaluate: + - Evaluate that bookmarks are not displayed before pressing `Hide axes` button + - Evaluate that bookmarks are displayed after pressing `Hide axes` button first time + - Evaluate that bookmarks are not displayed after pressing `Hide axes` button second time + _ Evaluate that only y_axis color are the same as selected channel color + """ + # Hide channels + iterator = QTreeWidgetItemIterator(self.plot.channel_selection) + while iterator.value(): + item = iterator.value() + if item.checkState(0) == QtCore.Qt.CheckState.Checked: + item.setCheckState(0, QtCore.Qt.CheckState.Unchecked) + iterator += 1 + + if self.plot.plot.y_axis.isVisible() or self.plot.plot.x_axis.isVisible(): + QtTest.QTest.mouseClick(self.plot.hide_axes_btn, QtCore.Qt.MouseButton.LeftButton) + # Evaluate + self.assertFalse(self.plot.plot.y_axis.isVisible()) + self.assertFalse(self.plot.plot.x_axis.isVisible()) + + # Press `Show axes` button + QtTest.QTest.mouseClick(self.plot.hide_axes_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + + # Evaluate + self.assertTrue(self.plot.plot.y_axis.isVisible()) + self.assertTrue(self.plot.plot.x_axis.isVisible()) + + with self.subTest("test pen color of axes"): + self.assertNotEqual(self.plot_tree_channel_0.color.name(), self.plot_tree_channel_1.color.name()) + x_axis_color = self.plot.plot.x_axis.pen().color().name() + + # Event + self.mouseClick_WidgetItem(self.plot_tree_channel_0) + self.processEvents() + + # Evaluate + self.assertEqual(x_axis_color, self.plot.plot.x_axis.pen().color().name()) + self.assertEqual(self.plot.plot.y_axis.pen().color().name(), self.plot_tree_channel_0.color.name()) + self.assertTrue(Pixmap.has_color(self.plot.plot.viewport().grab(), self.plot_tree_channel_0.color.name())) + + # Event + self.mouseClick_WidgetItem(self.plot_tree_channel_1) + self.processEvents() + + # Evaluate + self.assertEqual(x_axis_color, self.plot.plot.x_axis.pen().color().name()) + self.assertEqual(self.plot.plot.y_axis.pen().color().name(), self.plot_tree_channel_1.color.name()) + self.assertTrue(Pixmap.has_color(self.plot.plot.viewport().grab(), self.plot_tree_channel_1.color.name())) + + # Press `Hide axes` button + QtTest.QTest.mouseClick(self.plot.hide_axes_btn, QtCore.Qt.MouseButton.LeftButton) + # Evaluate + self.assertFalse(self.plot.plot.y_axis.isVisible()) + self.assertFalse(self.plot.plot.x_axis.isVisible()) + self.assertTrue(Pixmap.is_black(self.plot.plot.viewport().grab())) + + def test_Plot_ChannelSelection_PushButton_Lock(self): + """ + Events: + - Display 2 signal on plot + - Press `Lock` button in order to lock y-axis + - Move signal using drag-and-drop technique on both axis + - Press `Lock` button in order to lock y-axis + - Move signal using drag-and-drop technique on both axis + + Evaluate: + - Evaluate that if y-axis is locked, signal cannot be moved only on y-axis and common axis column is hidden + - Evaluate that if y-axis is unlocked, signal can be moved on both x and y axes + and common axis column isn't hidden + """ + + def move_signal(x, y): + def drag_and_drop(): + center = self.plot.plot.viewport().geometry().center() + QtTest.QTest.mouseMove(self.plot.plot.viewport(), QtCore.QPoint(center.x() + x, center.y() + y)) + + QtTest.QTest.mouseRelease( + self.plot.plot.viewport(), + QtCore.Qt.MouseButton.LeftButton, + QtCore.Qt.KeyboardModifier.NoModifier, + QtCore.QPoint(center.x() + x, center.y() + y), + 20, + ) + + self.mouseClick_WidgetItem(self.plot_tree_channel_0) + QtCore.QTimer.singleShot(100, drag_and_drop) + QtTest.QTest.mousePress(self.plot.plot.viewport(), QtCore.Qt.MouseButton.LeftButton) + + if self.plot.locked: + QtTest.QTest.mouseClick(self.plot.lock_btn, QtCore.Qt.MouseButton.LeftButton) + # Evaluate + self.assertFalse(self.plot.locked) + self.assertFalse(self.plot.channel_selection.isColumnHidden(self.plot.channel_selection.CommonAxisColumn)) + + # Evaluate that signal wasn't moved on x-axis + self.assertIsNone(self.plot_graph_channel_0.trim_info) + + # Press `Lock` button + QtTest.QTest.mouseClick(self.plot.lock_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + sig_0_y_range = self.plot_graph_channel_0.y_range + + # move channels on plot graphics + move_signal(50, 50) + self.processEvents(1) + + # Evaluate + self.assertTrue(self.plot.locked) + self.assertTrue(self.plot.channel_selection.isColumnHidden(self.plot.channel_selection.CommonAxisColumn)) + self.assertTupleEqual(sig_0_y_range, self.plot_graph_channel_0.y_range) + self.assertIsNotNone(self.plot_graph_channel_0.trim_info) + + # Press `Lock` button + QtTest.QTest.mouseClick(self.plot.lock_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + + # get trim info + trim_info = self.plot_graph_channel_0.trim_info + + # move channels on plot graphics + move_signal(-50, 50) + self.processEvents(1) + + # Evaluate + self.assertFalse(self.plot.locked) + self.assertFalse(self.plot.channel_selection.isColumnHidden(self.plot.channel_selection.CommonAxisColumn)) + self.assertNotEqual(sig_0_y_range, self.plot_graph_channel_0.y_range) + for _ in range(len(trim_info)): + self.assertGreaterEqual(self.plot_graph_channel_0.trim_info[_], trim_info[_]) + + def test_Plot_ChannelSelection_PushButtons_Zoom(self): + """ + This method will test 4 buttons: zoom in, zoom out, undo zoom, redo zoom. + Precondition: + - Zoom history is clean - trim info must be None + - Cursor is on center of plot. This step is required for easiest evaluation + + # Event + - Press button `Zoom in` + - Press button `Undo zoom` + - Press button `Redo zoom` + - Press button `Zoom out` + - Press button `Zoom in` + + # Evaluate + - Evaluate that `zoom` action was performed (zoom in will be tested at the end) + - Evaluate that `undo zoom` action was performed + - Evaluate that `redo zoom` action was performed and + channels timestamp trim info is equal with its value before undo zoom action was performed + - Evaluate that zoom `out action` was performed + - Evaluate that zoom `in action` was performed + + """ + # Precondition + self.assertIsNone(self.plot_graph_channel_0.trim_info) + QtTest.QTest.mouseClick(self.plot.plot.viewport(), QtCore.Qt.MouseButton.LeftButton) + + buttons = self.plot.findChildren(QBtn) + zoom_in_btn = "Zoom in" + zoom_out_btn = "Zoom out" + for btn in buttons: + if btn.toolTip() == zoom_in_btn: + zoom_in_btn = btn + continue + elif btn.toolTip() == zoom_out_btn: + zoom_out_btn = btn + continue + + # buttons was found + self.assertIsInstance(zoom_in_btn, QBtn) + self.assertIsInstance(zoom_out_btn, QBtn) + + # Event + QtTest.QTest.mouseClick(zoom_in_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + trim_info_0 = self.plot_graph_channel_0.trim_info + + # Evaluate + self.assertIsNotNone(trim_info_0) # zoom was performed + + # Store previous zoom + prev_zoom = trim_info_0[1] - trim_info_0[0] + + # Event + QtTest.QTest.mouseClick(self.plot.undo_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + trim_info_1 = self.plot_graph_channel_0.trim_info + + # Evaluate + self.assertGreater(trim_info_1[1] - trim_info_1[0], prev_zoom) + + # Store previous zoom + prev_zoom = trim_info_1[1] - trim_info_1[0] + + # Event + QtTest.QTest.mouseClick(self.plot.redo_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + trim_info_2 = self.plot_graph_channel_0.trim_info + + # Evaluate + self.assertLess(trim_info_2[1] - trim_info_2[0], prev_zoom) + self.assertTupleEqual(trim_info_0, trim_info_2) + + prev_zoom = trim_info_2[1] - trim_info_2[0] + + # Event + QtTest.QTest.mouseClick(zoom_out_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + trim_info_3 = self.plot_graph_channel_0.trim_info + + # Evaluate + self.assertGreater(trim_info_3[1] - trim_info_3[0], prev_zoom) + + # store previous zoom + prev_zoom = trim_info_3[1] - trim_info_3[0] + + # Event + QtTest.QTest.mouseClick(zoom_in_btn, QtCore.Qt.MouseButton.LeftButton) + self.processEvents() + trim_info_4 = self.plot_graph_channel_0.trim_info + + # Evaluate + self.assertLess(trim_info_4[1] - trim_info_4[0], prev_zoom) + self.assertTupleEqual(trim_info_2, trim_info_4) + + def test_Plot_ChannelSelection_PushButtons_CMD(self): + """ + This method will test 6 actions from Cmd menu: Home, Honeywell, Fit, Stack, Increase font, Decrease font. + + Precondition: + - No zoom actions performed. Y range for both signals are identical and timestamp trim info is None + + # Event + -[0] Press `Cmd` button to open menu, after that select `Stack` action from menu + -[1] Press `Cmd` button to open menu, after that select `Fit` action from menu + -[2] Press `Cmd` button to open menu, after that select `Honeywell` action from menu + -[3] Press `Cmd` button to open menu, after that select `Home` action from menu + -[4] Press `Cmd` button to open menu, after that check common axis checkboxes + and select `Stack` action from menu + -[5] Press `Cmd` button to open menu, after that select `Increase font` action from menu + -[6] Press `Cmd` button to open menu, after that select `Decrease font` action from menu + + # Evaluate + -[0] Evaluate that signals Y ranges were modified, + also Y range for first signal is less than Y range of second signal timestamp trim info is None + -[1] Evaluate that signals Y ranges for both signals are identical, timestamp trim info is still None + -[2] Evaluate that signals Y ranges for both signals are identical, timestamp trim info was modified, + also trim info for both signals are identical + -[3] Evaluate that signals Y ranges for both signals are identical, also timestamp trim info are identical, + timestamp range became greater + -[4] Evaluate that signals Y ranges for both signals are identical, also timestamp trim info are identical + + -[5] Evaluate that widget font size was increased + -[6] Evaluate that widget font size was decreased + """ + + def click_on_cmd_action(btn: QBtn, action_text: str): + for n, action in enumerate(btn.menu().actions(), 1): + if action.text() == action_text: + break + else: + n = 0 + QtTest.QTest.mouseClick(btn, QtCore.Qt.MouseButton.LeftButton) + for _ in range(n): + QtTest.QTest.keyClick(btn.menu(), QtCore.Qt.Key.Key_Down) + QtTest.QTest.qWait(100) + QtTest.QTest.keyClick(btn.menu(), QtCore.Qt.Key.Key_Enter) + + # Precondition + self.assertTupleEqual(self.plot_graph_channel_0.y_range, self.plot_graph_channel_1.y_range) + self.assertIsNone(self.plot_graph_channel_0.trim_info) + self.assertIsNone(self.plot_graph_channel_1.trim_info) + + buttons = self.plot.findChildren(QBtn) + cmd_btn = "Cmd" + for btn in buttons: + if btn.text() == cmd_btn: + cmd_btn = btn + break + + self.processEvents(0.1) + + with self.subTest("PlotGraphics tests"): + # Event + click_on_cmd_action(cmd_btn, "Stack") + self.processEvents(0.1) + + # Evaluate + self.assertLess(self.plot_graph_channel_0.y_range[0], self.plot_graph_channel_1.y_range[0]) + self.assertLess(self.plot_graph_channel_0.y_range[1], self.plot_graph_channel_1.y_range[1]) + + self.assertIsNone(self.plot_graph_channel_0.trim_info) # zoom on timestamp axis wasn't performed + self.assertIsNone(self.plot_graph_channel_1.trim_info) + + # Event + click_on_cmd_action(cmd_btn, "Fit") + self.processEvents(0.1) + + # Evaluate + self.assertTupleEqual(self.plot_graph_channel_0.y_range, self.plot_graph_channel_1.y_range) + self.assertIsNone(self.plot_graph_channel_0.trim_info) + self.assertIsNone(self.plot_graph_channel_1.trim_info) + + # Event + click_on_cmd_action(cmd_btn, "Honeywell") + self.processEvents(0.1) + + # Evaluate + self.assertEqual(self.plot_graph_channel_0.y_range[0], self.plot_graph_channel_1.y_range[0]) + self.assertEqual(self.plot_graph_channel_0.y_range[1], self.plot_graph_channel_1.y_range[1]) + self.assertTupleEqual(self.plot_graph_channel_0.trim_info, self.plot_graph_channel_1.trim_info) + + # save previous trim info + prev_trim_info = self.plot_graph_channel_0.trim_info[1] - self.plot_graph_channel_0.trim_info[0] + # Event + click_on_cmd_action(cmd_btn, "Home") + self.processEvents(0.1) + + # Evaluate + self.assertEqual(self.plot_graph_channel_0.y_range[0], self.plot_graph_channel_1.y_range[0]) + self.assertEqual(self.plot_graph_channel_0.y_range[1], self.plot_graph_channel_1.y_range[1]) + self.assertLess( + prev_trim_info, self.plot_graph_channel_0.trim_info[1] - self.plot_graph_channel_0.trim_info[0] + ) + self.assertTupleEqual(self.plot_graph_channel_0.trim_info, self.plot_graph_channel_1.trim_info) + + # Event + # Set checked individual axis for both channels + common_axis_column = self.plot_tree_channel_0.CommonAxisColumn + if self.plot_tree_channel_0.checkState(common_axis_column) == QtCore.Qt.CheckState.Unchecked: + self.plot_tree_channel_0.setCheckState(common_axis_column, QtCore.Qt.CheckState.Checked) + if self.plot_tree_channel_1.checkState(common_axis_column) == QtCore.Qt.CheckState.Unchecked: + self.plot_tree_channel_1.setCheckState(common_axis_column, QtCore.Qt.CheckState.Checked) + + click_on_cmd_action(cmd_btn, "Stack") + self.processEvents(0.1) + + # Evaluate + self.assertTupleEqual(self.plot_graph_channel_0.y_range, self.plot_graph_channel_1.y_range) + self.assertTupleEqual(self.plot_graph_channel_0.trim_info, self.plot_graph_channel_1.trim_info) + + with self.subTest("text actions tests"): + prev_text_size = self.plot.font().pointSize() + + # Event + click_on_cmd_action(cmd_btn, "Increase font") + self.processEvents(0.1) + + # Evaluate + self.assertGreater(self.plot.font().pointSize(), prev_text_size) + + prev_text_size = self.plot.font().pointSize() + + # Event + click_on_cmd_action(cmd_btn, "Decrease font") + self.processEvents(0.1) + + # Evaluate + self.assertLess(self.plot.font().pointSize(), prev_text_size) diff --git a/test/asammdf/gui/widgets/test_BaseFileWidget.py b/test/asammdf/gui/widgets/test_BaseFileWidget.py index 0e866961f..88678a558 100644 --- a/test/asammdf/gui/widgets/test_BaseFileWidget.py +++ b/test/asammdf/gui/widgets/test_BaseFileWidget.py @@ -1,5 +1,7 @@ import json import os +import pathlib +import shutil from random import randint from unittest import mock @@ -23,19 +25,7 @@ def setUp(self): self.mc_widget_ed = patcher.start() self.addCleanup(patcher.stop) - def tearDown(self): - path_ = os.path.join(self.screenshots, self.__module__) - if not os.path.exists(path_): - os.makedirs(path_) - - self.widget.grab().save(os.path.join(path_, f"{self.id().split('.')[-1]}.png")) - - if self.widget: - self.widget.close() - self.widget.destroy() - self.widget.deleteLater() - self.mc_ErrorDialog.reset_mock() - super().tearDown() + self.measurement_file = shutil.copy(pathlib.Path(self.resource, "ASAP2_Demo_V171.mf4"), self.test_workspace) def setUpFileWidget(self, *args, measurement_file, default): """ diff --git a/test/asammdf/gui/widgets/test_BasePlotWidget.py b/test/asammdf/gui/widgets/test_BasePlotWidget.py index 57a3e0e0a..c183cf389 100644 --- a/test/asammdf/gui/widgets/test_BasePlotWidget.py +++ b/test/asammdf/gui/widgets/test_BasePlotWidget.py @@ -1,4 +1,5 @@ import pathlib +import shutil import threading as td from unittest import mock @@ -32,8 +33,6 @@ class Column: COMMON_AXIS = 3 INDIVIDUAL_AXIS = 4 - measurement_file = str(pathlib.Path(TestFileWidget.resource, "ASAP2_Demo_V171.mf4")) - def add_channel_to_plot(self, plot=None, channel_name=None, channel_index=None): if not plot and self.plot: plot = self.plot @@ -118,14 +117,13 @@ def move_item_inside_channels_tree_widget(self, plot=None, src=None, dst=None): ) QtTest.QTest.mouseMove(channels_tree_widget, QPoint(drag_x, drag_y)) # minimum necessary time for drag action to be implemented - t = 0.8 + t = 1 def call_drop_event(x, y, duration, h): x *= h / y pyautogui.drag(int(x), y, duration=duration) - timer = td.Timer(0.0001, call_drop_event, args=(int(drag_x * 0.5), drop_y - drag_y, t, item_h)) - timer.start() + td.Timer(0.0001, call_drop_event, args=(int(drag_x * 0.5), drop_y - drag_y, t, item_h)).start() self.manual_use(self.widget, duration=t + 0.002) def wheel_action(self, w: QWidget, x: float, y: float, angle_delta: int):