From d3f45f2e38404b575a713c3cc354fb9c4208b723 Mon Sep 17 00:00:00 2001 From: Rebecca Breu Date: Sun, 3 Dec 2023 22:47:43 +0100 Subject: [PATCH] Additional unit tests --- beeref/view.py | 2 +- beeref/widgets/__init__.py | 137 +--------------------- beeref/widgets/welcome_overlay.py | 156 ++++++++++++++++++++++++++ tests/items/test_textitem.py | 14 +++ tests/test_view.py | 6 +- tests/widgets/test_settings.py | 45 ++++++++ tests/widgets/test_welcome_overlay.py | 25 +++++ tests/{ => widgets}/test_widgets.py | 23 ---- 8 files changed, 247 insertions(+), 161 deletions(-) create mode 100644 beeref/widgets/welcome_overlay.py create mode 100644 tests/widgets/test_settings.py create mode 100644 tests/widgets/test_welcome_overlay.py rename tests/{ => widgets}/test_widgets.py (71%) diff --git a/beeref/view.py b/beeref/view.py index a4b8d32..2501a12 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -46,7 +46,7 @@ def __init__(self, app, parent=None): self.app = app self.parent = parent self.settings = BeeSettings() - self.welcome_overlay = widgets.WelcomeOverlay(self) + self.welcome_overlay = widgets.welcome_overlay.WelcomeOverlay(self) self.setBackgroundBrush( QtGui.QBrush(QtGui.QColor(*constants.COLORS['Scene:Canvas']))) diff --git a/beeref/widgets/__init__.py b/beeref/widgets/__init__.py index f0918ae..0aba20b 100644 --- a/beeref/widgets/__init__.py +++ b/beeref/widgets/__init__.py @@ -16,148 +16,17 @@ import logging import os.path -from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6 import QtCore, QtWidgets from PyQt6.QtCore import Qt from beeref import constants -from beeref.config import logfile_name, BeeSettings -from beeref.main_controls import MainControlsMixin -from beeref.widgets import settings # noqa: F401 +from beeref.config import logfile_name +from beeref.widgets import settings, welcome_overlay # noqa: F401 logger = logging.getLogger(__name__) -class RecentFilesModel(QtCore.QAbstractListModel): - """An entry in the 'Recent Files' list.""" - - def __init__(self, files): - super().__init__() - self.files = files - - def rowCount(self, parent): - return len(self.files) - - def data(self, index, role): - if role == QtCore.Qt.ItemDataRole.DisplayRole: - return os.path.basename(self.files[index.row()]) - if role == QtCore.Qt.ItemDataRole.FontRole: - font = QtGui.QFont() - font.setUnderline(True) - return font - - -class RecentFilesView(QtWidgets.QListView): - - def __init__(self, parent, files=None): - super().__init__(parent) - self.files = files or [] - self.clicked.connect(self.on_clicked) - self.setModel(RecentFilesModel(self.files)) - self.setMouseTracking(True) - - def on_clicked(self, index): - self.parent().parent().open_from_file(self.files[index.row()]) - - def update_files(self, files): - self.files = files - self.model().files = files - self.reset() - - def sizeHint(self): - size = QtCore.QSize() - height = sum( - (self.sizeHintForRow(i) + 2) for i in range(len(self.files))) - width = max(self.sizeHintForColumn(i) for i in range(len(self.files))) - size.setHeight(height) - size.setWidth(width + 2) - return size - - def mouseMoveEvent(self, event): - index = self.indexAt( - QtCore.QPoint(int(event.position().x()), - int(event.position().y()))) - if index.isValid(): - self.setCursor(Qt.CursorShape.PointingHandCursor) - else: - self.setCursor(Qt.CursorShape.ArrowCursor) - - super().mouseMoveEvent(event) - - -class WelcomeOverlay(MainControlsMixin, QtWidgets.QWidget): - """Some basic info to be displayed when the scene is empty.""" - - txt = """

Paste or drop images here.

-

Right-click for more options.

