diff --git a/beeref/selection.py b/beeref/selection.py index 9d58bcc..8d39411 100644 --- a/beeref/selection.py +++ b/beeref/selection.py @@ -34,7 +34,7 @@ SELECT_COLOR = QtGui.QColor(116, 234, 231, 255) def with_anchor(func): - """Decorator that handles anchors for translate operations. + """Decorator that adds an anchor parameter to transform operations. The anchor is given in item coordinates. """ @@ -45,7 +45,7 @@ def with_anchor(func): anchor = anchor if anchor else QtCore.QPointF(0, 0) prev = self.mapToScene(anchor) - func(self, value, anchor) + func(self, value) diff = self.mapToScene(anchor) - prev self.setPos(self.pos() - diff) @@ -55,7 +55,7 @@ def with_anchor(func): class BaseItemMixin: @with_anchor - def setScale(self, value, anchor=None): + def setScale(self, value): if value <= 0: return @@ -72,14 +72,14 @@ class BaseItemMixin: self.setZValue(self.scene().max_z + 0.001) @with_anchor - def setRotation(self, value, anchor): + def setRotation(self, value): logger.debug(f'Setting rotation for {self} to {value}') super().setRotation(value) @property def center_scene_coords(self): """The item's center in scene coordinates.""" - return self.mapToScene(QtCore.QPointF(self.width/2, self.height/2)) + return self.mapToScene(QtCore.QPointF(self.width, self.height) / 2) class SelectableMixin(BaseItemMixin): diff --git a/tests/assets/test1item.bee b/tests/assets/test1item.bee new file mode 100644 index 0000000..df0a213 Binary files /dev/null and b/tests/assets/test1item.bee differ diff --git a/tests/test_items.py b/tests/test_items.py index 5aa7897..7f8d4d4 100644 --- a/tests/test_items.py +++ b/tests/test_items.py @@ -44,6 +44,17 @@ class BeePixmapItemTestCase(BeeTestCase): 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') diff --git a/tests/test_scene.py b/tests/test_scene.py index c3631e3..dc792af 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -20,6 +20,7 @@ class BeeGraphicsSceneTestCase(BeeTestCase): views_patcher = patch('beeref.scene.BeeGraphicsScene.views', return_value=[self.view]) views_patcher.start() + self.addCleanup(views_patcher.stop) def test_normalize_height(self): item1 = MagicMock(height=100, scale_factor=1) diff --git a/tests/test_view.py b/tests/test_view.py index 899f7cf..259b263 100644 --- a/tests/test_view.py +++ b/tests/test_view.py @@ -1,21 +1,125 @@ +import os.path +import tempfile from unittest.mock import patch +import pytest + +from PyQt6 import QtGui + +from beeref.items import BeePixmapItem from beeref.view import BeeGraphicsView from .base import BeeTestCase class BeeGraphicsViewTestCase(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.view = BeeGraphicsView(self.app) + + def tearDown(self): + del self.view + @patch('beeref.view.BeeGraphicsView.open_from_file') def test_init_without_filename(self, open_file_mock): - with patch('beeref.view.commandline_args') as args_mock: - args_mock.filename = None - BeeGraphicsView(self.app) - open_file_mock.assert_not_called() + self.config_mock.filename = None + view = BeeGraphicsView(self.app) + open_file_mock.assert_not_called() + del view @patch('beeref.view.BeeGraphicsView.open_from_file') def test_init_with_filename(self, open_file_mock): - with patch('beeref.view.commandline_args') as args_mock: - args_mock.filename = 'test.bee' - BeeGraphicsView(self.app) - open_file_mock.assert_called_once_with('test.bee') + self.config_mock.filename = 'test.bee' + view = BeeGraphicsView(self.app) + open_file_mock.assert_called_once_with('test.bee') + del view + + @pytest.mark.skip('Causes segfaults sometimes') + @patch('beeref.view.BeeGraphicsView.recalc_scene_rect') + @patch('beeref.gui.WelcomeOverlay.hide') + def test_on_scene_changed_when_items(self, hide_mock, recalc_mock): + item = BeePixmapItem(QtGui.QImage()) + self.view.scene.addItem(item) + self.view.on_scene_changed(None) + recalc_mock.assert_called_once_with() + hide_mock.assert_called_once_with() + + @patch('beeref.view.BeeGraphicsView.recalc_scene_rect') + @patch('beeref.gui.WelcomeOverlay.show') + def test_on_scene_changed_when_no_items(self, show_mock, recalc_mock): + self.view.on_scene_changed(None) + recalc_mock.assert_called_once_with() + show_mock.assert_called_once_with() + + 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 + + @pytest.mark.skip('Causes segfaults sometimes') + def test_open_from_file(self): + root = os.path.dirname(__file__) + filename = os.path.join(root, 'assets', 'test1item.bee') + self.view.open_from_file(filename) + assert len(self.view.scene.items()) == 1 + + @pytest.mark.skip('Causes segfaults sometimes') + @patch('PyQt6.QtWidgets.QMessageBox.warning') + def test_open_from_file_when_error(self, warn_mock): + self.view.open_from_file('uieauiae') + assert len(self.view.scene.items()) == 0 + warn_mock.assert_called_once() + + @pytest.mark.skip('Causes segfaults sometimes') + @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') + def test_on_action_open(self, dialog_mock): + root = os.path.dirname(__file__) + dialog_mock.return_value = ( + os.path.join(root, 'assets', 'test1item.bee'), + None) + self.view.on_action_open() + assert len(self.view.scene.items()) == 1 + + @pytest.mark.skip('Causes segfaults sometimes') + @patch('PyQt6.QtWidgets.QFileDialog.getOpenFileName') + def test_on_action_open_when_no_filename(self, dialog_mock): + dialog_mock.return_value = (None, None) + self.view.on_action_open() + assert len(self.view.scene.items()) == 0 + + @pytest.mark.skip('Causes segfaults sometimes') + @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() + assert os.path.exists(filename) is True + + @pytest.mark.skip('Causes segfaults sometimes') + @patch('PyQt6.QtWidgets.QFileDialog.getSaveFileName') + @patch('beeref.fileio.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() + + @pytest.mark.skip('Causes segfaults sometimes') + @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() + assert os.path.exists(f'{filename}.bee') is True