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
255 lines
8.0 KiB
Python
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
|