diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index c292743..10d3c10 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -18,11 +18,12 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt install libgl1-mesa-glx libegl1 libglib2.0-0 libxcb-image0 libxkbcommon-x11-0 libxcb-icccm4 libxcb-keysyms1-dev xserver-xephyr libfontconfig1 libxkbcommon-dev libdbus-1-3 + sudo apt install libgl1-mesa-glx libegl1 libglib2.0-0 libxcb-image0 libxkbcommon-x11-0 libxcb-icccm4 libxcb-keysyms1 xserver-xephyr libfontconfig1 libxkbcommon-dev libdbus-1-3 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 sudo apt install xvfb python -m pip install --upgrade pip pip install . pip install -r requirements/dev.txt + /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -screen 0 1920x1200x24 -ac +extension GLX - name: Run Unittests with pytest run: | xvfb-run coverage run --source=beeref -m pytest -v -s diff --git a/beeref/__main__.py b/beeref/__main__.py index db0b8f7..6de727c 100755 --- a/beeref/__main__.py +++ b/beeref/__main__.py @@ -76,7 +76,7 @@ def main(): logger.info(f'Starting {constants.APPNAME} version {constants.VERSION}') settings = BeeSettings() logger.info(f'Using settings: {settings.fileName()}') - logger.info(f'Logging to: {logfile_name}') + logger.info(f'Logging to: {logfile_name()}') CommandlineArgs(with_check=True) # Force checking app = QtWidgets.QApplication(sys.argv) diff --git a/beeref/config.py b/beeref/config.py index d415645..5b28a1f 100644 --- a/beeref/config.py +++ b/beeref/config.py @@ -137,8 +137,10 @@ class BeeSettings(QtCore.QSettings): return values -logfile_name = os.path.join( - os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log') +def logfile_name(): + return os.path.join( + os.path.dirname(BeeSettings().fileName()), f'{constants.APPNAME}.log') + logging_conf = { 'version': 1, @@ -161,7 +163,7 @@ logging_conf = { 'file': { 'class': 'beeref.utils.BeeRotatingFileHandler', 'formatter': 'verbose', - 'filename': logfile_name, + 'filename': logfile_name(), 'maxBytes': 1024 * 1000 * 50, 'backupCount': 1, 'level': 'DEBUG', diff --git a/beeref/fileio/sql.py b/beeref/fileio/sql.py index c0fc24e..fd8b06d 100644 --- a/beeref/fileio/sql.py +++ b/beeref/fileio/sql.py @@ -43,8 +43,9 @@ def handle_sqlite_errors(func): func(self, *args, **kwargs) except sqlite3.Error as e: logger.exception(f'Error while reading/writing {self.filename}') + self._close_connection() if self.worker: - self.worker.finished.emit('', [str(e)]) + self.worker.finished.emit(self.filename, [str(e)]) else: raise BeeFileIOError(msg=str(e), filename=self.filename) from e diff --git a/beeref/gui.py b/beeref/gui.py index a20824b..1356da5 100644 --- a/beeref/gui.py +++ b/beeref/gui.py @@ -105,23 +105,23 @@ class DebugLogDialog(QtWidgets.QDialog): def __init__(self, parent): super().__init__(parent) self.setWindowTitle(f'{constants.APPNAME} Debug Log') - with open(logfile_name) as f: + with open(logfile_name()) as f: self.log_txt = f.read() - log = QtWidgets.QLabel(self.log_txt) - log.setTextInteractionFlags( + self.log = QtWidgets.QLabel(self.log_txt) + self.log.setTextInteractionFlags( Qt.TextInteractionFlag.TextSelectableByMouse) scroll = QtWidgets.QScrollArea(self) scroll.setWidgetResizable(True) - scroll.setWidget(log) + scroll.setWidget(self.log) buttons = QtWidgets.QDialogButtonBox( QtWidgets.QDialogButtonBox.StandardButton.Close) buttons.rejected.connect(self.reject) - copy_button = QtWidgets.QPushButton('Co&py To Clipboard') - copy_button.released.connect(self.copy_to_clipboard) + self.copy_button = QtWidgets.QPushButton('Co&py To Clipboard') + self.copy_button.released.connect(self.copy_to_clipboard) buttons.addButton( - copy_button, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole) + self.copy_button, QtWidgets.QDialogButtonBox.ButtonRole.ActionRole) layout = QtWidgets.QVBoxLayout() self.setLayout(layout) diff --git a/beeref/view.py b/beeref/view.py index c4543b5..b1bb906 100644 --- a/beeref/view.py +++ b/beeref/view.py @@ -38,6 +38,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def __init__(self, app, parent=None): super().__init__(parent) self.app = app + self.parent = parent self.settings = BeeSettings() self.setBackgroundBrush(QtGui.QBrush(QtGui.QColor(60, 60, 60))) @@ -94,7 +95,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): name = os.path.basename(self.filename or '[Untitled]') clean = '' if clean else '*' title = f'{name}{clean} - {constants.APPNAME}' - self.parent().setWindowTitle(title) + self.parent.setWindowTitle(title) def on_scene_changed(self, region): if not self.scene.items(): @@ -169,16 +170,16 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_action_fullscreen(self, checked): if checked: - self.parent().showFullScreen() + self.parent.showFullScreen() else: - self.parent().showNormal() + self.parent.showNormal() def on_action_always_on_top(self, checked): - self.parent().setWindowFlag( + self.parent.setWindowFlag( Qt.WindowType.WindowStaysOnTopHint, on=checked) - self.parent().destroy() - self.parent().create() - self.parent().show() + self.parent.destroy() + self.parent.create() + self.parent.show() def on_action_show_scrollbars(self, checked): if checked: @@ -194,9 +195,9 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): def on_action_show_menubar(self, checked): if checked: - self.parent().setMenuBar(self.create_menubar()) + self.parent.setMenuBar(self.create_menubar()) else: - self.parent().setMenuBar(None) + self.parent.setMenuBar(None) def on_action_undo(self): logger.debug('Undo: %s' % self.undo_stack.undoText()) @@ -270,8 +271,6 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): self.scene.add_queued_items() def on_loading_finished(self, filename, errors): - if filename: - self.filename = filename if errors: QtWidgets.QMessageBox.warning( self, @@ -279,6 +278,7 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): ('

Problem loading file %s

' '

Not accessible or not a proper bee file

') % filename) else: + self.filename = filename self.scene.add_queued_items() self.on_action_fit_scene() @@ -305,15 +305,15 @@ class BeeGraphicsView(QtWidgets.QGraphicsView, ActionsMixin): self.filename = filename def on_saving_finished(self, filename, errors): - if filename: - self.filename = filename - self.undo_stack.setClean() - else: + if errors: QtWidgets.QMessageBox.warning( self, 'Problem saving file', ('

Problem saving file %s

' '

File/directory not accessible