""" - - def __init__(self, parent): - super().__init__(parent) - self.control_target = parent - self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground) - self.init_main_controls(main_window=parent.parent) - - # Recent files - self.files_layout = QtWidgets.QVBoxLayout() - self.files_layout.addStretch(50) - self.files_layout.addWidget( - QtWidgets.QLabel('

Recent Files

', self)) - self.files_view = RecentFilesView(self) - self.files_layout.addWidget(self.files_view) - self.files_layout.addStretch(50) - - # Help text - self.label = QtWidgets.QLabel(self.txt, self) - self.label.setAlignment(Qt.AlignmentFlag.AlignVCenter - | Qt.AlignmentFlag.AlignCenter) - self.layout = QtWidgets.QHBoxLayout() - self.layout.addStretch(50) - self.layout.addWidget(self.label) - self.layout.addStretch(50) - self.setLayout(self.layout) - - def show(self): - files = BeeSettings().get_recent_files(existing_only=True) - self.files_view.update_files(files) - if files and self.layout.indexOf(self.files_layout) < 0: - self.layout.insertLayout(0, self.files_layout) - super().show() - - def disable_mouse_events(self): - self.files_view.setAttribute( - Qt.WidgetAttribute.WA_TransparentForMouseEvents) - self.label.setAttribute( - Qt.WidgetAttribute.WA_TransparentForMouseEvents) - - def enable_mouse_events(self): - self.files_view.setAttribute( - Qt.WidgetAttribute.WA_TransparentForMouseEvents, - on=False) - self.label.setAttribute( - Qt.WidgetAttribute.WA_TransparentForMouseEvents, - on=False) - - def mousePressEvent(self, event): - if self.mousePressEventMainControls(event): - return - super().mousePressEvent(event) - - def mouseMoveEvent(self, event): - if self.mouseMoveEventMainControls(event): - return - super().mouseMoveEvent(event) - - def mouseReleaseEvent(self, event): - if self.mouseReleaseEventMainControls(event): - return - super().mouseReleaseEvent(event) - - def keyPressEvent(self, event): - if self.keyPressEventMainControls(event): - return - super().keyPressEvent(event) - - class BeeProgressDialog(QtWidgets.QProgressDialog): def __init__(self, label, worker, maximum=0, parent=None): diff --git a/beeref/widgets/welcome_overlay.py b/beeref/widgets/welcome_overlay.py new file mode 100644 index 0000000..510ee0c --- /dev/null +++ b/beeref/widgets/welcome_overlay.py @@ -0,0 +1,156 @@ +# This file is part of BeeRef. +# +# BeeRef is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# BeeRef is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with BeeRef. If not, see . + +import logging +import os.path + +from PyQt6 import QtCore, QtGui, QtWidgets +from PyQt6.QtCore import Qt + +from beeref.config import BeeSettings +from beeref.main_controls import MainControlsMixin + + +logger = logging.getLogger(__name__) + + +class RecentFilesModel(QtCore.QAbstractListModel): + """An entry in the 'Recent Files' list.""" + + def __init__(self, files): + super().__init__() + self.files = files + + def rowCount(self, parent): + return len(self.files) + + def data(self, index, role): + if role == QtCore.Qt.ItemDataRole.DisplayRole: + return os.path.basename(self.files[index.row()]) + if role == QtCore.Qt.ItemDataRole.FontRole: + font = QtGui.QFont() + font.setUnderline(True) + return font + + +class RecentFilesView(QtWidgets.QListView): + + def __init__(self, parent, files=None): + super().__init__(parent) + self.files = files or [] + self.clicked.connect(self.on_clicked) + self.setModel(RecentFilesModel(self.files)) + self.setMouseTracking(True) + + def on_clicked(self, index): + self.parent().parent().open_from_file(self.files[index.row()]) + + def update_files(self, files): + self.files = files + self.model().files = files + self.reset() + + def sizeHint(self): + size = QtCore.QSize() + height = sum( + (self.sizeHintForRow(i) + 2) for i in range(len(self.files))) + width = max(self.sizeHintForColumn(i) for i in range(len(self.files))) + size.setHeight(height) + size.setWidth(width + 2) + return size + + def mouseMoveEvent(self, event): + index = self.indexAt( + QtCore.QPoint(int(event.position().x()), + int(event.position().y()))) + if index.isValid(): + self.setCursor(Qt.CursorShape.PointingHandCursor) + else: + self.setCursor(Qt.CursorShape.ArrowCursor) + + super().mouseMoveEvent(event) + + +class WelcomeOverlay(MainControlsMixin, QtWidgets.QWidget): + """Some basic info to be displayed when the scene is empty.""" + + txt = """

Paste or drop images here.

+

Right-click for more options.

