test_main_window.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import pytest
  2. from PySide6.QtWidgets import QMainWindow
  3. from hookvision.models import HookState, Config
  4. from hookvision.main_window import MainWindow
  5. from hookvision.config_loader import load_config
  6. class TestParseAIObjects:
  7. def test_none_payload(self, qapp, sample_config_path):
  8. config = load_config(sample_config_path)
  9. mw = MainWindow(config)
  10. assert mw.parse_ai_objects(None, 640, 360) == []
  11. assert mw.parse_ai_objects({}, 640, 360) == []
  12. def test_empty_objects(self, qapp, sample_config_path):
  13. config = load_config(sample_config_path)
  14. mw = MainWindow(config)
  15. result = mw.parse_ai_objects({"width": 640, "height": 360, "objects": []}, 640, 360)
  16. assert result == []
  17. def test_valid_objects(self, qapp, sample_config_path):
  18. config = load_config(sample_config_path)
  19. mw = MainWindow(config)
  20. payload = {
  21. "width": 640,
  22. "height": 360,
  23. "objects": [
  24. "0|100|200|50|80|hoist",
  25. "0|300|150|40|60|cup",
  26. ]
  27. }
  28. result = mw.parse_ai_objects(payload, 640, 360)
  29. assert len(result) == 2
  30. assert result[0]["label"] == "hook"
  31. assert result[0]["x"] == 100
  32. assert result[0]["y"] == 200
  33. assert result[0]["color"] == (0, 0, 255)
  34. assert result[1]["label"] == "lug"
  35. assert result[1]["color"] == (255, 0, 0)
  36. def test_scale_transformation(self, qapp, sample_config_path):
  37. config = load_config(sample_config_path)
  38. mw = MainWindow(config)
  39. payload = {
  40. "width": 320,
  41. "height": 180,
  42. "objects": ["0|50|50|100|100|hoist"],
  43. }
  44. result = mw.parse_ai_objects(payload, 640, 360)
  45. assert result[0]["x"] == 100
  46. assert result[0]["y"] == 100
  47. assert result[0]["w"] == 200
  48. assert result[0]["h"] == 200
  49. def test_ready_object_excluded(self, qapp, sample_config_path):
  50. config = load_config(sample_config_path)
  51. mw = MainWindow(config)
  52. payload = {
  53. "width": 640,
  54. "height": 360,
  55. "objects": [
  56. "0|100|200|50|80|hoist",
  57. "0|300|150|40|60|ready",
  58. ]
  59. }
  60. result = mw.parse_ai_objects(payload, 640, 360, include_ready=False)
  61. assert len(result) == 1
  62. assert result[0]["label"] == "hook"
  63. def test_ready_object_included(self, qapp, sample_config_path):
  64. config = load_config(sample_config_path)
  65. mw = MainWindow(config)
  66. payload = {
  67. "width": 640,
  68. "height": 360,
  69. "objects": [
  70. "0|100|200|50|80|ready",
  71. ]
  72. }
  73. result = mw.parse_ai_objects(payload, 640, 360, include_ready=True)
  74. assert len(result) == 1
  75. assert "ready" in result[0]["label"]
  76. def test_malformed_objects_skipped(self, qapp, sample_config_path):
  77. config = load_config(sample_config_path)
  78. mw = MainWindow(config)
  79. payload = {
  80. "width": 640,
  81. "height": 360,
  82. "objects": [
  83. "not_enough_parts",
  84. "0|abc|def|ghi|jkl|hoist",
  85. "0|100|200|50|80|hoist",
  86. ]
  87. }
  88. result = mw.parse_ai_objects(payload, 640, 360)
  89. assert len(result) == 1
  90. def test_zero_orig_size_no_division_error(self, qapp, sample_config_path):
  91. config = load_config(sample_config_path)
  92. mw = MainWindow(config)
  93. payload = {
  94. "width": 0,
  95. "height": 0,
  96. "objects": ["0|100|200|50|80|hoist"],
  97. }
  98. result = mw.parse_ai_objects(payload, 640, 360)
  99. assert len(result) == 1
  100. assert result[0]["x"] == 100
  101. assert result[0]["y"] == 200
  102. class TestRectsIntersect:
  103. def setup_method(self):
  104. self.mw = MainWindow(Config())
  105. def test_overlapping(self, qapp):
  106. mw = MainWindow(Config())
  107. a = {"x": 0, "y": 0, "w": 100, "h": 100}
  108. b = {"x": 50, "y": 50, "w": 100, "h": 100}
  109. assert mw.rects_intersect(a, b) is True
  110. def test_non_overlapping(self, qapp):
  111. mw = MainWindow(Config())
  112. a = {"x": 0, "y": 0, "w": 50, "h": 50}
  113. b = {"x": 100, "y": 100, "w": 50, "h": 50}
  114. assert mw.rects_intersect(a, b) is False
  115. def test_touching_edge_intersecting(self, qapp):
  116. mw = MainWindow(Config())
  117. a = {"x": 0, "y": 0, "w": 50, "h": 50}
  118. b = {"x": 50, "y": 0, "w": 50, "h": 50}
  119. # 边沿相接触视为相交(用于挂钩/吊耳判定的宽松策略)
  120. assert mw.rects_intersect(a, b) is True
  121. def test_one_contains_other(self, qapp):
  122. mw = MainWindow(Config())
  123. a = {"x": 0, "y": 0, "w": 200, "h": 200}
  124. b = {"x": 50, "y": 50, "w": 50, "h": 50}
  125. assert mw.rects_intersect(a, b) is True
  126. class TestHasIntersectingPair:
  127. def test_no_pairs(self, qapp):
  128. mw = MainWindow(Config())
  129. assert mw._has_intersecting_pair(None, None) is False
  130. def test_left_pair_intersects(self, qapp):
  131. mw = MainWindow(Config())
  132. left_pair = (
  133. {"x": 0, "y": 0, "w": 100, "h": 100},
  134. {"x": 50, "y": 50, "w": 100, "h": 100},
  135. )
  136. assert mw._has_intersecting_pair(left_pair, None) is True
  137. def test_right_pair_intersects(self, qapp):
  138. mw = MainWindow(Config())
  139. right_pair = (
  140. {"x": 0, "y": 0, "w": 100, "h": 100},
  141. {"x": 50, "y": 50, "w": 100, "h": 100},
  142. )
  143. assert mw._has_intersecting_pair(None, right_pair) is True
  144. def test_pair_not_intersecting(self, qapp):
  145. mw = MainWindow(Config())
  146. pair = (
  147. {"x": 0, "y": 0, "w": 50, "h": 50},
  148. {"x": 100, "y": 100, "w": 50, "h": 50},
  149. )
  150. assert mw._has_intersecting_pair(pair, None) is False