pyPhotoAlbum/tests/test_interaction_command_builders.py
Duncan Tourolle f6ed11b0bc
All checks were successful
Python CI / test (push) Successful in 1m20s
Lint / lint (push) Successful in 1m4s
Tests / test (3.11) (push) Successful in 1m27s
Tests / test (3.12) (push) Successful in 2m25s
Tests / test (3.13) (push) Successful in 2m52s
Tests / test (3.14) (push) Successful in 1m9s
black formatting
2025-11-27 23:07:16 +01:00

255 lines
8.0 KiB
Python

"""
Unit tests for interaction command builders.
"""
import pytest
from unittest.mock import Mock, MagicMock
from pyPhotoAlbum.mixins.interaction_command_builders import (
MoveCommandBuilder,
ResizeCommandBuilder,
RotateCommandBuilder,
ImagePanCommandBuilder,
)
from pyPhotoAlbum.mixins.interaction_validators import InteractionChangeDetector
class TestMoveCommandBuilder:
"""Tests for MoveCommandBuilder."""
def test_can_build_with_significant_change(self):
"""Test that can_build returns True for significant position changes."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
start_state = {"position": (0.0, 0.0)}
assert builder.can_build(element, start_state)
def test_can_build_with_insignificant_change(self):
"""Test that can_build returns False for insignificant changes."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (0.05, 0.05)
start_state = {"position": (0.0, 0.0)}
assert not builder.can_build(element, start_state)
def test_can_build_with_no_position(self):
"""Test that can_build returns False when no position in start_state."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
start_state = {}
assert not builder.can_build(element, start_state)
def test_build_creates_command(self):
"""Test that build creates a MoveElementCommand."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
start_state = {"position": (0.0, 0.0)}
command = builder.build(element, start_state)
assert command is not None
assert command.element == element
def test_build_returns_none_for_insignificant_change(self):
"""Test that build returns None for insignificant changes."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (0.05, 0.05)
start_state = {"position": (0.0, 0.0)}
command = builder.build(element, start_state)
assert command is None
class TestResizeCommandBuilder:
"""Tests for ResizeCommandBuilder."""
def test_can_build_with_size_change(self):
"""Test that can_build returns True when size changes."""
builder = ResizeCommandBuilder()
element = Mock()
element.position = (0.0, 0.0)
element.size = (200.0, 200.0)
start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}
assert builder.can_build(element, start_state)
def test_can_build_with_position_change(self):
"""Test that can_build returns True when position changes."""
builder = ResizeCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
element.size = (100.0, 100.0)
start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}
assert builder.can_build(element, start_state)
def test_can_build_with_both_changes(self):
"""Test that can_build returns True when both position and size change."""
builder = ResizeCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
element.size = (200.0, 200.0)
start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}
assert builder.can_build(element, start_state)
def test_can_build_with_no_change(self):
"""Test that can_build returns False when nothing changes."""
builder = ResizeCommandBuilder()
element = Mock()
element.position = (0.0, 0.0)
element.size = (100.0, 100.0)
start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}
assert not builder.can_build(element, start_state)
def test_build_creates_command(self):
"""Test that build creates a ResizeElementCommand."""
builder = ResizeCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
element.size = (200.0, 200.0)
start_state = {"position": (0.0, 0.0), "size": (100.0, 100.0)}
command = builder.build(element, start_state)
assert command is not None
assert command.element == element
class TestRotateCommandBuilder:
"""Tests for RotateCommandBuilder."""
def test_can_build_with_significant_change(self):
"""Test that can_build returns True for significant rotation changes."""
builder = RotateCommandBuilder()
element = Mock()
element.rotation = 45.0
start_state = {"rotation": 0.0}
assert builder.can_build(element, start_state)
def test_can_build_with_insignificant_change(self):
"""Test that can_build returns False for insignificant changes."""
builder = RotateCommandBuilder()
element = Mock()
element.rotation = 0.05
start_state = {"rotation": 0.0}
assert not builder.can_build(element, start_state)
def test_build_creates_command(self):
"""Test that build creates a RotateElementCommand."""
builder = RotateCommandBuilder()
element = Mock()
element.rotation = 45.0
start_state = {"rotation": 0.0}
command = builder.build(element, start_state)
assert command is not None
assert command.element == element
class TestImagePanCommandBuilder:
"""Tests for ImagePanCommandBuilder."""
def test_can_build_with_image_data(self):
"""Test that can_build works with ImageData elements."""
from pyPhotoAlbum.models import ImageData
builder = ImagePanCommandBuilder()
element = Mock(spec=ImageData)
element.crop_info = (0.1, 0.1, 0.9, 0.9)
start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}
assert builder.can_build(element, start_state)
def test_can_build_with_non_image_data(self):
"""Test that can_build returns False for non-ImageData elements."""
builder = ImagePanCommandBuilder()
element = Mock()
element.crop_info = (0.1, 0.1, 0.9, 0.9)
start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}
assert not builder.can_build(element, start_state)
def test_can_build_with_insignificant_change(self):
"""Test that can_build returns False for insignificant crop changes."""
from pyPhotoAlbum.models import ImageData
builder = ImagePanCommandBuilder()
element = Mock(spec=ImageData)
element.crop_info = (0.0001, 0.0001, 1.0, 1.0)
start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}
assert not builder.can_build(element, start_state)
def test_build_creates_command(self):
"""Test that build creates an AdjustImageCropCommand."""
from pyPhotoAlbum.models import ImageData
builder = ImagePanCommandBuilder()
element = Mock(spec=ImageData)
element.crop_info = (0.1, 0.1, 0.9, 0.9)
start_state = {"crop_info": (0.0, 0.0, 1.0, 1.0)}
command = builder.build(element, start_state)
assert command is not None
assert command.element == element
class TestCommandBuilderIntegration:
"""Integration tests for command builders."""
def test_builders_use_custom_detector(self):
"""Test that builders can use custom change detectors."""
detector = InteractionChangeDetector(threshold=10.0)
builder = MoveCommandBuilder(change_detector=detector)
element = Mock()
element.position = (5.0, 5.0)
start_state = {"position": (0.0, 0.0)}
# With high threshold, this should not build
assert not builder.can_build(element, start_state)
def test_builder_logging(self, capsys):
"""Test that builders log command creation."""
builder = MoveCommandBuilder()
element = Mock()
element.position = (10.0, 10.0)
start_state = {"position": (0.0, 0.0)}
builder.build(element, start_state)
captured = capsys.readouterr()
assert "Move command created" in captured.out