""" + + def __init__(self, parent): + super().__init__(parent) + self.control_target = parent + self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground) + self.init_main_controls(main_window=parent.parent) + + # Recent files + self.files_layout = QtWidgets.QVBoxLayout() + self.files_layout.addStretch(50) + self.files_layout.addWidget( + QtWidgets.QLabel('

Recent Files

', self)) + self.files_view = RecentFilesView(self) + self.files_layout.addWidget(self.files_view) + self.files_layout.addStretch(50) + + # Help text + self.label = QtWidgets.QLabel(self.txt, self) + self.label.setAlignment(Qt.AlignmentFlag.AlignVCenter + | Qt.AlignmentFlag.AlignCenter) + self.layout = QtWidgets.QHBoxLayout() + self.layout.addStretch(50) + self.layout.addWidget(self.label) + self.layout.addStretch(50) + self.setLayout(self.layout) + + def show(self): + files = BeeSettings().get_recent_files(existing_only=True) + self.files_view.update_files(files) + if files and self.layout.indexOf(self.files_layout) < 0: + self.layout.insertLayout(0, self.files_layout) + super().show() + + def disable_mouse_events(self): + self.files_view.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents) + self.label.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents) + + def enable_mouse_events(self): + self.files_view.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, + on=False) + self.label.setAttribute( + Qt.WidgetAttribute.WA_TransparentForMouseEvents, + on=False) + + def mousePressEvent(self, event): + if self.mousePressEventMainControls(event): + return + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + if self.mouseMoveEventMainControls(event): + return + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + if self.mouseReleaseEventMainControls(event): + return + super().mouseReleaseEvent(event) + + def keyPressEvent(self, event): + if self.keyPressEventMainControls(event): + return + super().keyPressEvent(event) diff --git a/tests/items/test_textitem.py b/tests/items/test_textitem.py index 607a216..ba125ab 100644 --- a/tests/items/test_textitem.py +++ b/tests/items/test_textitem.py @@ -336,6 +336,20 @@ def test_key_press_event_enter(exit_mock, key_press_mock, view): exit_mock.assert_called_once_with() +@patch('PyQt6.QtWidgets.QGraphicsTextItem.keyPressEvent') +@patch('beeref.items.BeeTextItem.exit_edit_mode') +def test_key_press_event_escape(exit_mock, key_press_mock, view): + item = BeeTextItem('foo bar') + view.scene.addItem(item) + view.scene.edit_item = item + event = MagicMock() + event.key.return_value = Qt.Key.Key_Escape + event.modifiers.return_value = Qt.KeyboardModifier.NoModifier + item.keyPressEvent(event) + key_press_mock.assert_not_called() + exit_mock.assert_called_once_with(commit=False) + + def test_item_to_clipboard(qapp): clipboard = QtWidgets.QApplication.clipboard() item = BeeTextItem('foo bar') diff --git a/tests/test_view.py b/tests/test_view.py index 2dda4b1..2b6226f 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -39,7 +39,7 @@ def test_init_with_filename(open_file_mock, view, qapp, commandline_args): del view -@patch('beeref.widgets.WelcomeOverlay.hide') +@patch('beeref.widgets.welcome_overlay.WelcomeOverlay.hide') def test_on_scene_changed_when_items(hide_mock, view): item = BeePixmapItem(QtGui.QImage()) view.scene.addItem(item) @@ -51,7 +51,7 @@ def test_on_scene_changed_when_items(hide_mock, view): assert view.get_scale() == 2 -@patch('beeref.widgets.WelcomeOverlay.show') +@patch('beeref.widgets.welcome_overlay.WelcomeOverlay.show') def test_on_scene_changed_when_no_items(show_mock, view): view.scale(2, 2) with patch('beeref.view.BeeGraphicsView.recalc_scene_rect') as r: @@ -600,7 +600,7 @@ def test_on_action_show_titlebar_unchecked( create_mock.assert_called_once() -@patch('beeref.widgets.WelcomeOverlay.cursor') +@patch('beeref.widgets.welcome_overlay.WelcomeOverlay.cursor') def test_on_action_move_window_when_welcome_overlay(cursor_mock, view): cursor_mock.return_value = MagicMock( pos=MagicMock(return_value=QtCore.QPointF(10.0, 20.0))) diff --git a/tests/widgets/test_settings.py b/tests/widgets/test_settings.py new file mode 100644 index 0000000..5999ac8 --- /dev/null +++ b/tests/widgets/test_settings.py @@ -0,0 +1,45 @@ +from unittest.mock import patch + +from PyQt6 import QtWidgets +from beeref.widgets.settings import ( + ImageStorageFormatWidget, + SettingsDialog, +) + + +def test_image_storage_format_selects_radiobox(settings, view): + settings.setValue('Items/image_storage_format', 'jpg') + widget = ImageStorageFormatWidget() + assert widget.buttons['best'].isChecked() is False + assert widget.buttons['png'].isChecked() is False + assert widget.buttons['jpg'].isChecked() is True + + +def test_image_storage_format_saves_change(settings, view): + settings.setValue('Items/image_storage_format', 'best') + widget = ImageStorageFormatWidget() + widget.buttons['jpg'].setChecked(True) + assert widget.buttons['best'].isChecked() is False + assert widget.buttons['png'].isChecked() is False + assert widget.buttons['jpg'].isChecked() is True + assert settings.valueOrDefault('Items/image_storage_format', 'jpg') + + +def test_image_storage_format_on_restore_defaults(settings, view): + widget = ImageStorageFormatWidget() + widget.buttons['jpg'].setChecked(True) + settings.setValue('Items/image_storage_format', 'best') + widget.on_restore_defaults() + assert widget.buttons['best'].isChecked() is True + assert widget.buttons['png'].isChecked() is False + assert widget.buttons['jpg'].isChecked() is False + + +@patch('PyQt6.QtWidgets.QMessageBox.question', + return_value=QtWidgets.QMessageBox.StandardButton.Yes) +def test_settings_dialog_on_restore_defaults(msg_mock, settings, view): + dialog = SettingsDialog(view) + settings.setValue('Items/image_storage_format', 'jpg') + dialog.on_restore_defaults() + msg_mock.assert_called_once() + assert settings.valueOrDefault('Items/image_storage_format') == 'best' diff --git a/tests/widgets/test_welcome_overlay.py b/tests/widgets/test_welcome_overlay.py new file mode 100644 index 0000000..28e2109 --- /dev/null +++ b/tests/widgets/test_welcome_overlay.py @@ -0,0 +1,25 @@ +from unittest.mock import MagicMock + +from PyQt6 import QtCore + +from beeref.widgets.welcome_overlay import RecentFilesModel + + +def test_recent_files_model_rowcount(view): + model = RecentFilesModel(['foo.png', 'bar.png']) + assert model.rowCount(None) == 2 + + +def test_recent_files_model_data_diplayrole(view): + model = RecentFilesModel(['foo.png', 'bar.png']) + index = MagicMock() + index.row.return_value = 1 + assert model.data(index, QtCore.Qt.ItemDataRole.DisplayRole) == 'bar.png' + + +def test_recent_files_model_data_fontrole(view): + model = RecentFilesModel(['foo.png', 'bar.png']) + index = MagicMock() + index.row.return_value = 1 + font = model.data(index, QtCore.Qt.ItemDataRole.FontRole) + assert font.underline() is True diff --git a/tests/test_widgets.py b/tests/widgets/test_widgets.py similarity index 71% rename from tests/test_widgets.py rename to tests/widgets/test_widgets.py index 33f1dae..26382a1 100644 --- a/tests/test_widgets.py +++ b/tests/widgets/test_widgets.py @@ -1,12 +1,9 @@ -from unittest.mock import MagicMock - from PyQt6 import QtCore, QtWidgets from PyQt6.QtCore import Qt from beeref.config import logfile_name from beeref.widgets import ( DebugLogDialog, - RecentFilesModel, SceneToPixmapExporterDialog) @@ -23,26 +20,6 @@ def test_debug_log_dialog(qtbot, settings, view): assert clipboard.text() == 'my log output' -def test_recent_files_model_rowcount(view): - model = RecentFilesModel(['foo.png', 'bar.png']) - assert model.rowCount(None) == 2 - - -def test_recent_files_model_data_diplayrole(view): - model = RecentFilesModel(['foo.png', 'bar.png']) - index = MagicMock() - index.row.return_value = 1 - assert model.data(index, QtCore.Qt.ItemDataRole.DisplayRole) == 'bar.png' - - -def test_recent_files_model_data_fontrole(view): - model = RecentFilesModel(['foo.png', 'bar.png']) - index = MagicMock() - index.row.return_value = 1 - font = model.data(index, QtCore.Qt.ItemDataRole.FontRole) - assert font.underline() is True - - def test_scene_to_pixmap_exporter_dialog_sets_defaults(view): dlg = SceneToPixmapExporterDialog(view, QtCore.QSize(1200, 1600)) assert dlg.width_input.value() == 1200