Spaces:
Sleeping
Sleeping
File size: 3,969 Bytes
9f084b2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | """Tests for geometry/polygon.py — polygon operations."""
from __future__ import annotations
import pytest
from src.app.geometry import polygon
class TestPolygonToBbox:
def test_rectangle(self) -> None:
poly = [(100, 200), (400, 200), (400, 250), (100, 250)]
result = polygon.polygon_to_bbox(poly)
assert result == (100, 200, 300, 50)
def test_triangle(self) -> None:
poly = [(0, 0), (100, 0), (50, 80)]
result = polygon.polygon_to_bbox(poly)
assert result == (0, 0, 100, 80)
def test_too_few_points(self) -> None:
with pytest.raises(ValueError, match="at least 3"):
polygon.polygon_to_bbox([(0, 0), (1, 1)])
class TestBboxToPolygon:
def test_rectangle(self) -> None:
result = polygon.bbox_to_polygon((10, 20, 100, 50))
assert result == [(10, 20), (110, 20), (110, 70), (10, 70)]
def test_roundtrip(self) -> None:
original = (10, 20, 100, 50)
poly = polygon.bbox_to_polygon(original)
back = polygon.polygon_to_bbox(poly)
assert back == original
class TestPolygonArea:
def test_unit_square(self) -> None:
poly = [(0, 0), (1, 0), (1, 1), (0, 1)]
assert polygon.polygon_area(poly) == pytest.approx(1.0)
def test_rectangle(self) -> None:
poly = [(0, 0), (10, 0), (10, 5), (0, 5)]
assert polygon.polygon_area(poly) == pytest.approx(50.0)
def test_triangle(self) -> None:
poly = [(0, 0), (10, 0), (5, 8)]
assert polygon.polygon_area(poly) == pytest.approx(40.0)
def test_too_few_points(self) -> None:
assert polygon.polygon_area([(0, 0)]) == 0.0
class TestPolygonCentroid:
def test_square(self) -> None:
poly = [(0, 0), (10, 0), (10, 10), (0, 10)]
cx, cy = polygon.polygon_centroid(poly)
assert cx == pytest.approx(5.0)
assert cy == pytest.approx(5.0)
def test_too_few(self) -> None:
with pytest.raises(ValueError, match="at least 3"):
polygon.polygon_centroid([(0, 0)])
class TestValidatePolygon:
def test_valid_polygon(self) -> None:
poly = [(0, 0), (10, 0), (10, 10), (0, 10)]
assert polygon.validate_polygon(poly) == []
def test_too_few_points(self) -> None:
warnings = polygon.validate_polygon([(0, 0)])
assert any("fewer than 3" in w for w in warnings)
def test_duplicate_consecutive(self) -> None:
poly = [(0, 0), (10, 0), (10, 0), (0, 10)]
warnings = polygon.validate_polygon(poly)
assert any("Duplicate" in w for w in warnings)
def test_negative_coords(self) -> None:
poly = [(-5, 0), (10, 0), (10, 10)]
warnings = polygon.validate_polygon(poly)
assert any("Negative" in w for w in warnings)
def test_degenerate_line(self) -> None:
poly = [(0, 0), (10, 0), (20, 0)]
warnings = polygon.validate_polygon(poly)
assert any("zero area" in w for w in warnings)
class TestClockwise:
"""In screen coordinates (y-axis down), the signed-area formula gives
positive for CW when vertices go right-then-down (like (0,0)→(0,10)→(10,10)→(10,0)).
"""
def test_clockwise(self) -> None:
# In screen coords: left-down-right-up = clockwise
cw = [(0, 0), (0, 10), (10, 10), (10, 0)]
assert polygon.is_clockwise(cw) is True
def test_counter_clockwise(self) -> None:
# In screen coords: right-down-left-up = counter-clockwise
ccw = [(0, 0), (10, 0), (10, 10), (0, 10)]
assert polygon.is_clockwise(ccw) is False
def test_ensure_clockwise_already(self) -> None:
cw = [(0, 0), (0, 10), (10, 10), (10, 0)]
result = polygon.ensure_clockwise(cw)
assert result == cw
def test_ensure_clockwise_reverses(self) -> None:
ccw = [(0, 0), (10, 0), (10, 10), (0, 10)]
result = polygon.ensure_clockwise(ccw)
assert polygon.is_clockwise(result) is True
|