Coverage for tests/components/test_circular_progress_bar.py: 100%
429 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
3from PyQt6.QtCore import Qt, QRectF, QSize
4from PyQt6.QtGui import QColor, QPainter, QPaintEvent, QFont
5from PyQt6.QtWidgets import QWidget
7from main.components.circular_progress_bar import CircularProgress
10class TestCircularProgressInitialization:
11 """Tests for the initialization of CircularProgress."""
13 def test_initialization_default(self, qtbot):
14 """Test initialization with default color."""
15 widget = CircularProgress()
16 qtbot.addWidget(widget)
18 assert widget.value == 0
19 assert widget.color == QColor("#3498DB")
21 def test_initialization_custom_color(self, qtbot):
22 """Test initialization with custom color."""
23 widget = CircularProgress(color="#FF0000")
24 qtbot.addWidget(widget)
26 assert widget.value == 0
27 assert widget.color == QColor("#FF0000")
29 def test_initialization_color_variations(self, qtbot):
30 """Test initialization with various color formats."""
31 # Hex color
32 widget1 = CircularProgress(color="#00FF00")
33 qtbot.addWidget(widget1)
34 assert widget1.color == QColor("#00FF00")
36 # Named color
37 widget2 = CircularProgress(color="red")
38 qtbot.addWidget(widget2)
39 assert widget2.color == QColor("red")
41 # RGB format
42 widget3 = CircularProgress(color="#AABBCC")
43 qtbot.addWidget(widget3)
44 assert widget3.color == QColor("#AABBCC")
47class TestSetValue:
48 """Tests for the setValue method."""
50 def test_set_value_basic(self, qtbot):
51 """Test setValue with a valid value."""
52 widget = CircularProgress()
53 qtbot.addWidget(widget)
55 widget.setValue(50)
57 assert widget.value == 50
59 def test_set_value_zero(self, qtbot):
60 """Test setValue with value 0."""
61 widget = CircularProgress()
62 qtbot.addWidget(widget)
64 widget.setValue(0)
66 assert widget.value == 0
68 def test_set_value_hundred(self, qtbot):
69 """Test setValue with value 100."""
70 widget = CircularProgress()
71 qtbot.addWidget(widget)
73 widget.setValue(100)
75 assert widget.value == 100
77 def test_set_value_negative_clamped(self, qtbot):
78 """Test that negative values are clamped to 0."""
79 widget = CircularProgress()
80 qtbot.addWidget(widget)
82 widget.setValue(-10)
84 assert widget.value == 0
86 def test_set_value_over_hundred_clamped(self, qtbot):
87 """Test that values >100 are clamped to 100."""
88 widget = CircularProgress()
89 qtbot.addWidget(widget)
91 widget.setValue(150)
93 assert widget.value == 100
95 def test_set_value_extreme_negative(self, qtbot):
96 """Test with extremely negative value."""
97 widget = CircularProgress()
98 qtbot.addWidget(widget)
100 widget.setValue(-999999)
102 assert widget.value == 0
104 def test_set_value_extreme_positive(self, qtbot):
105 """Test with extremely positive value."""
106 widget = CircularProgress()
107 qtbot.addWidget(widget)
109 widget.setValue(999999)
111 assert widget.value == 100
113 def test_set_value_sequence(self, qtbot):
114 """Test sequence of setValue calls."""
115 widget = CircularProgress()
116 qtbot.addWidget(widget)
118 values = [0, 25, 50, 75, 100, 50, 0]
119 for val in values:
120 widget.setValue(val)
121 assert widget.value == val
123 def test_set_value_triggers_update(self, qtbot):
124 """Test that setValue calls update()."""
125 widget = CircularProgress()
126 qtbot.addWidget(widget)
128 with patch.object(widget, 'update') as mock_update:
129 widget.setValue(50)
131 mock_update.assert_called_once()
133 def test_set_value_float_converted_to_int(self, qtbot):
134 """Test with a float value (should be converted)."""
135 widget = CircularProgress()
136 qtbot.addWidget(widget)
138 widget.setValue(50.7)
140 # Value should be treated as int
141 assert widget.value == 50
144class TestSetColor:
145 """Tests for the setColor method."""
147 def test_set_color_string(self, qtbot):
148 """Test setColor with a string."""
149 widget = CircularProgress()
150 qtbot.addWidget(widget)
152 widget.setColor("#FF0000")
154 assert widget.color == QColor("#FF0000")
156 def test_set_color_qcolor(self, qtbot):
157 """Test setColor with a QColor."""
158 widget = CircularProgress()
159 qtbot.addWidget(widget)
161 new_color = QColor(255, 0, 0)
162 widget.setColor(new_color)
164 assert widget.color == new_color
166 def test_set_color_named(self, qtbot):
167 """Test setColor with a named color."""
168 widget = CircularProgress()
169 qtbot.addWidget(widget)
171 widget.setColor("blue")
173 assert widget.color == QColor("blue")
175 def test_set_color_rgb_hex(self, qtbot):
176 """Test setColor with RGB hex format."""
177 widget = CircularProgress()
178 qtbot.addWidget(widget)
180 widget.setColor("#AABBCC")
182 assert widget.color == QColor("#AABBCC")
184 def test_set_color_triggers_update(self, qtbot):
185 """Test that setColor calls update()."""
186 widget = CircularProgress()
187 qtbot.addWidget(widget)
189 with patch.object(widget, 'update') as mock_update:
190 widget.setColor("#FF0000")
192 mock_update.assert_called_once()
194 def test_set_color_multiple_times(self, qtbot):
195 """Test multiple color changes."""
196 widget = CircularProgress()
197 qtbot.addWidget(widget)
199 colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00"]
200 for color in colors:
201 widget.setColor(color)
202 assert widget.color == QColor(color)
204 def test_set_color_invalid_string(self, qtbot):
205 """Test setColor with an invalid string."""
206 widget = CircularProgress()
207 qtbot.addWidget(widget)
209 # QColor should handle invalid strings
210 widget.setColor("not_a_valid_color")
212 # Color should still be a QColor object (even if invalid)
213 assert isinstance(widget.color, QColor)
216class TestSizeHint:
217 """Tests for the sizeHint method."""
219 def test_size_hint_with_parent(self, qtbot):
220 """Test sizeHint when it has a parent."""
221 parent = QWidget()
222 parent.resize(200, 200)
223 qtbot.addWidget(parent)
225 widget = CircularProgress()
226 widget.setParent(parent)
228 size_hint = widget.sizeHint()
230 # Should return the parent's size
231 assert size_hint == parent.size()
233 def test_size_hint_without_parent(self, qtbot):
234 """Test sizeHint without a parent."""
235 widget = CircularProgress()
236 qtbot.addWidget(widget)
238 size_hint = widget.sizeHint()
240 # Should return QWidget's default size hint
241 assert isinstance(size_hint, QSize)
243 def test_size_hint_parent_resized(self, qtbot):
244 """Test sizeHint when the parent is resized."""
245 parent = QWidget()
246 parent.resize(300, 300)
247 qtbot.addWidget(parent)
249 widget = CircularProgress()
250 widget.setParent(parent)
252 size_hint1 = widget.sizeHint()
254 # Resize parent
255 parent.resize(400, 400)
257 size_hint2 = widget.sizeHint()
259 # Size hint should update
260 assert size_hint1 != size_hint2
261 assert size_hint2 == parent.size()
263class TestPaintEvent:
264 """Tests for the paintEvent method."""
266 def test_paint_event_called(self, qtbot):
267 """Test that paintEvent is called."""
268 widget = CircularProgress()
269 qtbot.addWidget(widget)
270 widget.show()
272 # Force a paint event
273 with patch.object(widget, 'paintEvent', wraps=widget.paintEvent) as mock_paint:
274 widget.repaint()
275 qtbot.wait(100)
277 # paintEvent should have been called (it may be called multiple times)
278 assert mock_paint.called
280 def test_paint_event_creates_painter(self, qtbot):
281 """Test that paintEvent creates a QPainter."""
282 widget = CircularProgress()
283 qtbot.addWidget(widget)
284 widget.resize(200, 200)
285 widget.show()
287 # Use a non-intrusive mock that does not break QPainter
288 with patch("main.components.circular_progress_bar.QPainter", autospec=True) as mock_painter_class:
289 event = QPaintEvent(widget.rect())
290 widget.paintEvent(event)
292 # Verify that QPainter's constructor was called at least once
293 mock_painter_class.assert_called_with(widget)
295 def test_paint_event_different_values(self, qtbot):
296 """Test paintEvent with different values."""
297 widget = CircularProgress()
298 qtbot.addWidget(widget)
299 widget.resize(200, 200)
300 widget.show()
302 values = [0, 25, 50, 75, 100]
303 for val in values:
304 widget.setValue(val)
306 # Force repaint
307 event = QPaintEvent(widget.rect())
308 # Should not crash
309 widget.paintEvent(event)
311 def test_paint_event_different_colors(self, qtbot):
312 """Test paintEvent with different colors."""
313 widget = CircularProgress()
314 qtbot.addWidget(widget)
315 widget.resize(200, 200)
316 widget.show()
318 colors = ["#FF0000", "#00FF00", "#0000FF"]
319 for color in colors:
320 widget.setColor(color)
322 event = QPaintEvent(widget.rect())
323 # Should not crash
324 widget.paintEvent(event)
326 def test_paint_event_small_size(self, qtbot):
327 """Test paintEvent with small widget size."""
328 widget = CircularProgress()
329 qtbot.addWidget(widget)
330 widget.resize(20, 20)
331 widget.show()
333 widget.setValue(50)
334 event = QPaintEvent(widget.rect())
336 # Should not crash with small sizes
337 widget.paintEvent(event)
339 def test_paint_event_large_size(self, qtbot):
340 """Test paintEvent with large widget size."""
341 widget = CircularProgress()
342 qtbot.addWidget(widget)
343 widget.resize(1000, 1000)
344 widget.show()
346 widget.setValue(50)
347 event = QPaintEvent(widget.rect())
349 # Should not crash with large sizes
350 widget.paintEvent(event)
352 def test_paint_event_rectangular_shape(self, qtbot):
353 """Test paintEvent with a rectangular (non-square) shape."""
354 widget = CircularProgress()
355 qtbot.addWidget(widget)
357 # Rectangular shape
358 widget.resize(300, 200)
359 widget.show()
361 widget.setValue(50)
362 event = QPaintEvent(widget.rect())
364 # Should properly center the circle
365 widget.paintEvent(event)
367 def test_paint_event_wide_rectangle(self, qtbot):
368 """Test paintEvent with a wide rectangle."""
369 widget = CircularProgress()
370 qtbot.addWidget(widget)
372 widget.resize(400, 200)
373 widget.show()
375 widget.setValue(75)
376 event = QPaintEvent(widget.rect())
378 widget.paintEvent(event)
380 def test_paint_event_tall_rectangle(self, qtbot):
381 """Test paintEvent with a tall rectangle."""
382 widget = CircularProgress()
383 qtbot.addWidget(widget)
385 widget.resize(200, 400)
386 widget.show()
388 widget.setValue(25)
389 event = QPaintEvent(widget.rect())
391 widget.paintEvent(event)
394class TestEdgeCases:
395 """Tests for edge cases."""
397 def test_very_small_widget(self, qtbot):
398 """Test with a very small widget (1x1)."""
399 widget = CircularProgress()
400 qtbot.addWidget(widget)
401 widget.resize(1, 1)
402 widget.show()
404 widget.setValue(50)
405 event = QPaintEvent(widget.rect())
407 # Should not crash
408 widget.paintEvent(event)
410 def test_zero_width(self, qtbot):
411 """Test with zero width."""
412 widget = CircularProgress()
413 qtbot.addWidget(widget)
414 widget.resize(0, 100)
415 widget.show()
417 widget.setValue(50)
418 event = QPaintEvent(widget.rect())
420 # Should not crash
421 widget.paintEvent(event)
423 def test_zero_height(self, qtbot):
424 """Test with zero height."""
425 widget = CircularProgress()
426 qtbot.addWidget(widget)
427 widget.resize(100, 0)
428 widget.show()
430 widget.setValue(50)
431 event = QPaintEvent(widget.rect())
433 # Should not crash
434 widget.paintEvent(event)
436 def test_rapid_value_changes(self, qtbot):
437 """Test with rapid value changes."""
438 widget = CircularProgress()
439 qtbot.addWidget(widget)
440 widget.resize(200, 200)
441 widget.show()
443 # Change value rapidly 100 times
444 for i in range(100):
445 widget.setValue(i % 101)
447 # Should not crash
448 assert widget.value >= 0 and widget.value <= 100
450 def test_rapid_color_changes(self, qtbot):
451 """Test with rapid color changes."""
452 widget = CircularProgress()
453 qtbot.addWidget(widget)
454 widget.resize(200, 200)
455 widget.show()
457 colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF"]
459 # Change colors rapidly
460 for _ in range(20):
461 for color in colors:
462 widget.setColor(color)
464 # Should not crash
465 assert isinstance(widget.color, QColor)
467 def test_simultaneous_value_and_color_change(self, qtbot):
468 """Test simultaneous value and color changes."""
469 widget = CircularProgress()
470 qtbot.addWidget(widget)
471 widget.resize(200, 200)
472 widget.show()
474 for i in range(50):
475 widget.setValue(i * 2)
476 widget.setColor(f"#{i * 5:02x}{i * 3:02x}{i * 4:02x}")
478 # Should not crash
479 assert True
481 def test_unicode_in_existing_files(self, qtbot):
482 """Test unicode characters in existing_files."""
483 widget = CircularProgress()
484 qtbot.addWidget(widget)
486 widget.existing_files = ["файл.txt", "文件.txt", "αρχείο.txt"]
488 widget.setValue(50)
490 # Should not crash
491 assert len(widget.existing_files) == 3
494class TestIntegration:
495 """Integration tests."""
497 def test_full_progress_cycle(self, qtbot):
498 """Test a full progress cycle 0–100."""
499 widget = CircularProgress()
500 qtbot.addWidget(widget)
501 widget.resize(200, 200)
502 widget.show()
504 # Simulate full progress
505 for i in range(0, 101, 10):
506 widget.setValue(i)
507 event = QPaintEvent(widget.rect())
508 widget.paintEvent(event)
509 qtbot.wait(10)
511 assert widget.value == 100
513 def test_progress_with_color_change(self, qtbot):
514 """Test progress with color change mid-way."""
515 widget = CircularProgress()
516 qtbot.addWidget(widget)
517 widget.resize(200, 200)
518 widget.show()
520 # 0–50%: blue
521 for i in range(0, 51, 10):
522 widget.setValue(i)
524 # Switch to green
525 widget.setColor("#00FF00")
527 # 51–100%: green
528 for i in range(51, 102, 10):
529 widget.setValue(i)
531 assert widget.value == 100
532 assert widget.color == QColor("#00FF00")
534 def test_multiple_complete_cycles(self, qtbot):
535 """Test multiple full cycles."""
536 widget = CircularProgress()
537 qtbot.addWidget(widget)
538 widget.resize(200, 200)
539 widget.show()
541 # 3 complete cycles
542 for cycle in range(3):
543 for i in range(0, 101, 25):
544 widget.setValue(i)
546 # Reset
547 widget.setValue(0)
549 assert widget.value == 0
551 def test_widget_in_parent_container(self, qtbot):
552 """Test widget inside a parent container."""
553 parent = QWidget()
554 parent.resize(400, 400)
555 qtbot.addWidget(parent)
557 widget = CircularProgress()
558 widget.setParent(parent)
559 parent.show()
561 widget.setValue(50)
563 # Verify sizeHint uses parent's size
564 assert widget.sizeHint() == parent.size()
567class TestRenderingCalculations:
568 """Test for rendering calculations."""
570 def test_square_widget_centering(self, qtbot):
571 """Test centering in squared widget."""
572 widget = CircularProgress()
573 qtbot.addWidget(widget)
574 widget.resize(200, 200)
575 widget.show()
577 widget.setValue(50)
579 width = widget.width()
580 height = widget.height()
581 size = min(width, height)
583 assert size == 200 # Con widget quadrato il minimo è 200
585 def test_rectangular_widget_centering(self, qtbot):
586 """Test centering in rectangular widget."""
587 widget = CircularProgress()
588 qtbot.addWidget(widget)
589 widget.resize(300, 200)
590 widget.show()
592 widget.setValue(50)
594 width = widget.width()
595 height = widget.height()
596 size = min(width, height)
598 assert size == 200
600 def test_pen_width_calculation(self, qtbot):
601 """Test pen width calculation."""
602 widget = CircularProgress()
603 qtbot.addWidget(widget)
605 sizes = [50, 100, 200, 500]
607 for s in sizes:
608 widget.resize(s, s)
609 widget.show()
611 size = min(widget.width(), widget.height())
612 expected_pen_width = max(5, int(size / 12))
614 assert expected_pen_width >= 5
616 def test_font_size_calculation(self, qtbot):
617 """Test font size calculation."""
618 widget = CircularProgress()
619 qtbot.addWidget(widget)
621 sizes = [50, 100, 200, 500]
623 for s in sizes:
624 widget.resize(s, s)
625 widget.show()
627 size = min(widget.width(), widget.height())
628 expected_font_size = max(5, int(size / 9))
630 assert expected_font_size >= 5
633class TestValueBoundaries:
634 """Test for value boundaries."""
636 def test_boundary_value_zero(self, qtbot):
637 """Test for 0."""
638 widget = CircularProgress()
639 qtbot.addWidget(widget)
641 widget.setValue(-1)
642 assert widget.value == 0
644 widget.setValue(0)
645 assert widget.value == 0
647 def test_boundary_value_hundred(self, qtbot):
648 """Test for 100."""
649 widget = CircularProgress()
650 qtbot.addWidget(widget)
652 widget.setValue(100)
653 assert widget.value == 100
655 widget.setValue(101)
656 assert widget.value == 100
658 def test_all_integer_values(self, qtbot):
659 """Test for every value between 0 and 100."""
660 widget = CircularProgress()
661 qtbot.addWidget(widget)
663 for i in range(101):
664 widget.setValue(i)
665 assert widget.value == i
668class TestColorFormats:
669 """Test for color formats."""
671 def test_hex_color_3_digits(self, qtbot):
672 """Test color 3 hex digits."""
673 widget = CircularProgress()
674 qtbot.addWidget(widget)
676 widget.setColor("#F00")
678 assert isinstance(widget.color, QColor)
680 def test_hex_color_6_digits(self, qtbot):
681 """Test color 6 hex digits."""
682 widget = CircularProgress()
683 qtbot.addWidget(widget)
685 widget.setColor("#FF0000")
687 assert widget.color == QColor("#FF0000")
689 def test_named_colors(self, qtbot):
690 """Test color with names."""
691 widget = CircularProgress()
692 qtbot.addWidget(widget)
694 named_colors = ["red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"]
696 for color_name in named_colors:
697 widget.setColor(color_name)
698 assert isinstance(widget.color, QColor)
700 def test_qcolor_from_rgb(self, qtbot):
701 """Test QColor with RGB color"""
702 widget = CircularProgress()
703 qtbot.addWidget(widget)
705 color = QColor(255, 128, 64)
706 widget.setColor(color)
708 assert widget.color == color
711class TestMemoryAndPerformance:
712 """Test for memory and performace."""
714 def test_no_memory_leak_on_repeated_updates(self, qtbot):
715 """Test for memory leak."""
716 widget = CircularProgress()
717 qtbot.addWidget(widget)
718 widget.resize(200, 200)
719 widget.show()
721 for _ in range(1000):
722 widget.setValue(50)
724 assert widget.value == 50
726 def test_rapid_repaints(self, qtbot):
727 """Test repaint."""
728 widget = CircularProgress()
729 qtbot.addWidget(widget)
730 widget.resize(200, 200)
731 widget.show()
733 for i in range(100):
734 widget.setValue(i % 101)
735 widget.repaint()
737 assert True