') % filename) + else: + self.filename = filename + self.undo_stack.setClean() def do_save(self, filename, create_new): if not filename.endswith('.bee'): diff --git a/requirements/dev.txt b/requirements/dev.txt index 7e7258f..1861881 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -2,3 +2,4 @@ coverage flake8 httpretty pytest +git+https://github.com/pytest-dev/pytest-qt.git diff --git a/tests/actions/test_mixin.py b/tests/actions/test_mixin.py index afb9c8b..6124336 100644 --- a/tests/actions/test_mixin.py +++ b/tests/actions/test_mixin.py @@ -5,7 +5,6 @@ from PyQt6 import QtWidgets from beeref.actions import ActionsMixin from beeref.actions.menu_structure import MENU_SEPARATOR -from ..base import BeeTestCase class FooWidget(QtWidgets.QWidget, ActionsMixin): @@ -23,226 +22,265 @@ class FooWidget(QtWidgets.QWidget, ActionsMixin): pass -class ActionsMixinTestCase(BeeTestCase): +@patch('PyQt6.QtGui.QAction.triggered') +@patch('PyQt6.QtGui.QAction.toggled') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_create_actions( + actions_mock, menu_mock, toggle_mock, trigger_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'shortcuts': ['Ctrl+F'], + 'callback': 'on_foo', + }] - def setUp(self): - menu_patcher = patch('beeref.actions.mixin.menu_structure') - self.menu_mock = menu_patcher.start() - self.addCleanup(menu_patcher.stop) - actions_patcher = patch('beeref.actions.mixin.actions') - self.actions_mock = actions_patcher.start() - self.addCleanup(actions_patcher.stop) - self.widget = FooWidget() + menu_mock.__iter__.return_value = ['foo'] + widget.build_menu_and_actions() + trigger_mock.connect.assert_called_once_with(widget.on_foo) + toggle_mock.connect.assert_not_called() - @patch('PyQt6.QtGui.QAction.triggered') - @patch('PyQt6.QtGui.QAction.toggled') - def test_create_actions(self, toggle_mock, trigger_mock): - self.actions_mock.__iter__.return_value = [{ - 'id': 'foo', - 'text': '&Foo', - 'shortcuts': ['Ctrl+F'], - 'callback': 'on_foo', - }] + assert len(widget.actions()) == 1 + qaction = widget.actions()[0] + assert qaction.text() == '&Foo' + assert qaction.shortcut() == 'Ctrl+F' + assert qaction.isEnabled() is True + assert widget.bee_actions['foo'] == qaction - self.menu_mock.__iter__.return_value = ['foo'] - self.widget.build_menu_and_actions() - trigger_mock.connect.assert_called_once_with(self.widget.on_foo) - toggle_mock.connect.assert_not_called() - assert len(self.widget.actions()) == 1 - qaction = self.widget.actions()[0] - assert qaction.text() == '&Foo' - assert qaction.shortcut() == 'Ctrl+F' - assert qaction.isEnabled() is True - assert self.widget.bee_actions['foo'] == qaction +@patch('PyQt6.QtGui.QAction.triggered') +@patch('PyQt6.QtGui.QAction.toggled') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_create_actions_checkable( + actions_mock, menu_mock, toggle_mock, trigger_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'checkable': True, + 'callback': 'on_foo', + }] - @patch('PyQt6.QtGui.QAction.triggered') - @patch('PyQt6.QtGui.QAction.toggled') - def test_create_actions_checkable(self, toggle_mock, trigger_mock): - self.actions_mock.__iter__.return_value = [{ - 'id': 'foo', - 'text': '&Foo', - 'checkable': True, - 'callback': 'on_foo', - }] + menu_mock.__iter__.return_value = ['foo'] + widget.build_menu_and_actions() + trigger_mock.connect.assert_not_called() + toggle_mock.connect.assert_called_once_with(widget.on_foo) - self.menu_mock.__iter__.return_value = ['foo'] - self.widget.build_menu_and_actions() - trigger_mock.connect.assert_not_called() - toggle_mock.connect.assert_called_once_with(self.widget.on_foo) + assert len(widget.actions()) == 1 + qaction = widget.actions()[0] + assert qaction.text() == '&Foo' + assert qaction.isEnabled() is True + assert qaction.isChecked() is False + assert widget.bee_actions['foo'] == qaction - assert len(self.widget.actions()) == 1 - qaction = self.widget.actions()[0] - assert qaction.text() == '&Foo' - assert qaction.isEnabled() is True - assert qaction.isChecked() is False - assert self.widget.bee_actions['foo'] == qaction - @patch.object(FooWidget, 'on_foo') - @patch.object(FooWidget, 'settings') - @patch('PyQt6.QtGui.QAction.toggled') - def test_create_actions_checkable_with_settings( - self, toggle_mock, settings_mock, callback_mock): - self.actions_mock.__iter__.return_value = [{ - 'id': 'foo', - 'text': '&Foo', - 'checkable': True, - 'callback': 'on_foo', - 'settings': 'foo/bar', - }] +@patch.object(FooWidget, 'on_foo') +@patch.object(FooWidget, 'settings') +@patch('PyQt6.QtGui.QAction.toggled') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_create_actions_checkable_with_settings( + actions_mock, menu_mock, toggle_mock, settings_mock, callback_mock, + qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'checkable': True, + 'callback': 'on_foo', + 'settings': 'foo/bar', + }] - self.menu_mock.__iter__.return_value = ['foo'] - settings_mock.value.return_value = True - self.widget.build_menu_and_actions() - settings_mock.value.assert_called_once_with( - 'foo/bar', False, type=bool) - qaction = self.widget.actions()[0] - assert qaction.isChecked() is True - assert toggle_mock.connect.call_count == 2 - callback_mock.assert_called_once_with(True) + menu_mock.__iter__.return_value = ['foo'] + settings_mock.value.return_value = True + widget.build_menu_and_actions() + settings_mock.value.assert_called_once_with( + 'foo/bar', False, type=bool) + qaction = widget.actions()[0] + assert qaction.isChecked() is True + assert toggle_mock.connect.call_count == 2 + callback_mock.assert_called_once_with(True) - def test_create_actions_with_group(self): - self.actions_mock.__iter__.return_value = [{ + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_create_actions_with_group(actions_mock, menu_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'callback': 'on_foo', + 'group': 'bar', + }] + menu_mock.__iter__.return_value = ['foo'] + widget.build_menu_and_actions() + assert len(widget.actions()) == 1 + qaction = widget.actions()[0] + assert widget.bee_actiongroups['bar'] == [qaction] + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_build_menu_and_actions_with_actions(actions_mock, menu_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'callback': 'on_foo', + 'group': 'bar', + }] + menu_mock.__iter__.return_value = ['foo'] + with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock: + widget.build_menu_and_actions() + assert isinstance(widget.context_menu, QtWidgets.QMenu) + add_mock.assert_called_once_with(widget.bee_actions['foo']) + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_build_menu_and_actions_with_separator(actions_mock, menu_mock, qapp): + widget = FooWidget() + menu_mock.__iter__.return_value = [MENU_SEPARATOR] + with patch('PyQt6.QtWidgets.QMenu.addSeparator') as sep_mock: + widget.build_menu_and_actions() + assert isinstance(widget.context_menu, QtWidgets.QMenu) + sep_mock.assert_called_once_with() + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_build_menu_and_actions_with_submenu(actions_mock, menu_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [{ + 'id': 'foo', + 'text': '&Foo', + 'callback': 'on_foo', + 'group': 'bar', + }] + menu_mock.__iter__.return_value = [ + {'menu': '&Bar', 'items': ['foo']}] + with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock: + with patch('PyQt6.QtWidgets.QMenu.addMenu') as addmenu_mock: + addmenu_mock.return_value = QtWidgets.QMenu() + widget.build_menu_and_actions() + assert isinstance(widget.context_menu, QtWidgets.QMenu) + addmenu_mock.assert_called_once_with('&Bar') + add_mock.assert_called_once_with(widget.bee_actions['foo']) + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_actiongroup_set_enabled(actions_mock, menu_mock, qapp): + widget = FooWidget() + actions_mock.__iter__.return_value = [ + { 'id': 'foo', 'text': '&Foo', 'callback': 'on_foo', - 'group': 'bar', - }] - self.menu_mock.__iter__.return_value = ['foo'] - self.widget.build_menu_and_actions() - assert len(self.widget.actions()) == 1 - qaction = self.widget.actions()[0] - assert self.widget.bee_actiongroups['bar'] == [qaction] + 'group': 'g1', + }, + { + 'id': 'bar', + 'text': '&Bar', + 'callback': 'on_foo', + 'group': 'g2', + }, + ] - def test_build_menu_and_actions_with_actions(self): - self.actions_mock.__iter__.return_value = [{ + menu_mock.__iter__.return_value = ['foo'] + widget.build_menu_and_actions() + widget.actiongroup_set_enabled('g1', True) + assert widget.bee_actions['foo'].isEnabled() is True + assert widget.bee_actions['bar'].isEnabled() is False + + +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_build_menu_and_actions_disables_actiongroups( + actions_mock, menu_mock, qapp): + widget = FooWidget() + widget.scene.has_selection.return_value = False + actions_mock.__iter__.return_value = [ + { 'id': 'foo', 'text': '&Foo', 'callback': 'on_foo', - 'group': 'bar', - }] - self.menu_mock.__iter__.return_value = ['foo'] - with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock: - self.widget.build_menu_and_actions() - assert isinstance(self.widget.context_menu, QtWidgets.QMenu) - add_mock.assert_called_once_with(self.widget.bee_actions['foo']) + 'group': 'active_when_selection', + }, + ] - def test_build_menu_and_actions_with_separator(self): - self.menu_mock.__iter__.return_value = [MENU_SEPARATOR] - with patch('PyQt6.QtWidgets.QMenu.addSeparator') as sep_mock: - self.widget.build_menu_and_actions() - assert isinstance(self.widget.context_menu, QtWidgets.QMenu) - sep_mock.assert_called_once_with() + menu_mock.__iter__.return_value = ['foo'] + widget.build_menu_and_actions() + qaction = widget.actions()[0] + assert qaction.isEnabled() is False - def test_build_menu_and_actions_with_submenu(self): - self.actions_mock.__iter__.return_value = [{ - 'id': 'foo', - 'text': '&Foo', - 'callback': 'on_foo', - 'group': 'bar', - }] - self.menu_mock.__iter__.return_value = [ - {'menu': '&Bar', 'items': ['foo']}] - with patch('PyQt6.QtWidgets.QMenu.addAction') as add_mock: - with patch('PyQt6.QtWidgets.QMenu.addMenu') as addmenu_mock: - addmenu_mock.return_value = QtWidgets.QMenu() - self.widget.build_menu_and_actions() - assert isinstance(self.widget.context_menu, QtWidgets.QMenu) - addmenu_mock.assert_called_once_with('&Bar') - add_mock.assert_called_once_with( - self.widget.bee_actions['foo']) - def test_actiongroup_set_enabled(self): - self.actions_mock.__iter__.return_value = [ - { - 'id': 'foo', - 'text': '&Foo', - 'callback': 'on_foo', - 'group': 'g1', - }, - { - 'id': 'bar', - 'text': '&Bar', - 'callback': 'on_foo', - 'group': 'g2', - }, - ] +@patch('beeref.config.BeeSettings.get_recent_files') +@patch('PyQt6.QtGui.QAction.triggered') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_create_recent_files( + actions_mock, menu_mock, triggered_mock, files_mock, qapp): + widget = FooWidget() + files_mock.return_value = [ + os.path.abspath(f'{i}.bee') for i in range(15)] + menu_mock.__iter__.return_value = [{ + 'menu': 'Open &Recent', + 'items': '_build_recent_files', + }] - self.menu_mock.__iter__.return_value = ['foo'] - self.widget.build_menu_and_actions() - self.widget.actiongroup_set_enabled('g1', True) - assert self.widget.bee_actions['foo'].isEnabled() is True - assert self.widget.bee_actions['bar'].isEnabled() is False + widget.build_menu_and_actions() + triggered_mock.connect.assert_called() + assert len(widget.actions()) == 15 + qaction1 = widget.actions()[0] + assert qaction1.text() == '0.bee' + assert qaction1.shortcut() == 'Ctrl+1' + assert qaction1.isEnabled() is True + assert widget.bee_actions['recent_files_0'] == qaction1 + qaction10 = widget.actions()[9] + assert qaction10.text() == '9.bee' + assert qaction10.shortcut() == 'Ctrl+0' + assert qaction10.isEnabled() is True + assert widget.bee_actions['recent_files_9'] == qaction10 + qaction15 = widget.actions()[-1] + assert qaction15.text() == '14.bee' + assert qaction15.shortcut() == '' + assert qaction15.isEnabled() is True + assert widget.bee_actions['recent_files_14'] == qaction15 - def test_build_menu_and_actions_disables_actiongroups(self): - self.widget.scene.has_selection.return_value = False - self.actions_mock.__iter__.return_value = [ - { - 'id': 'foo', - 'text': '&Foo', - 'callback': 'on_foo', - 'group': 'active_when_selection', - }, - ] - self.menu_mock.__iter__.return_value = ['foo'] - self.widget.build_menu_and_actions() - qaction = self.widget.actions()[0] - assert qaction.isEnabled() is False +@patch('beeref.config.BeeSettings.get_recent_files') +@patch('PyQt6.QtGui.QAction.triggered') +@patch('beeref.actions.mixin.menu_structure') +@patch('beeref.actions.mixin.actions') +def test_update_recent_files( + actions_mock, menu_mock, triggered_mock, files_mock, qapp): + widget = FooWidget() + files_mock.return_value = [os.path.abspath('foo.bee')] + menu_mock.__iter__.return_value = [{ + 'menu': 'Open &Recent', + 'items': '_build_recent_files', + }] - @patch('beeref.config.BeeSettings.get_recent_files') - @patch('PyQt6.QtGui.QAction.triggered') - def test_create_recent_files(self, triggered_mock, files_mock): - files_mock.return_value = [ - os.path.abspath(f'{i}.bee') for i in range(15)] - self.menu_mock.__iter__.return_value = [{ - 'menu': 'Open &Recent', - 'items': '_build_recent_files', - }] + widget.build_menu_and_actions() + triggered_mock.connect.reset_mock() + assert len(widget.actions()) == 1 + qaction1 = widget.actions()[0] + assert qaction1.text() == 'foo.bee' - self.widget.build_menu_and_actions() - triggered_mock.connect.assert_called() - assert len(self.widget.actions()) == 15 - qaction1 = self.widget.actions()[0] - assert qaction1.text() == '0.bee' - assert qaction1.shortcut() == 'Ctrl+1' - assert qaction1.isEnabled() is True - assert self.widget.bee_actions['recent_files_0'] == qaction1 - qaction10 = self.widget.actions()[9] - assert qaction10.text() == '9.bee' - assert qaction10.shortcut() == 'Ctrl+0' - assert qaction10.isEnabled() is True - assert self.widget.bee_actions['recent_files_9'] == qaction10 - qaction15 = self.widget.actions()[-1] - assert qaction15.text() == '14.bee' - assert qaction15.shortcut() == '' - assert qaction15.isEnabled() is True - assert self.widget.bee_actions['recent_files_14'] == qaction15 + files_mock.return_value = [os.path.abspath('bar.bee')] + widget.update_menu_and_actions() + triggered_mock.connect.assert_called() + assert len(widget.actions()) == 1 + qaction1 = widget.actions()[0] + assert qaction1.text() == 'bar.bee' - @patch('beeref.config.BeeSettings.get_recent_files') - @patch('PyQt6.QtGui.QAction.triggered') - def test_update_recent_files(self, triggered_mock, files_mock): - files_mock.return_value = [os.path.abspath('foo.bee')] - self.menu_mock.__iter__.return_value = [{ - 'menu': 'Open &Recent', - 'items': '_build_recent_files', - }] - self.widget.build_menu_and_actions() - triggered_mock.connect.reset_mock() - assert len(self.widget.actions()) == 1 - qaction1 = self.widget.actions()[0] - assert qaction1.text() == 'foo.bee' - - files_mock.return_value = [os.path.abspath('bar.bee')] - self.widget.update_menu_and_actions() - triggered_mock.connect.assert_called() - assert len(self.widget.actions()) == 1 - qaction1 = self.widget.actions()[0] - assert qaction1.text() == 'bar.bee' - - def test_create_menubar(self): - self.widget.toplevel_menus = [QtWidgets.QMenu('Foo')] - menubar = self.widget.create_menubar() - assert isinstance(menubar, QtWidgets.QMenuBar) - assert len(menubar.actions()) == 1 +def test_create_menubar(qapp): + widget = FooWidget() + widget.toplevel_menus = [QtWidgets.QMenu('Foo')] + menubar = widget.create_menubar() + assert isinstance(menubar, QtWidgets.QMenuBar) + assert len(menubar.actions()) == 1 diff --git a/tests/base.py b/tests/base.py deleted file mode 100644 index 9599308..0000000 --- a/tests/base.py +++ /dev/null @@ -1,43 +0,0 @@ -import os.path -import tempfile -from unittest import mock, TestCase - -from PyQt6 import QtWidgets - -from beeref.config import BeeSettings - - -root = os.path.dirname(__file__) -imgfilename3x3 = os.path.join(root, 'assets', 'test3x3.png') -with open(imgfilename3x3, 'rb') as f: - imgdata3x3 = f.read() - - -class BeeTestCase(TestCase): - - imgfilename3x3 = imgfilename3x3 - imgdata3x3 = imgdata3x3 - - @classmethod - def setUpClass(cls): - cls._settings_dir = tempfile.TemporaryDirectory() - settings_dir_patcher = mock.patch( - 'beeref.config.BeeSettings.get_settings_dir', - return_value=cls._settings_dir.name) - cls._settings_dir_mock = settings_dir_patcher.start() - inst = QtWidgets.QApplication.instance() - cls.app = inst if inst else QtWidgets.QApplication([]) - - @classmethod - def tearDownClass(cls): - cls._settings_dir_mock.stop() - cls._settings_dir.cleanup() - - def tearDown(self): - BeeSettings().clear() - - def queue2list(self, queue): - qlist = [] - while not queue.empty(): - qlist.append(queue.get()) - return qlist diff --git a/tests/conftest.py b/tests/conftest.py index bbd9e92..b1c87f4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,4 +1,8 @@ -from unittest.mock import MagicMock +import os.path +import pytest +from unittest.mock import MagicMock, patch + +from PyQt6 import QtGui, QtWidgets def pytest_configure(config): @@ -10,3 +14,57 @@ def pytest_configure(config): # logging configuration happens on module level import logging.config logging.config.dictConfig = MagicMock + + +@pytest.fixture(autouse=True) +def commandline_args(): + config_patcher = patch('beeref.view.commandline_args') + config_mock = config_patcher.start() + config_mock.filename = None + yield config_mock + config_patcher.stop() + + +@pytest.fixture(autouse=True) +def settings(tmpdir): + from beeref.config import BeeSettings + dir_patcher = patch('beeref.config.BeeSettings.get_settings_dir', + return_value=tmpdir.dirname) + dir_patcher.start() + settings = BeeSettings() + yield settings + settings.clear() + dir_patcher.stop() + + +@pytest.fixture +def main_window(qtbot): + from beeref.__main__ import BeeRefMainWindow + app = QtWidgets.QApplication.instance() + main = BeeRefMainWindow(app) + qtbot.addWidget(main) + yield main + + +@pytest.fixture +def view(main_window): + yield main_window.view + + +@pytest.fixture +def imgfilename3x3(): + root = os.path.dirname(__file__) + yield os.path.join(root, 'assets', 'test3x3.png') + + +@pytest.fixture +def imgdata3x3(imgfilename3x3): + with open(imgfilename3x3, 'rb') as f: + imgdata3x3 = f.read() + yield imgdata3x3 + + +@pytest.fixture +def item(): + from beeref.items import BeePixmapItem + yield BeePixmapItem(QtGui.QImage()) diff --git a/tests/fileio/test_image.py b/tests/fileio/test_image.py index 2e067e8..0210d7a 100644 --- a/tests/fileio/test_image.py +++ b/tests/fileio/test_image.py @@ -3,47 +3,48 @@ import httpretty from PyQt6 import QtCore from beeref.fileio.image import load_image -from ..base import BeeTestCase -class LoadImageTestCase(BeeTestCase): +def test_load_image_loads_from_filename(view, imgfilename3x3): + img, filename = load_image(imgfilename3x3) + assert img.isNull() is False + assert filename == imgfilename3x3 - def test_loads_from_filename(self): - img, filename = load_image(self.imgfilename3x3) - assert img.isNull() is False - assert filename == self.imgfilename3x3 - def test_loads_from_nonexisting_filename(self): - img, filename = load_image('foo.png') - assert img.isNull() is True - assert filename == 'foo.png' +def test_load_image_loads_from_nonexisting_filename(view, imgfilename3x3): + img, filename = load_image('foo.png') + assert img.isNull() is True + assert filename == 'foo.png' - def test_loads_from_existing_local_url(self): - url = QtCore.QUrl.fromLocalFile(self.imgfilename3x3) - img, filename = load_image(url) - assert img.isNull() is False - assert filename == self.imgfilename3x3 - @httpretty.activate - def test_loads_from_existing_web_url(self): - url = 'http://example.com/foo.png' - httpretty.register_uri( - httpretty.GET, - url, - body=self.imgdata3x3, - ) - img, filename = load_image(QtCore.QUrl(url)) - assert img.isNull() is False - assert filename == url +def test_load_image_loads_from_existing_local_url(view, imgfilename3x3): + url = QtCore.QUrl.fromLocalFile(imgfilename3x3) + img, filename = load_image(url) + assert img.isNull() is False + assert filename == imgfilename3x3 - @httpretty.activate - def test_loads_from_web_url_errors(self): - url = 'http://example.com/foo.png' - httpretty.register_uri( - httpretty.GET, - url, - status=500, - ) - img, filename = load_image(QtCore.QUrl(url)) - assert img.isNull() is True - assert filename == url + +@httpretty.activate +def test_load_image_loads_from_existing_web_url(view, imgdata3x3): + url = 'http://example.com/foo.png' + httpretty.register_uri( + httpretty.GET, + url, + body=imgdata3x3, + ) + img, filename = load_image(QtCore.QUrl(url)) + assert img.isNull() is False + assert filename == url + + +@httpretty.activate +def test_load_image_loads_from_web_url_errors(view, imgfilename3x3): + url = 'http://example.com/foo.png' + httpretty.register_uri( + httpretty.GET, + url, + status=500, + ) + img, filename = load_image(QtCore.QUrl(url)) + assert img.isNull() is True + assert filename == url diff --git a/tests/fileio/test_init.py b/tests/fileio/test_init.py index 2c3126d..d3514d2 100644 --- a/tests/fileio/test_init.py +++ b/tests/fileio/test_init.py @@ -6,8 +6,7 @@ from PyQt6 import QtCore from beeref import fileio from beeref import commands -from beeref.scene import BeeGraphicsScene -from ..base import BeeTestCase +from ..utils import queue2list @patch('beeref.fileio.sql.SQLiteIO.write') @@ -26,62 +25,62 @@ def test_read_bee(read_mock): read_mock.assert_called_once() -class LoadImagesTestCase(BeeTestCase): +def test_load_images_loads(view, imgfilename3x3): + view.scene.undo_stack = MagicMock() + worker = MagicMock(canceled=False) + fileio.load_images([imgfilename3x3], + QtCore.QPointF(5, 6), view.scene, worker) + worker.begin_processing.emit.assert_called_once_with(1) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with('', []) + items = queue2list(view.scene.items_to_add) + assert len(items) == 1 + item = items[0][0] + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + assert isinstance(cmd, commands.InsertItems) + assert cmd.items == [item] + assert cmd.scene == view.scene + assert cmd.ignore_first_redo is True + assert item.pos() == QtCore.QPointF(3.5, 4.5) - def setUp(self): - self.scene = BeeGraphicsScene(MagicMock()) - def test_loads(self): - worker = MagicMock(canceled=False) - fileio.load_images([self.imgfilename3x3], - QtCore.QPointF(5, 6), self.scene, worker) - worker.begin_processing.emit.assert_called_once_with(1) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with('', []) - items = self.queue2list(self.scene.items_to_add) - assert len(items) == 1 - item = items[0][0] - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - assert isinstance(cmd, commands.InsertItems) - assert cmd.items == [item] - assert cmd.scene == self.scene - assert cmd.ignore_first_redo is True - assert item.pos() == QtCore.QPointF(3.5, 4.5) +def test_load_images_canceled(view, imgfilename3x3): + view.scene.undo_stack = MagicMock() + worker = MagicMock(canceled=True) + fileio.load_images([imgfilename3x3, imgfilename3x3], + QtCore.QPointF(5, 6), view.scene, worker) + worker.begin_processing.emit.assert_called_once_with(2) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with('', []) + items = queue2list(view.scene.items_to_add) + assert len(items) == 1 + item = items[0][0] + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + assert isinstance(cmd, commands.InsertItems) + assert cmd.items == [item] + assert cmd.scene == view.scene + assert cmd.ignore_first_redo is True + assert item.pos() == QtCore.QPointF(3.5, 4.5) - def test_canceled(self): - worker = MagicMock(canceled=True) - fileio.load_images([self.imgfilename3x3, self.imgfilename3x3], - QtCore.QPointF(5, 6), self.scene, worker) - worker.begin_processing.emit.assert_called_once_with(2) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with('', []) - items = self.queue2list(self.scene.items_to_add) - assert len(items) == 1 - item = items[0][0] - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - assert isinstance(cmd, commands.InsertItems) - assert cmd.items == [item] - assert cmd.scene == self.scene - assert cmd.ignore_first_redo is True - assert item.pos() == QtCore.QPointF(3.5, 4.5) - def test_error(self): - worker = MagicMock(canceled=False) - fileio.load_images(['foo.jpg', self.imgfilename3x3], - QtCore.QPointF(5, 6), self.scene, worker) - worker.begin_processing.emit.assert_called_once_with(2) - worker.progress.emit.assert_any_call(0) - worker.progress.emit.assert_any_call(1) - worker.finished.emit.assert_called_once_with('', ['foo.jpg']) - items = self.queue2list(self.scene.items_to_add) - assert len(items) == 1 - item = items[0][0] - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - assert isinstance(cmd, commands.InsertItems) - assert cmd.items == [item] - assert cmd.scene == self.scene - assert cmd.ignore_first_redo is True - assert item.pos() == QtCore.QPointF(3.5, 4.5) +def test_load_images_error(view, imgfilename3x3): + view.scene.undo_stack = MagicMock() + worker = MagicMock(canceled=False) + fileio.load_images(['foo.jpg', imgfilename3x3], + QtCore.QPointF(5, 6), view.scene, worker) + worker.begin_processing.emit.assert_called_once_with(2) + worker.progress.emit.assert_any_call(0) + worker.progress.emit.assert_any_call(1) + worker.finished.emit.assert_called_once_with('', ['foo.jpg']) + items = queue2list(view.scene.items_to_add) + assert len(items) == 1 + item = items[0][0] + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + assert isinstance(cmd, commands.InsertItems) + assert cmd.items == [item] + assert cmd.scene == view.scene + assert cmd.ignore_first_redo is True + assert item.pos() == QtCore.QPointF(3.5, 4.5) diff --git a/tests/fileio/test_sql.py b/tests/fileio/test_sql.py index e4b24fb..d5082e8 100644 --- a/tests/fileio/test_sql.py +++ b/tests/fileio/test_sql.py @@ -1,5 +1,4 @@ import os.path -import tempfile from unittest.mock import MagicMock, patch from PyQt6 import QtGui @@ -8,314 +7,314 @@ import pytest from beeref.fileio.errors import BeeFileIOError from beeref.fileio.sql import SQLiteIO from beeref.items import BeePixmapItem -from beeref.scene import BeeGraphicsScene -from ..base import BeeTestCase -class SQLiteIOTestCase(BeeTestCase): +def test_sqliteio_ẁrite_meta_application_id(): + io = SQLiteIO(':memory:', MagicMock(), create_new=True) + io.write_meta() + result = io.fetchone('PRAGMA application_id') + assert result[0] == SQLiteIO.APPLICATION_ID - def setUp(self): - self.scene_mock = MagicMock() - self.io = SQLiteIO(':memory:', self.scene_mock, create_new=True) - def test_ẁrite_meta_application_id(self): - self.io.write_meta() - result = self.io.fetchone('PRAGMA application_id') - assert result[0] == SQLiteIO.APPLICATION_ID +def test_sqliteio_ẁrite_meta_user_version(): + io = SQLiteIO(':memory:', MagicMock(), create_new=True) + io.write_meta() + result = io.fetchone('PRAGMA user_version') + assert result[0] == SQLiteIO.USER_VERSION - def test_ẁrite_meta_user_version(self): - self.io.write_meta() - result = self.io.fetchone('PRAGMA user_version') - assert result[0] == SQLiteIO.USER_VERSION - def test_ẁrite_meta_foreign_keys(self): - self.io.write_meta() - result = self.io.fetchone('PRAGMA foreign_keys') - assert result[0] == 1 +def test_sqliteio_ẁrite_meta_foreign_keys(): + io = SQLiteIO(':memory:', MagicMock(), create_new=True) + io.write_meta() + result = io.fetchone('PRAGMA foreign_keys') + assert result[0] == 1 - def test_create_schema_on_new_when_create_new(self): - self.io.create_schema_on_new() - result = self.io.fetchone( - 'SELECT COUNT(*) FROM sqlite_master ' - 'WHERE type="table" AND name NOT LIKE "sqlite_%"') - assert result[0] == 2 - self.scene_mock.clear_save_ids.assert_called_once() - def test_create_schema_on_new_when_not_create_new(self): - self.io.create_new = False - self.io.create_schema_on_new() - result = self.io.fetchone( - 'SELECT COUNT(*) FROM sqlite_master ' - 'WHERE type="table" AND name NOT LIKE "sqlite_%"') - assert result[0] == 0 - self.scene_mock.clear_save_ids.assert_not_called() +def test_sqliteio_create_schema_on_new_when_create_new(): + scene_mock = MagicMock() + io = SQLiteIO(':memory:', scene_mock, create_new=True) + io.create_schema_on_new() + result = io.fetchone( + 'SELECT COUNT(*) FROM sqlite_master ' + 'WHERE type="table" AND name NOT LIKE "sqlite_%"') + assert result[0] == 2 + scene_mock.clear_save_ids.assert_called_once() - def test_readonly_doesnt_allow_write(self): - scene = BeeGraphicsScene(None) - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - with open(fname, 'w') as f: - f.write('foobar') - io = SQLiteIO(fname, scene, readonly=True) - with pytest.raises(BeeFileIOError) as exinfo: +def test_sqliteio_create_schema_on_new_when_not_create_new(): + scene_mock = MagicMock() + io = SQLiteIO(':memory:', scene_mock, create_new=False) + io.create_schema_on_new() + result = io.fetchone( + 'SELECT COUNT(*) FROM sqlite_master ' + 'WHERE type="table" AND name NOT LIKE "sqlite_%"') + assert result[0] == 0 + scene_mock.clear_save_ids.assert_not_called() + + +def test_sqliteio_readonly_doesnt_allow_write(view, tmpdir): + fname = os.path.join(tmpdir, 'test.bee') + with open(fname, 'w') as f: + f.write('foobar') + io = SQLiteIO(fname, view.scene, readonly=True) + + with pytest.raises(BeeFileIOError) as exinfo: + io.write() + + assert exinfo.value.filename == fname + with open(fname, 'r') as f: + f.read() == 'foobar' + + +def test_sqliteio_write_calls_create_schema_on_new(view): + io = SQLiteIO(':memory:', view.scene, create_new=True) + with patch.object(io, 'create_schema_on_new') as crmock: + with patch.object(io, 'fetchall'): + with patch.object(io, 'exmany'): io.write() - - assert exinfo.value.filename == fname - with open(fname, 'r') as f: - f.read() == 'foobar' + crmock.assert_called_once() -class SQLiteIOWriteTestCase(BeeTestCase): - - def setUp(self): - self.scene = BeeGraphicsScene(None) - self.io = SQLiteIO(':memory:', self.scene, create_new=True) - - def test_calls_create_schema_on_new(self): - with patch.object(self.io, 'create_schema_on_new') as crmock: - with patch.object(self.io, 'fetchall'): - with patch.object(self.io, 'exmany'): - self.io.write() - crmock.assert_called_once() - - def test_calls_write_meta(self): - with patch.object(self.io, 'write_meta') as metamock: - with patch.object(self.io, 'fetchall'): - with patch.object(self.io, 'exmany'): - self.io.write() - metamock.assert_called_once() - - def test_inserts_new_item(self): - item = BeePixmapItem(QtGui.QImage(), filename='bee.jpg') - self.scene.addItem(item) - item.setScale(1.3) - item.setPos(44, 55) - item.setZValue(0.22) - item.setRotation(33) - item.do_flip() - item.pixmap_to_bytes = MagicMock(return_value=b'abc') - self.io.write() - - assert item.save_id == 1 - result = self.io.fetchone( - 'SELECT x, y, z, scale, rotation, flip, filename, type, ' - 'sqlar.data, sqlar.name ' - 'FROM items ' - 'INNER JOIN sqlar on sqlar.item_id = items.id') - assert result[0] == 44.0 - assert result[1] == 55.0 - assert result[2] == 0.22 - assert result[3] == 1.3 - assert result[4] == 33 - assert result[5] == -1 - assert result[6] == 'bee.jpg' - assert result[7] == 'pixmap' - assert result[8] == b'abc' - assert result[9] == '0001-bee.png' - - def test_inserts_new_item_without_filename(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - self.io.write() - - assert item.save_id == 1 - result = self.io.fetchone( - 'SELECT filename, sqlar.name FROM items ' - 'INNER JOIN sqlar on sqlar.item_id = items.id') - assert result[0] is None - assert result[1] == '0001.png' - - def test_updates_existing_item(self): - item = BeePixmapItem(QtGui.QImage(), filename='bee.png') - self.scene.addItem(item) - item.setScale(1.3) - item.setPos(44, 55) - item.setZValue(0.22) - item.setRotation(33) - item.save_id = 1 - item.pixmap_to_bytes = MagicMock(return_value=b'abc') - self.io.write() - item.setScale(0.7) - item.setPos(20, 30) - item.setZValue(0.33) - item.setRotation(100) - item.do_flip() - item.filename = 'new.png' - item.pixmap_to_bytes.return_value = b'updated' - self.io.create_new = False - self.io.write() - - assert self.io.fetchone('SELECT COUNT(*) from items') == (1,) - result = self.io.fetchone( - 'SELECT x, y, z, scale, rotation, flip, filename, sqlar.data ' - 'FROM items ' - 'INNER JOIN sqlar on sqlar.item_id = items.id') - assert result[0] == 20 - assert result[1] == 30 - assert result[2] == 0.33 - assert result[3] == 0.7 - assert result[4] == 100 - assert result[5] == -1 - assert result[6] == 'new.png' - assert result[7] == b'abc' - - def test_removes_nonexisting_item(self): - item = BeePixmapItem(QtGui.QImage(), filename='bee.png') - item.setScale(1.3) - item.setPos(44, 55) - self.scene.addItem(item) - self.io.write() - - self.scene.removeItem(item) - self.io.create_new = False - self.io.write() - - assert self.io.fetchone('SELECT COUNT(*) from items') == (0,) - assert self.io.fetchone('SELECT COUNT(*) from sqlar') == (0,) - - def test_update_recovers_from_borked_file(self): - item = BeePixmapItem(QtGui.QImage(), filename='bee.png') - self.scene.addItem(item) - - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - with open(fname, 'w') as f: - f.write('foobar') - - io = SQLiteIO(fname, self.scene, create_new=False) - io.write() - result = io.fetchone('SELECT COUNT(*) FROM items') - assert result[0] == 1 - - def test_updates_progress(self): - worker = MagicMock(canceled=False) - io = SQLiteIO(':memory:', self.scene, create_new=True, - worker=worker) - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - io.write() - worker.begin_processing.emit.assert_called_once_with(1) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with(':memory:', []) - - def test_canceled(self): - worker = MagicMock(canceled=True) - io = SQLiteIO(':memory:', self.scene, create_new=True, - worker=worker) - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - io.write() - worker.begin_processing.emit.assert_called_once_with(2) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with(':memory:', []) +def test_sqliteio_write_calls_write_meta(view): + io = SQLiteIO(':memory:', view.scene, create_new=True) + with patch.object(io, 'write_meta') as metamock: + with patch.object(io, 'fetchall'): + with patch.object(io, 'exmany'): + io.write() + metamock.assert_called_once() -class SQLiteIOReadTestCase(BeeTestCase): +def test_sqliteio_write_inserts_new_item(view): + item = BeePixmapItem(QtGui.QImage(), filename='bee.jpg') + view.scene.addItem(item) + item.setScale(1.3) + item.setPos(44, 55) + item.setZValue(0.22) + item.setRotation(33) + item.do_flip() + item.pixmap_to_bytes = MagicMock(return_value=b'abc') + io = SQLiteIO(':memory:', view.scene, create_new=True) + io.write() - def setUp(self): - self.scene = BeeGraphicsScene(None) + assert item.save_id == 1 + result = io.fetchone( + 'SELECT x, y, z, scale, rotation, flip, filename, type, ' + 'sqlar.data, sqlar.name ' + 'FROM items ' + 'INNER JOIN sqlar on sqlar.item_id = items.id') + assert result[0] == 44.0 + assert result[1] == 55.0 + assert result[2] == 0.22 + assert result[3] == 1.3 + assert result[4] == 33 + assert result[5] == -1 + assert result[6] == 'bee.jpg' + assert result[7] == 'pixmap' + assert result[8] == b'abc' + assert result[9] == '0001-bee.png' - def test_reads_readonly(self): - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - io = SQLiteIO(fname, self.scene, create_new=True) - io.create_schema_on_new() - io.ex('INSERT INTO items ' - '(type, x, y, z, scale, rotation, flip, filename) ' - 'VALUES (?, ?, ?, ?, ?, ?, ?, ?) ', - ('pixmap', 22.2, 33.3, 0.22, 3.4, 45, -1, 'bee.png')) - io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', - (1, self.imgdata3x3)) - io.connection.commit() - del(io) - io = SQLiteIO(fname, self.scene, readonly=True) - io.read() - item, selected = self.scene.items_to_add.get() - assert selected is False - assert item.save_id == 1 - assert item.pos().x() == 22.2 - assert item.pos().y() == 33.3 - assert item.zValue() == 0.22 - assert item.scale() == 3.4 - assert item.rotation() == 45 - assert item.flip() == -1 - assert item.filename == 'bee.png' - assert item.width == 3 - assert item.height == 3 - assert self.scene.items_to_add.empty() is True +def test_sqliteio_write_inserts_new_item_without_filename(view, item): + view.scene.addItem(item) + io = SQLiteIO(':memory:', view.scene, create_new=True) + io.write() - def test_updates_progress(self): - worker = MagicMock(canceled=False) - io = SQLiteIO(':memory:', self.scene, create_new=True, - worker=worker) + assert item.save_id == 1 + result = io.fetchone( + 'SELECT filename, sqlar.name FROM items ' + 'INNER JOIN sqlar on sqlar.item_id = items.id') + assert result[0] is None + assert result[1] == '0001.png' - io.create_schema_on_new() - io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' - 'VALUES (?, ?, ?, ?, ?, ?) ', - ('pixmap', 0, 0, 0, 1, 'bee.png')) - io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) - io.connection.commit() + +def test_sqliteio_write_updates_existing_item(view): + item = BeePixmapItem(QtGui.QImage(), filename='bee.png') + view.scene.addItem(item) + item.setScale(1.3) + item.setPos(44, 55) + item.setZValue(0.22) + item.setRotation(33) + item.save_id = 1 + item.pixmap_to_bytes = MagicMock(return_value=b'abc') + io = SQLiteIO(':memory:', view.scene, create_new=True) + io.write() + item.setScale(0.7) + item.setPos(20, 30) + item.setZValue(0.33) + item.setRotation(100) + item.do_flip() + item.filename = 'new.png' + item.pixmap_to_bytes.return_value = b'updated' + io.create_new = False + io.write() + + assert io.fetchone('SELECT COUNT(*) from items') == (1,) + result = io.fetchone( + 'SELECT x, y, z, scale, rotation, flip, filename, sqlar.data ' + 'FROM items ' + 'INNER JOIN sqlar on sqlar.item_id = items.id') + assert result[0] == 20 + assert result[1] == 30 + assert result[2] == 0.33 + assert result[3] == 0.7 + assert result[4] == 100 + assert result[5] == -1 + assert result[6] == 'new.png' + assert result[7] == b'abc' + + +def test_sqliteio_write_removes_nonexisting_item(view): + item = BeePixmapItem(QtGui.QImage(), filename='bee.png') + item.setScale(1.3) + item.setPos(44, 55) + view.scene.addItem(item) + io = SQLiteIO(':memory:', view.scene, create_new=True) + io.write() + + view.scene.removeItem(item) + io.create_new = False + io.write() + + assert io.fetchone('SELECT COUNT(*) from items') == (0,) + assert io.fetchone('SELECT COUNT(*) from sqlar') == (0,) + + +def test_sqliteio_write_update_recovers_from_borked_file(view, tmpdir): + item = BeePixmapItem(QtGui.QImage(), filename='bee.png') + view.scene.addItem(item) + + fname = os.path.join(tmpdir, 'test.bee') + with open(fname, 'w') as f: + f.write('foobar') + + io = SQLiteIO(fname, view.scene, create_new=False) + io.write() + result = io.fetchone('SELECT COUNT(*) FROM items') + assert result[0] == 1 + + +def test_sqliteio_write_updates_progress(view): + worker = MagicMock(canceled=False) + io = SQLiteIO(':memory:', view.scene, create_new=True, worker=worker) + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + io.write() + worker.begin_processing.emit.assert_called_once_with(1) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with(':memory:', []) + + +def test_sqliteio_write_canceled(view): + worker = MagicMock(canceled=True) + io = SQLiteIO(':memory:', view.scene, create_new=True, worker=worker) + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + io.write() + worker.begin_processing.emit.assert_called_once_with(2) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with(':memory:', []) + + +def test_sqliteio_read_reads_readonly(view, tmpdir, imgdata3x3): + fname = os.path.join(tmpdir, 'test.bee') + io = SQLiteIO(fname, view.scene, create_new=True) + io.create_schema_on_new() + io.ex('INSERT INTO items ' + '(type, x, y, z, scale, rotation, flip, filename) ' + 'VALUES (?, ?, ?, ?, ?, ?, ?, ?) ', + ('pixmap', 22.2, 33.3, 0.22, 3.4, 45, -1, 'bee.png')) + io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', + (1, imgdata3x3)) + io.connection.commit() + del(io) + + io = SQLiteIO(fname, view.scene, readonly=True) + io.read() + item, selected = view.scene.items_to_add.get() + assert selected is False + assert item.save_id == 1 + assert item.pos().x() == 22.2 + assert item.pos().y() == 33.3 + assert item.zValue() == 0.22 + assert item.scale() == 3.4 + assert item.rotation() == 45 + assert item.flip() == -1 + assert item.filename == 'bee.png' + assert item.width == 3 + assert item.height == 3 + assert view.scene.items_to_add.empty() is True + + +def test_sqliteio_read_updates_progress(view): + worker = MagicMock(canceled=False) + io = SQLiteIO(':memory:', view.scene, create_new=True, + worker=worker) + + io.create_schema_on_new() + io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' + 'VALUES (?, ?, ?, ?, ?, ?) ', + ('pixmap', 0, 0, 0, 1, 'bee.png')) + io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) + io.connection.commit() + io.read() + worker.begin_processing.emit.assert_called_once_with(1) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with(':memory:', []) + + +def test_sqliteio_read_canceled(view): + worker = MagicMock(canceled=True) + io = SQLiteIO(':memory:', view.scene, create_new=True, worker=worker) + + io.create_schema_on_new() + io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' + 'VALUES (?, ?, ?, ?, ?, ?) ', + ('pixmap', 0, 0, 0, 1, 'bee.png')) + io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) + io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' + 'VALUES (?, ?, ?, ?, ?, ?) ', + ('pixmap', 50, 50, 0, 1, 'bee2.png')) + io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) + io.connection.commit() + io.read() + worker.begin_processing.emit.assert_called_once_with(2) + worker.progress.emit.assert_called_once_with(0) + worker.finished.emit.assert_called_once_with('', []) + + +def test_sqliteio_read_raises_error_when_file_borked(view, tmpdir): + fname = os.path.join(tmpdir, 'test.bee') + with open(fname, 'w') as f: + f.write('foobar') + + io = SQLiteIO(fname, view.scene, readonly=True) + with pytest.raises(BeeFileIOError) as exinfo: io.read() - worker.begin_processing.emit.assert_called_once_with(1) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with(':memory:', []) + assert exinfo.value.filename == fname - def test_canceled(self): - worker = MagicMock(canceled=True) - io = SQLiteIO(':memory:', self.scene, create_new=True, - worker=worker) - io.create_schema_on_new() - io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' - 'VALUES (?, ?, ?, ?, ?, ?) ', - ('pixmap', 0, 0, 0, 1, 'bee.png')) - io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) - io.ex('INSERT INTO items (type, x, y, z, scale, filename) ' - 'VALUES (?, ?, ?, ?, ?, ?) ', - ('pixmap', 50, 50, 0, 1, 'bee2.png')) - io.ex('INSERT INTO sqlar (item_id, data) VALUES (?, ?)', (1, b'')) - io.connection.commit() +def test_sqliteio_read_emits_error_message_when_file_borked(view, tmpdir): + fname = os.path.join(tmpdir, 'test.bee') + with open(fname, 'w') as f: + f.write('foobar') + + worker = MagicMock() + io = SQLiteIO(fname, view.scene, readonly=True, worker=worker) + io.read() + worker.finished.emit.assert_called_once() + args = worker.finished.emit.call_args_list[0][0] + assert args[0] == fname + assert len(args[1]) == 1 + + +def test_sqliteio_read_raises_error_when_file_empty(view, tmpdir): + fname = os.path.join(tmpdir, 'test.bee') + io = SQLiteIO(fname, view.scene, readonly=True) + with pytest.raises(BeeFileIOError) as exinfo: io.read() - worker.begin_processing.emit.assert_called_once_with(2) - worker.progress.emit.assert_called_once_with(0) - worker.finished.emit.assert_called_once_with('', []) + assert exinfo.value.filename == fname - def test_raises_error_when_file_borked(self): - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - with open(fname, 'w') as f: - f.write('foobar') - - io = SQLiteIO(fname, self.scene, readonly=True) - with pytest.raises(BeeFileIOError) as exinfo: - io.read() - assert exinfo.value.filename == fname - - def test_emits_error_message_when_file_borked(self): - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - with open(fname, 'w') as f: - f.write('foobar') - - worker = MagicMock() - io = SQLiteIO(fname, self.scene, readonly=True, worker=worker) - io.read() - worker.finished.emit.assert_called_once() - args = worker.finished.emit.call_args_list[0][0] - assert args[0] == '' - assert len(args[1]) == 1 - - def test_reads_raises_error_when_file_empty(self): - with tempfile.TemporaryDirectory() as dirname: - fname = os.path.join(dirname, 'test.bee') - io = SQLiteIO(fname, self.scene, readonly=True) - with pytest.raises(BeeFileIOError) as exinfo: - io.read() - assert exinfo.value.filename == fname - - # should not create a file on reading! - assert os.path.isfile(fname) is False + # should not create a file on reading! + assert os.path.isfile(fname) is False diff --git a/tests/selection/test_base_item_mixin.py b/tests/selection/test_base_item_mixin.py new file mode 100644 index 0000000..919112f --- /dev/null +++ b/tests/selection/test_base_item_mixin.py @@ -0,0 +1,128 @@ +from unittest.mock import patch, MagicMock, PropertyMock + +from PyQt6 import QtCore, QtGui + +from beeref.items import BeePixmapItem + + +def test_set_scale(qapp, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3), imgfilename3x3) + item.prepareGeometryChange = MagicMock() + item.setScale(3) + assert item.scale() == 3 + assert item.pos().x() == 0 + assert item.pos().y() == 0 + item.prepareGeometryChange.assert_called_once() + + +def test_set_scale_ignores_zero(qapp, item): + item.setScale(0) + assert item.scale() == 1 + + +def test_set_scale_ignores_negative(qapp, item): + item.setScale(-0.1) + assert item.scale() == 1 + + +def test_set_scale_with_anchor(qapp, item): + item.setScale(2, anchor=QtCore.QPointF(100, 100)) + assert item.scale() == 2 + assert item.pos() == QtCore.QPointF(-100, -100) + + +def test_set_zvalue_sets_new_max(view, item): + view.scene.addItem(item) + item.setZValue(1.1) + assert item.zValue() == 1.1 + assert view.scene.max_z == 1.1 + + +def test_set_zvalue_keeps_old_max(view, item): + view.scene.addItem(item) + view.scene.max_z = 3.3 + item.setZValue(1.1) + assert item.zValue() == 1.1 + assert view.scene.max_z == 3.3 + + +def test_bring_to_front(view, item): + view.scene.addItem(item) + item.setZValue(3.3) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.bring_to_front() + assert item2.zValue() > item.zValue() + assert item2.zValue() == view.scene.max_z + + +def test_set_rotation_no_anchor(qapp, item): + item.setRotation(45) + assert item.rotation() == 45 + assert item.pos().x() == 0 + assert item.pos().y() == 0 + + +def test_set_rotation_anchor_bottomright_cw(qapp, item): + item.setRotation(90, QtCore.QPointF(100, 100)) + assert item.rotation() == 90 + assert item.pos().x() == 200 + assert item.pos().y() == 0 + + +def test_set_rotation_anchor_bottomright_ccw(qapp, item): + item.setRotation(-90, QtCore.QPointF(100, 100)) + assert item.rotation() == 270 + assert item.pos().x() == 0 + assert item.pos().y() == 200 + + +def test_set_rotation_caps_above_360(qapp, item): + item.setRotation(400) + assert item.rotation() == 40 + + +def test_flip(qapp, item): + assert item.flip() == 1 + + +def test_flip_when_flipped(qapp, item): + item.do_flip() + assert item.flip() == -1 + + +def test_do_flip_no_anchor(qapp, item): + item.do_flip() + assert item.flip() == -1 + item.do_flip() + assert item.flip() == 1 + + +def test_do_flip_vertical(qapp, item): + item.do_flip(vertical=True) + assert item.flip() == -1 + assert item.rotation() == 180 + + +def test_do_flip_anchor(qapp, item): + item.do_flip(anchor=QtCore.QPointF(100, 100)) + assert item.flip() == -1 + assert item.pos() == QtCore.QPointF(200, 0) + + +def test_do_flip_vertical_anchor(qapp, item): + item.do_flip(vertical=True, anchor=QtCore.QPointF(100, 100)) + assert item.flip() == -1 + assert item.rotation() == 180 + assert item.pos() == QtCore.QPointF(0, 200) + + +def test_base_item_mixin_center_scene_coords(view, item): + view.scene.addItem(item) + item.setPos(5, 5) + item.setScale(2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.center_scene_coords == QtCore.QPointF(105, 85) diff --git a/tests/selection/test_multi_select_item.py b/tests/selection/test_multi_select_item.py new file mode 100644 index 0000000..348808f --- /dev/null +++ b/tests/selection/test_multi_select_item.py @@ -0,0 +1,99 @@ +from unittest.mock import patch, MagicMock + +from PyQt6 import QtCore, QtGui +from PyQt6.QtCore import Qt + +from beeref.items import BeePixmapItem +from beeref.selection import MultiSelectItem + + +@patch('beeref.selection.SelectableMixin.init_selectable') +def test_init(selectable_mock): + item = MultiSelectItem() + assert hasattr(item, 'save_id') is False + selectable_mock.assert_called_once() + + +def test_width(): + item = MultiSelectItem() + item.setRect(0, 0, 50, 100) + assert item.width == 50 + + +def test_height(): + item = MultiSelectItem() + item.setRect(0, 0, 50, 100) + assert item.height == 100 + + +def test_paint(): + item = MultiSelectItem() + item.paint_selectable = MagicMock() + painter = MagicMock() + item.paint(painter, None, None) + item.paint_selectable.assert_called_once() + painter.drawRect.assert_not_called() + + +def test_has_selection_outline(): + item = MultiSelectItem() + item.has_selection_outline() is True + + +def test_has_selection_handles(): + item = MultiSelectItem() + item.has_selection_handles() is True + + +def test_selection_action_items(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + item3.setSelected(False) + action_items = set(view.scene.multi_select_item.selection_action_items()) + assert action_items == {item1, item2, view.scene.multi_select_item} + + +def test_fit_selection_area(): + item = MultiSelectItem() + item.setScale(5) + item.setRotation(-20) + item.do_flip() + item.fit_selection_area(QtCore.QRectF(-10, -20, 100, 80)) + assert item.pos().x() == -10 + assert item.pos().y() == -20 + assert item.width == 100 + assert item.height == 80 + assert item.scale() == 1 + assert item.rotation() == 0 + assert item.flip() == 1 + assert item.isSelected() is True + + +@patch('PyQt6.QtWidgets.QGraphicsRectItem.mousePressEvent') +def test_mouse_press_event_when_ctrl_leftclick(mouse_mock): + item = MultiSelectItem() + item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80)) + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.LeftButton), + modifiers=MagicMock( + return_value=Qt.KeyboardModifier.ControlModifier)) + item.mousePressEvent(event) + event.ignore.assert_called_once() + mouse_mock.assert_not_called() + + +@patch('beeref.selection.SelectableMixin.mousePressEvent') +def test_mouse_press_event_when_leftclick(mouse_mock): + item = MultiSelectItem() + item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80)) + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.LeftButton)) + item.mousePressEvent(event) + event.ignore.assert_not_called() + mouse_mock.assert_called_once_with(event) diff --git a/tests/selection/test_rubberband_item.py b/tests/selection/test_rubberband_item.py new file mode 100644 index 0000000..e10cc09 --- /dev/null +++ b/tests/selection/test_rubberband_item.py @@ -0,0 +1,33 @@ +from PyQt6 import QtCore + +from beeref.selection import RubberbandItem + + +def test_width(): + item = RubberbandItem() + item.setRect(5, 5, 100, 80) + assert item.width == 100 + + +def test_height(): + item = RubberbandItem() + item.setRect(5, 5, 100, 80) + assert item.height == 80 + + +def test_fit_topleft_to_bottomright(): + item = RubberbandItem() + item.fit(QtCore.QPointF(-10, -20), QtCore.QPointF(30, 40)) + assert item.rect().topLeft().x() == -10 + assert item.rect().topLeft().y() == -20 + assert item.rect().bottomRight().x() == 30 + assert item.rect().bottomRight().y() == 40 + + +def test_fit_topright_to_bottomleft(): + item = RubberbandItem() + item.fit(QtCore.QPointF(50, -20), QtCore.QPointF(-30, 40)) + assert item.rect().topLeft().x() == -30 + assert item.rect().topLeft().y() == -20 + assert item.rect().bottomRight().x() == 50 + assert item.rect().bottomRight().y() == 40 diff --git a/tests/selection/test_selectable_mixin.py b/tests/selection/test_selectable_mixin.py new file mode 100644 index 0000000..fd6d3ad --- /dev/null +++ b/tests/selection/test_selectable_mixin.py @@ -0,0 +1,1062 @@ +import math +from pytest import approx, mark +from unittest.mock import patch, MagicMock, PropertyMock + +from PyQt6 import QtCore, QtGui +from PyQt6.QtCore import Qt + +from beeref.assets import BeeAssets +from beeref import commands +from beeref.items import BeePixmapItem +from beeref.scene import BeeGraphicsScene + + +@mark.skip +class SelectableMixinBaseTestCase(): + + def setUp(self): + self.scene = BeeGraphicsScene(None) + self.item = BeePixmapItem(QtGui.QImage()) + self.scene.addItem(self.item) + self.view = MagicMock(get_scale=MagicMock(return_value=1)) + views_patcher = patch('beeref.scene.BeeGraphicsScene.views', + return_value=[self.view]) + views_patcher.start() + self.addCleanup(views_patcher.stop) + width_patcher = patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, + return_value=100) + width_patcher.start() + self.addCleanup(width_patcher.stop) + height_patcher = patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, + return_value=80) + height_patcher.start() + self.addCleanup(height_patcher.stop) + + +def test_init_selectable(view): + item = BeePixmapItem(QtGui.QImage()) + assert item.viewport_scale == 1 + assert item.scale_active is False + assert item.rotate_active is False + assert item.flip_active is False + assert item.just_selected is False + + +def test_is_action_active_when_no_action(view, item): + assert item.is_action_active() is False + + +def test_is_action_active_when_action(view, item): + item.scale_active = True + assert item.is_action_active() is True + + +def test_on_view_scale_change(view, item): + with patch('beeref.items.BeePixmapItem.prepareGeometryChange') as m: + item.on_view_scale_change() + m.assert_called_once() + + +def test_fixed_length_for_viewport_when_default_scale(view, item): + view.scene.addItem(item) + assert item.fixed_length_for_viewport(100) == 100 + assert item._view_scale == 1 + + +def test_fixed_length_for_viewport_when_viewport_scaled(view, item): + view.scene.addItem(item) + view.scale(2, 2) + assert item.fixed_length_for_viewport(100) == 50 + assert item._view_scale == 2 + + +def test_fixed_length_for_viewport_when_item_scaled(view, item): + view.scene.addItem(item) + item.setScale(5) + assert item.fixed_length_for_viewport(100) == 20 + assert item._view_scale == 1 + + +def test_fixed_length_for_viewport_when_no_scene(view, item): + item._view_scale = 0.5 + assert item.fixed_length_for_viewport(100) == 200 + + +def test_resize_size_when_scaled(view, item): + view.scene.addItem(item) + view.scale(2, 2) + item.setScale(2) + item.SELECT_RESIZE_SIZE = 100 + assert item.select_resize_size == 25 + + +def test_rotate_size_when_scaled(view, item): + view.scene.addItem(item) + view.scale(2, 2) + item.setScale(2) + item.SELECT_ROTATE_SIZE = 100 + assert item.select_rotate_size == 25 + + +def test_draw_debug_shape_rect(view, item): + view.scene.addItem(item) + painter = MagicMock() + item.draw_debug_shape( + painter, + QtCore.QRectF(5, 6, 20, 30), + 255, 0, 0) + painter.fillRect.assert_called_once() + painter.fillPath.assert_not_called() + + +def test_draw_debug_shape_path(view, item): + view.scene.addItem(item) + painter = MagicMock() + path = QtGui.QPainterPath() + path.addRect(QtCore.QRectF(5, 6, 20, 30)) + item.draw_debug_shape( + painter, + path, + 0, 255, 0) + painter.fillPath.assert_called_once() + painter.fillRect.assert_not_called() + + +@patch('beeref.items.BeePixmapItem.draw_debug_shape') +def test_paint_when_not_selected(debug_mock, view, item): + view.scene.addItem(item) + painter = MagicMock() + item.setSelected(False) + pixmap = MagicMock() + item.pixmap = MagicMock(return_value=pixmap) + item.paint(painter, None, None) + painter.drawPixmap.assert_called_once_with(0, 0, pixmap) + painter.drawRect.assert_not_called() + painter.drawPoint.assert_not_called() + debug_mock.assert_not_called() + + +def test_paint_when_selected_single_selection(view, item): + view.scene.addItem(item) + painter = MagicMock() + item.setSelected(True) + item.paint(painter, None, None) + painter.drawPixmap.assert_called_once() + painter.drawRect.assert_called_once() + assert painter.drawPoint.call_count == 4 + + +def test_paint_when_selected_multi_selection(view, item): + view.scene.addItem(item) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setSelected(True) + view.scene.addItem(item2) + painter = MagicMock() + item.setSelected(True) + item.paint(painter, None, None) + painter.drawPixmap.assert_called_once() + painter.drawRect.assert_called_once() + painter.drawPoint.assert_not_called() + + +def test_paint_when_debug_shapes(view): + with patch('beeref.selection.commandline_args') as args_mock: + with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: + args_mock.debug_shapes = True + args_mock.debug_boundingrects = False + args_mock.debug_handles = False + item = BeePixmapItem(QtGui.QImage()) + item.paint(MagicMock(), None, None) + m.assert_called_once() + + +def test_paint_when_debug_boundingrects(view): + with patch('beeref.selection.commandline_args') as args_mock: + with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: + args_mock.debug_shapes = False + args_mock.debug_boundingrects = True + args_mock.debug_handles = False + item = BeePixmapItem(QtGui.QImage()) + item.paint(MagicMock(), None, None) + m.assert_called_once() + + +def test_paint_when_debug_handles(view): + with patch('beeref.selection.commandline_args') as args_mock: + with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: + args_mock.debug_shapes = False + args_mock.debug_boundingrects = False + args_mock.debug_handles = True + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + item.setSelected(True) + item.paint(MagicMock(), None, None) + m.assert_called() + + +def test_corners(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert len(item.corners) == 4 + assert QtCore.QPointF(0, 0) in item.corners + assert QtCore.QPointF(100, 0) in item.corners + assert QtCore.QPointF(0, 80) in item.corners + assert QtCore.QPointF(100, 80) in item.corners + + +def test_corners_scene_coords_translated(view, item): + item.setPos(5, 5) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + corners = item.corners_scene_coords + assert len(item.corners) == 4 + assert QtCore.QPointF(5, 5) in corners + assert QtCore.QPointF(105, 5) in corners + assert QtCore.QPointF(5, 85) in corners + assert QtCore.QPointF(105, 85) in corners + + +def test_corners_scene_coords_scaled(view, item): + item.setScale(2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + corners = item.corners_scene_coords + assert len(item.corners) == 4 + assert QtCore.QPointF(0, 0) in corners + assert QtCore.QPointF(200, 0) in corners + assert QtCore.QPointF(0, 160) in corners + assert QtCore.QPointF(200, 160) in corners + + +def test_get_scale_bounds(view, item): + item.SELECT_RESIZE_SIZE = 10 + rect = item.get_scale_bounds( + QtCore.QPointF(100, 80)).boundingRect() + assert rect.topLeft().x() == 95 + assert rect.topLeft().y() == 75 + assert rect.bottomRight().x() == 105 + assert rect.bottomRight().y() == 85 + + +def test_get_scale_bounds_with_margin(view, item): + item.SELECT_RESIZE_SIZE = 10 + rect = item.get_scale_bounds( + QtCore.QPointF(100, 80), margin=1).boundingRect() + assert rect.topLeft().x() == 94 + assert rect.topLeft().y() == 74 + assert rect.bottomRight().x() == 106 + assert rect.bottomRight().y() == 86 + + +def test_rotate_bounds_bottomright(view, item): + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + path = item.get_rotate_bounds(QtCore.QPointF(100, 80)) + assert path.boundingRect().topLeft().x() == 95 + assert path.boundingRect().topLeft().y() == 75 + assert path.boundingRect().bottomRight().x() == 115 + assert path.boundingRect().bottomRight().y() == 95 + assert path.contains(QtCore.QPointF(104, 84)) is False + + +def test_rotate_bounds_topleft(view, item): + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + path = item.get_rotate_bounds(QtCore.QPointF(0, 0)) + assert path.boundingRect().topLeft().x() == -15 + assert path.boundingRect().topLeft().y() == -15 + assert path.boundingRect().bottomRight().x() == 5 + assert path.boundingRect().bottomRight().y() == 5 + assert path.contains(QtCore.QPointF(-4, -4)) is False + + +def test_get_flip_bounds(view, item): + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + edges = item.get_flip_bounds() + assert edges[0]['rect'].topLeft() == QtCore.QPointF(5, -15) + assert edges[0]['rect'].bottomRight() == QtCore.QPointF(95, 5) + assert edges[0]['flip_v'] is True + assert edges[1]['rect'].topLeft() == QtCore.QPointF(5, 75) + assert edges[1]['rect'].bottomRight() == QtCore.QPointF(95, 95) + assert edges[1]['flip_v'] is True + assert edges[2]['rect'].topLeft() == QtCore.QPointF(-15, 5) + assert edges[2]['rect'].bottomRight() == QtCore.QPointF(5, 75) + assert edges[2]['flip_v'] is False + assert edges[3]['rect'].topLeft() == QtCore.QPointF(95, 5) + assert edges[3]['rect'].bottomRight() == QtCore.QPointF(115, 75) + assert edges[3]['flip_v'] is False + + +def test_bounding_rect_when_not_selected(view, item): + item.setSelected(False) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', + return_value=QtCore.QRectF(0, 0, 100, 80)): + rect = item.boundingRect() + assert rect.topLeft().x() == 0 + assert rect.topLeft().y() == 0 + assert rect.bottomRight().x() == 100 + assert rect.bottomRight().y() == 80 + + +def test_bounding_rect_when_selected(view, item): + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + item.setSelected(True) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + rect = item.boundingRect() + assert rect.topLeft().x() == -15 + assert rect.topLeft().y() == -15 + assert rect.bottomRight().x() == 115 + assert rect.bottomRight().y() == 95 + + +def test_shape_when_not_selected(view, item): + view.scene.addItem(item) + item.setSelected(False) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', + return_value=QtCore.QRectF(0, 0, 100, 80)): + shape = item.shape().boundingRect() + assert shape.topLeft().x() == 0 + assert shape.topLeft().y() == 0 + assert shape.bottomRight().x() == 100 + assert shape.bottomRight().y() == 80 + + +def test_shape_when_selected_single(view, item): + view.scene.addItem(item) + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + item.setSelected(True) + path = QtGui.QPainterPath() + path.addRect(QtCore.QRectF(0, 0, 100, 80)) + + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape', + return_value=path): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + shape = item.shape().boundingRect() + assert shape.topLeft().x() == -15 + assert shape.topLeft().y() == -15 + assert shape.bottomRight().x() == 115 + assert shape.bottomRight().y() == 95 + + +def test_shape_when_selected_multi(view, item): + view.scene.addItem(item) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item.SELECT_RESIZE_SIZE = 10 + item.SELECT_ROTATE_SIZE = 10 + item.setSelected(True) + + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', + return_value=QtCore.QRectF(0, 0, 100, 80)): + shape = item.shape().boundingRect() + assert shape.topLeft().x() == 0 + assert shape.topLeft().y() == 0 + assert shape.bottomRight().x() == 100 + assert shape.bottomRight().y() == 80 + + +def test_get_scale_factor_bottomright(view, item): + item.event_start = QtCore.QPointF(10, 10) + item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) + item.scale_orig_factor = 1 + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(20, 90) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_scale_factor(event) == approx(1.5, 0.01) + + +def test_get_scale_factor_topleft(view, item): + item.event_start = QtCore.QPointF(10, 10) + item.event_direction = QtCore.QPointF(-1, -1) / math.sqrt(2) + item.scale_orig_factor = 0.5 + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(-10, -60) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_scale_factor(event) == approx(2, 0.01) + + +def test_get_scale_anchor_topleft(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + anchor = item.get_scale_anchor(QtCore.QPointF(0, 0)) + assert anchor.x() == 100 + assert anchor.y() == 80 + + +def test_get_scale_anchor_bottomright(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + anchor = item.get_scale_anchor(QtCore.QPointF(100, 80)) + assert anchor.x() == 0 + assert anchor.y() == 0 + + +def test_get_scale_anchor_topright(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + anchor = item.get_scale_anchor(QtCore.QPointF(100, 0)) + assert anchor.x() == 0 + assert anchor.y() == 80 + + +def test_get_scale_anchor_bottomleft(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + anchor = item.get_scale_anchor(QtCore.QPointF(0, 80)) + assert anchor.x() == 100 + assert anchor.y() == 0 + + +def test_get_corner_direction_topleft(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_corner_direction( + QtCore.QPointF(0, 0)) == QtCore.QPointF(-1, -1) + + +def test_get_corner_direction_bottomright(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_corner_direction( + QtCore.QPointF(100, 80)) == QtCore.QPointF(1, 1) + + +def test_get_corner_direction_topright(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_corner_direction( + QtCore.QPointF(100, 0)) == QtCore.QPointF(1, -1) + + +def test_get_corner_direction_bottomleft(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + assert item.get_corner_direction( + QtCore.QPointF(0, 80)) == QtCore.QPointF(-1, 1) + + +def test_get_direction_from_center_bottomright(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + direction = item.get_direction_from_center(QtCore.QPointF(100, 90)) + assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2)) + + +def test_get_direction_from_center_topleft(view, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + direction = item.get_direction_from_center(QtCore.QPointF(0, -10)) + assert direction == approx(QtCore.QPointF(-1, -1) / math.sqrt(2)) + + +def test_get_direction_from_center_bottomright_when_rotated_180(view, item): + item.setRotation(180, QtCore.QPointF(50, 40)) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + direction = item.get_direction_from_center(QtCore.QPointF(100, 90)) + assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2)) + + +def test_get_rotate_angle(view, item): + item.event_anchor = QtCore.QPointF(10, 20) + assert item.get_rotate_angle(QtCore.QPointF(15, 25)) == -45 + + +def test_get_rotate_delta(view, item): + item.event_anchor = QtCore.QPointF(10, 20) + item.rotate_start_angle = -3 + assert item.get_rotate_delta(QtCore.QPointF(15, 25)) == -42 + + +def test_get_rotate_delta_snaps(view, item): + item.event_anchor = QtCore.QPointF(10, 20) + item.rotate_start_angle = -3 + item.rotate_orig_degrees = 5 + assert item.get_rotate_delta(QtCore.QPointF(15, 25), snap=True) == -35 + + +def test_edge_flips_v_when_item_horizontal(view, item): + item.setRotation(20) + assert item.get_edge_flips_v({'flip_v': True}) is True + + +def test_edge_flips_v_when_item_horizontal_upside_down(view, item): + item.setRotation(200) + assert item.get_edge_flips_v({'flip_v': True}) is True + + +def test_edge_flips_v_when_item_vertical_cw(view, item): + item.setRotation(80) + assert item.get_edge_flips_v({'flip_v': True}) is False + + +def test_edge_flips_v_when_item_vertical_ccw(view, item): + item.setRotation(120) + assert item.get_edge_flips_v({'flip_v': True}) is False + + +def test_hover_move_event_no_selection(view, item): + view.scene.addItem(item) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 0) + item.setCursor = MagicMock() + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + item.setCursor.assert_not_called() + + +def test_hover_move_event_topleft_scale(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeFDiagCursor + + +def test_hover_move_event_bottomright_scale(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(100, 80) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeFDiagCursor + + +def test_hover_move_event_topright_scale(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(100, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeBDiagCursor + + +def test_hover_move_event_topright_scale_rotated_90(view, item): + view.scene.addItem(item) + item.setRotation(90) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeBDiagCursor + + +def test_hover_move_event_top_scale_rotated_45(view, item): + view.scene.addItem(item) + item.setRotation(45) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeVerCursor + + +def test_hover_move_event_left_scale_rotated_45(view, item): + view.scene.addItem(item) + item.setRotation(45) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 80) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.SizeHorCursor + + +def test_hover_move_event_rotate(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(115, 95) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_rotate + + +def test_hover_flip_event_top_edge(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(50, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_v + + +def test_hover_flip_event_bottom_edge(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(50, 80) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_v + + +def test_hover_flip_event_left_edge(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 50) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_h + + +def test_hover_flip_event_right_edge(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(100, 50) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_h + + +def test_hover_flip_event_top_edge_rotated_90(view, item): + view.scene.addItem(item) + item.setSelected(True) + item.setRotation(90) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(50, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_h + + +def test_hover_flip_event_left_edge_when_rotated_90(view, item): + view.scene.addItem(item) + event = MagicMock() + item.setSelected(True) + item.setRotation(90) + event.pos.return_value = QtCore.QPointF(0, 50) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == BeeAssets().cursor_flip_v + + +def test_hover_move_event_not_in_handles(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(50, 50) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.hoverMoveEvent(event) + assert item.cursor() == Qt.CursorShape.ArrowCursor + + +def test_hover_enter_event_when_selected(view, item): + view.scene.addItem(item) + event = MagicMock() + item.setSelected(True) + item.setCursor = MagicMock() + item.hoverEnterEvent(event) + item.setCursor.assert_not_called() + + +def test_hover_enter_event_when_not_selected(view, item): + view.scene.addItem(item) + event = MagicMock() + item.setSelected(False) + item.hoverEnterEvent(event) + assert item.cursor() == Qt.CursorShape.ArrowCursor + + +def test_mouse_press_event_just_selected(view, item): + view.scene.addItem(item) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(-100, -100) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): + item.mousePressEvent(event) + assert item.just_selected is True + + +def test_mouse_press_event_previously_selected(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(-100, -100) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): + item.mousePressEvent(event) + assert item.just_selected is False + + +def test_mouse_press_event_topleft_scale(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(2, 2) + event.scenePos.return_value = QtCore.QPointF(-1, -1) + event.button.return_value = Qt.MouseButton.LeftButton + item.mousePressEvent(event) + assert item.scale_active is True + assert item.event_start == QtCore.QPointF(-1, -1) + assert item.event_direction.x() < 0 + assert item.event_direction.y() < 0 + assert item.scale_orig_factor == 1 + + +def test_mouse_press_event_bottomright_scale(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(99, 79) + event.scenePos.return_value = QtCore.QPointF(101, 81) + event.button.return_value = Qt.MouseButton.LeftButton + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mousePressEvent(event) + assert item.scale_active is True + assert item.event_start == QtCore.QPointF(101, 81) + assert item.event_direction.x() > 0 + assert item.event_direction.y() > 0 + assert item.scale_orig_factor == 1 + + +def test_mouse_press_event_rotate(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(111, 91) + event.scenePos.return_value = QtCore.QPointF(66, 99) + event.button.return_value = Qt.MouseButton.LeftButton + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mousePressEvent(event) + assert item.rotate_active is True + assert item.event_anchor == QtCore.QPointF(50, 40) + assert item.rotate_orig_degrees == 0 + + +def test_mouse_press_event_flip(view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 40) + event.button.return_value = Qt.MouseButton.LeftButton + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mousePressEvent(event) + assert item.flip_active is True + + +def test_mouse_press_event_not_selected(view, item): + view.scene.addItem(item) + view.reset_previous_transform = MagicMock() + item.setSelected(False) + event = MagicMock() + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m: + item.mousePressEvent(event) + m.assert_called_once_with(event) + assert item.scale_active is False + assert item.rotate_active is False + assert item.flip_active is False + + +def test_mouse_press_event_not_in_handles(view, item): + view.scene.addItem(item) + view.reset_previous_transform = MagicMock() + item.setSelected(True) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(50, 40) + event.button.return_value = Qt.MouseButton.LeftButton + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m: + item.mousePressEvent(event) + m.assert_called_once_with(event) + assert item.scale_active is False + assert item.rotate_active is False + assert item.flip_active is False + + +def test_mouse_move_event_when_no_action_reset_prev_transform(view, item): + view.scene.addItem(item) + view.reset_previous_transform = MagicMock() + event = MagicMock() + item.event_start = QtCore.QPointF(10, 10) + event.scenePos.return_value = QtCore.QPointF(50, 40) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: + item.mouseMoveEvent(event) + m.assert_called_once_with(event) + view.reset_previous_transform.assert_called_once() + + +def test_mouse_move_event_when_no_action_doesnt_reset_prev_transf(view, item): + view.scene.addItem(item) + view.reset_previous_transform = MagicMock() + event = MagicMock() + item.event_start = QtCore.QPointF(10, 10) + event.scenePos.return_value = QtCore.QPointF(11, 11) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: + item.mouseMoveEvent(event) + m.assert_called_once_with(event) + view.reset_previous_transform.assert_not_called() + + +def test_mouse_move_event_when_scale_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(20, 90) + item.scale_active = True + item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) + item.event_anchor = QtCore.QPointF(100, 80) + item.event_start = QtCore.QPointF(10, 10) + item.scale_orig_factor = 1 + + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mouseMoveEvent(event) + m.assert_not_called() + assert item.scale() == approx(1.5, 0.01) + + +def test_mouse_move_event_when_rotate_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(15, 25) + item.event_start = QtCore.QPointF(10, 10) + item.rotate_active = True + item.rotate_orig_degrees = 0 + item.rotate_start_angle = -3 + item.event_anchor = QtCore.QPointF(10, 20) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: + item.mouseMoveEvent(event) + m.assert_not_called() + assert item.rotation() == 318 + + +def test_mouse_move_event_when_flip_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(15, 25) + item.event_start = QtCore.QPointF(10, 10) + item.flip_active = True + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: + item.mouseMoveEvent(event) + m.assert_not_called() + + +def test_mouse_release_event_when_no_action(view, item): + view.scene.addItem(item) + event = MagicMock() + item.flip_active = True + event.pos.return_value = QtCore.QPointF(-100, -100) + with patch('PyQt6.QtWidgets.QGraphicsPixmapItem' + '.mouseReleaseEvent') as m: + item.mouseReleaseEvent(event) + m.assert_called_once_with(event) + item.flip_active is False + + +def test_mouse_release_event_when_scale_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(20, 90) + item.scale_active = True + item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) + item.event_anchor = QtCore.QPointF(100, 80) + item.event_start = QtCore.QPointF(10, 10) + item.scale_orig_factor = 1 + view.scene.undo_stack = MagicMock(push=MagicMock()) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_called_once() + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + isinstance(cmd, commands.ScaleItemsBy) + assert cmd.items == [item] + assert cmd.factor == approx(1.5, 0.01) + assert cmd.anchor == QtCore.QPointF(100, 80) + assert cmd.ignore_first_redo is True + assert item.scale_active is False + + +def test_mouse_release_event_when_scale_action_zero(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(20, 90) + item.scale_active = True + item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) + item.event_anchor = QtCore.QPointF(100, 80) + item.event_start = QtCore.QPointF(20, 90) + item.scale_orig_factor = 1 + view.scene.undo_stack = MagicMock(push=MagicMock()) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + assert item.scale_active is False + + +def test_mouse_release_event_when_rotate_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(15, 25) + item.rotate_active = True + item.rotate_orig_degrees = 0 + item.rotate_start_angle = -3 + item.event_anchor = QtCore.QPointF(10, 20) + view.scene.undo_stack = MagicMock(push=MagicMock()) + + item.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_called_once() + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + isinstance(cmd, commands.RotateItemsBy) + assert cmd.items == [item] + assert cmd.delta == -42 + assert cmd.anchor == QtCore.QPointF(10, 20) + assert cmd.ignore_first_redo is True + assert item.rotate_active is False + + +def test_mouse_release_event_when_rotate_action_zero(view, item): + view.scene.addItem(item) + event = MagicMock() + event.scenePos.return_value = QtCore.QPointF(15, 25) + item.rotate_active = True + item.rotate_orig_degrees = 0 + item.rotate_start_angle = -45 + item.event_anchor = QtCore.QPointF(10, 20) + view.scene.undo_stack = MagicMock(push=MagicMock()) + + item.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + assert item.rotate_active is False + + +def test_mouse_release_event_when_flip_action(view, item): + view.scene.addItem(item) + event = MagicMock() + event.pos.return_value = QtCore.QPointF(0, 40) + item.flip_active = True + view.scene.undo_stack = MagicMock(push=MagicMock()) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + item.mouseReleaseEvent(event) + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + isinstance(cmd, commands.FlipItems) + assert cmd.items == [item] + assert cmd.anchor == QtCore.QPointF(50, 40) + assert cmd.vertical is False + assert item.flip_active is False diff --git a/tests/test_assets.py b/tests/test_assets.py index 98b21f6..8d4c68a 100644 --- a/tests/test_assets.py +++ b/tests/test_assets.py @@ -1,14 +1,12 @@ from PyQt6 import QtGui from beeref.assets import BeeAssets -from .base import BeeTestCase -class GetRectFromPointsTestCase(BeeTestCase): +def test_singleton(view): + assert BeeAssets() is BeeAssets() + assert BeeAssets().logo is BeeAssets().logo - def test_singleton(self): - assert BeeAssets() is BeeAssets() - assert BeeAssets().logo is BeeAssets().logo - def test_has_logo(self): - assert isinstance(BeeAssets().logo, QtGui.QIcon) +def test_has_logo(view): + assert isinstance(BeeAssets().logo, QtGui.QIcon) diff --git a/tests/test_commands.py b/tests/test_commands.py index a6ad7bf..93a3edc 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -4,453 +4,417 @@ from PyQt6 import QtCore, QtGui from beeref import commands from beeref.items import BeePixmapItem -from beeref.scene import BeeGraphicsScene -from .base import BeeTestCase -class InsertItemsTestCase(BeeTestCase): - - @patch('beeref.scene.BeeGraphicsScene.views') - def test_redo_undo(self, views_mock): - scene = BeeGraphicsScene(None) - view = MagicMock(get_scale=MagicMock(return_value=1)) - views_mock.return_value = [view] - scene.update_selection = MagicMock() - scene.max_z = 5 - item1 = BeePixmapItem(QtGui.QImage()) - scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setPos(50, 40) - command = commands.InsertItems(scene, [item2]) - command.redo() - assert list(scene.items_for_save()) == [item1, item2] - assert item1.isSelected() is False - assert item2.isSelected() is True - assert item2.pos() == QtCore.QPointF(50, 40) - item2.zValue() > 5 - command.undo() - assert list(scene.items_for_save()) == [item1] - assert item1.isSelected() is False - assert item2.pos() == QtCore.QPointF(50, 40) - - @patch('beeref.scene.BeeGraphicsScene.views') - def test_redo_undo_with_position(self, views_mock): - scene = BeeGraphicsScene(None) - view = MagicMock(get_scale=MagicMock(return_value=1)) - views_mock.return_value = [view] - scene.update_selection = MagicMock() - - item1 = BeePixmapItem(QtGui.QImage()) - item1.setPos(10, 20) - scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setPos(50, 40) - scene.addItem(item2) - - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.InsertItems( - scene, [item1, item2], QtCore.QPointF(100, 200)) - command.redo() - assert set(scene.items_for_save()) == {item1, item2} - assert item1.pos() == QtCore.QPointF(30, 150) - assert item2.pos() == QtCore.QPointF(70, 170) - command.undo() - assert list(scene.items_for_save()) == [] - assert item1.pos() == QtCore.QPointF(10, 20) - assert item2.pos() == QtCore.QPointF(50, 40) - - @patch('beeref.scene.BeeGraphicsScene.views') - def test_ignore_first_redo(self, views_mock): - scene = BeeGraphicsScene(None) - view = MagicMock(get_scale=MagicMock(return_value=1)) - views_mock.return_value = [view] - scene.update_selection = MagicMock() - scene.max_z = 5 - item1 = BeePixmapItem(QtGui.QImage()) - scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - command = commands.InsertItems(scene, [item2], ignore_first_redo=True) - command.redo() - assert list(scene.items_for_save()) == [item1] - assert item1.isSelected() is False - command.redo() - assert list(scene.items_for_save()) == [item1, item2] - assert item1.isSelected() is False - assert item2.isSelected() is True - item2.zValue() > 5 +def test_insert_items(view): + view.scene.update_selection = MagicMock() + view.scene.max_z = 5 + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setPos(50, 40) + command = commands.InsertItems(view.scene, [item2]) + command.redo() + assert list(view.scene.items_for_save()) == [item1, item2] + assert item1.isSelected() is False + assert item2.isSelected() is True + assert item2.pos() == QtCore.QPointF(50, 40) + item2.zValue() > 5 + command.undo() + assert list(view.scene.items_for_save()) == [item1] + assert item1.isSelected() is False + assert item2.pos() == QtCore.QPointF(50, 40) -class DeleteItemsTestCase(BeeTestCase): +def test_insert_items_with_position(view): + view.scene.update_selection = MagicMock() - def test_redo_undo(self): - scene = BeeGraphicsScene(None) - scene.update_selection = MagicMock() - item1 = BeePixmapItem(QtGui.QImage()) - scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - scene.addItem(item2) - item2.setSelected(True) - command = commands.DeleteItems(scene, [item2]) - command.redo() - assert list(scene.items_for_save()) == [item1] - command.undo() - assert list(scene.items_for_save()) == [item1, item2] - assert item1.isSelected() is False - assert item2.isSelected() is True + item1 = BeePixmapItem(QtGui.QImage()) + item1.setPos(10, 20) + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setPos(50, 40) + view.scene.addItem(item2) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.InsertItems( + view.scene, [item1, item2], QtCore.QPointF(100, 200)) + command.redo() + assert set(view.scene.items_for_save()) == {item1, item2} + assert item1.pos() == QtCore.QPointF(30, 150) + assert item2.pos() == QtCore.QPointF(70, 170) + command.undo() + assert list(view.scene.items_for_save()) == [] + assert item1.pos() == QtCore.QPointF(10, 20) + assert item2.pos() == QtCore.QPointF(50, 40) -class MoveItemsByTestCase(BeeTestCase): - - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setPos(0, 0) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setPos(30, 40) - command = commands.MoveItemsBy([item1, item2], QtCore.QPointF(50, 100)) - command.redo() - assert item1.pos().x() == 50 - assert item1.pos().y() == 100 - assert item2.pos().x() == 80 - assert item2.pos().y() == 140 - - command.undo() - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.pos().x() == 30 - assert item2.pos().y() == 40 - - def test_ignore_first_redo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setPos(0, 0) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setPos(30, 40) - command = commands.MoveItemsBy([item1, item2], - QtCore.QPointF(50, 100), - ignore_first_redo=True) - command.redo() - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.pos().x() == 30 - assert item2.pos().y() == 40 - command.redo() - assert item1.pos().x() == 50 - assert item1.pos().y() == 100 - assert item2.pos().x() == 80 - assert item2.pos().y() == 140 +def test_insert_items_ignore_first_redo(view): + view.scene.update_selection = MagicMock() + view.scene.max_z = 5 + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + command = commands.InsertItems(view.scene, [item2], ignore_first_redo=True) + command.redo() + assert list(view.scene.items_for_save()) == [item1] + assert item1.isSelected() is False + command.redo() + assert list(view.scene.items_for_save()) == [item1, item2] + assert item1.isSelected() is False + assert item2.isSelected() is True + item2.zValue() > 5 -class ScaleItemsByTestCase(BeeTestCase): - - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setScale(1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setScale(3) - item2.setPos(100, 100) - command = commands.ScaleItemsBy([item1, item2], 2, - QtCore.QPointF(100, 100)) - command.redo() - assert item1.scale() == 2 - assert item1.pos().x() == -100 - assert item1.pos().y() == -100 - assert item2.scale() == 6 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - command.undo() - assert item1.scale() == 1 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.scale() == 3 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - - def test_ignore_first_redo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setScale(1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setScale(3) - item2.setPos(100, 100) - command = commands.ScaleItemsBy([item1, item2], 2, - QtCore.QPointF(100, 100), - ignore_first_redo=True) - command.redo() - assert item1.scale() == 1 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.scale() == 3 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - command.redo() - assert item1.scale() == 2 - assert item1.pos().x() == -100 - assert item1.pos().y() == -100 - assert item2.scale() == 6 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 +def test_delete_items(view): + view.scene.update_selection = MagicMock() + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + command = commands.DeleteItems(view.scene, [item2]) + command.redo() + assert list(view.scene.items_for_save()) == [item1] + command.undo() + assert list(view.scene.items_for_save()) == [item1, item2] + assert item1.isSelected() is False + assert item2.isSelected() is True -class RotateItemsByTestCase(BeeTestCase): +def test_move_items_by(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setPos(0, 0) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setPos(30, 40) + command = commands.MoveItemsBy([item1, item2], QtCore.QPointF(50, 100)) + command.redo() + assert item1.pos().x() == 50 + assert item1.pos().y() == 100 + assert item2.pos().x() == 80 + assert item2.pos().y() == 140 - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setRotation(0) - - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(30) - item2.setPos(100, 100) - item2.do_flip() - command = commands.RotateItemsBy([item1, item2], -90, - QtCore.QPointF(100, 100)) - command.redo() - assert item1.rotation() == 270 - assert item1.pos().x() == 0 - assert item1.pos().y() == 200 - assert item2.rotation() == 120 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - command.undo() - assert item1.rotation() == 0 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.rotation() == 30 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - - def test_ignore_first_redo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setRotation(0) - - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(30) - item2.setPos(100, 100) - item2.do_flip() - command = commands.RotateItemsBy([item1, item2], -90, - QtCore.QPointF(100, 100), - ignore_first_redo=True) - command.redo() - assert item1.rotation() == 0 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.rotation() == 30 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 - command.redo() - assert item1.rotation() == 270 - assert item1.pos().x() == 0 - assert item1.pos().y() == 200 - assert item2.rotation() == 120 - assert item2.pos().x() == 100 - assert item2.pos().y() == 100 + command.undo() + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.pos().x() == 30 + assert item2.pos().y() == 40 -class NormalizeItemsTestCase(BeeTestCase): - - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setScale(1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setScale(3) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.NormalizeItems([item1, item2], [2, 0.5]) - command.redo() - assert item1.scale() == 2 - assert item1.pos() == QtCore.QPointF(-50, -40) - assert item2.scale() == 1.5 - assert item2.pos() == QtCore.QPointF(75, 60) - command.undo() - assert item1.scale() == 1 - assert item1.pos() == QtCore.QPointF(0, 0) - assert item2.scale() == 3 - assert item2.pos() == QtCore.QPointF(0, 0) +def test_move_items_by_ignore_first_redo(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setPos(0, 0) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setPos(30, 40) + command = commands.MoveItemsBy([item1, item2], + QtCore.QPointF(50, 100), + ignore_first_redo=True) + command.redo() + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.pos().x() == 30 + assert item2.pos().y() == 40 + command.redo() + assert item1.pos().x() == 50 + assert item1.pos().y() == 100 + assert item2.pos().x() == 80 + assert item2.pos().y() == 140 -class FlipItemsTestCase(BeeTestCase): +def test_scale_items_by(view): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setScale(1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setScale(3) + item2.setPos(100, 100) + command = commands.ScaleItemsBy([item1, item2], 2, + QtCore.QPointF(100, 100)) + command.redo() + assert item1.scale() == 2 + assert item1.pos().x() == -100 + assert item1.pos().y() == -100 + assert item2.scale() == 6 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + command.undo() + assert item1.scale() == 1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.scale() == 3 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 - def test_redo_undo_horizontal(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setRotation(0) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(30) - item2.setPos(100, 100) - item2.do_flip() - command = commands.FlipItems([item1, item2], +def test_scale_items_by_ignore_first_redo(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setScale(1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setScale(3) + item2.setPos(100, 100) + command = commands.ScaleItemsBy([item1, item2], 2, + QtCore.QPointF(100, 100), + ignore_first_redo=True) + command.redo() + assert item1.scale() == 1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.scale() == 3 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + command.redo() + assert item1.scale() == 2 + assert item1.pos().x() == -100 + assert item1.pos().y() == -100 + assert item2.scale() == 6 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + + +def test_rotate_items_by(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setRotation(0) + + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(30) + item2.setPos(100, 100) + item2.do_flip() + command = commands.RotateItemsBy([item1, item2], -90, + QtCore.QPointF(100, 100)) + command.redo() + assert item1.rotation() == 270 + assert item1.pos().x() == 0 + assert item1.pos().y() == 200 + assert item2.rotation() == 120 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + command.undo() + assert item1.rotation() == 0 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.rotation() == 30 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + + +def test_rotate_items_by_ignore_first_redo(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setRotation(0) + + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(30) + item2.setPos(100, 100) + item2.do_flip() + command = commands.RotateItemsBy([item1, item2], -90, QtCore.QPointF(100, 100), - vertical=False) - command.redo() - assert item1.flip() == -1 - assert item1.rotation() == 0 - assert item1.pos() == QtCore.QPointF(200, 0) - assert item2.flip() == 1 - assert item2.rotation() == 30 - assert item2.pos() == QtCore.QPointF(100, 100) - command.undo() - assert item1.flip() == 1 - assert item1.rotation() == 0 - assert item1.pos() == QtCore.QPointF(0, 0) - assert item2.flip() == -1 - assert item2.rotation() == 30 - assert item2.pos() == QtCore.QPointF(100, 100) - - def test_redo_undo_vertical(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setRotation(0) - - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(30) - item2.setPos(100, 100) - item2.do_flip() - command = commands.FlipItems([item1, item2], - QtCore.QPointF(100, 100), - vertical=True) - command.redo() - assert item1.flip() == -1 - assert item1.rotation() == 180 - assert item1.pos() == QtCore.QPointF(0, 200) - assert item2.flip() == 1 - assert item2.rotation() == 210 - assert item2.pos() == QtCore.QPointF(100, 100) - command.undo() - assert item1.flip() == 1 - assert item1.rotation() == 0 - assert item1.pos() == QtCore.QPointF(0, 0) - assert item2.flip() == -1 - assert item2.rotation() == 30 - assert item2.pos() == QtCore.QPointF(100, 100) + ignore_first_redo=True) + command.redo() + assert item1.rotation() == 0 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.rotation() == 30 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 + command.redo() + assert item1.rotation() == 270 + assert item1.pos().x() == 0 + assert item1.pos().y() == 200 + assert item2.rotation() == 120 + assert item2.pos().x() == 100 + assert item2.pos().y() == 100 -class ResetScaleTestCase(BeeTestCase): - - def test_redo_undo(self): - item = BeePixmapItem(QtGui.QImage()) - item.setScale(2) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.ResetScale([item]) - command.redo() - assert item.scale() == 1 - assert item.pos().x() == 50 - assert item.pos().y() == 40 - command.undo() - assert item.scale() == 2 - assert item.pos().x() == 0 - assert item.pos().y() == 0 +def test_normalize_items(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setScale(1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setScale(3) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.NormalizeItems([item1, item2], [2, 0.5]) + command.redo() + assert item1.scale() == 2 + assert item1.pos() == QtCore.QPointF(-50, -40) + assert item2.scale() == 1.5 + assert item2.pos() == QtCore.QPointF(75, 60) + command.undo() + assert item1.scale() == 1 + assert item1.pos() == QtCore.QPointF(0, 0) + assert item2.scale() == 3 + assert item2.pos() == QtCore.QPointF(0, 0) -class ResetRotateTestCase(BeeTestCase): +def test_flip_items_horizontal(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setRotation(0) - def test_redo_undo(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(180) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.ResetRotation([item]) - command.redo() - assert item.rotation() == 0 - assert item.pos().x() == -100 - assert item.pos().y() == -80 - command.undo() - assert item.rotation() == 180 - assert item.pos().x() == 0 - assert item.pos().y() == 0 + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(30) + item2.setPos(100, 100) + item2.do_flip() + command = commands.FlipItems([item1, item2], + QtCore.QPointF(100, 100), + vertical=False) + command.redo() + assert item1.flip() == -1 + assert item1.rotation() == 0 + assert item1.pos() == QtCore.QPointF(200, 0) + assert item2.flip() == 1 + assert item2.rotation() == 30 + assert item2.pos() == QtCore.QPointF(100, 100) + command.undo() + assert item1.flip() == 1 + assert item1.rotation() == 0 + assert item1.pos() == QtCore.QPointF(0, 0) + assert item2.flip() == -1 + assert item2.rotation() == 30 + assert item2.pos() == QtCore.QPointF(100, 100) -class ResetFlipTestCase(BeeTestCase): +def test_flip_items_vertical(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setRotation(0) - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.do_flip() - item2 = BeePixmapItem(QtGui.QImage()) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.ResetFlip([item1, item2]) - command.redo() - assert item1.flip() == 1 - assert item1.pos().x() == -100 - assert item1.pos().y() == 0 - assert item2.flip() == 1 - assert item2.pos().x() == 0 - assert item2.pos().y() == 0 - command.undo() - assert item1.flip() == -1 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.flip() == 1 - assert item2.pos().x() == 0 - assert item2.pos().y() == 0 + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(30) + item2.setPos(100, 100) + item2.do_flip() + command = commands.FlipItems([item1, item2], + QtCore.QPointF(100, 100), + vertical=True) + command.redo() + assert item1.flip() == -1 + assert item1.rotation() == 180 + assert item1.pos() == QtCore.QPointF(0, 200) + assert item2.flip() == 1 + assert item2.rotation() == 210 + assert item2.pos() == QtCore.QPointF(100, 100) + command.undo() + assert item1.flip() == 1 + assert item1.rotation() == 0 + assert item1.pos() == QtCore.QPointF(0, 0) + assert item2.flip() == -1 + assert item2.rotation() == 30 + assert item2.pos() == QtCore.QPointF(100, 100) -class ResetTransformsTestCase(BeeTestCase): - - def test_redo_undo(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.setScale(2) - item1.do_flip() - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(180) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.ResetTransforms([item1, item2]) - command.redo() - assert item1.scale() == 1 - assert item1.rotation() == 0 - assert item1.flip() == 1 - assert item1.pos().x() == -150 - assert item1.pos().y() == 40 - assert item2.scale() == 1 - assert item2.rotation() == 0 - assert item2.flip() == 1 - assert item2.pos().x() == -100 - assert item2.pos().y() == -80 - command.undo() - assert item1.scale() == 2 - assert item1.rotation() == 0 - assert item1.flip() == -1 - assert item1.pos().x() == 0 - assert item1.pos().y() == 0 - assert item2.scale() == 1 - assert item2.rotation() == 180 - assert item2.flip() == 1 - assert item2.pos().x() == 0 - assert item2.pos().y() == 0 +def test_reset_scale(view, item): + item.setScale(2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetScale([item]) + command.redo() + assert item.scale() == 1 + assert item.pos().x() == 50 + assert item.pos().y() == 40 + command.undo() + assert item.scale() == 2 + assert item.pos().x() == 0 + assert item.pos().y() == 0 -class ArrangeItemsTestCase(BeeTestCase): +def test_reset_rotate(view, item): + item.setRotation(180) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetRotation([item]) + command.redo() + assert item.rotation() == 0 + assert item.pos().x() == -100 + assert item.pos().y() == -80 + command.undo() + assert item.rotation() == 180 + assert item.pos().x() == 0 + assert item.pos().y() == 0 - def test_redo_undo(self): - scene = BeeGraphicsScene(None) - item1 = BeePixmapItem(QtGui.QImage()) - item1.do_flip() - scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setRotation(90) - scene.addItem(item2) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - command = commands.ArrangeItems( - scene, - [item1, item2], - [QtCore.QPointF(1, 2), QtCore.QPointF(203, 204)]) - command.redo() - assert item1.pos() == QtCore.QPointF(101, 2) - assert item2.pos() == QtCore.QPointF(283, 204) - command.undo() - assert item1.pos() == QtCore.QPointF(0, 0) - assert item2.pos() == QtCore.QPointF(0, 0) +def test_reset_flip(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.do_flip() + item2 = BeePixmapItem(QtGui.QImage()) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetFlip([item1, item2]) + command.redo() + assert item1.flip() == 1 + assert item1.pos().x() == -100 + assert item1.pos().y() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0 + command.undo() + assert item1.flip() == -1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0 + + +def test_reset_transforms(qapp): + item1 = BeePixmapItem(QtGui.QImage()) + item1.setScale(2) + item1.do_flip() + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(180) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ResetTransforms([item1, item2]) + command.redo() + assert item1.scale() == 1 + assert item1.rotation() == 0 + assert item1.flip() == 1 + assert item1.pos().x() == -150 + assert item1.pos().y() == 40 + assert item2.scale() == 1 + assert item2.rotation() == 0 + assert item2.flip() == 1 + assert item2.pos().x() == -100 + assert item2.pos().y() == -80 + command.undo() + assert item1.scale() == 2 + assert item1.rotation() == 0 + assert item1.flip() == -1 + assert item1.pos().x() == 0 + assert item1.pos().y() == 0 + assert item2.scale() == 1 + assert item2.rotation() == 180 + assert item2.flip() == 1 + assert item2.pos().x() == 0 + assert item2.pos().y() == 0 + + +def test_arrange_items(view): + item1 = BeePixmapItem(QtGui.QImage()) + item1.do_flip() + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setRotation(90) + view.scene.addItem(item2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + command = commands.ArrangeItems( + view.scene, + [item1, item2], + [QtCore.QPointF(1, 2), QtCore.QPointF(203, 204)]) + + command.redo() + assert item1.pos() == QtCore.QPointF(101, 2) + assert item2.pos() == QtCore.QPointF(283, 204) + command.undo() + assert item1.pos() == QtCore.QPointF(0, 0) + assert item2.pos() == QtCore.QPointF(0, 0) diff --git a/tests/test_config.py b/tests/test_config.py index 9f93546..7536df9 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,67 +4,70 @@ from unittest.mock import patch import pytest -from beeref.config import CommandlineArgs, BeeSettings -from .base import BeeTestCase +from beeref.config import CommandlineArgs -class CommandlineArgsTestCase(BeeTestCase): - - def test_singleton(self): - assert CommandlineArgs() is CommandlineArgs() - assert CommandlineArgs()._args is CommandlineArgs()._args - - @patch('beeref.config.parser.parse_args') - def test_with_check_forces_new_parsing(self, parse_mock): - args1 = CommandlineArgs() - args2 = CommandlineArgs(with_check=True) - parse_mock.assert_called_once() - assert args1 is not args2 - - def test_get(self): - args = CommandlineArgs() - assert args.loglevel == 'INFO' - - def test_get_unknown(self): - args = CommandlineArgs() - with pytest.raises(AttributeError): - args.foo +def test_command_line_args_singleton(): + assert CommandlineArgs() is CommandlineArgs() + assert CommandlineArgs()._args is CommandlineArgs()._args + CommandlineArgs._instance = None -class BeeSettingsRecentFilesTestCase(BeeTestCase): +@patch('beeref.config.parser.parse_args') +def test_command_line_args_with_check_forces_new_parsing(parse_mock): + args1 = CommandlineArgs() + args2 = CommandlineArgs(with_check=True) + parse_mock.assert_called_once() + assert args1 is not args2 + CommandlineArgs._instance = None - def setUp(self): - self.settings = BeeSettings() - def test_get_empty(self): - self.settings.get_recent_files() == [] +def test_command_line_args_get(): + args = CommandlineArgs() + assert args.loglevel == 'INFO' + CommandlineArgs._instance = None - def test_get_existing_only(self): - with tempfile.NamedTemporaryFile() as f: - self.settings.update_recent_files('foo.bee') - self.settings.update_recent_files(f.name) - self.settings.get_recent_files(existing_only=True) == [f.name] - def test_update(self): - self.settings.update_recent_files('foo.bee') - self.settings.update_recent_files('bar.bee') - assert self.settings.get_recent_files() == [ - os.path.abspath('bar.bee'), - os.path.abspath('foo.bee')] +def test_command_line_args_get_unknown(): + args = CommandlineArgs() + with pytest.raises(AttributeError): + args.foo + CommandlineArgs._instance = None - def test_update_existing(self): - self.settings.update_recent_files('foo.bee') - self.settings.update_recent_files('bar.bee') - self.settings.update_recent_files('foo.bee') - assert self.settings.get_recent_files() == [ - os.path.abspath('foo.bee'), - os.path.abspath('bar.bee')] - def test_update_respects_max_num(self): - for i in range(15): - self.settings.update_recent_files(f'{i}.bee') +def test_settings_recent_files_get_empty(settings): + settings.get_recent_files() == [] - recent = self.settings.get_recent_files() - assert len(recent) == 10 - assert recent[0] == os.path.abspath('14.bee') - assert recent[-1] == os.path.abspath('5.bee') + +def test_settings_recent_files_get_existing_only(settings): + with tempfile.NamedTemporaryFile() as f: + settings.update_recent_files('foo.bee') + settings.update_recent_files(f.name) + settings.get_recent_files(existing_only=True) == [f.name] + + +def test_settings_recent_files_update(settings): + settings.update_recent_files('foo.bee') + settings.update_recent_files('bar.bee') + assert settings.get_recent_files() == [ + os.path.abspath('bar.bee'), + os.path.abspath('foo.bee')] + + +def test_settings_recent_files_update_existing(settings): + settings.update_recent_files('foo.bee') + settings.update_recent_files('bar.bee') + settings.update_recent_files('foo.bee') + assert settings.get_recent_files() == [ + os.path.abspath('foo.bee'), + os.path.abspath('bar.bee')] + + +def test_settings_recent_files_update_respects_max_num(settings): + for i in range(15): + settings.update_recent_files(f'{i}.bee') + + recent = settings.get_recent_files() + assert len(recent) == 10 + assert recent[0] == os.path.abspath('14.bee') + assert recent[-1] == os.path.abspath('5.bee') diff --git a/tests/test_gui.py b/tests/test_gui.py new file mode 100644 index 0000000..38b39e7 --- /dev/null +++ b/tests/test_gui.py @@ -0,0 +1,18 @@ +from PyQt6 import QtWidgets +from PyQt6.QtCore import Qt + +from beeref.config import logfile_name +from beeref.gui import DebugLogDialog + + +def test_debug_log_dialog(qtbot, settings, view): + with open(logfile_name(), 'w') as f: + f.write('my log output') + + dialog = DebugLogDialog(view) + dialog.show() + qtbot.addWidget(dialog) + assert dialog.log.text() == 'my log output' + qtbot.mouseClick(dialog.copy_button, Qt.MouseButton.LeftButton) + clipboard = QtWidgets.QApplication.clipboard() + assert clipboard.text() == 'my log output' diff --git a/tests/test_items.py b/tests/test_items.py index 9ab02c3..07e9cd3 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -3,138 +3,130 @@ from unittest.mock import patch, MagicMock, PropertyMock from PyQt6 import QtCore, QtGui from beeref.items import BeePixmapItem -from beeref.scene import BeeGraphicsScene -from .base import BeeTestCase -class BeePixmapItemTestCase(BeeTestCase): +@patch('beeref.selection.SelectableMixin.init_selectable') +def test_init(selectable_mock, qapp, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3), imgfilename3x3) + assert item.save_id is None + assert item.width == 3 + assert item.height == 3 + assert item.scale() == 1 + assert item.filename == imgfilename3x3 + selectable_mock.assert_called_once() - def setUp(self): - self.scene = BeeGraphicsScene(None) - @patch('beeref.selection.SelectableMixin.init_selectable') - def test_init(self, selectable_mock): - item = BeePixmapItem( - QtGui.QImage(self.imgfilename3x3), self.imgfilename3x3) - assert item.save_id is None - assert item.width == 3 - assert item.height == 3 - assert item.scale() == 1 - assert item.filename == self.imgfilename3x3 - selectable_mock.assert_called_once() +def test_set_pos_center(qapp, item): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=200): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + item.set_pos_center(QtCore.QPointF(0, 0)) + assert item.pos().x() == -100 + assert item.pos().y() == -50 - def test_set_pos_center(self): - item = BeePixmapItem(QtGui.QImage()) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=200): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - item.set_pos_center(QtCore.QPointF(0, 0)) - assert item.pos().x() == -100 - assert item.pos().y() == -50 - def test_set_pos_center_when_scaled(self): - item = BeePixmapItem(QtGui.QImage()) - item.setScale(2) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=200): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - item.set_pos_center(QtCore.QPointF(0, 0)) - assert item.pos().x() == -200 - assert item.pos().y() == -100 +def test_set_pos_center_when_scaled(qapp, item): + item.setScale(2) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=200): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + item.set_pos_center(QtCore.QPointF(0, 0)) + assert item.pos().x() == -200 + assert item.pos().y() == -100 - def test_set_pos_center_when_rotated(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(90) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=200): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - item.set_pos_center(QtCore.QPointF(0, 0)) - assert item.pos().x() == 50 - assert item.pos().y() == -100 - def test_pixmap_to_bytes(self): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - assert item.pixmap_to_bytes().startswith(b'\x89PNG') +def test_set_pos_center_when_rotated(qapp, item): + item.setRotation(90) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=200): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + item.set_pos_center(QtCore.QPointF(0, 0)) + assert item.pos().x() == 50 + assert item.pos().y() == -100 - def test_pixmap_from_bytes(self): - item = BeePixmapItem(QtGui.QImage()) - with open(self.imgfilename3x3, 'rb') as f: - imgdata = f.read() - item.pixmap_from_bytes(imgdata) - assert item.width == 3 - assert item.height == 3 - def test_paint(self): - item = BeePixmapItem(QtGui.QImage()) - item.pixmap = MagicMock(return_value='bee') - item.paint_selectable = MagicMock() - painter = MagicMock() - item.paint(painter, None, None) - item.paint_selectable.assert_called_once() - painter.drawPixmap.assert_called_with(0, 0, 'bee') +def test_pixmap_to_bytes(qapp, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + assert item.pixmap_to_bytes().startswith(b'\x89PNG') - def test_has_selection_outline_when_not_selected(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(False) - item.has_selection_outline() is False - def test_has_selection_outline_when_selected(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - item.has_selection_outline() is True +def test_pixmap_from_bytes(qapp, item, imgfilename3x3): + with open(imgfilename3x3, 'rb') as f: + imgdata = f.read() + item.pixmap_from_bytes(imgdata) + assert item.width == 3 + assert item.height == 3 - def test_has_selection_handles_when_not_selected(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(False) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(False) - item1.has_selection_handles() is False - def test_has_selection_handles_when_selected_single(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(False) - item1.has_selection_handles() is True +def test_paint(qapp, item): + item.pixmap = MagicMock(return_value='bee') + item.paint_selectable = MagicMock() + painter = MagicMock() + item.paint(painter, None, None) + item.paint_selectable.assert_called_once() + painter.drawPixmap.assert_called_with(0, 0, 'bee') - def test_has_selection_handles_when_selected_multi(self): - view = MagicMock(get_scale=MagicMock(return_value=1)) - with patch('beeref.scene.BeeGraphicsScene.views', - return_value=[view]): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item1.has_selection_handles() is False - def test_selection_action_items(self): - item = BeePixmapItem(QtGui.QImage()) - assert item.selection_action_items() == [item] +def test_has_selection_outline_when_not_selected(view, item): + view.scene.addItem(item) + item.setSelected(False) + item.has_selection_outline() is False - def test_create_copy(self): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3), 'foo.png') - item.setPos(20, 30) - item.setRotation(33) - item.do_flip() - item.setZValue(0.5) - item.setScale(2.2) - copy = item.create_copy() - assert copy.pixmap_to_bytes() == item.pixmap_to_bytes() - assert copy.filename == 'foo.png' - assert copy.pos() == QtCore.QPointF(20, 30) - assert copy.rotation() == 33 - assert item.flip() == -1 - assert item.zValue() == 0.5 - assert item.scale() == 2.2 +def test_has_selection_outline_when_selected(view, item): + view.scene.addItem(item) + item.setSelected(True) + item.has_selection_outline() is True + + +def test_has_selection_handles_when_not_selected(view, item): + view.scene.addItem(item) + item.setSelected(False) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(False) + item.has_selection_handles() is False + + +def test_has_selection_handles_when_selected_single(view, item): + view.scene.addItem(item) + item.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(False) + item.has_selection_handles() is True + + +def test_has_selection_handles_when_selected_multi(view, item): + view.scene.addItem(item) + item.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item.has_selection_handles() is False + + +def test_selection_action_items(qapp): + item = BeePixmapItem(QtGui.QImage()) + assert item.selection_action_items() == [item] + + +def test_create_copy(qapp, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3), 'foo.png') + item.setPos(20, 30) + item.setRotation(33) + item.do_flip() + item.setZValue(0.5) + item.setScale(2.2) + + copy = item.create_copy() + assert copy.pixmap_to_bytes() == item.pixmap_to_bytes() + assert copy.filename == 'foo.png' + assert copy.pos() == QtCore.QPointF(20, 30) + assert copy.rotation() == 33 + assert item.flip() == -1 + assert item.zValue() == 0.5 + assert item.scale() == 2.2 diff --git a/tests/test_main.py b/tests/test_main.py index f48e15e..7779db2 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -6,31 +6,25 @@ from beeref.__main__ import BeeRefMainWindow, main from beeref.assets import BeeAssets from beeref.view import BeeGraphicsView -from .base import BeeTestCase + +@patch('PyQt6.QtWidgets.QWidget.show') +def test_beeref_mainwindow_init(show_mock, qapp): + window = BeeRefMainWindow(qapp) + assert window.windowTitle() == 'BeeRef' + assert BeeAssets().logo == BeeAssets().logo + assert window.windowIcon() + assert window.contentsMargins() == QtCore.QMargins(0, 0, 0, 0) + assert isinstance(window.view, BeeGraphicsView) + show_mock.assert_called_once() -class BeeRefMainWindowTestCase(BeeTestCase): - - @patch('PyQt6.QtWidgets.QWidget.show') - def test_init(self, show_mock): - window = BeeRefMainWindow(self.app) - assert window.windowTitle() == 'BeeRef' - assert BeeAssets().logo == BeeAssets().logo - assert window.windowIcon() - assert window.contentsMargins() == QtCore.QMargins(0, 0, 0, 0) - assert isinstance(window.view, BeeGraphicsView) - show_mock.assert_called_once() - - -class BeeRefMainTestCase(BeeTestCase): - - @patch('PyQt6.QtWidgets.QApplication') - @patch('beeref.__main__.CommandlineArgs') - def test_run(self, args_mock, app_mock): - app_mock.return_value = self.app - args_mock.return_value.filename = None - args_mock.return_value.loglevel = 'WARN' - with patch.object(self.app, 'exec') as exec_mock: - main() - args_mock.assert_called_once_with(with_check=True) - exec_mock.assert_called_once_with() +@patch('PyQt6.QtWidgets.QApplication') +@patch('beeref.__main__.CommandlineArgs') +def test_main(args_mock, app_mock, qapp): + app_mock.return_value = qapp + args_mock.return_value.filename = None + args_mock.return_value.loglevel = 'WARN' + with patch.object(qapp, 'exec') as exec_mock: + main() + args_mock.assert_called_once_with(with_check=True) + exec_mock.assert_called_once_with() diff --git a/tests/test_scene.py b/tests/test_scene.py index e9b8143..e9197e2 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -8,895 +8,923 @@ from PyQt6.QtCore import Qt from beeref import commands from beeref.items import BeePixmapItem -from beeref.scene import BeeGraphicsScene -from .base import BeeTestCase -class BeeGraphicsSceneTestCase(BeeTestCase): +def test_add_remove_item(view, item): + view.scene.addItem(item) + assert view.scene.items() == [item] + view.scene.removeItem(item) + assert view.scene.items() == [] - def setUp(self): - self.undo_stack = QtGui.QUndoStack() - self.scene = BeeGraphicsScene(self.undo_stack) - self.view = MagicMock(get_scale=MagicMock(return_value=1)) - views_patcher = patch('beeref.scene.BeeGraphicsScene.views', - return_value=[self.view]) - views_patcher.start() - self.addCleanup(views_patcher.stop) - def test_add_remove_item(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - assert self.scene.items() == [item] - self.scene.removeItem(item) - assert self.scene.items() == [] +def test_normalize_height(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(3) - def test_normalize_height(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setScale(3) - - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.normalize_height() - - assert item1.scale() == 2 - assert item1.pos() == QtCore.QPointF(-50, -40) - assert item2.scale() == 2 - assert item2.pos() == QtCore.QPointF(50, 40) - - def test_normalize_height_with_rotation(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setRotation(90) - - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=200): - self.scene.normalize_height() - - assert item1.scale() == 0.75 - assert item2.scale() == 1.5 - - def test_normalize_height_when_no_items(self): - self.scene.normalize_height() - - def test_normalize_width(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setScale(3) - - with patch('beeref.items.BeePixmapItem.width', + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=80): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - self.scene.normalize_width() + view.scene.normalize_height() - assert item1.scale() == 2 - assert item1.pos() == QtCore.QPointF(-40, -50) - assert item2.scale() == 2 - assert item2.pos() == QtCore.QPointF(40, 50) + assert item1.scale() == 2 + assert item1.pos() == QtCore.QPointF(-50, -40) + assert item2.scale() == 2 + assert item2.pos() == QtCore.QPointF(50, 40) - def test_normalize_width_with_rotation(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setRotation(90) - with patch('beeref.items.BeePixmapItem.width', +def test_normalize_height_with_rotation(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setRotation(90) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=200): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - self.scene.normalize_height() + view.scene.normalize_height() - assert item1.scale() == 1.5 - assert item2.scale() == 0.75 + assert item1.scale() == 0.75 + assert item2.scale() == 1.5 - def test_normalize_width_when_no_items(self): - self.scene.normalize_width() - def test_normalize_size(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setScale(2) +def test_normalize_height_when_no_items(view): + view.scene.normalize_height() - with patch('beeref.items.BeePixmapItem.width', + +def test_normalize_width(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(3) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=80): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - self.scene.normalize_size() + view.scene.normalize_width() - assert item1.scale() == approx(math.sqrt(2.5)) - assert item2.scale() == approx(math.sqrt(2.5)) + assert item1.scale() == 2 + assert item1.pos() == QtCore.QPointF(-40, -50) + assert item2.scale() == 2 + assert item2.pos() == QtCore.QPointF(40, 50) - def test_normalize_size_with_rotation(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setRotation(90) - with patch('beeref.items.BeePixmapItem.width', +def test_normalize_width_with_rotation(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setRotation(90) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=200): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=200): - self.scene.normalize_size() + view.scene.normalize_height() - assert item1.scale() == 1 - assert item2.scale() == 1 + assert item1.scale() == 1.5 + assert item2.scale() == 0.75 - def test_normalize_size_when_no_items(self): - self.scene.normalize_size() - def test_arrange_horizontal(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item1.setPos(10, -100) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setPos(-10, 40) +def test_normalize_width_when_no_items(view): + view.scene.normalize_width() - with patch('beeref.items.BeePixmapItem.width', + +def test_normalize_size(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setScale(2) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.arrange() + view.scene.normalize_size() - assert item2.pos() == QtCore.QPointF(-50, -30) - assert item1.pos() == QtCore.QPointF(50, -30) + assert item1.scale() == approx(math.sqrt(2.5)) + assert item2.scale() == approx(math.sqrt(2.5)) - def test_arrange_vertical(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item1.setPos(10, -100) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setPos(-10, 40) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.arrange(vertical=True) +def test_normalize_size_with_rotation(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setRotation(90) - assert item1.pos() == QtCore.QPointF(0, -70) - assert item2.pos() == QtCore.QPointF(0, 10) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=200): + view.scene.normalize_size() - def test_arrange_when_rotated(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item1.setPos(10, -100) - item1.setRotation(90) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setPos(-10, 40) - item2.setRotation(90) + assert item1.scale() == 1 + assert item2.scale() == 1 - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.arrange() - assert item2.pos() == QtCore.QPointF(-40, -30) - assert item1.pos() == QtCore.QPointF(40, -30) +def test_normalize_size_when_no_items(view): + view.scene.normalize_size() - def test_arrange_when_no_items(self): - self.scene.arrange() - def test_arrange_optimal(self): - for i in range(4): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.arrange_optimal() +def test_arrange_horizontal(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) - expected_positions = {(-50, -40), (50, -40), (-50, 40), (50, 40)} - actual_positions = { - (i.pos().x(), i.pos().y()) - for i in self.scene.selectedItems(user_only=True)} - assert expected_positions == actual_positions + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + view.scene.arrange() - def test_arrange_optimal_when_rotated(self): - for i in range(4): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setRotation(90) - item.setSelected(True) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - self.scene.arrange_optimal() + assert item2.pos() == QtCore.QPointF(-50, -30) + assert item1.pos() == QtCore.QPointF(50, -30) - expected_positions = {(-40, -50), (40, -50), (-40, 50), (40, 50)} - actual_positions = { - (i.pos().x(), i.pos().y()) - for i in self.scene.selectedItems(user_only=True)} - assert expected_positions == actual_positions - def test_arrange_optimal_when_no_items(self): - self.scene.arrange_optimal() +def test_arrange_vertical(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) - def test_flip_items(self): + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + view.scene.arrange(vertical=True) + + assert item1.pos() == QtCore.QPointF(0, -70) + assert item2.pos() == QtCore.QPointF(0, 10) + + +def test_arrange_when_rotated(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(10, -100) + item1.setRotation(90) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-10, 40) + item2.setRotation(90) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + view.scene.arrange() + + assert item2.pos() == QtCore.QPointF(-40, -30) + assert item1.pos() == QtCore.QPointF(40, -30) + + +def test_arrange_when_no_items(view): + view.scene.arrange() + + +def test_arrange_optimal(view): + for i in range(4): item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) + view.scene.addItem(item) item.setSelected(True) - self.scene.undo_stack = MagicMock(push=MagicMock()) - with patch('beeref.scene.BeeGraphicsScene.itemsBoundingRect', - return_value=QtCore.QRectF(10, 20, 100, 60)): - self.scene.flip_items(vertical=True) - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - assert isinstance(cmd, commands.FlipItems) - assert cmd.items == [item] - assert cmd.anchor == QtCore.QPointF(60, 50) - assert cmd.vertical is True + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + view.scene.arrange_optimal() - def test_set_selection_all_items_when_true(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) + expected_positions = {(-50, -40), (50, -40), (-50, 40), (50, 40)} + actual_positions = { + (i.pos().x(), i.pos().y()) + for i in view.scene.selectedItems(user_only=True)} + assert expected_positions == actual_positions - self.scene.set_selected_all_items(True) - assert item1.isSelected() is True - assert item2.isSelected() is True - def test_set_selection_all_items_when_false(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - - self.scene.set_selected_all_items(False) - assert item1.isSelected() is False - assert item2.isSelected() is False - - def test_has_selection_when_no_selection(self): +def test_arrange_optimal_when_rotated(view): + for i in range(4): item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - assert self.scene.has_selection() is False - - def test_has_selection_when_selection(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) + view.scene.addItem(item) + item.setRotation(90) item.setSelected(True) - assert self.scene.has_selection() is True + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=80): + view.scene.arrange_optimal() - def test_has_single_selection_when_no_selection(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - assert self.scene.has_single_selection() is False + expected_positions = {(-40, -50), (40, -50), (-40, 50), (40, 50)} + actual_positions = { + (i.pos().x(), i.pos().y()) + for i in view.scene.selectedItems(user_only=True)} + assert expected_positions == actual_positions - def test_has_single_selection_when_single_selection(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - assert self.scene.has_single_selection() is True - def test_has_single_selection_when_multi_selection(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - assert self.scene.has_single_selection() is False +def test_arrange_optimal_when_no_items(view): + view.scene.arrange_optimal() - def test_has_multi_selection_when_no_selection(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - assert self.scene.has_multi_selection() is False - def test_has_multi_selection_when_single_selection(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - assert self.scene.has_multi_selection() is False - - def test_has_multi_selection_when_multi_selection(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - assert self.scene.has_multi_selection() is True - - @patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') - def test_mouse_press_event_when_right_click(self, mouse_mock): - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.RightButton)) - self.scene.mousePressEvent(event) - event.accept.assert_not_called() - mouse_mock.assert_not_called() - - @patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') - def test_mouse_press_event_when_left_click_over_item(self, mouse_mock): - self.scene.itemAt = MagicMock( - return_value=BeePixmapItem(QtGui.QImage())) - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.LeftButton), - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - self.scene.mousePressEvent(event) - event.accept.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is True - assert self.scene.rubberband_active is False - assert self.scene.event_start == QtCore.QPointF(10, 20) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') - def test_mouse_press_event_when_left_click_not_over_item(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - self.scene.itemAt = MagicMock(return_value=None) - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.LeftButton), - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - self.scene.mousePressEvent(event) - event.accept.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False - assert self.scene.rubberband_active is True - assert self.scene.event_start == QtCore.QPointF(10, 20) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') - def test_mouse_press_event_when_no_items(self, mouse_mock): - self.scene.itemAt = MagicMock(return_value=None) - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.LeftButton), - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - self.scene.mousePressEvent(event) - event.accept.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False - assert self.scene.rubberband_active is False - mouse_mock.assert_called_once_with(event) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') - def test_mouse_doubleclick_event_when_over_item(self, mouse_mock): - event = MagicMock() - self.scene.move_active = True - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setPos(30, 40) - item.setSelected(True) - self.scene.itemAt = MagicMock(return_value=item) - - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - self.scene.mouseDoubleClickEvent(event) - - assert self.scene.move_active is False - self.view.fit_rect.assert_called_once_with( - QtCore.QRectF(30, 40, 100, 100), toggle_item=item) - mouse_mock.assert_not_called() - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') - def test_mouse_doubleclick_event_when_item_not_selected( - self, mouse_mock): - event = MagicMock() - self.scene.move_active = True - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setPos(30, 40) - item.setSelected(False) - self.scene.itemAt = MagicMock(return_value=item) - - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - self.scene.mouseDoubleClickEvent(event) - - assert self.scene.move_active is False - self.view.fit_rect.assert_called_once_with( - QtCore.QRectF(30, 40, 100, 100), toggle_item=item) - mouse_mock.assert_not_called() - assert item.isSelected() is True - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') - def test_mouse_doubleclick_event_when_not_over_item(self, mouse_mock): - event = MagicMock() - self.scene.itemAt = MagicMock(return_value=None) - self.scene.mouseDoubleClickEvent(event) - self.view.fit_rect.assert_not_called() - mouse_mock.assert_called_once_with(event) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') - def test_mouse_move_event_when_rubberband_new(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.scene.addItem(item) - self.scene.rubberband_active = True - self.scene.addItem = MagicMock() - self.scene.event_start = QtCore.QPointF(0, 0) - self.scene.rubberband_item.bring_to_front = MagicMock() - assert self.scene.rubberband_item.scene() is None - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - - self.scene.mouseMoveEvent(event) - - self.scene.addItem.assert_called_once_with(self.scene.rubberband_item) - self.scene.rubberband_item.bring_to_front.assert_called_once() - self.scene.rubberband_item.rect().topLeft().x() == 0 - self.scene.rubberband_item.rect().topLeft().y() == 0 - self.scene.rubberband_item.rect().bottomRight().x() == 10 - self.scene.rubberband_item.rect().bottomRight().y() == 20 - assert item.isSelected() is True - assert mouse_mock.called_once_with(event) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') - def test_mouse_move_event_when_rubberband_not_new(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.scene.addItem(item) - self.scene.rubberband_active = True - self.scene.event_start = QtCore.QPointF(0, 0) - self.scene.rubberband_item.bring_to_front = MagicMock() - self.scene.addItem(self.scene.rubberband_item) - self.scene.addItem = MagicMock() - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - - self.scene.mouseMoveEvent(event) - - self.scene.addItem.assert_not_called() - self.scene.rubberband_item.bring_to_front.assert_not_called() - self.scene.rubberband_item.rect().topLeft().x() == 0 - self.scene.rubberband_item.rect().topLeft().y() == 0 - self.scene.rubberband_item.rect().bottomRight().x() == 10 - self.scene.rubberband_item.rect().bottomRight().y() == 20 - assert item.isSelected() is True - assert mouse_mock.called_once_with(event) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') - def test_mouse_move_event_when_no_rubberband(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.scene.addItem(item) - self.scene.rubberband_active = False - self.scene.event_start = QtCore.QPointF(0, 0) - self.scene.rubberband_item.bring_to_front = MagicMock() - self.scene.addItem = MagicMock() - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), - ) - - self.scene.mouseMoveEvent(event) - - self.scene.addItem.assert_not_called() - self.scene.rubberband_item.bring_to_front.assert_not_called() - self.scene.rubberband_item.rect().topLeft().x() == 0 - self.scene.rubberband_item.rect().topLeft().y() == 0 - self.scene.rubberband_item.rect().bottomRight().x() == 0 - self.scene.rubberband_item.rect().bottomRight().y() == 0 - assert item.isSelected() is False - assert mouse_mock.called_once_with(event) - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_rubberband_active(self, mouse_mock): - event = MagicMock() - self.scene.rubberband_active = True - self.scene.addItem(self.scene.rubberband_item) - self.scene.removeItem = MagicMock() - - self.scene.mouseReleaseEvent(event) - self.scene.removeItem.assert_called_once_with( - self.scene.rubberband_item) - self.scene.rubberband_active is False - - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_move_active(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) - self.scene.move_active = True - self.scene.event_start = QtCore.QPoint(0, 0) - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.scene.mouseReleaseEvent(event) - self.scene.undo_stack.push.assert_called_once() - args = self.scene.undo_stack.push.call_args_list[0][0] +def test_flip_items(view, item): + view.scene.addItem(item) + item.setSelected(True) + view.scene.undo_stack = MagicMock(push=MagicMock()) + with patch('beeref.scene.BeeGraphicsScene.itemsBoundingRect', + return_value=QtCore.QRectF(10, 20, 100, 60)): + view.scene.flip_items(vertical=True) + args = view.scene.undo_stack.push.call_args_list[0][0] cmd = args[0] - assert isinstance(cmd, commands.MoveItemsBy) + assert isinstance(cmd, commands.FlipItems) assert cmd.items == [item] - assert cmd.ignore_first_redo is True - assert cmd.delta.x() == 10 - assert cmd.delta.y() == 20 - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False + assert cmd.anchor == QtCore.QPointF(60, 50) + assert cmd.vertical is True - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_move_not_active(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) - self.scene.move_active = False - self.scene.undo_stack = MagicMock(push=MagicMock()) - self.scene.mouseReleaseEvent(event) - self.scene.undo_stack.push.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False +def test_set_selection_all_items_when_true(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_no_selection(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(False) - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) - self.scene.move_active = True - self.scene.undo_stack = MagicMock(push=MagicMock()) + view.scene.set_selected_all_items(True) + assert item1.isSelected() is True + assert item2.isSelected() is True - self.scene.mouseReleaseEvent(event) - self.scene.undo_stack.push.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_item_action_active(self, mouse_mock): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) - self.scene.move_active = True - item.scale_active = True - self.scene.undo_stack = MagicMock(push=MagicMock()) +def test_set_selection_all_items_when_false(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) - self.scene.mouseReleaseEvent(event) - self.scene.undo_stack.push.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False + view.scene.set_selected_all_items(False) + assert item1.isSelected() is False + assert item2.isSelected() is False - @patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') - def test_mouse_release_event_when_multiselect_action_active( - self, mouse_mock): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - event = MagicMock( - scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) - self.scene.move_active = True - self.scene.multi_select_item.scale_active = True - self.scene.undo_stack = MagicMock(push=MagicMock()) - self.scene.mouseReleaseEvent(event) - self.scene.undo_stack.push.assert_not_called() - mouse_mock.assert_called_once_with(event) - assert self.scene.move_active is False +def test_has_selection_when_no_selection(view, item): + view.scene.addItem(item) + assert view.scene.has_selection() is False - def test_selected_items(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - selected = self.scene.selectedItems() - assert len(selected) == 3 # Multi select item! - assert item1 in selected - assert item2 in selected - def test_selected_items_user_only(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - selected = self.scene.selectedItems(user_only=True) - assert len(selected) == 2 # No multi select item! - assert item1 in selected - assert item2 in selected +def test_has_selection_when_selection(view, item): + view.scene.addItem(item) + item.setSelected(True) + assert view.scene.has_selection() is True - def test_items_for_save(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item3 = QtWidgets.QGraphicsRectItem() - self.scene.addItem(item3) - items = list(self.scene.items_for_save()) - assert items == [item1, item2] +def test_has_single_selection_when_no_selection(view, item): + view.scene.addItem(item) + assert view.scene.has_single_selection() is False - def test_clear_save_ids(self): - item1 = BeePixmapItem(QtGui.QImage()) - item1.save_id = 5 - self.scene.addItem(item1) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item3 = QtWidgets.QGraphicsRectItem() - self.scene.addItem(item3) - self.scene.clear_save_ids() - assert item1.save_id is None - assert item2.save_id is None - assert hasattr(item3, 'save_id') is False +def test_has_single_selection_when_single_selection(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + assert view.scene.has_single_selection() is True - def test_on_view_scale_change(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - item.on_view_scale_change = MagicMock() - self.scene.on_view_scale_change() - item.on_view_scale_change.assert_called_once() - def test_items_bounding_rect_given_items(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item1.setPos(4, -6) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setPos(-33, 22) - item3 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item3) - item3.setSelected(True) - item3.setPos(1000, 1000) +def test_has_single_selection_when_multi_selection(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + assert view.scene.has_single_selection() is False - with patch('beeref.items.BeePixmapItem.width', + +def test_has_multi_selection_when_no_selection(view, item): + view.scene.addItem(item) + assert view.scene.has_multi_selection() is False + + +def test_has_multi_selection_when_single_selection(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + assert view.scene.has_multi_selection() is False + + +def test_has_multi_selection_when_multi_selection(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + assert view.scene.has_multi_selection() is True + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') +def test_mouse_press_event_when_right_click(mouse_mock, view): + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.RightButton)) + view.scene.mousePressEvent(event) + event.accept.assert_not_called() + mouse_mock.assert_not_called() + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') +def test_mouse_press_event_when_left_click_over_item(mouse_mock, view, item): + view.scene.itemAt = MagicMock(return_value=item) + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.LeftButton), + scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), + ) + view.scene.mousePressEvent(event) + event.accept.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is True + assert view.scene.rubberband_active is False + assert view.scene.event_start == QtCore.QPointF(10, 20) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') +def test_mouse_press_event_when_left_click_not_over_item( + mouse_mock, view, item): + view.scene.addItem(item) + view.scene.itemAt = MagicMock(return_value=None) + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.LeftButton), + scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), + ) + view.scene.mousePressEvent(event) + event.accept.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + assert view.scene.rubberband_active is True + assert view.scene.event_start == QtCore.QPointF(10, 20) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mousePressEvent') +def test_mouse_press_event_when_no_items(mouse_mock, view): + view.scene.itemAt = MagicMock(return_value=None) + event = MagicMock( + button=MagicMock(return_value=Qt.MouseButton.LeftButton), + scenePos=MagicMock(return_value=QtCore.QPointF(10, 20)), + ) + view.scene.mousePressEvent(event) + event.accept.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + assert view.scene.rubberband_active is False + mouse_mock.assert_called_once_with(event) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') +def test_mouse_doubleclick_event_when_over_item(mouse_mock, view, item): + event = MagicMock() + view.scene.move_active = True + view.scene.addItem(item) + item.setPos(30, 40) + item.setSelected(True) + view.scene.itemAt = MagicMock(return_value=item) + view.fit_rect = MagicMock() + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - rect = self.scene.itemsBoundingRect(items=[item1, item2]) + view.scene.mouseDoubleClickEvent(event) - assert rect.topLeft().x() == -33 - assert rect.topLeft().y() == -6 - assert rect.bottomRight().x() == 104 - assert rect.bottomRight().y() == 122 + assert view.scene.move_active is False + view.fit_rect.assert_called_once_with( + QtCore.QRectF(30, 40, 100, 100), toggle_item=item) + mouse_mock.assert_not_called() - def test_items_bounding_rect_two_items_selection_only(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item1.setPos(4, -6) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item2.setPos(-33, 22) - item3 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item3) - item3.setSelected(False) - item3.setPos(1000, 1000) - with patch('beeref.items.BeePixmapItem.width', +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') +def test_mouse_doubleclick_event_when_item_not_selected( + mouse_mock, view, item): + event = MagicMock() + view.scene.move_active = True + view.scene.addItem(item) + item.setPos(30, 40) + item.setSelected(False) + view.scene.itemAt = MagicMock(return_value=item) + view.fit_rect = MagicMock() + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - rect = self.scene.itemsBoundingRect(selection_only=True) + view.scene.mouseDoubleClickEvent(event) - assert rect.topLeft().x() == -33 - assert rect.topLeft().y() == -6 - assert rect.bottomRight().x() == 104 - assert rect.bottomRight().y() == 122 + assert view.scene.move_active is False + view.fit_rect.assert_called_once_with( + QtCore.QRectF(30, 40, 100, 100), toggle_item=item) + mouse_mock.assert_not_called() + assert item.isSelected() is True - def test_items_bounding_rect_rotated_item(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setRotation(-45) - with patch('beeref.items.BeePixmapItem.width', +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseDoubleClickEvent') +def test_mouse_doubleclick_event_when_not_over_item(mouse_mock, view): + event = MagicMock() + view.fit_rect = MagicMock() + view.scene.itemAt = MagicMock(return_value=None) + view.scene.mouseDoubleClickEvent(event) + view.fit_rect.assert_not_called() + mouse_mock.assert_called_once_with(event) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') +def test_mouse_move_event_when_rubberband_new( + mouse_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.scene.rubberband_active = True + view.scene.addItem = MagicMock() + view.scene.event_start = QtCore.QPointF(0, 0) + view.scene.rubberband_item.bring_to_front = MagicMock() + assert view.scene.rubberband_item.scene() is None + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPointF(10, 20))) + + view.scene.mouseMoveEvent(event) + + view.scene.addItem.assert_called_once_with(view.scene.rubberband_item) + view.scene.rubberband_item.bring_to_front.assert_called_once() + view.scene.rubberband_item.rect().topLeft().x() == 0 + view.scene.rubberband_item.rect().topLeft().y() == 0 + view.scene.rubberband_item.rect().bottomRight().x() == 10 + view.scene.rubberband_item.rect().bottomRight().y() == 20 + assert item.isSelected() is True + assert mouse_mock.called_once_with(event) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') +def test_mouse_move_event_when_rubberband_not_new( + mouse_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.scene.rubberband_active = True + view.scene.event_start = QtCore.QPointF(0, 0) + view.scene.rubberband_item.bring_to_front = MagicMock() + view.scene.addItem(view.scene.rubberband_item) + view.scene.addItem = MagicMock() + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPointF(10, 20))) + + view.scene.mouseMoveEvent(event) + + view.scene.addItem.assert_not_called() + view.scene.rubberband_item.bring_to_front.assert_not_called() + view.scene.rubberband_item.rect().topLeft().x() == 0 + view.scene.rubberband_item.rect().topLeft().y() == 0 + view.scene.rubberband_item.rect().bottomRight().x() == 10 + view.scene.rubberband_item.rect().bottomRight().y() == 20 + assert item.isSelected() is True + assert mouse_mock.called_once_with(event) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseMoveEvent') +def test_mouse_move_event_when_no_rubberband(mouse_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.scene.rubberband_active = False + view.scene.event_start = QtCore.QPointF(0, 0) + view.scene.rubberband_item.bring_to_front = MagicMock() + view.scene.addItem = MagicMock() + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPointF(10, 20))) + + view.scene.mouseMoveEvent(event) + + view.scene.addItem.assert_not_called() + view.scene.rubberband_item.bring_to_front.assert_not_called() + view.scene.rubberband_item.rect().topLeft().x() == 0 + view.scene.rubberband_item.rect().topLeft().y() == 0 + view.scene.rubberband_item.rect().bottomRight().x() == 0 + view.scene.rubberband_item.rect().bottomRight().y() == 0 + assert item.isSelected() is False + assert mouse_mock.called_once_with(event) + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_rubberband_active(mouse_mock, view): + event = MagicMock() + view.scene.rubberband_active = True + view.scene.addItem(view.scene.rubberband_item) + view.scene.removeItem = MagicMock() + + view.scene.mouseReleaseEvent(event) + view.scene.removeItem.assert_called_once_with(view.scene.rubberband_item) + view.scene.rubberband_active is False + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_move_active(mouse_mock, view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) + view.scene.move_active = True + view.scene.event_start = QtCore.QPoint(0, 0) + view.scene.undo_stack = MagicMock(push=MagicMock()) + + view.scene.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_called_once() + args = view.scene.undo_stack.push.call_args_list[0][0] + cmd = args[0] + assert isinstance(cmd, commands.MoveItemsBy) + assert cmd.items == [item] + assert cmd.ignore_first_redo is True + assert cmd.delta.x() == 10 + assert cmd.delta.y() == 20 + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_move_not_active(mouse_mock, view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) + view.scene.move_active = False + view.scene.undo_stack = MagicMock(push=MagicMock()) + + view.scene.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_no_selection(mouse_mock, view, item): + view.scene.addItem(item) + item.setSelected(False) + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) + view.scene.move_active = True + view.scene.undo_stack = MagicMock(push=MagicMock()) + + view.scene.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_item_action_active(mouse_mock, view, item): + view.scene.addItem(item) + item.setSelected(True) + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) + view.scene.move_active = True + item.scale_active = True + view.scene.undo_stack = MagicMock(push=MagicMock()) + + view.scene.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + + +@patch('PyQt6.QtWidgets.QGraphicsScene.mouseReleaseEvent') +def test_mouse_release_event_when_multiselect_action_active(mouse_mock, view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + event = MagicMock(scenePos=MagicMock(return_value=QtCore.QPoint(10, 20))) + view.scene.move_active = True + view.scene.multi_select_item.scale_active = True + view.scene.undo_stack = MagicMock(push=MagicMock()) + + view.scene.mouseReleaseEvent(event) + view.scene.undo_stack.push.assert_not_called() + mouse_mock.assert_called_once_with(event) + assert view.scene.move_active is False + + +def test_selected_items(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + selected = view.scene.selectedItems() + assert len(selected) == 3 # Multi select item! + assert item1 in selected + assert item2 in selected + + +def test_selected_items_user_only(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + selected = view.scene.selectedItems(user_only=True) + assert len(selected) == 2 # No multi select item! + assert item1 in selected + assert item2 in selected + + +def test_items_for_save(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item3 = QtWidgets.QGraphicsRectItem() + view.scene.addItem(item3) + + items = list(view.scene.items_for_save()) + assert items == [item1, item2] + + +def test_clear_save_ids(view): + item1 = BeePixmapItem(QtGui.QImage()) + item1.save_id = 5 + view.scene.addItem(item1) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item3 = QtWidgets.QGraphicsRectItem() + view.scene.addItem(item3) + + view.scene.clear_save_ids() + assert item1.save_id is None + assert item2.save_id is None + assert hasattr(item3, 'save_id') is False + + +def test_on_view_scale_change(view, item): + view.scene.addItem(item) + item.setSelected(True) + item.on_view_scale_change = MagicMock() + view.scene.on_view_scale_change() + item.on_view_scale_change.assert_called_once() + + +def test_items_bounding_rect_given_items(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(4, -6) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-33, 22) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + item3.setSelected(True) + item3.setPos(1000, 1000) + + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - rect = self.scene.itemsBoundingRect() + rect = view.scene.itemsBoundingRect(items=[item1, item2]) - assert rect.topLeft().x() == 0 - assert rect.topLeft().y() == approx(-math.sqrt(2) * 50) - assert rect.bottomRight().x() == approx(math.sqrt(2) * 100) - assert rect.bottomRight().y() == approx(math.sqrt(2) * 50) + assert rect.topLeft().x() == -33 + assert rect.topLeft().y() == -6 + assert rect.bottomRight().x() == 104 + assert rect.bottomRight().y() == 122 - def test_items_bounding_rect_flipped_item(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.do_flip() - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=50): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=100): - rect = self.scene.itemsBoundingRect() - assert rect.topLeft().x() == -50 - assert rect.topLeft().y() == 0 - assert rect.bottomRight().x() == 0 - assert rect.bottomRight().y() == 100 +def test_items_bounding_rect_two_items_selection_only(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item1.setPos(4, -6) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item2.setPos(-33, 22) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + item3.setSelected(False) + item3.setPos(1000, 1000) - def test_items_bounding_rect_when_no_items(self): - rect = self.scene.itemsBoundingRect() - assert rect == QtCore.QRectF(0, 0, 0, 0) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + rect = view.scene.itemsBoundingRect(selection_only=True) - def test_get_selection_center(self): - with patch('beeref.scene.BeeGraphicsScene.itemsBoundingRect', - return_value=QtCore.QRectF(10, 20, 100, 60)): - center = self.scene.get_selection_center() - assert center == QtCore.QPointF(60, 50) + assert rect.topLeft().x() == -33 + assert rect.topLeft().y() == -6 + assert rect.bottomRight().x() == 104 + assert rect.bottomRight().y() == 122 - def test_on_selection_change_when_multi_selection_new(self): - self.scene.has_multi_selection = MagicMock(return_value=True) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.bring_to_front = MagicMock() - self.scene.itemsBoundingRect = MagicMock( - return_value=QtCore.QRectF(0, 0, 100, 80)) - self.scene.addItem = MagicMock() - self.scene.on_selection_change() +def test_items_bounding_rect_rotated_item(view, item): + view.scene.addItem(item) + item.setRotation(-45) - m_item = self.scene.multi_select_item - m_item.fit_selection_area.assert_called_once_with( - QtCore.QRectF(0, 0, 100, 80)) - m_item.bring_to_front.assert_called_once() - self.scene.addItem.assert_called_once_with(m_item) + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=100): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + rect = view.scene.itemsBoundingRect() - def test_on_selection_change_when_multi_selection_existing(self): - self.scene.addItem(self.scene.multi_select_item) - self.scene.has_multi_selection = MagicMock(return_value=True) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.bring_to_front = MagicMock() - self.scene.itemsBoundingRect = MagicMock( - return_value=QtCore.QRectF(0, 0, 100, 80)) - self.scene.addItem = MagicMock() + assert rect.topLeft().x() == 0 + assert rect.topLeft().y() == approx(-math.sqrt(2) * 50) + assert rect.bottomRight().x() == approx(math.sqrt(2) * 100) + assert rect.bottomRight().y() == approx(math.sqrt(2) * 50) - self.scene.on_selection_change() - m_item = self.scene.multi_select_item - m_item.fit_selection_area.assert_called_once_with( - QtCore.QRectF(0, 0, 100, 80)) - m_item.bring_to_front.assert_not_called() - self.scene.addItem.assert_not_called() +def test_items_bounding_rect_flipped_item(view): + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + item.do_flip() + with patch('beeref.items.BeePixmapItem.width', + new_callable=PropertyMock, return_value=50): + with patch('beeref.items.BeePixmapItem.height', + new_callable=PropertyMock, return_value=100): + rect = view.scene.itemsBoundingRect() - def test_on_selection_change_when_multi_selection_ended(self): - self.scene.addItem(self.scene.multi_select_item) - self.scene.has_multi_selection = MagicMock(return_value=False) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.bring_to_front = MagicMock() - self.scene.itemsBoundingRect = MagicMock( - return_value=QtCore.QRectF(0, 0, 100, 80)) - self.scene.removeItem = MagicMock() + assert rect.topLeft().x() == -50 + assert rect.topLeft().y() == 0 + assert rect.bottomRight().x() == 0 + assert rect.bottomRight().y() == 100 - self.scene.on_selection_change() - m_item = self.scene.multi_select_item - m_item.fit_selection_area.assert_not_called() - self.scene.removeItem.assert_called_once_with(m_item) +def test_items_bounding_rect_when_no_items(view): + rect = view.scene.itemsBoundingRect() + assert rect == QtCore.QRectF(0, 0, 0, 0) - def test_on_change_when_multi_select_when_no_scale_no_rotate(self): - self.scene.addItem(self.scene.multi_select_item) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.scale_active = False - self.scene.multi_select_item.rotate_active = False - self.scene.on_change(None) - self.scene.multi_select_item.fit_selection_area.assert_called_once() - def test_on_change_when_multi_select_when_scale_active(self): - self.scene.addItem(self.scene.multi_select_item) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.scale_active = True - self.scene.multi_select_item.rotate_active = False - self.scene.on_change(None) - self.scene.multi_select_item.fit_selection_area.assert_not_called() +def test_get_selection_center(view): + with patch('beeref.scene.BeeGraphicsScene.itemsBoundingRect', + return_value=QtCore.QRectF(10, 20, 100, 60)): + center = view.scene.get_selection_center() + assert center == QtCore.QPointF(60, 50) - def test_on_change_when_multi_select_when_rotate_active(self): - self.scene.addItem(self.scene.multi_select_item) - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.scale_active = False - self.scene.multi_select_item.rotate_active = True - self.scene.on_change(None) - self.scene.multi_select_item.fit_selection_area.assert_not_called() - def test_on_change_when_no_multi_select(self): - self.scene.multi_select_item.fit_selection_area = MagicMock() - self.scene.multi_select_item.scale_active = True - self.scene.multi_select_item.rotate_active = True - self.scene.on_change(None) - self.scene.multi_select_item.fit_selection_area.assert_not_called() +def test_on_selection_change_when_multi_selection_new(view): + view.scene.has_multi_selection = MagicMock(return_value=True) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.bring_to_front = MagicMock() + view.scene.itemsBoundingRect = MagicMock( + return_value=QtCore.QRectF(0, 0, 100, 80)) + view.scene.addItem = MagicMock() - def test_add_queued_items_unselected(self): - item = BeePixmapItem(QtGui.QImage()) - item.setZValue(0.33) - self.scene.add_item_later(item, selected=False) - self.scene.add_queued_items() - assert self.scene.items() == [item] - assert item.isSelected() is False - assert self.scene.max_z == 0.33 + view.scene.on_selection_change() - def test_add_queued_items_selected(self): - self.scene.max_z = 0.6 - item = BeePixmapItem(QtGui.QImage()) - item.setZValue(0.33) - self.scene.add_item_later(item, selected=True) - self.scene.add_queued_items() - assert self.scene.items() == [item] - assert item.isSelected() is True - assert item.zValue() > 0.6 + m_item = view.scene.multi_select_item + m_item.fit_selection_area.assert_called_once_with( + QtCore.QRectF(0, 0, 100, 80)) + m_item.bring_to_front.assert_called_once() + view.scene.addItem.assert_called_once_with(m_item) - def test_add_queued_items_when_no_items(self): - self.scene.add_queued_items() - assert self.scene.items() == [] - def test_copy_selection_to_internal_clipboard(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - item3 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item3) +def test_on_selection_change_when_multi_selection_existing(view): + view.scene.addItem(view.scene.multi_select_item) + view.scene.has_multi_selection = MagicMock(return_value=True) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.bring_to_front = MagicMock() + view.scene.itemsBoundingRect = MagicMock( + return_value=QtCore.QRectF(0, 0, 100, 80)) + view.scene.addItem = MagicMock() - self.scene.copy_selection_to_internal_clipboard() - assert set(self.scene.internal_clipboard) == {item1, item2} - assert set(self.scene.items_for_save()) == {item1, item2, item3} + view.scene.on_selection_change() - def test_paste_from_internal_clipboard(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setSelected(True) - item2 = BeePixmapItem(QtGui.QImage()) - item2.setScale(3.3) - self.scene.internal_clipboard = [item2] + m_item = view.scene.multi_select_item + m_item.fit_selection_area.assert_called_once_with( + QtCore.QRectF(0, 0, 100, 80)) + m_item.bring_to_front.assert_not_called() + view.scene.addItem.assert_not_called() - self.scene.paste_from_internal_clipboard(None) - assert len(list(self.scene.items_for_save())) == 2 - assert item1.isSelected() is False - new_item = self.scene.selectedItems(user_only=True)[0] - assert new_item.scale() == 3.3 - assert new_item is not item2 + +def test_on_selection_change_when_multi_selection_ended(view): + view.scene.addItem(view.scene.multi_select_item) + view.scene.has_multi_selection = MagicMock(return_value=False) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.bring_to_front = MagicMock() + view.scene.itemsBoundingRect = MagicMock( + return_value=QtCore.QRectF(0, 0, 100, 80)) + view.scene.removeItem = MagicMock() + + view.scene.on_selection_change() + + m_item = view.scene.multi_select_item + m_item.fit_selection_area.assert_not_called() + view.scene.removeItem.assert_called_once_with(m_item) + + +def test_on_change_when_multi_select_when_no_scale_no_rotate(view): + view.scene.addItem(view.scene.multi_select_item) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.scale_active = False + view.scene.multi_select_item.rotate_active = False + view.scene.on_change(None) + view.scene.multi_select_item.fit_selection_area.assert_called_once() + + +def test_on_change_when_multi_select_when_scale_active(view): + view.scene.addItem(view.scene.multi_select_item) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.scale_active = True + view.scene.multi_select_item.rotate_active = False + view.scene.on_change(None) + view.scene.multi_select_item.fit_selection_area.assert_not_called() + + +def test_on_change_when_multi_select_when_rotate_active(view): + view.scene.addItem(view.scene.multi_select_item) + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.scale_active = False + view.scene.multi_select_item.rotate_active = True + view.scene.on_change(None) + view.scene.multi_select_item.fit_selection_area.assert_not_called() + + +def test_on_change_when_no_multi_select(view): + view.scene.multi_select_item.fit_selection_area = MagicMock() + view.scene.multi_select_item.scale_active = True + view.scene.multi_select_item.rotate_active = True + view.scene.on_change(None) + view.scene.multi_select_item.fit_selection_area.assert_not_called() + + +def test_add_queued_items_unselected(view, item): + item.setZValue(0.33) + view.scene.add_item_later(item, selected=False) + view.scene.add_queued_items() + assert view.scene.items() == [item] + assert item.isSelected() is False + assert view.scene.max_z == 0.33 + + +def test_add_queued_items_selected(view, item): + view.scene.max_z = 0.6 + item.setZValue(0.33) + view.scene.add_item_later(item, selected=True) + view.scene.add_queued_items() + assert view.scene.items() == [item] + assert item.isSelected() is True + assert item.zValue() > 0.6 + + +def test_add_queued_items_when_no_items(view): + view.scene.add_queued_items() + assert view.scene.items() == [] + + +def test_copy_selection_to_internal_clipboard(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item2) + item2.setSelected(True) + item3 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item3) + + view.scene.copy_selection_to_internal_clipboard() + assert set(view.scene.internal_clipboard) == {item1, item2} + assert set(view.scene.items_for_save()) == {item1, item2, item3} + + +def test_paste_from_internal_clipboard(view): + item1 = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item1) + item1.setSelected(True) + item2 = BeePixmapItem(QtGui.QImage()) + item2.setScale(3.3) + view.scene.internal_clipboard = [item2] + + view.scene.paste_from_internal_clipboard(None) + assert len(list(view.scene.items_for_save())) == 2 + assert item1.isSelected() is False + new_item = view.scene.selectedItems(user_only=True)[0] + assert new_item.scale() == 3.3 + assert new_item is not item2 diff --git a/tests/test_selection.py b/tests/test_selection.py deleted file mode 100644 index 48ae205..0000000 --- a/tests/test_selection.py +++ /dev/null @@ -1,1024 +0,0 @@ -import math -from pytest import approx -from unittest.mock import patch, MagicMock, PropertyMock - -from PyQt6 import QtCore, QtGui -from PyQt6.QtCore import Qt - -from beeref.assets import BeeAssets -from beeref import commands -from beeref.items import BeePixmapItem -from beeref.scene import BeeGraphicsScene -from beeref.selection import MultiSelectItem, RubberbandItem -from .base import BeeTestCase - - -class BaseItemMixinTestCase(BeeTestCase): - - def setUp(self): - self.scene = BeeGraphicsScene(None) - - def test_set_scale(self): - item = BeePixmapItem( - QtGui.QImage(self.imgfilename3x3), self.imgfilename3x3) - item.prepareGeometryChange = MagicMock() - item.setScale(3) - assert item.scale() == 3 - assert item.pos().x() == 0 - assert item.pos().y() == 0 - item.prepareGeometryChange.assert_called_once() - - def test_set_scale_ignores_zero(self): - item = BeePixmapItem(QtGui.QImage()) - item.setScale(0) - assert item.scale() == 1 - - def test_set_scale_ignores_negative(self): - item = BeePixmapItem(QtGui.QImage()) - item.setScale(-0.1) - assert item.scale() == 1 - - def test_set_scale_with_anchor(self): - item = BeePixmapItem(QtGui.QImage()) - item.setScale(2, anchor=QtCore.QPointF(100, 100)) - assert item.scale() == 2 - assert item.pos() == QtCore.QPointF(-100, -100) - - def test_set_zvalue_sets_new_max(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setZValue(1.1) - assert item.zValue() == 1.1 - assert self.scene.max_z == 1.1 - - def test_set_zvalue_keeps_old_max(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - self.scene.max_z = 3.3 - item.setZValue(1.1) - assert item.zValue() == 1.1 - assert self.scene.max_z == 3.3 - - def test_bring_to_front(self): - item1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item1) - item1.setZValue(3.3) - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.bring_to_front() - assert item2.zValue() > item1.zValue() - assert item2.zValue() == self.scene.max_z - - def test_set_rotation_no_anchor(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(45) - assert item.rotation() == 45 - assert item.pos().x() == 0 - assert item.pos().y() == 0 - - def test_set_rotation_anchor_bottomright_cw(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(90, QtCore.QPointF(100, 100)) - assert item.rotation() == 90 - assert item.pos().x() == 200 - assert item.pos().y() == 0 - - def test_set_rotation_anchor_bottomright_ccw(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(-90, QtCore.QPointF(100, 100)) - assert item.rotation() == 270 - assert item.pos().x() == 0 - assert item.pos().y() == 200 - - def test_set_rotation_caps_above_360(self): - item = BeePixmapItem(QtGui.QImage()) - item.setRotation(400) - assert item.rotation() == 40 - - def test_flip(self): - item = BeePixmapItem(QtGui.QImage()) - assert item.flip() == 1 - - def test_flip_when_flipped(self): - item = BeePixmapItem(QtGui.QImage()) - item.do_flip() - assert item.flip() == -1 - - def test_do_flip_no_anchor(self): - item = BeePixmapItem(QtGui.QImage()) - item.do_flip() - assert item.flip() == -1 - item.do_flip() - assert item.flip() == 1 - - def test_do_flip_vertical(self): - item = BeePixmapItem(QtGui.QImage()) - item.do_flip(vertical=True) - assert item.flip() == -1 - assert item.rotation() == 180 - - def test_do_flip_anchor(self): - item = BeePixmapItem(QtGui.QImage()) - item.do_flip(anchor=QtCore.QPointF(100, 100)) - assert item.flip() == -1 - assert item.pos() == QtCore.QPointF(200, 0) - - def test_do_flip_vertical_anchor(self): - item = BeePixmapItem(QtGui.QImage()) - item.do_flip(vertical=True, anchor=QtCore.QPointF(100, 100)) - assert item.flip() == -1 - assert item.rotation() == 180 - assert item.pos() == QtCore.QPointF(0, 200) - - def test_center_scene_coords(self): - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setPos(5, 5) - item.setScale(2) - with patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, return_value=100): - with patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, return_value=80): - assert item.center_scene_coords == QtCore.QPointF(105, 85) - - -class SelectableMixinBaseTestCase(BeeTestCase): - - def setUp(self): - self.scene = BeeGraphicsScene(None) - self.item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(self.item) - self.view = MagicMock(get_scale=MagicMock(return_value=1)) - views_patcher = patch('beeref.scene.BeeGraphicsScene.views', - return_value=[self.view]) - views_patcher.start() - self.addCleanup(views_patcher.stop) - width_patcher = patch('beeref.items.BeePixmapItem.width', - new_callable=PropertyMock, - return_value=100) - width_patcher.start() - self.addCleanup(width_patcher.stop) - height_patcher = patch('beeref.items.BeePixmapItem.height', - new_callable=PropertyMock, - return_value=80) - height_patcher.start() - self.addCleanup(height_patcher.stop) - - -class SelectableMixinTestCase(SelectableMixinBaseTestCase): - - def test_init_selectable(self): - item = BeePixmapItem(QtGui.QImage()) - assert item.viewport_scale == 1 - assert item.scale_active is False - assert item.rotate_active is False - assert item.flip_active is False - assert item.just_selected is False - - def test_is_action_active_when_no_action(self): - item = BeePixmapItem(QtGui.QImage()) - assert item.is_action_active() is False - - def test_is_action_active_when_action(self): - item = BeePixmapItem(QtGui.QImage()) - item.scale_active = True - assert item.is_action_active() is True - - def test_on_view_scale_change(self): - item = BeePixmapItem(QtGui.QImage()) - with patch('beeref.items.BeePixmapItem.prepareGeometryChange') as m: - item.on_view_scale_change() - m.assert_called_once() - - def test_fixed_length_for_viewport_when_default_scales(self): - self.view.get_scale.return_value = 1 - assert self.item.fixed_length_for_viewport(100) == 100 - assert self.item._view_scale == 1 - - def test_fixed_length_for_viewport_when_viewport_scaled(self): - self.view.get_scale.return_value = 2 - assert self.item.fixed_length_for_viewport(100) == 50 - assert self.item._view_scale == 2 - - def test_fixed_length_for_viewport_when_item_scaled(self): - self.view.get_scale.return_value = 1 - self.item.setScale(5) - assert self.item.fixed_length_for_viewport(100) == 20 - assert self.item._view_scale == 1 - - def test_fixed_length_for_viewport_when_no_scene(self): - item = BeePixmapItem(QtGui.QImage()) - item._view_scale = 0.5 - assert item.fixed_length_for_viewport(100) == 200 - - def test_resize_size_when_scaled(self): - self.view.get_scale.return_value = 2 - self.item.setScale(2) - self.item.SELECT_RESIZE_SIZE = 100 - assert self.item.select_resize_size == 25 - - def test_rotate_size_when_scaled(self): - self.view.get_scale.return_value = 2 - self.item.setScale(2) - self.item.SELECT_ROTATE_SIZE = 100 - assert self.item.select_rotate_size == 25 - - def test_draw_debug_shape_rect(self): - painter = MagicMock() - self.item.draw_debug_shape( - painter, - QtCore.QRectF(5, 6, 20, 30), - 255, 0, 0) - painter.fillRect.assert_called_once() - painter.fillPath.assert_not_called() - - def test_draw_debug_shape_path(self): - painter = MagicMock() - path = QtGui.QPainterPath() - path.addRect(QtCore.QRectF(5, 6, 20, 30)) - self.item.draw_debug_shape( - painter, - path, - 0, 255, 0) - painter.fillPath.assert_called_once() - painter.fillRect.assert_not_called() - - @patch('beeref.items.BeePixmapItem.draw_debug_shape') - def test_paint_when_not_selected(self, debug_mock): - painter = MagicMock() - self.item.setSelected(False) - self.item.pixmap = MagicMock(return_value='pixmap') - self.item.paint(painter, None, None) - painter.drawPixmap.assert_called_once_with(0, 0, 'pixmap') - painter.drawRect.assert_not_called() - painter.drawPoint.assert_not_called() - debug_mock.assert_not_called() - - def test_paint_when_selected_single_selection(self): - painter = MagicMock() - self.item.setSelected(True) - self.item.paint(painter, None, None) - painter.drawPixmap.assert_called_once() - painter.drawRect.assert_called_once() - assert painter.drawPoint.call_count == 4 - - def test_paint_when_selected_multi_selection(self): - item2 = BeePixmapItem(QtGui.QImage()) - item2.setSelected(True) - self.scene.addItem(item2) - painter = MagicMock() - self.item.setSelected(True) - self.item.paint(painter, None, None) - painter.drawPixmap.assert_called_once() - painter.drawRect.assert_called_once() - painter.drawPoint.assert_not_called() - - def test_paint_when_debug_shapes(self): - with patch('beeref.selection.commandline_args') as args_mock: - with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: - args_mock.debug_shapes = True - args_mock.debug_boundingrects = False - args_mock.debug_handles = False - item = BeePixmapItem(QtGui.QImage()) - item.paint(MagicMock(), None, None) - m.assert_called_once() - - def test_paint_when_debug_boundingrects(self): - with patch('beeref.selection.commandline_args') as args_mock: - with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: - args_mock.debug_shapes = False - args_mock.debug_boundingrects = True - args_mock.debug_handles = False - item = BeePixmapItem(QtGui.QImage()) - item.paint(MagicMock(), None, None) - m.assert_called_once() - - def test_paint_when_debug_handles(self): - with patch('beeref.selection.commandline_args') as args_mock: - with patch('beeref.items.BeePixmapItem.draw_debug_shape') as m: - args_mock.debug_shapes = False - args_mock.debug_boundingrects = False - args_mock.debug_handles = True - item = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item) - item.setSelected(True) - item.paint(MagicMock(), None, None) - m.assert_called() - - def test_corners(self): - assert len(self.item.corners) == 4 - assert QtCore.QPointF(0, 0) in self.item.corners - assert QtCore.QPointF(100, 0) in self.item.corners - assert QtCore.QPointF(0, 80) in self.item.corners - assert QtCore.QPointF(100, 80) in self.item.corners - - def test_corners_scene_coords_translated(self): - self.item.setPos(5, 5) - corners = self.item.corners_scene_coords - assert len(self.item.corners) == 4 - assert QtCore.QPointF(5, 5) in corners - assert QtCore.QPointF(105, 5) in corners - assert QtCore.QPointF(5, 85) in corners - assert QtCore.QPointF(105, 85) in corners - - def test_corners_scene_coords_scaled(self): - self.item.setScale(2) - corners = self.item.corners_scene_coords - assert len(self.item.corners) == 4 - assert QtCore.QPointF(0, 0) in corners - assert QtCore.QPointF(200, 0) in corners - assert QtCore.QPointF(0, 160) in corners - assert QtCore.QPointF(200, 160) in corners - - def test_get_scale_bounds(self): - self.view.get_scale.return_value = 1 - self.item.SELECT_RESIZE_SIZE = 10 - rect = self.item.get_scale_bounds( - QtCore.QPointF(100, 80)).boundingRect() - assert rect.topLeft().x() == 95 - assert rect.topLeft().y() == 75 - assert rect.bottomRight().x() == 105 - assert rect.bottomRight().y() == 85 - - def test_get_scale_bounds_with_margin(self): - self.view.get_scale.return_value = 1 - self.item.SELECT_RESIZE_SIZE = 10 - rect = self.item.get_scale_bounds( - QtCore.QPointF(100, 80), margin=1).boundingRect() - assert rect.topLeft().x() == 94 - assert rect.topLeft().y() == 74 - assert rect.bottomRight().x() == 106 - assert rect.bottomRight().y() == 86 - - def test_rotate_bounds_bottomright(self): - self.view.get_scale.return_value = 1 - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - path = self.item.get_rotate_bounds(QtCore.QPointF(100, 80)) - assert path.boundingRect().topLeft().x() == 95 - assert path.boundingRect().topLeft().y() == 75 - assert path.boundingRect().bottomRight().x() == 115 - assert path.boundingRect().bottomRight().y() == 95 - assert path.contains(QtCore.QPointF(104, 84)) is False - - def test_rotate_bounds_topleft(self): - self.view.get_scale.return_value = 1 - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - path = self.item.get_rotate_bounds(QtCore.QPointF(0, 0)) - assert path.boundingRect().topLeft().x() == -15 - assert path.boundingRect().topLeft().y() == -15 - assert path.boundingRect().bottomRight().x() == 5 - assert path.boundingRect().bottomRight().y() == 5 - assert path.contains(QtCore.QPointF(-4, -4)) is False - - def test_get_flip_bounds(self): - self.view.get_scale.return_value = 1 - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - edges = self.item.get_flip_bounds() - assert edges[0]['rect'].topLeft() == QtCore.QPointF(5, -15) - assert edges[0]['rect'].bottomRight() == QtCore.QPointF(95, 5) - assert edges[0]['flip_v'] is True - assert edges[1]['rect'].topLeft() == QtCore.QPointF(5, 75) - assert edges[1]['rect'].bottomRight() == QtCore.QPointF(95, 95) - assert edges[1]['flip_v'] is True - assert edges[2]['rect'].topLeft() == QtCore.QPointF(-15, 5) - assert edges[2]['rect'].bottomRight() == QtCore.QPointF(5, 75) - assert edges[2]['flip_v'] is False - assert edges[3]['rect'].topLeft() == QtCore.QPointF(95, 5) - assert edges[3]['rect'].bottomRight() == QtCore.QPointF(115, 75) - assert edges[3]['flip_v'] is False - - def test_bounding_rect_when_not_selected(self): - self.view.get_scale.return_value = 1 - self.item.setSelected(False) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', - return_value=QtCore.QRectF(0, 0, 100, 80)): - rect = self.item.boundingRect() - assert rect.topLeft().x() == 0 - assert rect.topLeft().y() == 0 - assert rect.bottomRight().x() == 100 - assert rect.bottomRight().y() == 80 - - def test_bounding_rect_when_selected(self): - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - self.view.get_scale.return_value = 1 - self.item.setSelected(True) - rect = self.item.boundingRect() - assert rect.topLeft().x() == -15 - assert rect.topLeft().y() == -15 - assert rect.bottomRight().x() == 115 - assert rect.bottomRight().y() == 95 - - def test_shape_when_not_selected(self): - self.view.get_scale.return_value = 1 - self.item.setSelected(False) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', - return_value=QtCore.QRectF(0, 0, 100, 80)): - shape = self.item.shape().boundingRect() - assert shape.topLeft().x() == 0 - assert shape.topLeft().y() == 0 - assert shape.bottomRight().x() == 100 - assert shape.bottomRight().y() == 80 - - def test_shape_when_selected_single(self): - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - self.view.get_scale.return_value = 1 - self.item.setSelected(True) - path = QtGui.QPainterPath() - path.addRect(QtCore.QRectF(0, 0, 100, 80)) - - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.shape', - return_value=path): - shape = self.item.shape().boundingRect() - assert shape.topLeft().x() == -15 - assert shape.topLeft().y() == -15 - assert shape.bottomRight().x() == 115 - assert shape.bottomRight().y() == 95 - - def test_shape_when_selected_multi(self): - item2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(item2) - item2.setSelected(True) - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - self.view.get_scale.return_value = 1 - self.item.setSelected(True) - - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.boundingRect', - return_value=QtCore.QRectF(0, 0, 100, 80)): - shape = self.item.shape().boundingRect() - assert shape.topLeft().x() == 0 - assert shape.topLeft().y() == 0 - assert shape.bottomRight().x() == 100 - assert shape.bottomRight().y() == 80 - - -class SelectableMixinCalculationsTestCase(SelectableMixinBaseTestCase): - - def test_get_scale_factor_bottomright(self): - self.item.event_start = QtCore.QPointF(10, 10) - self.item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) - self.item.scale_orig_factor = 1 - event = MagicMock() - event.scenePos.return_value = QtCore.QPointF(20, 90) - assert self.item.get_scale_factor(event) == approx(1.5, 0.01) - - def test_get_scale_factor_topleft(self): - self.item.event_start = QtCore.QPointF(10, 10) - self.item.event_direction = QtCore.QPointF(-1, -1) / math.sqrt(2) - self.item.scale_orig_factor = 0.5 - event = MagicMock() - event.scenePos.return_value = QtCore.QPointF(-10, -60) - assert self.item.get_scale_factor(event) == approx(2, 0.01) - - def test_get_scale_anchor_topleft(self): - anchor = self.item.get_scale_anchor(QtCore.QPointF(0, 0)) - assert anchor.x() == 100 - assert anchor.y() == 80 - - def test_get_scale_anchor_bottomright(self): - anchor = self.item.get_scale_anchor(QtCore.QPointF(100, 80)) - assert anchor.x() == 0 - assert anchor.y() == 0 - - def test_get_scale_anchor_topright(self): - anchor = self.item.get_scale_anchor(QtCore.QPointF(100, 0)) - assert anchor.x() == 0 - assert anchor.y() == 80 - - def test_get_scale_anchor_bottomleft(self): - anchor = self.item.get_scale_anchor(QtCore.QPointF(0, 80)) - assert anchor.x() == 100 - assert anchor.y() == 0 - - def test_get_corner_direction_topleft(self): - assert self.item.get_corner_direction( - QtCore.QPointF(0, 0)) == QtCore.QPointF(-1, -1) - - def test_get_corner_direction_bottomright(self): - assert self.item.get_corner_direction( - QtCore.QPointF(100, 80)) == QtCore.QPointF(1, 1) - - def test_get_corner_direction_topright(self): - assert self.item.get_corner_direction( - QtCore.QPointF(100, 0)) == QtCore.QPointF(1, -1) - - def test_get_corner_direction_bottomleft(self): - assert self.item.get_corner_direction( - QtCore.QPointF(0, 80)) == QtCore.QPointF(-1, 1) - - def test_get_direction_from_center_bottomright(self): - direction = self.item.get_direction_from_center( - QtCore.QPointF(100, 90)) - assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2)) - - def test_get_direction_from_center_topleft(self): - direction = self.item.get_direction_from_center( - QtCore.QPointF(0, -10)) - assert direction == approx(QtCore.QPointF(-1, -1) / math.sqrt(2)) - - def test_get_direction_from_center_bottomright_when_rotated_180(self): - self.item.setRotation(180, QtCore.QPointF(50, 40)) - direction = self.item.get_direction_from_center( - QtCore.QPointF(100, 90)) - assert direction == approx(QtCore.QPointF(1, 1) / math.sqrt(2)) - - def test_get_rotate_angle(self): - self.item.event_anchor = QtCore.QPointF(10, 20) - assert self.item.get_rotate_angle(QtCore.QPointF(15, 25)) == -45 - - def test_get_rotate_delta(self): - self.item.event_anchor = QtCore.QPointF(10, 20) - self.item.rotate_start_angle = -3 - assert self.item.get_rotate_delta(QtCore.QPointF(15, 25)) == -42 - - def test_get_rotate_delta_snaps(self): - self.item.event_anchor = QtCore.QPointF(10, 20) - self.item.rotate_start_angle = -3 - self.item.rotate_orig_degrees = 5 - assert self.item.get_rotate_delta( - QtCore.QPointF(15, 25), snap=True) == -35 - - def test_edge_flips_v_when_item_horizontal(self): - self.item.setRotation(20) - assert self.item.get_edge_flips_v({'flip_v': True}) is True - - def test_edge_flips_v_when_item_horizontal_upside_down(self): - self.item.setRotation(200) - assert self.item.get_edge_flips_v({'flip_v': True}) is True - - def test_edge_flips_v_when_item_vertical_cw(self): - self.item.setRotation(80) - assert self.item.get_edge_flips_v({'flip_v': True}) is False - - def test_edge_flips_v_when_item_vertical_ccw(self): - self.item.setRotation(120) - assert self.item.get_edge_flips_v({'flip_v': True}) is False - - -class SelectableMixinMouseEventsTestCase(SelectableMixinBaseTestCase): - - def setUp(self): - super().setUp() - self.event = MagicMock() - self.item.setCursor = MagicMock() - self.item.SELECT_RESIZE_SIZE = 10 - self.item.SELECT_ROTATE_SIZE = 10 - - def test_hover_move_event_no_selection(self): - self.event.pos.return_value = QtCore.QPointF(0, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_not_called() - - def test_hover_move_event_topleft_scale(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeFDiagCursor) - - def test_hover_move_event_bottomright_scale(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(100, 80) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeFDiagCursor) - - def test_hover_move_event_topright_scale(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(100, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeBDiagCursor) - - def test_hover_move_event_topright_scale_rotated_90(self): - self.item.setRotation(90) - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeBDiagCursor) - - def test_hover_move_event_top_scale_rotated_45(self): - self.item.setRotation(45) - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeVerCursor) - - def test_hover_move_event_left_scale_rotated_45(self): - self.item.setRotation(45) - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 80) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.SizeHorCursor) - - def test_hover_move_event_rotate(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(110, 90) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with(BeeAssets().cursor_rotate) - - def test_hover_flip_event_top_edge(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(50, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_v) - - def test_hover_flip_event_bottom_edge(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(50, 80) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_v) - - def test_hover_flip_event_left_edge(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 50) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_h) - - def test_hover_flip_event_right_edge(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(100, 50) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_h) - - def test_hover_flip_event_top_edge_rotated_90(self): - self.item.setSelected(True) - self.item.setRotation(90) - self.event.pos.return_value = QtCore.QPointF(50, 0) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_h) - - def test_hover_flip_event_left_edge_when_rotated_90(self): - self.item.setSelected(True) - self.item.setRotation(90) - self.event.pos.return_value = QtCore.QPointF(0, 50) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - BeeAssets().cursor_flip_v) - - def test_hover_move_event_not_in_handles(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(50, 50) - self.item.hoverMoveEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.ArrowCursor) - - def test_hover_enter_event_when_selected(self): - self.item.setSelected(True) - self.item.hoverEnterEvent(self.event) - self.item.setCursor.assert_not_called() - - def test_hover_enter_event_when_not_selected(self): - self.item.setSelected(False) - self.item.hoverEnterEvent(self.event) - self.item.setCursor.assert_called_once_with( - Qt.CursorShape.ArrowCursor) - - def test_mouse_press_event_just_selected(self): - self.event.pos.return_value = QtCore.QPointF(-100, -100) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): - self.item.mousePressEvent(self.event) - assert self.item.just_selected is True - - def test_mouse_press_event_previously_selected(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(-100, -100) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): - self.item.mousePressEvent(self.event) - assert self.item.just_selected is False - - def test_mouse_press_event_topleft_scale(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(2, 2) - self.event.scenePos.return_value = QtCore.QPointF(-1, -1) - self.event.button.return_value = Qt.MouseButton.LeftButton - self.item.mousePressEvent(self.event) - assert self.item.scale_active is True - assert self.item.event_start == QtCore.QPointF(-1, -1) - assert self.item.event_direction.x() < 0 - assert self.item.event_direction.y() < 0 - assert self.item.scale_orig_factor == 1 - - def test_mouse_press_event_bottomright_scale(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(99, 79) - self.event.scenePos.return_value = QtCore.QPointF(101, 81) - self.event.button.return_value = Qt.MouseButton.LeftButton - self.item.mousePressEvent(self.event) - assert self.item.scale_active is True - assert self.item.event_start == QtCore.QPointF(101, 81) - assert self.item.event_direction.x() > 0 - assert self.item.event_direction.y() > 0 - assert self.item.scale_orig_factor == 1 - - def test_mouse_press_event_rotate(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(111, 91) - self.event.scenePos.return_value = QtCore.QPointF(66, 99) - self.event.button.return_value = Qt.MouseButton.LeftButton - self.item.mousePressEvent(self.event) - assert self.item.rotate_active is True - assert self.item.event_anchor == QtCore.QPointF(50, 40) - assert self.item.rotate_orig_degrees == 0 - - def test_mouse_press_event_flip(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(0, 40) - self.event.button.return_value = Qt.MouseButton.LeftButton - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent'): - self.item.mousePressEvent(self.event) - assert self.item.flip_active is True - - def test_mouse_press_event_not_selected(self): - self.item.setSelected(False) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m: - self.item.mousePressEvent(self.event) - m.assert_called_once_with(self.event) - assert self.item.scale_active is False - assert self.item.rotate_active is False - assert self.item.flip_active is False - - def test_mouse_press_event_not_in_handles(self): - self.item.setSelected(True) - self.event.pos.return_value = QtCore.QPointF(50, 40) - self.event.button.return_value = Qt.MouseButton.LeftButton - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mousePressEvent') as m: - self.item.mousePressEvent(self.event) - m.assert_called_once_with(self.event) - assert self.item.scale_active is False - assert self.item.rotate_active is False - assert self.item.flip_active is False - - def test_mouse_move_event_when_no_action_reset_prev_transform(self): - self.item.event_start = QtCore.QPointF(10, 10) - self.event.scenePos.return_value = QtCore.QPointF(50, 40) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: - self.item.mouseMoveEvent(self.event) - m.assert_called_once_with(self.event) - self.view.reset_previous_transform.assert_called_once() - - def test_mouse_move_event_when_no_action_doesnt_reset_prev_transf(self): - self.item.event_start = QtCore.QPointF(10, 10) - self.event.scenePos.return_value = QtCore.QPointF(11, 11) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: - self.item.mouseMoveEvent(self.event) - m.assert_called_once_with(self.event) - self.view.reset_previous_transform.assert_not_called() - - def test_mouse_move_event_when_scale_action(self): - self.event.scenePos.return_value = QtCore.QPointF(20, 90) - self.item.scale_active = True - self.item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) - self.item.event_anchor = QtCore.QPointF(100, 80) - self.item.event_start = QtCore.QPointF(10, 10) - self.item.scale_orig_factor = 1 - - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: - self.item.mouseMoveEvent(self.event) - m.assert_not_called() - assert self.item.scale() == approx(1.5, 0.01) - - def test_mouse_move_event_when_rotate_action(self): - self.event.scenePos.return_value = QtCore.QPointF(15, 25) - self.item.event_start = QtCore.QPointF(10, 10) - self.item.rotate_active = True - self.item.rotate_orig_degrees = 0 - self.item.rotate_start_angle = -3 - self.item.event_anchor = QtCore.QPointF(10, 20) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: - self.item.mouseMoveEvent(self.event) - m.assert_not_called() - assert self.item.rotation() == 318 - - def test_mouse_move_event_when_flip_action(self): - self.event.scenePos.return_value = QtCore.QPointF(15, 25) - self.item.event_start = QtCore.QPointF(10, 10) - self.item.flip_active = True - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem.mouseMoveEvent') as m: - self.item.mouseMoveEvent(self.event) - m.assert_not_called() - - def test_mouse_release_event_when_no_action(self): - self.item.flip_active = True - self.event.pos.return_value = QtCore.QPointF(-100, -100) - with patch('PyQt6.QtWidgets.QGraphicsPixmapItem' - '.mouseReleaseEvent') as m: - self.item.mouseReleaseEvent(self.event) - m.assert_called_once_with(self.event) - self.item.flip_active is False - - def test_mouse_release_event_when_scale_action(self): - self.event.scenePos.return_value = QtCore.QPointF(20, 90) - self.item.scale_active = True - self.item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) - self.item.event_anchor = QtCore.QPointF(100, 80) - self.item.event_start = QtCore.QPointF(10, 10) - self.item.scale_orig_factor = 1 - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.item.mouseReleaseEvent(self.event) - self.scene.undo_stack.push.assert_called_once() - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - isinstance(cmd, commands.ScaleItemsBy) - assert cmd.items == [self.item] - assert cmd.factor == approx(1.5, 0.01) - assert cmd.anchor == QtCore.QPointF(100, 80) - assert cmd.ignore_first_redo is True - assert self.item.scale_active is False - - def test_mouse_release_event_when_scale_action_zero(self): - self.event.scenePos.return_value = QtCore.QPointF(20, 90) - self.item.scale_active = True - self.item.event_direction = QtCore.QPointF(1, 1) / math.sqrt(2) - self.item.event_anchor = QtCore.QPointF(100, 80) - self.item.event_start = QtCore.QPointF(20, 90) - self.item.scale_orig_factor = 1 - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.item.mouseReleaseEvent(self.event) - self.scene.undo_stack.push.assert_not_called() - assert self.item.scale_active is False - - def test_mouse_release_event_when_rotate_action(self): - self.event.scenePos.return_value = QtCore.QPointF(15, 25) - self.item.rotate_active = True - self.item.rotate_orig_degrees = 0 - self.item.rotate_start_angle = -3 - self.item.event_anchor = QtCore.QPointF(10, 20) - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.item.mouseReleaseEvent(self.event) - self.scene.undo_stack.push.assert_called_once() - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - isinstance(cmd, commands.RotateItemsBy) - assert cmd.items == [self.item] - assert cmd.delta == -42 - assert cmd.anchor == QtCore.QPointF(10, 20) - assert cmd.ignore_first_redo is True - assert self.item.rotate_active is False - - def test_mouse_release_event_when_rotate_action_zero(self): - self.event.scenePos.return_value = QtCore.QPointF(15, 25) - self.item.rotate_active = True - self.item.rotate_orig_degrees = 0 - self.item.rotate_start_angle = -45 - self.item.event_anchor = QtCore.QPointF(10, 20) - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.item.mouseReleaseEvent(self.event) - self.scene.undo_stack.push.assert_not_called() - assert self.item.rotate_active is False - - def test_mouse_release_event_when_flip_action(self): - self.event.pos.return_value = QtCore.QPointF(0, 40) - self.item.flip_active = True - self.scene.undo_stack = MagicMock(push=MagicMock()) - - self.item.mouseReleaseEvent(self.event) - args = self.scene.undo_stack.push.call_args_list[0][0] - cmd = args[0] - isinstance(cmd, commands.FlipItems) - assert cmd.items == [self.item] - assert cmd.anchor == QtCore.QPointF(50, 40) - assert cmd.vertical is False - assert self.item.flip_active is False - - -class MultiSelectItemTestCase(BeeTestCase): - - def setUp(self): - self.scene = BeeGraphicsScene(None) - - @patch('beeref.selection.SelectableMixin.init_selectable') - def test_init(self, selectable_mock): - item = MultiSelectItem() - assert hasattr(item, 'save_id') is False - selectable_mock.assert_called_once() - - def test_width(self): - item = MultiSelectItem() - item.setRect(0, 0, 50, 100) - assert item.width == 50 - - def test_height(self): - item = MultiSelectItem() - item.setRect(0, 0, 50, 100) - assert item.height == 100 - - def test_paint(self): - item = MultiSelectItem() - item.paint_selectable = MagicMock() - painter = MagicMock() - item.paint(painter, None, None) - item.paint_selectable.assert_called_once() - painter.drawRect.assert_not_called() - - def test_has_selection_outline(self): - item = MultiSelectItem() - item.has_selection_outline() is True - - def test_has_selection_handles(self): - item = MultiSelectItem() - item.has_selection_handles() is True - - def test_selection_action_items(self): - view = MagicMock(get_scale=MagicMock(return_value=1)) - with patch('beeref.scene.BeeGraphicsScene.views', - return_value=[view]): - img1 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(img1) - img1.setSelected(True) - img2 = BeePixmapItem(QtGui.QImage()) - self.scene.addItem(img2) - img2.setSelected(False) - item = MultiSelectItem() - self.scene.addItem(item) - item.setSelected(True) - action_items = item.selection_action_items() - assert img1 in action_items - assert item in action_items - assert img2 not in action_items - - def test_fit_selection_area(self): - item = MultiSelectItem() - item.setScale(5) - item.setRotation(-20) - item.do_flip() - item.fit_selection_area(QtCore.QRectF(-10, -20, 100, 80)) - assert item.pos().x() == -10 - assert item.pos().y() == -20 - assert item.width == 100 - assert item.height == 80 - assert item.scale() == 1 - assert item.rotation() == 0 - assert item.flip() == 1 - assert item.isSelected() is True - - @patch('PyQt6.QtWidgets.QGraphicsRectItem.mousePressEvent') - def test_mouse_press_event_when_ctrl_leftclick(self, mouse_mock): - item = MultiSelectItem() - item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80)) - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.LeftButton), - modifiers=MagicMock( - return_value=Qt.KeyboardModifier.ControlModifier)) - item.mousePressEvent(event) - event.ignore.assert_called_once() - mouse_mock.assert_not_called() - - @patch('beeref.selection.SelectableMixin.mousePressEvent') - def test_mouse_press_event_when_leftclick(self, mouse_mock): - item = MultiSelectItem() - item.fit_selection_area(QtCore.QRectF(0, 0, 100, 80)) - event = MagicMock( - button=MagicMock(return_value=Qt.MouseButton.LeftButton)) - item.mousePressEvent(event) - event.ignore.assert_not_called() - mouse_mock.assert_called_once_with(event) - - -class RubberbandItemTestCase(BeeTestCase): - - def setUp(self): - self.scene = BeeGraphicsScene(None) - - def test_width(self): - item = RubberbandItem() - item.setRect(5, 5, 100, 80) - assert item.width == 100 - - def test_height(self): - item = RubberbandItem() - item.setRect(5, 5, 100, 80) - assert item.height == 80 - - def test_fit_topleft_to_bottomright(self): - item = RubberbandItem() - item.fit(QtCore.QPointF(-10, -20), QtCore.QPointF(30, 40)) - assert item.rect().topLeft().x() == -10 - assert item.rect().topLeft().y() == -20 - assert item.rect().bottomRight().x() == 30 - assert item.rect().bottomRight().y() == 40 - - def test_fit_topright_to_bottomleft(self): - item = RubberbandItem() - item.fit(QtCore.QPointF(50, -20), QtCore.QPointF(-30, 40)) - assert item.rect().topLeft().x() == -30 - assert item.rect().topLeft().y() == -20 - assert item.rect().bottomRight().x() == 50 - assert item.rect().bottomRight().y() == 40 diff --git a/tests/test_utils.py b/tests/test_utils.py index 1989fc7..48365a5 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,31 +1,28 @@ import logging import os.path import pytest -import tempfile from PyQt6 import QtCore from beeref import utils -from .base import BeeTestCase -class GetRectFromPointsTestCase(BeeTestCase): +def test_get_rect_from_points_given_topleft_bottomright(): + rect = utils.get_rect_from_points(QtCore.QPointF(-10, -20), + QtCore.QPointF(30, 40)) + assert rect.topLeft().x() == -10 + assert rect.topLeft().y() == -20 + assert rect.bottomRight().x() == 30 + assert rect.bottomRight().y() == 40 - def test_given_topleft_bottomright(self): - rect = utils.get_rect_from_points(QtCore.QPointF(-10, -20), - QtCore.QPointF(30, 40)) - assert rect.topLeft().x() == -10 - assert rect.topLeft().y() == -20 - assert rect.bottomRight().x() == 30 - assert rect.bottomRight().y() == 40 - def test_given_topright_bottomleft(self): - rect = utils.get_rect_from_points(QtCore.QPointF(50, -20), - QtCore.QPointF(-30, 40)) - assert rect.topLeft().x() == -30 - assert rect.topLeft().y() == -20 - assert rect.bottomRight().x() == 50 - assert rect.bottomRight().y() == 40 +def test_get_rect_from_points_given_topright_bottomleft(): + rect = utils.get_rect_from_points(QtCore.QPointF(50, -20), + QtCore.QPointF(-30, 40)) + assert rect.topLeft().x() == -30 + assert rect.topLeft().y() == -20 + assert rect.bottomRight().x() == 50 + assert rect.bottomRight().y() == 40 @pytest.mark.parametrize('number,base,expected', @@ -37,22 +34,19 @@ def test_round_to(number, base, expected): assert utils.round_to(number, base) == expected -class BeeRotatingFileHandlerTestCase(BeeTestCase): +def test_rotating_file_handler_creates_new_dir(tmpdir): + logfile = os.path.join(tmpdir, 'foo', 'bar.log') + handler = utils.BeeRotatingFileHandler(logfile) + handler.emit(logging.LogRecord( + 'foo', logging.INFO, 'bar', 66, 'baz', [], None)) + handler.close() + assert os.path.exists(logfile) - def test_creates_new_dir(self): - with tempfile.TemporaryDirectory() as tmpdir: - logfile = os.path.join(tmpdir, 'foo', 'bar.log') - handler = utils.BeeRotatingFileHandler(logfile) - handler.emit(logging.LogRecord( - 'foo', logging.INFO, 'bar', 66, 'baz', [], None)) - handler.close() - assert os.path.exists(logfile) - def test_uses_existing_dir(self): - with tempfile.TemporaryDirectory() as tmpdir: - logfile = os.path.join(tmpdir, 'bar.log') - handler = utils.BeeRotatingFileHandler(logfile) - handler.emit(logging.LogRecord( - 'foo', logging.INFO, 'bar', 66, 'baz', [], None)) - handler.close() - assert os.path.exists(logfile) +def testrotating_file_handler_uses_existing_dir(tmpdir): + logfile = os.path.join(tmpdir, 'bar.log') + handler = utils.BeeRotatingFileHandler(logfile) + handler.emit(logging.LogRecord( + 'foo', logging.INFO, 'bar', 66, 'baz', [], None)) + handler.close() + assert os.path.exists(logfile) diff --git a/tests/test_view.py b/tests/test_view.py index ffab5b4..e9d5939 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -1,669 +1,703 @@ import os.path -import tempfile +import sqlite3 from unittest.mock import MagicMock, patch, mock_open -from pytest import mark - from PyQt6 import QtCore, QtGui, QtWidgets from PyQt6.QtCore import Qt from beeref.config import logfile_name from beeref.items import BeePixmapItem -from beeref import fileio from beeref.view import BeeGraphicsView -from .base import BeeTestCase -class ViewBaseTestCase(BeeTestCase): - - def setUp(self): - config_patcher = patch('beeref.view.commandline_args') - self.config_mock = config_patcher.start() - self.config_mock.filename = None - self.addCleanup(config_patcher.stop) - self.parent = QtWidgets.QMainWindow() - self.view = BeeGraphicsView(self.app, self.parent) +def test_inits_menu(view, qapp): + parent = QtWidgets.QMainWindow() + view = BeeGraphicsView(qapp, parent) + assert isinstance(view.context_menu, QtWidgets.QMenu) + assert len(view.actions()) > 0 + assert view.bee_actions + assert view.bee_actiongroups -class BeeGraphicsViewTestCase(ViewBaseTestCase): +@patch('beeref.view.BeeGraphicsView.open_from_file') +def test_init_without_filename(open_file_mock, qapp, commandline_args): + commandline_args.filename = None + parent = QtWidgets.QMainWindow() + view = BeeGraphicsView(qapp, parent) + open_file_mock.assert_not_called() + assert view.parent.windowTitle() == 'BeeRef' + del view - def setUp(self): - config_patcher = patch('beeref.view.commandline_args') - self.config_mock = config_patcher.start() - self.config_mock.filename = None - self.addCleanup(config_patcher.stop) - self.parent = QtWidgets.QMainWindow() - self.view = BeeGraphicsView(self.app, self.parent) - def test_inits_menu(self): - parent = QtWidgets.QMainWindow() - view = BeeGraphicsView(self.app, parent) - assert isinstance(view.context_menu, QtWidgets.QMenu) - assert len(view.actions()) > 0 - assert view.bee_actions - assert view.bee_actiongroups +@patch('beeref.view.BeeGraphicsView.open_from_file') +def test_init_with_filename(open_file_mock, view, qapp, commandline_args): + commandline_args.filename = 'test.bee' + parent = QtWidgets.QMainWindow() + view = BeeGraphicsView(qapp, parent) + open_file_mock.assert_called_once_with('test.bee') + del view - @patch('beeref.view.BeeGraphicsView.open_from_file') - def test_init_without_filename(self, open_file_mock): - self.config_mock.filename = None - parent = QtWidgets.QMainWindow() - view = BeeGraphicsView(self.app, parent) - open_file_mock.assert_not_called() - assert parent.windowTitle() == 'BeeRef' - del view - @patch('beeref.view.BeeGraphicsView.open_from_file') - def test_init_with_filename(self, open_file_mock): - self.config_mock.filename = 'test.bee' - parent = QtWidgets.QMainWindow() - view = BeeGraphicsView(self.app, parent) - open_file_mock.assert_called_once_with('test.bee') - del view +@patch('beeref.gui.WelcomeOverlay.hide') +def test_on_scene_changed_when_items(hide_mock, view): + item = BeePixmapItem(QtGui.QImage()) + view.scene.addItem(item) + view.scale(2, 2) + with patch('beeref.view.BeeGraphicsView.recalc_scene_rect') as r: + view.on_scene_changed(None) + r.assert_called_once_with() + hide_mock.assert_called_once_with() + assert view.get_scale() == 2 - @patch('beeref.gui.WelcomeOverlay.hide') - def test_on_scene_changed_when_items(self, hide_mock): - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.addItem(item) - self.view.scale(2, 2) - with patch('beeref.view.BeeGraphicsView.recalc_scene_rect') as r: - self.view.on_scene_changed(None) - r.assert_called_once_with() - hide_mock.assert_called_once_with() - assert self.view.get_scale() == 2 - @patch('beeref.gui.WelcomeOverlay.show') - def test_on_scene_changed_when_no_items(self, show_mock): - self.view.scale(2, 2) - with patch('beeref.view.BeeGraphicsView.recalc_scene_rect') as r: - self.view.on_scene_changed(None) - r.assert_called() - show_mock.assert_called_once_with() - assert self.view.get_scale() == 1 +@patch('beeref.gui.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: + view.on_scene_changed(None) + r.assert_called() + show_mock.assert_called_once_with() + assert view.get_scale() == 1 - def test_get_supported_image_formats_for_reading(self): - formats = self.view.get_supported_image_formats(QtGui.QImageReader) - assert '*.png' in formats - assert '*.jpg' in formats - def test_clear_scene(self): - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.addItem(item) - self.view.scale(2, 2) - self.view.translate(123, 456) - self.view.filename = 'test.bee' - self.view.undo_stack = MagicMock() +def test_get_supported_image_formats_for_reading(view): + formats = view.get_supported_image_formats(QtGui.QImageReader) + assert '*.png' in formats + assert '*.jpg' in formats - self.view.clear_scene() - assert not self.view.scene.items() - assert self.view.transform().isIdentity() - assert self.view.filename is None - self.view.undo_stack.clear.assert_called_once_with() - assert self.parent.windowTitle() == 'BeeRef' - def test_reset_previous_transform_when_other_item(self): - item1 = MagicMock() - item2 = MagicMock() - self.view.previous_transform = { - 'transform': 'foo', - 'toggle_item': item1, - } - self.view.reset_previous_transform(toggle_item=item2) - assert self.view.previous_transform is None +def test_clear_scene(view, item): + view.scene.addItem(item) + view.scale(2, 2) + view.translate(123, 456) + view.filename = 'test.bee' + view.undo_stack = MagicMock() - def test_reset_previous_transform_when_same_item(self): - item = MagicMock() - self.view.previous_transform = { - 'transform': 'foo', - 'toggle_item': item, - } - self.view.reset_previous_transform(toggle_item=item) - assert self.view.previous_transform == { - 'transform': 'foo', - 'toggle_item': item, - } + view.clear_scene() + assert not view.scene.items() + assert view.transform().isIdentity() + assert view.filename is None + view.undo_stack.clear.assert_called_once_with() + assert view.parent.windowTitle() == 'BeeRef' - @patch('beeref.view.BeeGraphicsView.fitInView') - def test_fit_rect_no_toggle(self, fit_mock): - rect = QtCore.QRectF(30, 40, 100, 80) - self.view.previous_transform = {'toggle_item': MagicMock()} - self.view.fit_rect(rect) - fit_mock.assert_called_with(rect, Qt.AspectRatioMode.KeepAspectRatio) - assert self.view.previous_transform is None - @patch('beeref.view.BeeGraphicsView.fitInView') - def test_fit_rect_toggle_when_no_previous(self, fit_mock): - item = MagicMock() - self.view.previous_transform = None - self.view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000)) - rect = QtCore.QRectF(30, 40, 100, 80) - self.view.scale(2, 2) - self.view.horizontalScrollBar().setValue(-40) - self.view.verticalScrollBar().setValue(-50) - self.view.fit_rect(rect, toggle_item=item) - fit_mock.assert_called_with(rect, Qt.AspectRatioMode.KeepAspectRatio) - assert self.view.previous_transform['toggle_item'] == item - assert self.view.previous_transform['transform'].m11() == 2 - assert isinstance(self.view.previous_transform['center'], - QtCore.QPointF) +def test_reset_previous_transform_when_other_item(view): + item1 = MagicMock() + item2 = MagicMock() + view.previous_transform = { + 'transform': 'foo', + 'toggle_item': item1, + } + view.reset_previous_transform(toggle_item=item2) + assert view.previous_transform is None - @patch('beeref.view.BeeGraphicsView.fitInView') - @patch('beeref.view.BeeGraphicsView.centerOn') - def test_fit_rect_toggle_when_previous(self, center_mock, fit_mock): - item = MagicMock() - self.view.previous_transform = { - 'toggle_item': item, - 'transform': QtGui.QTransform.fromScale(2, 2), - 'center': QtCore.QPointF(30, 40) - } - self.view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000)) - rect = QtCore.QRectF(30, 40, 100, 80) - self.view.fit_rect(rect, toggle_item=item) - fit_mock.assert_not_called() - center_mock.assert_called_once_with(QtCore.QPointF(30, 40)) - assert self.view.get_scale() == 2 - @patch('beeref.view.BeeGraphicsView.clear_scene') - def test_open_from_file(self, clear_mock): - root = os.path.dirname(__file__) - filename = os.path.join(root, 'assets', 'test1item.bee') - self.view.open_from_file(filename) - self.view.worker.wait() - items = self.queue2list(self.view.scene.items_to_add) - assert len(items) == 1 - item, selected = items[0] - assert items[0][0].pixmap() - assert items[0][1] is False - clear_mock.assert_called_once_with() - # FIXME: #1 - # Can't check signal handling currently - # assert self.parent.windowTitle() == 'test1item.bee - BeeRef' +def test_reset_previous_transform_when_same_item(view): + item = MagicMock() + view.previous_transform = { + 'transform': 'foo', + 'toggle_item': item, + } + view.reset_previous_transform(toggle_item=item) + assert view.previous_transform == { + 'transform': 'foo', + 'toggle_item': item, + } - @patch('PyQt6.QtWidgets.QMessageBox.warning') - def test_open_from_file_when_error(self, warn_mock): - # FIXME: #1 - # Can't check signal handling currently - self.view.open_from_file('uieauiae') - self.view.worker.wait() - assert self.view.scene.items_to_add.empty() is True - assert len(self.view.scene.items()) == 0 - @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') - def test_on_action_open(self, dialog_mock): - # FIXME: #1 - # Can't check signal handling currently - root = os.path.dirname(__file__) - dialog_mock.return_value = ( - os.path.join(root, 'assets', 'test1item.bee'), - None) - self.view.on_action_open() - self.view.worker.wait() - items = self.queue2list(self.view.scene.items_to_add) - assert len(items) == 1 - assert items[0][0].pixmap() - assert items[0][1] is False - # FIXME: #1 - # Can't check signal handling currently - # assert self.parent.windowTitle() == 'test1item.bee - BeeRef' +@patch('beeref.view.BeeGraphicsView.fitInView') +def test_fit_rect_no_toggle(fit_mock, view): + rect = QtCore.QRectF(30, 40, 100, 80) + view.previous_transform = {'toggle_item': MagicMock()} + view.fit_rect(rect) + fit_mock.assert_called_with(rect, Qt.AspectRatioMode.KeepAspectRatio) + assert view.previous_transform is None - @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') - @patch('beeref.view.BeeGraphicsView.on_action_open') - def test_on_action_open_when_no_filename(self, dialog_mock, open_mock): - dialog_mock.return_value = (None, None) - self.view.on_action_open() - open_mock.assert_not_called() - @patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') - def test_on_action_save_as(self, dialog_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - with tempfile.TemporaryDirectory() as tmpdir: - filename = os.path.join(tmpdir, 'test.bee') - assert os.path.exists(filename) is False - dialog_mock.return_value = (filename, None) - self.view.on_action_save_as() - self.view.worker.wait() - assert os.path.exists(filename) is True +@patch('beeref.view.BeeGraphicsView.fitInView') +def test_fit_rect_toggle_when_no_previous(fit_mock, view): + item = MagicMock() + view.previous_transform = None + view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000)) + rect = QtCore.QRectF(30, 40, 100, 80) + view.scale(2, 2) + view.horizontalScrollBar().setValue(-40) + view.verticalScrollBar().setValue(-50) + view.fit_rect(rect, toggle_item=item) + fit_mock.assert_called_with(rect, Qt.AspectRatioMode.KeepAspectRatio) + assert view.previous_transform['toggle_item'] == item + assert view.previous_transform['transform'].m11() == 2 + assert isinstance(view.previous_transform['center'], QtCore.QPointF) - @patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') - @patch('beeref.view.BeeGraphicsView.do_save') - def test_on_action_save_as_when_no_filename(self, save_mock, dialog_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - dialog_mock.return_value = (None, None) - self.view.on_action_save_as() - save_mock.assert_not_called() - @patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') - def test_on_action_save_as_filename_doesnt_end_with_bee(self, dialog_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - with tempfile.TemporaryDirectory() as tmpdir: - filename = os.path.join(tmpdir, 'test') - assert os.path.exists(filename) is False - dialog_mock.return_value = (filename, None) - self.view.on_action_save_as() - self.view.worker.wait() - assert os.path.exists(f'{filename}.bee') is True +@patch('beeref.view.BeeGraphicsView.fitInView') +@patch('beeref.view.BeeGraphicsView.centerOn') +def test_fit_rect_toggle_when_previous(center_mock, fit_mock, view): + item = MagicMock() + view.previous_transform = { + 'toggle_item': item, + 'transform': QtGui.QTransform.fromScale(2, 2), + 'center': QtCore.QPointF(30, 40) + } + view.setSceneRect(QtCore.QRectF(-2000, -2000, 4000, 4000)) + rect = QtCore.QRectF(30, 40, 100, 80) + view.fit_rect(rect, toggle_item=item) + fit_mock.assert_not_called() + center_mock.assert_called_once_with(QtCore.QPointF(30, 40)) + assert view.get_scale() == 2 - @mark.skip('needs pytest-qt') - @patch('PyQt6.QtWidgets.QMessageBox.warning') - @patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') - @patch('beeref.fileio.save') - def test_on_action_save_as_when_error( - self, save_mock, dialog_mock, warn_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - with tempfile.TemporaryDirectory() as tmpdir: - filename = os.path.join(tmpdir, 'test.bee') - assert os.path.exists(filename) is False - dialog_mock.return_value = (filename, None) - save_mock.side_effect = fileio.BeeFileIOError('foo', 'test.bee') - self.view.on_action_save_as() - warn_mock.assert_called_once() - assert os.path.exists(filename) is False - def test_on_action_save(self): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - with tempfile.TemporaryDirectory() as tmpdir: - self.view.filename = os.path.join(tmpdir, 'test.bee') - assert os.path.exists(self.view.filename) is False - self.view.on_action_save() - self.view.worker.wait() - assert os.path.exists(self.view.filename) is True +@patch('beeref.view.BeeGraphicsView.clear_scene') +def test_open_from_file(clear_mock, view, qtbot): + root = os.path.dirname(__file__) + filename = os.path.join(root, 'assets', 'test1item.bee') + view.on_loading_finished = MagicMock() + view.open_from_file(filename) + view.worker.wait() + qtbot.waitUntil(lambda: view.on_loading_finished.called is True) + assert len(view.scene.items()) == 1 + item = view.scene.items()[0] + assert item.isSelected() is False + assert item.pixmap() + clear_mock.assert_called_once_with() + view.on_loading_finished.assert_called_once_with(filename, []) - @patch('beeref.view.BeeGraphicsView.on_action_save_as') - def test_on_action_save_when_no_filename(self, save_as_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - self.view.filename = None - self.view.on_action_save() - save_as_mock.assert_called_once_with() - @patch('beeref.gui.HelpDialog.show') - def test_on_action_help(self, show_mock): - self.view.on_action_help() +def test_open_from_file_when_error(view, qtbot): + view.on_loading_finished = MagicMock() + view.open_from_file('uieauiae') + view.worker.wait() + qtbot.waitUntil(lambda: view.on_loading_finished.called is True) + assert list(view.scene.items()) == [] + view.on_loading_finished.assert_called_once_with( + 'uieauiae', ['unable to open database file']) + + +@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') +def test_on_action_open(dialog_mock, view, qtbot): + # FIXME: #1 + # Can't check signal handling currently + root = os.path.dirname(__file__) + filename = os.path.join(root, 'assets', 'test1item.bee') + dialog_mock.return_value = (filename, None) + view.on_loading_finished = MagicMock() + view.on_action_open() + qtbot.waitUntil(lambda: view.on_loading_finished.called is True) + assert len(view.scene.items()) == 1 + item = view.scene.items()[0] + assert item.isSelected() is False + assert item.pixmap() + view.on_loading_finished.assert_called_once_with(filename, []) + + +@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') +@patch('beeref.view.BeeGraphicsView.on_action_open') +def test_on_action_open_when_no_filename(dialog_mock, open_mock, view): + dialog_mock.return_value = (None, None) + view.on_action_open() + open_mock.assert_not_called() + + +@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') +def test_on_action_save_as(dialog_mock, view, imgfilename3x3, tmpdir): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + filename = os.path.join(tmpdir, 'test.bee') + assert os.path.exists(filename) is False + dialog_mock.return_value = (filename, None) + view.on_action_save_as() + view.worker.wait() + assert os.path.exists(filename) is True + + +@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') +@patch('beeref.view.BeeGraphicsView.do_save') +def test_on_action_save_as_when_no_filename( + save_mock, dialog_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + dialog_mock.return_value = (None, None) + view.on_action_save_as() + save_mock.assert_not_called() + + +@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') +def test_on_action_save_as_filename_doesnt_end_with_bee( + dialog_mock, view, qtbot, imgfilename3x3, tmpdir): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.on_saving_finished = MagicMock() + filename = os.path.join(tmpdir, 'test') + assert os.path.exists(filename) is False + dialog_mock.return_value = (filename, None) + view.on_action_save_as() + qtbot.waitUntil(lambda: view.on_saving_finished.called is True) + assert os.path.exists(f'{filename}.bee') is True + view.on_saving_finished.assert_called_once_with(f'{filename}.bee', []) + + +@patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') +@patch('beeref.fileio.sql.SQLiteIO.write_data') +def test_on_action_save_as_when_error( + save_mock, dialog_mock, view, qtbot, imgfilename3x3, tmpdir): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.on_saving_finished = MagicMock() + filename = os.path.join(tmpdir, 'test.bee') + assert os.path.exists(filename) is False + dialog_mock.return_value = (filename, None) + save_mock.side_effect = sqlite3.Error('foo') + view.on_action_save_as() + qtbot.waitUntil(lambda: view.on_saving_finished.called is True) + view.on_saving_finished.assert_called_once_with(filename, ['foo']) + + +def test_on_action_save(view, qtbot, imgfilename3x3, tmpdir): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.filename = os.path.join(tmpdir, 'test.bee') + view.on_saving_finished = MagicMock() + assert os.path.exists(view.filename) is False + view.on_action_save() + qtbot.waitUntil(lambda: view.on_saving_finished.called is True) + assert os.path.exists(view.filename) is True + view.on_saving_finished.assert_called_once_with(view.filename, []) + + +@patch('beeref.view.BeeGraphicsView.on_action_save_as') +def test_on_action_save_when_no_filename(save_as_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.filename = None + view.on_action_save() + save_as_mock.assert_called_once_with() + + +@patch('beeref.gui.HelpDialog.show') +def test_on_action_help(show_mock, view): + view.on_action_help() + show_mock.assert_called_once() + + +@patch('beeref.gui.DebugLogDialog.show') +def test_on_action_debuglog(show_mock, view): + with patch('builtins.open', mock_open(read_data='log')) as open_mock: + view.on_action_debuglog() show_mock.assert_called_once() - - @patch('beeref.gui.DebugLogDialog.show') - def test_on_action_debuglog(self, show_mock): - with patch('builtins.open', mock_open(read_data='log')) as open_mock: - self.view.on_action_debuglog() - show_mock.assert_called_once() - open_mock.assert_called_once_with(logfile_name) - - @patch('beeref.scene.BeeGraphicsScene.clearSelection') - @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileNames') - def test_on_action_insert_images(self, dialog_mock, clear_mock): - # FIXME: #1 - # Can't check signal handling currently - dialog_mock.return_value = ([self.imgfilename3x3], None) - self.view.on_action_insert_images() - self.view.worker.wait() - items = self.queue2list(self.view.scene.items_to_add) - assert len(items) == 1 - assert items[0][0].pixmap() - assert items[0][1] is True - clear_mock.assert_called_once_with() - - @patch('beeref.scene.BeeGraphicsScene.clearSelection') - @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileNames') - def test_on_action_insert_images_when_error(self, dialog_mock, clear_mock): - # FIXME: #1 - # Can't check signal handling currently - dialog_mock.return_value = ( - [self.imgfilename3x3, 'iaeiae', 'trntrn'], None) - self.view.on_action_insert_images() - self.view.worker.wait() - items = self.queue2list(self.view.scene.items_to_add) - assert len(items) == 1 - assert items[0][0].pixmap() - assert items[0][1] is True - clear_mock.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QApplication.clipboard') - def test_on_action_copy(self, clipboard_mock): - item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - self.view.scene.addItem(item) - item.setSelected(True) - mimedata = QtCore.QMimeData() - clipboard_mock.return_value.mimeData.return_value = mimedata - self.view.on_action_copy() - - clipboard_mock.return_value.setPixmap.assert_called_once() - self.view.scene.internal_clipboard == [item] - assert mimedata.data('beeref/items') == b'1' - - @patch('beeref.scene.BeeGraphicsScene.clearSelection') - @patch('PyQt6.QtGui.QClipboard.image') - def test_on_action_paste_external(self, clipboard_mock, clear_mock): - clipboard_mock.return_value = QtGui.QImage(self.imgfilename3x3) - self.view.on_action_paste() - assert len(self.view.scene.items()) == 1 - assert self.view.scene.items()[0].isSelected() is True - - @patch('beeref.scene.BeeGraphicsScene.clearSelection') - @patch('PyQt6.QtGui.QClipboard.mimeData') - def test_on_action_paste_internal(self, mimedata_mock, clear_mock): - mimedata = QtCore.QMimeData() - mimedata.setData('beeref/items', QtCore.QByteArray.number(1)) - mimedata_mock.return_value = mimedata - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.internal_clipboard = [item] - self.view.on_action_paste() - assert len(self.view.scene.items()) == 1 - assert self.view.scene.items()[0].isSelected() is True - clear_mock.assert_called_once_with() - - @patch('beeref.scene.BeeGraphicsScene.clearSelection') - @patch('PyQt6.QtGui.QClipboard.image') - def test_on_action_paste_when_empty(self, clipboard_mock, clear_mock): - clipboard_mock.return_value = QtGui.QImage() - self.view.on_action_paste() - assert len(self.view.scene.items()) == 0 - clear_mock.assert_not_called() - - @patch('beeref.view.BeeGraphicsView.on_action_copy') - def test_on_action_cut(self, copy_mock): - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.addItem(item) - item.setSelected(True) - self.view.on_action_cut() - copy_mock.assert_called_once_with() - assert self.view.scene.items() == [] - assert self.view.undo_stack.isClean() is False - - def test_on_action_show_menubar(self): - self.view.toplevel_menus = [QtWidgets.QMenu('Foo')] - self.view.on_action_show_menubar(True) - assert len(self.view.parent().menuBar().actions()) == 1 - self.view.on_action_show_menubar(False) - assert self.view.parent().menuBar().actions() == [] - - def test_on_action_delete_items(self): - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.addItem(item) - item.setSelected(True) - self.view.on_action_delete_items() - assert self.view.scene.items() == [] - assert self.view.undo_stack.isClean() is False + open_mock.assert_called_once_with(logfile_name()) -class UpdateWindowTitleTestCase(ViewBaseTestCase): - - @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) - def test_update_window_title_no_changes_no_filename(self, clear_mock): - self.view.filename = None - self.view.update_window_title() - assert self.parent.windowTitle() == 'BeeRef' - - @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) - def test_update_window_title_changes_no_filename(self, clear_mock): - self.view.filename = None - self.view.update_window_title() - assert self.parent.windowTitle() == '[Untitled]* - BeeRef' - - @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) - def test_update_window_title_no_changes_filename(self, clear_mock): - self.view.filename = 'test.bee' - self.view.update_window_title() - assert self.parent.windowTitle() == 'test.bee - BeeRef' - - @patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) - def test_update_window_title_changes_filename(self, clear_mock): - self.view.filename = 'test.bee' - self.view.update_window_title() - assert self.parent.windowTitle() == 'test.bee* - BeeRef' - - @patch('beeref.view.BeeGraphicsView.recalc_scene_rect') - @patch('beeref.scene.BeeGraphicsScene.on_view_scale_change') - def test_scale(self, view_scale_mock, recalc_mock): - self.view.scale(3.3, 3.3) - view_scale_mock.assert_called_once_with() - recalc_mock.assert_called_once_with() - assert self.view.get_scale() == 3.3 - - @patch('PyQt6.QtWidgets.QScrollBar.setValue') - def test_pan(self, scroll_value_mock): - item = BeePixmapItem(QtGui.QImage()) - self.view.scene.addItem(item) - self.view.pan(QtCore.QPointF(5, 10)) - assert scroll_value_mock.call_count == 2 - - @patch('PyQt6.QtWidgets.QScrollBar.setValue') - def test_pan_when_no_items(self, scroll_value_mock): - self.view.pan(QtCore.QPointF(5, 10)) - scroll_value_mock.assert_not_called() +@patch('beeref.scene.BeeGraphicsScene.clearSelection') +@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileNames') +def test_on_action_insert_images( + dialog_mock, clear_mock, view, imgfilename3x3, qtbot): + dialog_mock.return_value = ([imgfilename3x3], None) + view.on_insert_images_finished = MagicMock() + view.on_action_insert_images() + qtbot.waitUntil(lambda: view.on_insert_images_finished.called is True) + assert len(view.scene.items()) == 1 + item = view.scene.items()[0] + assert item.isSelected() is True + assert item.pixmap() + clear_mock.assert_called_once_with() + view.on_insert_images_finished.assert_called_once_with('', []) -class ZoomTestCase(ViewBaseTestCase): - - def setUp(self): - super().setUp() - pan_patcher = patch('beeref.view.BeeGraphicsView.pan') - self.pan_mock = pan_patcher.start() - self.addCleanup(pan_patcher.stop) - reset_patcher = patch( - 'beeref.view.BeeGraphicsView.reset_previous_transform') - self.reset_mock = reset_patcher.start() - self.addCleanup(reset_patcher.stop) - self.item = BeePixmapItem(QtGui.QImage(self.imgfilename3x3)) - - def test_zoom_in(self): - self.view.scene.addItem(self.item) - self.view.zoom(40, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 1.04 - self.reset_mock.assert_called_once_with() - self.pan_mock.assert_called_once_with(QtCore.QPoint(-52, -15)) - - def test_zoom_in_max_zoom_size(self): - self.view.scale(10000000, 10000000) - self.view.scene.addItem(self.item) - self.view.zoom(40, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 10000000 - self.reset_mock.assert_not_called() - self.pan_mock.assert_not_called() - - def test_zoom_out(self): - self.view.scale(100, 100) - self.view.scene.addItem(self.item) - self.view.zoom(-40, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 100 / 1.04 - self.reset_mock.assert_called_once_with() - self.pan_mock.assert_called_once_with(QtCore.QPoint(49, 14)) - - def test_zoom_out_min_zoom_size(self): - self.view.scene.addItem(self.item) - self.view.zoom(-40, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 1 - self.reset_mock.assert_not_called() - self.pan_mock.assert_not_called() - - def test_no_items(self): - self.view.zoom(40, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 1 - self.reset_mock.assert_not_called() - self.pan_mock.assert_not_called() - - def test_delta_zero(self): - self.view.scene.addItem(self.item) - self.view.zoom(0, QtCore.QPointF(10, 10)) - assert self.view.get_scale() == 1 - self.reset_mock.assert_not_called() - self.pan_mock.assert_not_called() +@patch('beeref.scene.BeeGraphicsScene.clearSelection') +@patch('PyQt6.QtWidgets.QFileDialog.getOpenFileNames') +def test_on_action_insert_images_when_error( + dialog_mock, clear_mock, view, imgfilename3x3, qtbot): + dialog_mock.return_value = ([imgfilename3x3, 'iaeiae', 'trntrn'], None) + view.on_insert_images_finished = MagicMock() + view.on_action_insert_images() + qtbot.waitUntil(lambda: view.on_insert_images_finished.called is True) + assert len(view.scene.items()) == 1 + item = view.scene.items()[0] + assert item.isSelected() is True + assert item.pixmap() + clear_mock.assert_called_once_with() + view.on_insert_images_finished.assert_called_once_with( + '', ['iaeiae', 'trntrn']) -class MouseEventsTestCase(ViewBaseTestCase): +@patch('PyQt6.QtWidgets.QApplication.clipboard') +def test_on_action_copy(clipboard_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + item.setSelected(True) + mimedata = QtCore.QMimeData() + clipboard_mock.return_value.mimeData.return_value = mimedata + view.on_action_copy() - def setUp(self): - super().setUp() - self.event = MagicMock() - - @patch('beeref.view.BeeGraphicsView.zoom') - def test_wheel_event(self, zoom_mock): - self.event.angleDelta.return_value = QtCore.QPointF(0, 40) - self.event.position.return_value = QtCore.QPointF(10, 20) - self.view.wheelEvent(self.event) - zoom_mock.assert_called_once_with(40, QtCore.QPointF(10, 20)) - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') - def test_mouse_press_zoom(self, mouse_event_mock): - self.event.position.return_value = QtCore.QPointF(10, 20) - self.event.button.return_value = Qt.MouseButton.MiddleButton - self.event.modifiers.return_value = Qt.KeyboardModifier.ControlModifier - self.view.mousePressEvent(self.event) - assert self.view.zoom_active is True - assert self.view.pan_active is False - assert self.view.event_start == QtCore.QPointF(10, 20) - assert self.view.event_anchor == QtCore.QPointF(10, 20) - mouse_event_mock.assert_not_called() - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') - def test_mouse_press_pan_middle_drag(self, mouse_event_mock): - self.event.position.return_value = QtCore.QPointF(10, 20) - self.event.button.return_value = Qt.MouseButton.MiddleButton - self.event.modifiers.return_value = None - self.view.mousePressEvent(self.event) - assert self.view.pan_active is True - assert self.view.zoom_active is False - assert self.view.event_start == QtCore.QPointF(10, 20) - mouse_event_mock.assert_not_called() - self.view.cursor() == Qt.CursorShape.ClosedHandCursor - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') - def test_mouse_press_pan_alt_left_drag(self, mouse_event_mock): - self.event.position.return_value = QtCore.QPointF(10, 20) - self.event.button.return_value = Qt.MouseButton.LeftButton - self.event.modifiers.return_value = Qt.KeyboardModifier.AltModifier - self.view.mousePressEvent(self.event) - assert self.view.pan_active is True - assert self.view.zoom_active is False - assert self.view.event_start == QtCore.QPointF(10, 20) - mouse_event_mock.assert_not_called() - self.view.cursor() == Qt.CursorShape.ClosedHandCursor - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') - def test_mouse_press_unhandled(self, mouse_event_mock): - self.event.button.return_value = Qt.MouseButton.LeftButton - self.event.modifiers.return_value = None - self.view.mousePressEvent(self.event) - assert self.view.pan_active is False - assert self.view.zoom_active is False - mouse_event_mock.assert_called_once_with(self.event) - self.event.accept.assert_not_called() - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') - @patch('beeref.view.BeeGraphicsView.pan') - def test_mouse_move_pan(self, pan_mock, mouse_event_mock): - self.view.pan_active = True - self.view.event_start = QtCore.QPointF(55, 66) - self.event.position.return_value = QtCore.QPointF(10, 20) - self.view.mouseMoveEvent(self.event) - pan_mock.assert_called_once_with(QtCore.QPointF(45, 46)) - mouse_event_mock.assert_not_called() - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') - @patch('beeref.view.BeeGraphicsView.zoom') - def test_mouse_move_zoom(self, zoom_mock, mouse_event_mock): - self.view.zoom_active = True - self.view.event_anchor = QtCore.QPointF(55, 66) - self.view.event_start = QtCore.QPointF(10, 20) - self.event.position.return_value = QtCore.QPointF(10, 18) - self.view.mouseMoveEvent(self.event) - zoom_mock.assert_called_once_with(40, QtCore.QPointF(55, 66)) - mouse_event_mock.assert_not_called() - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') - def test_mouse_move_unhandled(self, mouse_event_mock): - self.event.position.return_value = QtCore.QPointF(10, 20) - self.view.mouseMoveEvent(self.event) - mouse_event_mock.assert_called_once_with(self.event) - self.event.accept.assert_not_called() - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') - def test_mouse_release_pan(self, mouse_event_mock): - self.view.pan_active = True - self.view.setCursor(Qt.CursorShape.ClosedHandCursor) - self.view.mouseReleaseEvent(self.event) - mouse_event_mock.assert_not_called() - assert self.view.pan_active is False - self.event.accept.assert_called_once_with() - self.view.cursor() == Qt.CursorShape.ArrowCursor - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') - def test_mouse_release_zoom(self, mouse_event_mock): - self.view.zoom_active = True - self.view.mouseReleaseEvent(self.event) - mouse_event_mock.assert_not_called() - assert self.view.zoom_active is False - self.event.accept.assert_called_once_with() - - @patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') - def test_mouse_release_unhandled(self, mouse_event_mock): - self.view.mouseReleaseEvent(self.event) - mouse_event_mock.assert_called_once_with(self.event) - self.event.accept.assert_not_called() + clipboard_mock.return_value.setPixmap.assert_called_once() + view.scene.internal_clipboard == [item] + assert mimedata.data('beeref/items') == b'1' -class DragDropTestCase(ViewBaseTestCase): +@patch('beeref.scene.BeeGraphicsScene.clearSelection') +@patch('PyQt6.QtGui.QClipboard.image') +def test_on_action_paste_external( + clipboard_mock, clear_mock, view, imgfilename3x3): + clipboard_mock.return_value = QtGui.QImage(imgfilename3x3) + view.on_action_paste() + assert len(view.scene.items()) == 1 + assert view.scene.items()[0].isSelected() is True - def setUp(self): - super().setUp() - self.event = MagicMock() - def test_drag_enter_when_url(self): - url = QtCore.QUrl() - url.fromLocalFile(self.imgfilename3x3) - mimedata = QtCore.QMimeData() - mimedata.setUrls([url]) - self.event.mimeData.return_value = mimedata +@patch('beeref.scene.BeeGraphicsScene.clearSelection') +@patch('PyQt6.QtGui.QClipboard.mimeData') +def test_on_action_paste_internal(mimedata_mock, clear_mock, view): + mimedata = QtCore.QMimeData() + mimedata.setData('beeref/items', QtCore.QByteArray.number(1)) + mimedata_mock.return_value = mimedata + item = BeePixmapItem(QtGui.QImage()) + view.scene.internal_clipboard = [item] + view.on_action_paste() + assert len(view.scene.items()) == 1 + assert view.scene.items()[0].isSelected() is True + clear_mock.assert_called_once_with() - self.view.dragEnterEvent(self.event) - self.event.acceptProposedAction.assert_called_once() - def test_drag_enter_when_img(self): - mimedata = QtCore.QMimeData() - mimedata.setImageData(QtGui.QImage(self.imgfilename3x3)) - self.event.mimeData.return_value = mimedata +@patch('beeref.scene.BeeGraphicsScene.clearSelection') +@patch('PyQt6.QtGui.QClipboard.image') +def test_on_action_paste_when_empty(clipboard_mock, clear_mock, view): + clipboard_mock.return_value = QtGui.QImage() + view.on_action_paste() + assert len(view.scene.items()) == 0 + clear_mock.assert_not_called() - self.view.dragEnterEvent(self.event) - self.event.acceptProposedAction.assert_called_once() - def test_drag_enter_when_unsupported(self): - mimedata = QtCore.QMimeData() - self.event.mimeData.return_value = mimedata +@patch('beeref.view.BeeGraphicsView.on_action_copy') +def test_on_action_cut(copy_mock, view, item): + view.scene.addItem(item) + item.setSelected(True) + view.on_action_cut() + copy_mock.assert_called_once_with() + assert view.scene.items() == [] + assert view.undo_stack.isClean() is False - self.view.dragEnterEvent(self.event) - self.event.acceptProposedAction.assert_not_called() - def test_drag_move(self): - self.view.dragMoveEvent(self.event) - self.event.acceptProposedAction.assert_called_once() +def test_on_action_show_menubar(view): + view.toplevel_menus = [QtWidgets.QMenu('Foo')] + view.on_action_show_menubar(True) + assert len(view.parent.menuBar().actions()) == 1 + view.on_action_show_menubar(False) + assert view.parent.menuBar().actions() == [] - @patch('beeref.view.BeeGraphicsView.do_insert_images') - def test_drop_when_url(self, insert_mock): - url = QtCore.QUrl() - url.fromLocalFile(self.imgfilename3x3) - mimedata = QtCore.QMimeData() - mimedata.setUrls([url]) - self.event.mimeData.return_value = mimedata - self.event.position.return_value = QtCore.QPointF(10, 20) - self.view.dropEvent(self.event) - insert_mock.assert_called_once_with([url], QtCore.QPoint(10, 20)) +def test_on_action_delete_items(view, item): + view.scene.addItem(item) + item.setSelected(True) + view.on_action_delete_items() + assert view.scene.items() == [] + assert view.undo_stack.isClean() is False - def test_drop_when_img(self): - mimedata = QtCore.QMimeData() - mimedata.setImageData(QtGui.QImage(self.imgfilename3x3)) - self.event.mimeData.return_value = mimedata - self.event.position.return_value = QtCore.QPointF(10, 20) - self.view.dropEvent(self.event) - assert len(self.view.scene.items()) == 1 - assert self.view.scene.items()[0].isSelected() is True +@patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) +def test_update_window_title_no_changes_no_filename(clear_mock, view): + view.filename = None + view.update_window_title() + assert view.parent.windowTitle() == 'BeeRef' + + +@patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) +def test_update_window_title_changes_no_filename(clear_mock, view): + view.filename = None + view.update_window_title() + assert view.parent.windowTitle() == '[Untitled]* - BeeRef' + + +@patch('PyQt6.QtGui.QUndoStack.isClean', return_value=True) +def test_update_window_title_no_changes_filename(clear_mock, view): + view.filename = 'test.bee' + view.update_window_title() + assert view.parent.windowTitle() == 'test.bee - BeeRef' + + +@patch('PyQt6.QtGui.QUndoStack.isClean', return_value=False) +def test_update_window_title_changes_filename(clear_mock, view): + view.filename = 'test.bee' + view.update_window_title() + assert view.parent.windowTitle() == 'test.bee* - BeeRef' + + +@patch('beeref.view.BeeGraphicsView.recalc_scene_rect') +@patch('beeref.scene.BeeGraphicsScene.on_view_scale_change') +def test_scale(view_scale_mock, recalc_mock, view): + view.scale(3.3, 3.3) + view_scale_mock.assert_called_once_with() + recalc_mock.assert_called_once_with() + assert view.get_scale() == 3.3 + + +@patch('PyQt6.QtWidgets.QScrollBar.setValue') +def test_pan(scroll_value_mock, view, item): + view.scene.addItem(item) + view.pan(QtCore.QPointF(5, 10)) + assert scroll_value_mock.call_count == 2 + + +@patch('PyQt6.QtWidgets.QScrollBar.setValue') +def test_pan_when_no_items(scroll_value_mock, view): + view.pan(QtCore.QPointF(5, 10)) + scroll_value_mock.assert_not_called() + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_zoom_in(pan_mock, reset_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scene.addItem(item) + view.zoom(40, QtCore.QPointF(10, 10)) + assert view.get_scale() == 1.04 + reset_mock.assert_called_once_with() + pan_mock.assert_called_once_with(QtCore.QPoint(-10, -6)) + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_zoom_in_max_zoom_size(pan_mock, reset_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scale(10000000, 10000000) + view.scene.addItem(item) + view.zoom(40, QtCore.QPointF(10, 10)) + assert view.get_scale() == 10000000 + reset_mock.assert_not_called() + pan_mock.assert_not_called() + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_zoom_out(pan_mock, reset_mock, view, imgfilename3x3): + item = BeePixmapItem(QtGui.QImage(imgfilename3x3)) + view.scale(100, 100) + view.scene.addItem(item) + view.zoom(-40, QtCore.QPointF(10, 10)) + assert view.get_scale() == 100 / 1.04 + reset_mock.assert_called_once_with() + pan_mock.assert_called_once_with(QtCore.QPoint(9, 5)) + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_zoom_out_min_zoom_size(pan_mock, reset_mock, view, item): + view.scene.addItem(item) + view.zoom(-40, QtCore.QPointF(10, 10)) + assert view.get_scale() == 1 + reset_mock.assert_not_called() + pan_mock.assert_not_called() + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_no_items(pan_mock, reset_mock, view, item): + view.zoom(40, QtCore.QPointF(10, 10)) + assert view.get_scale() == 1 + reset_mock.assert_not_called() + pan_mock.assert_not_called() + + +@patch('beeref.view.BeeGraphicsView.reset_previous_transform') +@patch('beeref.view.BeeGraphicsView.pan') +def test_delta_zero(pan_mock, reset_mock, view, item): + view.scene.addItem(item) + view.zoom(0, QtCore.QPointF(10, 10)) + assert view.get_scale() == 1 + reset_mock.assert_not_called() + pan_mock.assert_not_called() + + +@patch('beeref.view.BeeGraphicsView.zoom') +def test_wheel_event(zoom_mock, view): + event = MagicMock() + event.angleDelta.return_value = QtCore.QPointF(0, 40) + event.position.return_value = QtCore.QPointF(10, 20) + view.wheelEvent(event) + zoom_mock.assert_called_once_with(40, QtCore.QPointF(10, 20)) + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') +def test_mouse_press_zoom(mouse_event_mock, view): + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 20) + event.button.return_value = Qt.MouseButton.MiddleButton + event.modifiers.return_value = Qt.KeyboardModifier.ControlModifier + view.mousePressEvent(event) + assert view.zoom_active is True + assert view.pan_active is False + assert view.event_start == QtCore.QPointF(10, 20) + assert view.event_anchor == QtCore.QPointF(10, 20) + mouse_event_mock.assert_not_called() + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') +def test_mouse_press_pan_middle_drag(mouse_event_mock, view): + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 20) + event.button.return_value = Qt.MouseButton.MiddleButton + event.modifiers.return_value = None + view.mousePressEvent(event) + assert view.pan_active is True + assert view.zoom_active is False + assert view.event_start == QtCore.QPointF(10, 20) + mouse_event_mock.assert_not_called() + view.cursor() == Qt.CursorShape.ClosedHandCursor + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') +def test_mouse_press_pan_alt_left_drag(mouse_event_mock, view): + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 20) + event.button.return_value = Qt.MouseButton.LeftButton + event.modifiers.return_value = Qt.KeyboardModifier.AltModifier + view.mousePressEvent(event) + assert view.pan_active is True + assert view.zoom_active is False + assert view.event_start == QtCore.QPointF(10, 20) + mouse_event_mock.assert_not_called() + view.cursor() == Qt.CursorShape.ClosedHandCursor + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mousePressEvent') +def test_mouse_press_unhandled(mouse_event_mock, view): + event = MagicMock() + event.button.return_value = Qt.MouseButton.LeftButton + event.modifiers.return_value = None + view.mousePressEvent(event) + assert view.pan_active is False + assert view.zoom_active is False + mouse_event_mock.assert_called_once_with(event) + event.accept.assert_not_called() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') +@patch('beeref.view.BeeGraphicsView.pan') +def test_mouse_move_pan(pan_mock, mouse_event_mock, view): + view.pan_active = True + view.event_start = QtCore.QPointF(55, 66) + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 20) + view.mouseMoveEvent(event) + pan_mock.assert_called_once_with(QtCore.QPointF(45, 46)) + mouse_event_mock.assert_not_called() + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') +@patch('beeref.view.BeeGraphicsView.zoom') +def test_mouse_move_zoom(zoom_mock, mouse_event_mock, view): + view.zoom_active = True + view.event_anchor = QtCore.QPointF(55, 66) + view.event_start = QtCore.QPointF(10, 20) + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 18) + view.mouseMoveEvent(event) + zoom_mock.assert_called_once_with(40, QtCore.QPointF(55, 66)) + mouse_event_mock.assert_not_called() + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseMoveEvent') +def test_mouse_move_unhandled(mouse_event_mock, view): + event = MagicMock() + event.position.return_value = QtCore.QPointF(10, 20) + view.mouseMoveEvent(event) + mouse_event_mock.assert_called_once_with(event) + event.accept.assert_not_called() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') +def test_mouse_release_pan(mouse_event_mock, view): + event = MagicMock() + view.pan_active = True + view.setCursor(Qt.CursorShape.ClosedHandCursor) + view.mouseReleaseEvent(event) + mouse_event_mock.assert_not_called() + assert view.pan_active is False + event.accept.assert_called_once_with() + view.cursor() == Qt.CursorShape.ArrowCursor + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') +def test_mouse_release_zoom(mouse_event_mock, view): + event = MagicMock() + view.zoom_active = True + view.mouseReleaseEvent(event) + mouse_event_mock.assert_not_called() + assert view.zoom_active is False + event.accept.assert_called_once_with() + + +@patch('PyQt6.QtWidgets.QGraphicsView.mouseReleaseEvent') +def test_mouse_release_unhandled(mouse_event_mock, view): + event = MagicMock() + view.mouseReleaseEvent(event) + mouse_event_mock.assert_called_once_with(event) + event.accept.assert_not_called() + + +def test_drag_enter_when_url(view, imgfilename3x3): + url = QtCore.QUrl() + url.fromLocalFile(imgfilename3x3) + mimedata = QtCore.QMimeData() + mimedata.setUrls([url]) + event = MagicMock() + event.mimeData.return_value = mimedata + + view.dragEnterEvent(event) + event.acceptProposedAction.assert_called_once() + + +def test_drag_enter_when_img(view, imgfilename3x3): + mimedata = QtCore.QMimeData() + mimedata.setImageData(QtGui.QImage(imgfilename3x3)) + event = MagicMock() + event.mimeData.return_value = mimedata + + view.dragEnterEvent(event) + event.acceptProposedAction.assert_called_once() + + +def test_drag_enter_when_unsupported(view): + mimedata = QtCore.QMimeData() + event = MagicMock() + event.mimeData.return_value = mimedata + + view.dragEnterEvent(event) + event.acceptProposedAction.assert_not_called() + + +def test_drag_move(view): + event = MagicMock() + view.dragMoveEvent(event) + event.acceptProposedAction.assert_called_once() + + +@patch('beeref.view.BeeGraphicsView.do_insert_images') +def test_drop_when_url(insert_mock, view, imgfilename3x3): + url = QtCore.QUrl() + url.fromLocalFile(imgfilename3x3) + mimedata = QtCore.QMimeData() + mimedata.setUrls([url]) + event = MagicMock() + event.mimeData.return_value = mimedata + event.position.return_value = QtCore.QPointF(10, 20) + + view.dropEvent(event) + insert_mock.assert_called_once_with([url], QtCore.QPoint(10, 20)) + + +def test_drop_when_img(view, imgfilename3x3): + mimedata = QtCore.QMimeData() + mimedata.setImageData(QtGui.QImage(imgfilename3x3)) + event = MagicMock() + event.mimeData.return_value = mimedata + event.position.return_value = QtCore.QPointF(10, 20) + + view.dropEvent(event) + assert len(view.scene.items()) == 1 + assert view.scene.items()[0].isSelected() is True diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..edb975e --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,5 @@ +def queue2list(queue): + qlist = [] + while not queue.empty(): + qlist.append(queue.get()) + return qlist