Coverage for tests/components/test_crosshair_graphic_view.py: 100%
636 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import pytest
2from unittest.mock import Mock, patch, MagicMock, PropertyMock
3from PyQt6.QtCore import Qt, QPointF, QRectF, QEvent
4from PyQt6.QtGui import QMouseEvent, QPainter, QColor
5from PyQt6.QtWidgets import QGraphicsView, QGraphicsScene, QGraphicsLineItem, QWidget
7from main.components.crosshair_graphic_view import CrosshairGraphicsView
10@pytest.fixture
11def mock_parent_viewer():
12 """Mock for the parent viewer."""
13 parent = QWidget()
14 parent.img_data = Mock() # Simulates image data presence
15 parent.handle_click_coordinates = Mock()
16 parent.update_cross_view_lines = Mock()
17 return parent
20@pytest.fixture
21def graphics_scene():
22 """Creates a QGraphicsScene for testing."""
23 scene = QGraphicsScene()
24 scene.setSceneRect(0, 0, 512, 512)
25 return scene
28class TestCrosshairGraphicsViewInitialization:
29 """Tests for CrosshairGraphicsView initialization."""
31 def test_initialization_basic(self, qtbot):
32 """Test basic initialization."""
33 view = CrosshairGraphicsView(view_idx=0)
34 qtbot.addWidget(view)
36 assert view.view_idx == 0
37 assert view.parent_viewer is None
38 assert view.crosshair_h is None
39 assert view.crosshair_v is None
40 assert view.crosshair_visible is False
42 def test_initialization_with_parent(self, qtbot, mock_parent_viewer):
43 """Test initialization with parent."""
44 view = CrosshairGraphicsView(view_idx=1, parent=mock_parent_viewer)
45 qtbot.addWidget(view)
47 assert view.view_idx == 1
48 assert view.parent_viewer == mock_parent_viewer
50 def test_initialization_different_view_indices(self, qtbot):
51 """Test initialization with different indices."""
52 for idx in [0, 1, 2, 3, 10]:
53 view = CrosshairGraphicsView(view_idx=idx)
54 qtbot.addWidget(view)
55 assert view.view_idx == idx
57 def test_mouse_tracking_enabled(self, qtbot):
58 """Test that mouse tracking is enabled."""
59 view = CrosshairGraphicsView(view_idx=0)
60 qtbot.addWidget(view)
62 assert view.hasMouseTracking()
64 def test_drag_mode_set(self, qtbot):
65 """Test that drag mode is NoDrag."""
66 view = CrosshairGraphicsView(view_idx=0)
67 qtbot.addWidget(view)
69 assert view.dragMode() == QGraphicsView.DragMode.NoDrag
71 def test_render_hints_set(self, qtbot):
72 """Test that render hints are set."""
73 view = CrosshairGraphicsView(view_idx=0)
74 qtbot.addWidget(view)
76 hints = view.renderHints()
78 assert hints & QPainter.RenderHint.Antialiasing
79 assert hints & QPainter.RenderHint.SmoothPixmapTransform
82class TestSetupCrosshairs:
83 """Tests for the setup_crosshairs method."""
85 def test_setup_crosshairs_with_scene(self, qtbot, graphics_scene):
86 """Test setup crosshairs with scene."""
87 view = CrosshairGraphicsView(view_idx=0)
88 qtbot.addWidget(view)
89 view.setScene(graphics_scene)
91 view.setup_crosshairs()
93 assert view.crosshair_h is not None
94 assert view.crosshair_v is not None
95 assert isinstance(view.crosshair_h, QGraphicsLineItem)
96 assert isinstance(view.crosshair_v, QGraphicsLineItem)
97 assert not view.crosshair_h.isVisible()
98 assert not view.crosshair_v.isVisible()
100 def test_setup_crosshairs_without_scene(self, qtbot):
101 """Test setup crosshairs without scene."""
102 view = CrosshairGraphicsView(view_idx=0)
103 qtbot.addWidget(view)
105 # Should not crash
106 view.setup_crosshairs()
108 assert view.crosshair_h is None
109 assert view.crosshair_v is None
111 def test_setup_crosshairs_color(self, qtbot, graphics_scene):
112 """Test that crosshairs have the correct color."""
113 view = CrosshairGraphicsView(view_idx=0)
114 qtbot.addWidget(view)
115 view.setScene(graphics_scene)
117 view.setup_crosshairs()
119 # Check color (yellow with alpha 180)
120 pen_h = view.crosshair_h.pen()
121 pen_v = view.crosshair_v.pen()
123 assert pen_h.color() == QColor(255, 255, 0, 180)
124 assert pen_v.color() == QColor(255, 255, 0, 180)
125 assert pen_h.width() == 1
126 assert pen_v.width() == 1
128 def test_setup_crosshairs_multiple_calls(self, qtbot, graphics_scene):
129 """Test multiple calls to setup_crosshairs."""
130 view = CrosshairGraphicsView(view_idx=0)
131 qtbot.addWidget(view)
132 view.setScene(graphics_scene)
134 view.setup_crosshairs()
135 first_h = view.crosshair_h
136 first_v = view.crosshair_v
138 view.setup_crosshairs()
140 # New crosshairs should have been created
141 assert view.crosshair_h is not None
142 assert view.crosshair_v is not None
145class TestMouseMoveEvent:
146 """Tests for mouseMoveEvent."""
148 def test_mouse_move_emits_coordinate_changed(self, qtbot, mock_parent_viewer, graphics_scene):
149 """Test that mouse move emits coordinate_changed."""
150 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
151 qtbot.addWidget(view)
152 view.setScene(graphics_scene)
153 view.setup_crosshairs()
155 with qtbot.waitSignal(view.coordinate_changed, timeout=1000) as blocker:
156 event = QMouseEvent(
157 QMouseEvent.Type.MouseMove,
158 QPointF(100, 100),
159 Qt.MouseButton.NoButton,
160 Qt.MouseButton.NoButton,
161 Qt.KeyboardModifier.NoModifier
162 )
163 view.mouseMoveEvent(event)
165 # Check signal emitted
166 assert blocker.signal_triggered
167 args = blocker.args
168 assert args[0] == 0 # view_idx
170 def test_mouse_move_updates_crosshairs(self, qtbot, mock_parent_viewer, graphics_scene):
171 """Test that mouse move updates crosshairs."""
172 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
173 qtbot.addWidget(view)
174 view.setScene(graphics_scene)
175 view.setup_crosshairs()
177 with patch.object(view, 'update_crosshairs') as mock_update:
178 event = QMouseEvent(
179 QMouseEvent.Type.MouseMove,
180 QPointF(100, 150),
181 Qt.MouseButton.NoButton,
182 Qt.MouseButton.NoButton,
183 Qt.KeyboardModifier.NoModifier
184 )
185 view.mouseMoveEvent(event)
187 mock_update.assert_called_once()
189 def test_mouse_move_without_scene(self, qtbot, mock_parent_viewer):
190 """Test mouse move without scene."""
191 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
192 qtbot.addWidget(view)
194 # Should not crash
195 event = QMouseEvent(
196 QMouseEvent.Type.MouseMove,
197 QPointF(100, 100),
198 Qt.MouseButton.NoButton,
199 Qt.MouseButton.NoButton,
200 Qt.KeyboardModifier.NoModifier
201 )
202 view.mouseMoveEvent(event)
204 def test_mouse_move_without_parent_viewer(self, qtbot, graphics_scene):
205 """Test mouse move without parent viewer."""
206 view = CrosshairGraphicsView(view_idx=0)
207 qtbot.addWidget(view)
208 view.setScene(graphics_scene)
210 # Should not crash
211 event = QMouseEvent(
212 QMouseEvent.Type.MouseMove,
213 QPointF(100, 100),
214 Qt.MouseButton.NoButton,
215 Qt.MouseButton.NoButton,
216 Qt.KeyboardModifier.NoModifier
217 )
218 view.mouseMoveEvent(event)
220 def test_mouse_move_without_img_data(self, qtbot, graphics_scene, mock_parent_viewer):
221 """Test mouse move without img_data."""
222 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
223 qtbot.addWidget(view)
224 view.setScene(graphics_scene)
226 # Should not crash
227 event = QMouseEvent(
228 QMouseEvent.Type.MouseMove,
229 QPointF(100, 100),
230 Qt.MouseButton.NoButton,
231 Qt.MouseButton.NoButton,
232 Qt.KeyboardModifier.NoModifier
233 )
234 view.mouseMoveEvent(event)
236 def test_mouse_move_out_of_bounds(self, qtbot, mock_parent_viewer, graphics_scene):
237 """Test mouse move out of bounds."""
238 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
239 qtbot.addWidget(view)
240 view.setScene(graphics_scene)
241 view.setup_crosshairs()
243 with patch.object(view, 'update_crosshairs') as mock_update:
244 # Coordinates out of bounds
245 event = QMouseEvent(
246 QMouseEvent.Type.MouseMove,
247 QPointF(1000, 1000),
248 Qt.MouseButton.NoButton,
249 Qt.MouseButton.NoButton,
250 Qt.KeyboardModifier.NoModifier
251 )
252 view.mouseMoveEvent(event)
254 # update_crosshairs should not be called
255 mock_update.assert_not_called()
257 def test_mouse_move_negative_coordinates(self, qtbot, mock_parent_viewer, graphics_scene):
258 """Test mouse move with negative coordinates."""
259 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
260 qtbot.addWidget(view)
261 view.setScene(graphics_scene)
262 view.setup_crosshairs()
264 with patch.object(view, 'update_crosshairs') as mock_update:
265 event = QMouseEvent(
266 QMouseEvent.Type.MouseMove,
267 QPointF(-10, -10),
268 Qt.MouseButton.NoButton,
269 Qt.MouseButton.NoButton,
270 Qt.KeyboardModifier.NoModifier
271 )
272 view.mouseMoveEvent(event)
274 # update_crosshairs should not be called
275 mock_update.assert_not_called()
278class TestMousePressEvent:
279 """Tests for mousePressEvent."""
281 def test_left_click_calls_handle_click_coordinates(self, qtbot, mock_parent_viewer, graphics_scene):
282 """Test that left click calls handle_click_coordinates."""
283 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
284 qtbot.addWidget(view)
285 view.setScene(graphics_scene)
287 event = QMouseEvent(
288 QMouseEvent.Type.MouseButtonPress,
289 QPointF(100, 150),
290 Qt.MouseButton.LeftButton,
291 Qt.MouseButton.LeftButton,
292 Qt.KeyboardModifier.NoModifier
293 )
294 view.mousePressEvent(event)
296 mock_parent_viewer.handle_click_coordinates.assert_called_once()
297 args = mock_parent_viewer.handle_click_coordinates.call_args[0]
298 assert args[0] == 0 # view_idx
300 def test_right_click_ignored(self, qtbot, mock_parent_viewer, graphics_scene):
301 """Test that right click is ignored."""
302 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
303 qtbot.addWidget(view)
304 view.setScene(graphics_scene)
306 event = QMouseEvent(
307 QMouseEvent.Type.MouseButtonPress,
308 QPointF(100, 150),
309 Qt.MouseButton.RightButton,
310 Qt.MouseButton.RightButton,
311 Qt.KeyboardModifier.NoModifier
312 )
313 view.mousePressEvent(event)
315 # handle_click_coordinates should not be called
316 mock_parent_viewer.handle_click_coordinates.assert_not_called()
318 def test_middle_click_ignored(self, qtbot, mock_parent_viewer, graphics_scene):
319 """Test that middle click is ignored."""
320 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
321 qtbot.addWidget(view)
322 view.setScene(graphics_scene)
324 event = QMouseEvent(
325 QMouseEvent.Type.MouseButtonPress,
326 QPointF(100, 150),
327 Qt.MouseButton.MiddleButton,
328 Qt.MouseButton.MiddleButton,
329 Qt.KeyboardModifier.NoModifier
330 )
331 view.mousePressEvent(event)
333 mock_parent_viewer.handle_click_coordinates.assert_not_called()
335 def test_click_without_scene(self, qtbot, mock_parent_viewer):
336 """Test click without scene."""
337 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
338 qtbot.addWidget(view)
340 event = QMouseEvent(
341 QMouseEvent.Type.MouseButtonPress,
342 QPointF(100, 150),
343 Qt.MouseButton.LeftButton,
344 Qt.MouseButton.LeftButton,
345 Qt.KeyboardModifier.NoModifier
346 )
347 view.mousePressEvent(event)
349 # Should not crash
350 mock_parent_viewer.handle_click_coordinates.assert_not_called()
352 def test_click_without_parent_viewer(self, qtbot, graphics_scene):
353 """Test click without parent viewer."""
354 view = CrosshairGraphicsView(view_idx=0)
355 qtbot.addWidget(view)
356 view.setScene(graphics_scene)
358 # Should not crash
359 event = QMouseEvent(
360 QMouseEvent.Type.MouseButtonPress,
361 QPointF(100, 150),
362 Qt.MouseButton.LeftButton,
363 Qt.MouseButton.LeftButton,
364 Qt.KeyboardModifier.NoModifier
365 )
366 view.mousePressEvent(event)
368 def test_click_without_img_data(self, qtbot, graphics_scene, mock_parent_viewer):
369 """Test click without img_data."""
370 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
371 qtbot.addWidget(view)
372 view.setScene(graphics_scene)
374 event = QMouseEvent(
375 QMouseEvent.Type.MouseButtonPress,
376 QPointF(100, 150),
377 Qt.MouseButton.LeftButton,
378 Qt.MouseButton.LeftButton,
379 Qt.KeyboardModifier.NoModifier
380 )
381 view.mousePressEvent(event)
383 # Should not crash
384 assert True
386 def test_click_out_of_bounds(self, qtbot, mock_parent_viewer, graphics_scene):
387 """Test click out of bounds."""
388 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
389 qtbot.addWidget(view)
390 view.setScene(graphics_scene)
392 event = QMouseEvent(
393 QMouseEvent.Type.MouseButtonPress,
394 QPointF(1000, 1000),
395 Qt.MouseButton.LeftButton,
396 Qt.MouseButton.LeftButton,
397 Qt.KeyboardModifier.NoModifier
398 )
399 view.mousePressEvent(event)
401 # handle_click_coordinates should not be called
402 mock_parent_viewer.handle_click_coordinates.assert_not_called()
404 def test_click_at_scene_edges(self, qtbot, mock_parent_viewer, graphics_scene):
405 """Test click at scene edges."""
406 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
407 qtbot.addWidget(view)
408 view.setScene(graphics_scene)
410 # Scene corners
411 corners = [
412 (0, 0),
413 (511, 0),
414 (0, 511),
415 (511, 511)
416 ]
418 for x, y in corners:
419 mock_parent_viewer.handle_click_coordinates.reset_mock()
421 event = QMouseEvent(
422 QMouseEvent.Type.MouseButtonPress,
423 QPointF(x, y),
424 Qt.MouseButton.LeftButton,
425 Qt.MouseButton.LeftButton,
426 Qt.KeyboardModifier.NoModifier
427 )
428 view.mousePressEvent(event)
430 mock_parent_viewer.handle_click_coordinates.assert_called_once()
433class TestUpdateCrosshairs:
434 """Tests for update_crosshairs."""
436 def test_update_crosshairs_basic(self, qtbot, graphics_scene):
437 """Test basic update crosshairs."""
438 view = CrosshairGraphicsView(view_idx=0)
439 qtbot.addWidget(view)
440 view.setScene(graphics_scene)
441 view.setup_crosshairs()
443 view.update_crosshairs(100, 200)
445 # Check updated lines
446 line_h = view.crosshair_h.line()
447 line_v = view.crosshair_v.line()
449 assert line_h.y1() == 200
450 assert line_h.y2() == 200
451 assert line_v.x1() == 100
452 assert line_v.x2() == 100
454 assert view.crosshair_visible
455 assert view.crosshair_h.isVisible()
456 assert view.crosshair_v.isVisible()
458 def test_update_crosshairs_different_positions(self, qtbot, graphics_scene):
459 """Test update with different positions."""
460 view = CrosshairGraphicsView(view_idx=0)
461 qtbot.addWidget(view)
462 view.setScene(graphics_scene)
463 view.setup_crosshairs()
465 positions = [(0, 0), (256, 256), (511, 511), (100, 400)]
467 for x, y in positions:
468 view.update_crosshairs(x, y)
470 line_h = view.crosshair_h.line()
471 line_v = view.crosshair_v.line()
473 assert line_h.y1() == y
474 assert line_v.x1() == x
476 def test_update_crosshairs_without_scene(self, qtbot):
477 """Test update crosshairs without scene."""
478 view = CrosshairGraphicsView(view_idx=0)
479 qtbot.addWidget(view)
481 # Should not crash
482 view.update_crosshairs(100, 200)
484 def test_update_crosshairs_without_setup(self, qtbot, graphics_scene):
485 """Test update crosshairs without setup."""
486 view = CrosshairGraphicsView(view_idx=0)
487 qtbot.addWidget(view)
488 view.setScene(graphics_scene)
490 # Should not crash (crosshair_h and crosshair_v are None)
491 view.update_crosshairs(100, 200)
493 def test_update_crosshairs_makes_visible(self, qtbot, graphics_scene):
494 """Test that update makes crosshairs visible."""
495 view = CrosshairGraphicsView(view_idx=0)
496 qtbot.addWidget(view)
497 view.setScene(graphics_scene)
498 view.setup_crosshairs()
500 assert not view.crosshair_visible
501 assert not view.crosshair_h.isVisible()
503 view.update_crosshairs(100, 200)
505 assert view.crosshair_visible
506 assert view.crosshair_h.isVisible()
507 assert view.crosshair_v.isVisible()
509 def test_update_crosshairs_spans_full_scene(self, qtbot, graphics_scene):
510 """Test that crosshairs span the full scene."""
511 view = CrosshairGraphicsView(view_idx=0)
512 qtbot.addWidget(view)
513 view.setScene(graphics_scene)
514 view.setup_crosshairs()
516 view.update_crosshairs(256, 256)
518 line_h = view.crosshair_h.line()
519 line_v = view.crosshair_v.line()
521 # Horizontal line: from 0 to width
522 assert line_h.x1() == 0
523 assert line_h.x2() == 512
525 # Vertical line: from 0 to height
526 assert line_v.y1() == 0
527 assert line_v.y2() == 512
530class TestSetCrosshairPosition:
531 """Tests for set_crosshair_position."""
533 def test_set_crosshair_position_basic(self, qtbot, graphics_scene):
534 """Test basic set crosshair position."""
535 view = CrosshairGraphicsView(view_idx=0)
536 qtbot.addWidget(view)
537 view.setScene(graphics_scene)
538 view.setup_crosshairs()
540 view.set_crosshair_position(150, 250)
542 line_h = view.crosshair_h.line()
543 line_v = view.crosshair_v.line()
545 assert line_h.y1() == 250
546 assert line_v.x1() == 150
547 assert view.crosshair_visible
548 assert view.crosshair_h.isVisible()
549 assert view.crosshair_v.isVisible()
551 def test_set_crosshair_position_out_of_bounds(self, qtbot, graphics_scene):
552 """Test set position out of bounds."""
553 view = CrosshairGraphicsView(view_idx=0)
554 qtbot.addWidget(view)
555 view.setScene(graphics_scene)
556 view.setup_crosshairs()
558 # Out of bounds coordinates should not update
559 view.set_crosshair_position(1000, 1000)
561 # Crosshairs should not be visible if they were hidden
562 # (depends on implementation, might not update)
564 def test_set_crosshair_position_without_scene(self, qtbot):
565 """Test set position without scene."""
566 view = CrosshairGraphicsView(view_idx=0)
567 qtbot.addWidget(view)
569 # Should not crash
570 view.set_crosshair_position(100, 200)
572 def test_set_crosshair_position_without_setup(self, qtbot, graphics_scene):
573 """Test set position without setup."""
574 view = CrosshairGraphicsView(view_idx=0)
575 qtbot.addWidget(view)
576 view.setScene(graphics_scene)
578 # Should not crash
579 view.set_crosshair_position(100, 200)
581 def test_set_crosshair_position_at_origin(self, qtbot, graphics_scene):
582 """Test set position at origin."""
583 view = CrosshairGraphicsView(view_idx=0)
584 qtbot.addWidget(view)
585 view.setScene(graphics_scene)
586 view.setup_crosshairs()
588 view.set_crosshair_position(0, 0)
590 line_h = view.crosshair_h.line()
591 line_v = view.crosshair_v.line()
593 assert line_h.y1() == 0
594 assert line_v.x1() == 0
596 def test_set_crosshair_position_at_max(self, qtbot, graphics_scene):
597 """Test set position at max."""
598 view = CrosshairGraphicsView(view_idx=0)
599 qtbot.addWidget(view)
600 view.setScene(graphics_scene)
601 view.setup_crosshairs()
603 view.set_crosshair_position(511, 511)
605 line_h = view.crosshair_h.line()
606 line_v = view.crosshair_v.line()
608 assert line_h.y1() == 511
609 assert line_v.x1() == 511
612class TestLeaveEvent:
613 """Tests for leaveEvent."""
615 def test_leave_event_calls_update_cross_view_lines(self, qtbot, mock_parent_viewer, graphics_scene):
616 """Test that leaveEvent calls update_cross_view_lines."""
617 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
618 qtbot.addWidget(view)
619 view.setScene(graphics_scene)
620 view.setup_crosshairs()
622 event = QEvent(QEvent.Type.Leave)
623 view.leaveEvent(event)
625 mock_parent_viewer.update_cross_view_lines.assert_called_once()
627 def test_leave_event_without_crosshairs(self, qtbot, mock_parent_viewer):
628 """Test leaveEvent without crosshairs."""
629 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
630 qtbot.addWidget(view)
632 # Should not crash
633 event = QEvent(QEvent.Type.Leave)
634 view.leaveEvent(event)
636 def test_leave_event_without_parent_viewer(self, qtbot, graphics_scene):
637 """Test leaveEvent without parent viewer."""
638 view = CrosshairGraphicsView(view_idx=0)
639 qtbot.addWidget(view)
640 view.setScene(graphics_scene)
641 view.setup_crosshairs()
643 # Should not crash
644 event = Mock()
645 # Might fail if parent_viewer is None
646 try:
647 view.leaveEvent(event)
648 except AttributeError:
649 pass # Acceptable if parent_viewer is None
652class TestSignals:
653 """Tests for signals."""
655 def test_coordinate_changed_signal_exists(self, qtbot):
656 """Test that the coordinate_changed signal exists."""
657 view = CrosshairGraphicsView(view_idx=0)
658 qtbot.addWidget(view)
660 assert hasattr(view, 'coordinate_changed')
662 def test_coordinate_changed_signal_parameters(self, qtbot, mock_parent_viewer, graphics_scene):
663 """Test coordinate_changed signal parameters."""
664 view = CrosshairGraphicsView(view_idx=2, parent=mock_parent_viewer)
665 qtbot.addWidget(view)
666 view.setScene(graphics_scene)
667 view.setup_crosshairs()
669 with qtbot.waitSignal(view.coordinate_changed) as blocker:
670 event = QMouseEvent(
671 QMouseEvent.Type.MouseMove,
672 QPointF(123, 456),
673 Qt.MouseButton.NoButton,
674 Qt.MouseButton.NoButton,
675 Qt.KeyboardModifier.NoModifier
676 )
677 view.mouseMoveEvent(event)
679 args = blocker.args
680 assert len(args) == 3
681 assert args[0] == 2 # view_idx
682 # args[1] and args[2] are x, y (might vary due to mapToScene)
685class TestEdgeCases:
686 """Tests for edge cases."""
688 def test_very_small_scene(self, qtbot):
689 """Test with very small scene."""
690 view = CrosshairGraphicsView(view_idx=0)
691 qtbot.addWidget(view)
693 small_scene = QGraphicsScene()
694 small_scene.setSceneRect(0, 0, 10, 10)
695 view.setScene(small_scene)
696 view.setup_crosshairs()
698 view.update_crosshairs(5, 5)
700 # Should not crash
701 assert view.crosshair_visible
703 def test_very_large_scene(self, qtbot):
704 """Test with very large scene."""
705 view = CrosshairGraphicsView(view_idx=0)
706 qtbot.addWidget(view)
708 large_scene = QGraphicsScene()
709 large_scene.setSceneRect(0, 0, 10000, 10000)
710 view.setScene(large_scene)
711 view.setup_crosshairs()
713 view.update_crosshairs(5000, 5000)
715 assert view.crosshair_visible
717 def test_rectangular_scene(self, qtbot):
718 """Test with rectangular scene."""
719 view = CrosshairGraphicsView(view_idx=0)
720 qtbot.addWidget(view)
722 rect_scene = QGraphicsScene()
723 rect_scene.setSceneRect(0, 0, 800, 400)
724 view.setScene(rect_scene)
725 view.setup_crosshairs()
727 view.update_crosshairs(400, 200)
729 line_h = view.crosshair_h.line()
730 line_v = view.crosshair_v.line()
732 assert line_h.x2() == 800
733 assert line_v.y2() == 400
735 def test_rapid_mouse_movements(self, qtbot, mock_parent_viewer, graphics_scene):
736 """Test rapid mouse movements."""
737 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
738 qtbot.addWidget(view)
739 view.setScene(graphics_scene)
740 view.setup_crosshairs()
742 # Simulate 100 rapid movements
743 for i in range(100):
744 x = (i * 5) % 512
745 y = (i * 3) % 512
746 event = QMouseEvent(
747 QMouseEvent.Type.MouseMove,
748 QPointF(x, y),
749 Qt.MouseButton.NoButton,
750 Qt.MouseButton.NoButton,
751 Qt.KeyboardModifier.NoModifier
752 )
753 view.mouseMoveEvent(event)
755 # Should not crash
756 assert view.crosshair_visible
758 def test_rapid_clicks(self, qtbot, mock_parent_viewer, graphics_scene):
759 """Test rapid clicks."""
760 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
761 qtbot.addWidget(view)
762 view.setScene(graphics_scene)
764 for i in range(50):
765 x = (i * 10) % 512
766 y = (i * 7) % 512
767 event = QMouseEvent(
768 QMouseEvent.Type.MouseButtonPress,
769 QPointF(x, y),
770 Qt.MouseButton.LeftButton,
771 Qt.MouseButton.LeftButton,
772 Qt.KeyboardModifier.NoModifier
773 )
774 view.mousePressEvent(event)
776 # handle_click_coordinates should have been called 50 times
777 assert mock_parent_viewer.handle_click_coordinates.call_count == 50
779 def test_scene_change(self, qtbot):
780 """Test scene change."""
781 view = CrosshairGraphicsView(view_idx=0)
782 qtbot.addWidget(view)
784 # First scene
785 scene1 = QGraphicsScene()
786 scene1.setSceneRect(0, 0, 256, 256)
787 view.setScene(scene1)
788 view.setup_crosshairs()
790 crosshair_h1 = view.crosshair_h
792 # Second scene
793 scene2 = QGraphicsScene()
794 scene2.setSceneRect(0, 0, 512, 512)
795 view.setScene(scene2)
796 view.setup_crosshairs()
798 # Crosshairs should be new
799 assert view.crosshair_h is not crosshair_h1
801 def test_coordinate_precision(self, qtbot, mock_parent_viewer, graphics_scene):
802 """Test coordinate precision with float."""
803 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
804 qtbot.addWidget(view)
805 view.setScene(graphics_scene)
807 # Float coordinates should be converted to int
808 event = QMouseEvent(
809 QMouseEvent.Type.MouseButtonPress,
810 QPointF(100.7, 200.3),
811 Qt.MouseButton.LeftButton,
812 Qt.MouseButton.LeftButton,
813 Qt.KeyboardModifier.NoModifier
814 )
815 view.mousePressEvent(event)
817 # Check that they were passed as int
818 args = mock_parent_viewer.handle_click_coordinates.call_args[0]
819 assert isinstance(args[1], int)
820 assert isinstance(args[2], int)
823class TestIntegration:
824 """Integration tests."""
826 def test_full_interaction_flow(self, qtbot, mock_parent_viewer, graphics_scene):
827 """Test full flow: setup -> mouse move -> click."""
828 view = CrosshairGraphicsView(view_idx=1, parent=mock_parent_viewer)
829 qtbot.addWidget(view)
830 view.setScene(graphics_scene)
831 view.setup_crosshairs()
833 # Mouse move
834 with qtbot.waitSignal(view.coordinate_changed):
835 move_event = QMouseEvent(
836 QMouseEvent.Type.MouseMove,
837 QPointF(150, 200),
838 Qt.MouseButton.NoButton,
839 Qt.MouseButton.NoButton,
840 Qt.KeyboardModifier.NoModifier
841 )
842 view.mouseMoveEvent(move_event)
844 assert view.crosshair_visible
846 # Click
847 click_event = QMouseEvent(
848 QMouseEvent.Type.MouseButtonPress,
849 QPointF(150, 200),
850 Qt.MouseButton.LeftButton,
851 Qt.MouseButton.LeftButton,
852 Qt.KeyboardModifier.NoModifier
853 )
854 view.mousePressEvent(click_event)
856 mock_parent_viewer.handle_click_coordinates.assert_called_once()
858 def test_multiple_views_interaction(self, qtbot, mock_parent_viewer):
859 """Test interaction between multiple views."""
860 scene1 = QGraphicsScene()
861 scene1.setSceneRect(0, 0, 512, 512)
863 scene2 = QGraphicsScene()
864 scene2.setSceneRect(0, 0, 512, 512)
866 view1 = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
867 view2 = CrosshairGraphicsView(view_idx=1, parent=mock_parent_viewer)
869 qtbot.addWidget(view1)
870 qtbot.addWidget(view2)
872 view1.setScene(scene1)
873 view2.setScene(scene2)
875 view1.setup_crosshairs()
876 view2.setup_crosshairs()
878 # Interaction on view1
879 event = QMouseEvent(
880 QMouseEvent.Type.MouseButtonPress,
881 QPointF(100, 100),
882 Qt.MouseButton.LeftButton,
883 Qt.MouseButton.LeftButton,
884 Qt.KeyboardModifier.NoModifier
885 )
886 view1.mousePressEvent(event)
888 # Check correct view_idx
889 args1 = mock_parent_viewer.handle_click_coordinates.call_args[0]
890 assert args1[0] == 0
892 # Interaction on view2
893 mock_parent_viewer.handle_click_coordinates.reset_mock()
894 view2.mousePressEvent(event)
896 args2 = mock_parent_viewer.handle_click_coordinates.call_args[0]
897 assert args2[0] == 1
899 def test_crosshair_synchronization(self, qtbot, graphics_scene):
900 """Test crosshair synchronization."""
901 view = CrosshairGraphicsView(view_idx=0)
902 qtbot.addWidget(view)
903 view.setScene(graphics_scene)
904 view.setup_crosshairs()
906 # Update via update_crosshairs
907 view.update_crosshairs(100, 200)
909 line_h1 = view.crosshair_h.line()
910 assert line_h1.y1() == 200
912 # Update via set_crosshair_position
913 view.set_crosshair_position(150, 250)
915 line_h2 = view.crosshair_h.line()
916 assert line_h2.y1() == 250
919class TestBoundaryConditions:
920 """Tests for boundary conditions."""
922 def test_click_at_zero_zero(self, qtbot, mock_parent_viewer, graphics_scene):
923 """Test click at (0, 0)."""
924 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
925 qtbot.addWidget(view)
926 view.setScene(graphics_scene)
928 event = QMouseEvent(
929 QMouseEvent.Type.MouseButtonPress,
930 QPointF(0, 0),
931 Qt.MouseButton.LeftButton,
932 Qt.MouseButton.LeftButton,
933 Qt.KeyboardModifier.NoModifier
934 )
935 view.mousePressEvent(event)
937 mock_parent_viewer.handle_click_coordinates.assert_called_once()
939 def test_click_at_max_coordinates(self, qtbot, mock_parent_viewer, graphics_scene):
940 """Test click at maximum coordinates."""
941 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
942 qtbot.addWidget(view)
943 view.setScene(graphics_scene)
945 # Scene is 512x512, so valid max is 511,511
946 event = QMouseEvent(
947 QMouseEvent.Type.MouseButtonPress,
948 QPointF(511, 511),
949 Qt.MouseButton.LeftButton,
950 Qt.MouseButton.LeftButton,
951 Qt.KeyboardModifier.NoModifier
952 )
953 view.mousePressEvent(event)
955 mock_parent_viewer.handle_click_coordinates.assert_called_once()
957 def test_click_just_outside_bounds(self, qtbot, mock_parent_viewer, graphics_scene):
958 """Test click just outside bounds."""
959 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
960 qtbot.addWidget(view)
961 view.setScene(graphics_scene)
963 # 512 is outside (scene is 0-511)
964 event = QMouseEvent(
965 QMouseEvent.Type.MouseButtonPress,
966 QPointF(512, 512),
967 Qt.MouseButton.LeftButton,
968 Qt.MouseButton.LeftButton,
969 Qt.KeyboardModifier.NoModifier
970 )
971 view.mousePressEvent(event)
973 # Should not call handle_click_coordinates
974 mock_parent_viewer.handle_click_coordinates.assert_not_called()
977class TestViewIndexHandling:
978 """Tests for view_idx handling."""
980 def test_different_view_indices_in_signals(self, qtbot, mock_parent_viewer, graphics_scene):
981 """Test that view_idx is correct in signals."""
982 indices = [0, 1, 2, 5, 10]
984 for idx in indices:
985 view = CrosshairGraphicsView(view_idx=idx, parent=mock_parent_viewer)
986 qtbot.addWidget(view)
987 view.setScene(graphics_scene)
989 with qtbot.waitSignal(view.coordinate_changed) as blocker:
990 event = QMouseEvent(
991 QMouseEvent.Type.MouseMove,
992 QPointF(100, 100),
993 Qt.MouseButton.NoButton,
994 Qt.MouseButton.NoButton,
995 Qt.KeyboardModifier.NoModifier
996 )
997 view.mouseMoveEvent(event)
999 assert blocker.args[0] == idx
1001 def test_view_idx_persists(self, qtbot):
1002 """Test that view_idx persists."""
1003 view = CrosshairGraphicsView(view_idx=7)
1004 qtbot.addWidget(view)
1006 assert view.view_idx == 7
1008 # After various operations
1009 scene = QGraphicsScene()
1010 view.setScene(scene)
1011 view.setup_crosshairs()
1013 assert view.view_idx == 7
1016class TestMemoryAndCleanup:
1017 """Tests for memory and cleanup."""
1019 def test_scene_items_cleanup(self, qtbot, graphics_scene):
1020 """Test cleanup of scene items."""
1021 view = CrosshairGraphicsView(view_idx=0)
1022 qtbot.addWidget(view)
1023 view.setScene(graphics_scene)
1025 initial_items = len(graphics_scene.items())
1027 view.setup_crosshairs()
1029 # 2 items (lines) should have been added
1030 assert len(graphics_scene.items()) == initial_items + 2
1032 def test_multiple_setup_doesnt_leak(self, qtbot, graphics_scene):
1033 """Test that multiple setups do not cause memory leaks."""
1034 view = CrosshairGraphicsView(view_idx=0)
1035 qtbot.addWidget(view)
1036 view.setScene(graphics_scene)
1038 # Multiple setups
1039 for _ in range(10):
1040 view.setup_crosshairs()
1042 # Old items should have been removed/replaced
1043 # (depends on implementation)
1044 assert True
1047class TestRenderHints:
1048 """Tests for render hints."""
1050 def test_antialiasing_enabled(self, qtbot):
1051 """Test that antialiasing is enabled."""
1052 view = CrosshairGraphicsView(view_idx=0)
1053 qtbot.addWidget(view)
1055 hints = view.renderHints()
1056 assert hints & QPainter.RenderHint.Antialiasing
1058 def test_smooth_pixmap_transform_enabled(self, qtbot):
1059 """Test that smooth pixmap transform is enabled."""
1060 view = CrosshairGraphicsView(view_idx=0)
1061 qtbot.addWidget(view)
1063 hints = view.renderHints()
1064 assert hints & QPainter.RenderHint.SmoothPixmapTransform
1066 def test_render_hints_persist(self, qtbot, graphics_scene):
1067 """Test that render hints persist after operations."""
1068 view = CrosshairGraphicsView(view_idx=0)
1069 qtbot.addWidget(view)
1071 view.setScene(graphics_scene)
1072 view.setup_crosshairs()
1074 hints = view.renderHints()
1075 assert hints & QPainter.RenderHint.Antialiasing
1076 assert hints & QPainter.RenderHint.SmoothPixmapTransform
1079class TestErrorHandling:
1080 """Tests for error handling."""
1082 def test_mapToScene_with_invalid_point(self, qtbot, mock_parent_viewer, graphics_scene):
1083 """Test mapToScene with invalid point."""
1084 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
1085 qtbot.addWidget(view)
1086 view.setScene(graphics_scene)
1088 # Point far out of bounds
1089 event = QMouseEvent(
1090 QMouseEvent.Type.MouseMove,
1091 QPointF(999999, 999999),
1092 Qt.MouseButton.NoButton,
1093 Qt.MouseButton.NoButton,
1094 Qt.KeyboardModifier.NoModifier
1095 )
1097 # Should not crash
1098 view.mouseMoveEvent(event)
1100 def test_operations_without_initialization(self, qtbot):
1101 """Test operations without complete initialization."""
1102 view = CrosshairGraphicsView(view_idx=0)
1103 qtbot.addWidget(view)
1105 # Various operations without scene/crosshair setup
1106 view.update_crosshairs(100, 100)
1107 view.set_crosshair_position(100, 100)
1109 event = QMouseEvent(
1110 QMouseEvent.Type.MouseMove,
1111 QPointF(100, 100),
1112 Qt.MouseButton.NoButton,
1113 Qt.MouseButton.NoButton,
1114 Qt.KeyboardModifier.NoModifier
1115 )
1116 view.mouseMoveEvent(event)
1118 # Should not crash
1119 assert True
1122class TestStateConsistency:
1123 """Tests for state consistency."""
1125 def test_crosshair_visible_state_consistency(self, qtbot, graphics_scene):
1126 """Test crosshair_visible state consistency."""
1127 view = CrosshairGraphicsView(view_idx=0)
1128 qtbot.addWidget(view)
1129 view.setScene(graphics_scene)
1130 view.setup_crosshairs()
1132 # Initially not visible
1133 assert not view.crosshair_visible
1134 assert not view.crosshair_h.isVisible()
1136 # After update, visible
1137 view.update_crosshairs(100, 100)
1138 assert view.crosshair_visible
1139 assert view.crosshair_h.isVisible()
1140 assert view.crosshair_v.isVisible()
1142 def test_crosshair_position_consistency(self, qtbot, graphics_scene):
1143 """Test crosshair position consistency."""
1144 view = CrosshairGraphicsView(view_idx=0)
1145 qtbot.addWidget(view)
1146 view.setScene(graphics_scene)
1147 view.setup_crosshairs()
1149 # Set position
1150 view.update_crosshairs(123, 456)
1152 line_h = view.crosshair_h.line()
1153 line_v = view.crosshair_v.line()
1155 # Check consistency
1156 assert line_h.y1() == line_h.y2() == 456
1157 assert line_v.x1() == line_v.x2() == 123
1160class TestDocumentation:
1161 """Tests for documentation."""
1163 def test_class_docstring(self):
1164 """Test that the class has a docstring."""
1165 assert CrosshairGraphicsView.__doc__ is not None
1166 assert "crosshair" in CrosshairGraphicsView.__doc__.lower()
1168 def test_setup_crosshairs_docstring(self, qtbot):
1169 """Test that setup_crosshairs has a docstring."""
1170 view = CrosshairGraphicsView(view_idx=0)
1171 qtbot.addWidget(view)
1173 assert view.setup_crosshairs.__doc__ is not None
1176class TestAccessibility:
1177 """Tests for accessibility."""
1179 def test_crosshair_color_visible(self, qtbot, graphics_scene):
1180 """Test that crosshair color is visible."""
1181 view = CrosshairGraphicsView(view_idx=0)
1182 qtbot.addWidget(view)
1183 view.setScene(graphics_scene)
1184 view.setup_crosshairs()
1186 pen = view.crosshair_h.pen()
1187 color = pen.color()
1189 # Yellow with alpha should be visible
1190 assert color.alpha() == 180
1191 assert color.red() == 255
1192 assert color.green() == 255
1193 assert color.blue() == 0
1195 def test_crosshair_width_appropriate(self, qtbot, graphics_scene):
1196 """Test that crosshair width is appropriate."""
1197 view = CrosshairGraphicsView(view_idx=0)
1198 qtbot.addWidget(view)
1199 view.setScene(graphics_scene)
1200 view.setup_crosshairs()
1202 pen_h = view.crosshair_h.pen()
1203 pen_v = view.crosshair_v.pen()
1205 # Width 1 should be visible but not intrusive
1206 assert pen_h.width() == 1
1207 assert pen_v.width() == 1
1210class TestPerformance:
1211 """Tests for performance."""
1213 def test_many_coordinate_updates(self, qtbot, mock_parent_viewer, graphics_scene):
1214 """Test with many coordinate updates."""
1215 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
1216 qtbot.addWidget(view)
1217 view.setScene(graphics_scene)
1218 view.setup_crosshairs()
1220 # 1000 updates
1221 for i in range(1000):
1222 view.update_crosshairs(i % 512, (i * 2) % 512)
1224 # Should not cause performance issues
1225 assert view.crosshair_visible
1227 def test_signal_emission_performance(self, qtbot, mock_parent_viewer, graphics_scene):
1228 """Test signal emission performance."""
1229 view = CrosshairGraphicsView(view_idx=0, parent=mock_parent_viewer)
1230 qtbot.addWidget(view)
1231 view.setScene(graphics_scene)
1233 signal_count = 0
1235 def count_signal(*args):
1236 nonlocal signal_count
1237 signal_count += 1
1239 view.coordinate_changed.connect(count_signal)
1241 # Many mouse events
1242 for i in range(100):
1243 event = QMouseEvent(
1244 QMouseEvent.Type.MouseMove,
1245 QPointF(i % 512, (i * 2) % 512),
1246 Qt.MouseButton.NoButton,
1247 Qt.MouseButton.NoButton,
1248 Qt.KeyboardModifier.NoModifier
1249 )
1250 view.mouseMoveEvent(event)
1252 # Signal should have been emitted many times
1253 assert signal_count > 0