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

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 

6 

7from main.components.circular_progress_bar import CircularProgress 

8 

9 

10class TestCircularProgressInitialization: 

11 """Tests for the initialization of CircularProgress.""" 

12 

13 def test_initialization_default(self, qtbot): 

14 """Test initialization with default color.""" 

15 widget = CircularProgress() 

16 qtbot.addWidget(widget) 

17 

18 assert widget.value == 0 

19 assert widget.color == QColor("#3498DB") 

20 

21 def test_initialization_custom_color(self, qtbot): 

22 """Test initialization with custom color.""" 

23 widget = CircularProgress(color="#FF0000") 

24 qtbot.addWidget(widget) 

25 

26 assert widget.value == 0 

27 assert widget.color == QColor("#FF0000") 

28 

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") 

35 

36 # Named color 

37 widget2 = CircularProgress(color="red") 

38 qtbot.addWidget(widget2) 

39 assert widget2.color == QColor("red") 

40 

41 # RGB format 

42 widget3 = CircularProgress(color="#AABBCC") 

43 qtbot.addWidget(widget3) 

44 assert widget3.color == QColor("#AABBCC") 

45 

46 

47class TestSetValue: 

48 """Tests for the setValue method.""" 

49 

50 def test_set_value_basic(self, qtbot): 

51 """Test setValue with a valid value.""" 

52 widget = CircularProgress() 

53 qtbot.addWidget(widget) 

54 

55 widget.setValue(50) 

56 

57 assert widget.value == 50 

58 

59 def test_set_value_zero(self, qtbot): 

60 """Test setValue with value 0.""" 

61 widget = CircularProgress() 

62 qtbot.addWidget(widget) 

63 

64 widget.setValue(0) 

65 

66 assert widget.value == 0 

67 

68 def test_set_value_hundred(self, qtbot): 

69 """Test setValue with value 100.""" 

70 widget = CircularProgress() 

71 qtbot.addWidget(widget) 

72 

73 widget.setValue(100) 

74 

75 assert widget.value == 100 

76 

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) 

81 

82 widget.setValue(-10) 

83 

84 assert widget.value == 0 

85 

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) 

90 

91 widget.setValue(150) 

92 

93 assert widget.value == 100 

94 

95 def test_set_value_extreme_negative(self, qtbot): 

96 """Test with extremely negative value.""" 

97 widget = CircularProgress() 

98 qtbot.addWidget(widget) 

99 

100 widget.setValue(-999999) 

101 

102 assert widget.value == 0 

103 

104 def test_set_value_extreme_positive(self, qtbot): 

105 """Test with extremely positive value.""" 

106 widget = CircularProgress() 

107 qtbot.addWidget(widget) 

108 

109 widget.setValue(999999) 

110 

111 assert widget.value == 100 

112 

113 def test_set_value_sequence(self, qtbot): 

114 """Test sequence of setValue calls.""" 

115 widget = CircularProgress() 

116 qtbot.addWidget(widget) 

117 

118 values = [0, 25, 50, 75, 100, 50, 0] 

119 for val in values: 

120 widget.setValue(val) 

121 assert widget.value == val 

122 

123 def test_set_value_triggers_update(self, qtbot): 

124 """Test that setValue calls update().""" 

125 widget = CircularProgress() 

126 qtbot.addWidget(widget) 

127 

128 with patch.object(widget, 'update') as mock_update: 

129 widget.setValue(50) 

130 

131 mock_update.assert_called_once() 

132 

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) 

137 

138 widget.setValue(50.7) 

139 

140 # Value should be treated as int 

141 assert widget.value == 50 

142 

143 

144class TestSetColor: 

145 """Tests for the setColor method.""" 

146 

147 def test_set_color_string(self, qtbot): 

148 """Test setColor with a string.""" 

149 widget = CircularProgress() 

150 qtbot.addWidget(widget) 

151 

152 widget.setColor("#FF0000") 

153 

154 assert widget.color == QColor("#FF0000") 

155 

156 def test_set_color_qcolor(self, qtbot): 

157 """Test setColor with a QColor.""" 

158 widget = CircularProgress() 

159 qtbot.addWidget(widget) 

160 

161 new_color = QColor(255, 0, 0) 

162 widget.setColor(new_color) 

163 

164 assert widget.color == new_color 

165 

166 def test_set_color_named(self, qtbot): 

167 """Test setColor with a named color.""" 

168 widget = CircularProgress() 

169 qtbot.addWidget(widget) 

170 

171 widget.setColor("blue") 

172 

173 assert widget.color == QColor("blue") 

174 

175 def test_set_color_rgb_hex(self, qtbot): 

176 """Test setColor with RGB hex format.""" 

177 widget = CircularProgress() 

178 qtbot.addWidget(widget) 

179 

180 widget.setColor("#AABBCC") 

181 

182 assert widget.color == QColor("#AABBCC") 

183 

184 def test_set_color_triggers_update(self, qtbot): 

185 """Test that setColor calls update().""" 

186 widget = CircularProgress() 

187 qtbot.addWidget(widget) 

188 

189 with patch.object(widget, 'update') as mock_update: 

190 widget.setColor("#FF0000") 

191 

192 mock_update.assert_called_once() 

193 

194 def test_set_color_multiple_times(self, qtbot): 

195 """Test multiple color changes.""" 

196 widget = CircularProgress() 

197 qtbot.addWidget(widget) 

198 

199 colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00"] 

200 for color in colors: 

201 widget.setColor(color) 

202 assert widget.color == QColor(color) 

203 

204 def test_set_color_invalid_string(self, qtbot): 

205 """Test setColor with an invalid string.""" 

206 widget = CircularProgress() 

207 qtbot.addWidget(widget) 

208 

209 # QColor should handle invalid strings 

210 widget.setColor("not_a_valid_color") 

211 

212 # Color should still be a QColor object (even if invalid) 

213 assert isinstance(widget.color, QColor) 

214 

215 

216class TestSizeHint: 

217 """Tests for the sizeHint method.""" 

218 

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) 

224 

225 widget = CircularProgress() 

226 widget.setParent(parent) 

227 

228 size_hint = widget.sizeHint() 

229 

230 # Should return the parent's size 

231 assert size_hint == parent.size() 

232 

233 def test_size_hint_without_parent(self, qtbot): 

234 """Test sizeHint without a parent.""" 

235 widget = CircularProgress() 

236 qtbot.addWidget(widget) 

237 

238 size_hint = widget.sizeHint() 

239 

240 # Should return QWidget's default size hint 

241 assert isinstance(size_hint, QSize) 

242 

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) 

248 

249 widget = CircularProgress() 

250 widget.setParent(parent) 

251 

252 size_hint1 = widget.sizeHint() 

253 

254 # Resize parent 

255 parent.resize(400, 400) 

256 

257 size_hint2 = widget.sizeHint() 

258 

259 # Size hint should update 

260 assert size_hint1 != size_hint2 

261 assert size_hint2 == parent.size() 

262 

263class TestPaintEvent: 

264 """Tests for the paintEvent method.""" 

265 

266 def test_paint_event_called(self, qtbot): 

267 """Test that paintEvent is called.""" 

268 widget = CircularProgress() 

269 qtbot.addWidget(widget) 

270 widget.show() 

271 

272 # Force a paint event 

273 with patch.object(widget, 'paintEvent', wraps=widget.paintEvent) as mock_paint: 

274 widget.repaint() 

275 qtbot.wait(100) 

276 

277 # paintEvent should have been called (it may be called multiple times) 

278 assert mock_paint.called 

279 

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() 

286 

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) 

291 

292 # Verify that QPainter's constructor was called at least once 

293 mock_painter_class.assert_called_with(widget) 

294 

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() 

301 

302 values = [0, 25, 50, 75, 100] 

303 for val in values: 

304 widget.setValue(val) 

305 

306 # Force repaint 

307 event = QPaintEvent(widget.rect()) 

308 # Should not crash 

309 widget.paintEvent(event) 

310 

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() 

317 

318 colors = ["#FF0000", "#00FF00", "#0000FF"] 

319 for color in colors: 

320 widget.setColor(color) 

321 

322 event = QPaintEvent(widget.rect()) 

323 # Should not crash 

324 widget.paintEvent(event) 

325 

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() 

332 

333 widget.setValue(50) 

334 event = QPaintEvent(widget.rect()) 

335 

336 # Should not crash with small sizes 

337 widget.paintEvent(event) 

338 

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() 

345 

346 widget.setValue(50) 

347 event = QPaintEvent(widget.rect()) 

348 

349 # Should not crash with large sizes 

350 widget.paintEvent(event) 

351 

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) 

356 

357 # Rectangular shape 

358 widget.resize(300, 200) 

359 widget.show() 

360 

361 widget.setValue(50) 

362 event = QPaintEvent(widget.rect()) 

363 

364 # Should properly center the circle 

365 widget.paintEvent(event) 

366 

367 def test_paint_event_wide_rectangle(self, qtbot): 

368 """Test paintEvent with a wide rectangle.""" 

369 widget = CircularProgress() 

370 qtbot.addWidget(widget) 

371 

372 widget.resize(400, 200) 

373 widget.show() 

374 

375 widget.setValue(75) 

376 event = QPaintEvent(widget.rect()) 

377 

378 widget.paintEvent(event) 

379 

380 def test_paint_event_tall_rectangle(self, qtbot): 

381 """Test paintEvent with a tall rectangle.""" 

382 widget = CircularProgress() 

383 qtbot.addWidget(widget) 

384 

385 widget.resize(200, 400) 

386 widget.show() 

387 

388 widget.setValue(25) 

389 event = QPaintEvent(widget.rect()) 

390 

391 widget.paintEvent(event) 

392 

393 

394class TestEdgeCases: 

395 """Tests for edge cases.""" 

396 

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() 

403 

404 widget.setValue(50) 

405 event = QPaintEvent(widget.rect()) 

406 

407 # Should not crash 

408 widget.paintEvent(event) 

409 

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() 

416 

417 widget.setValue(50) 

418 event = QPaintEvent(widget.rect()) 

419 

420 # Should not crash 

421 widget.paintEvent(event) 

422 

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() 

429 

430 widget.setValue(50) 

431 event = QPaintEvent(widget.rect()) 

432 

433 # Should not crash 

434 widget.paintEvent(event) 

435 

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() 

442 

443 # Change value rapidly 100 times 

444 for i in range(100): 

445 widget.setValue(i % 101) 

446 

447 # Should not crash 

448 assert widget.value >= 0 and widget.value <= 100 

449 

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() 

456 

457 colors = ["#FF0000", "#00FF00", "#0000FF", "#FFFF00", "#FF00FF", "#00FFFF"] 

458 

459 # Change colors rapidly 

460 for _ in range(20): 

461 for color in colors: 

462 widget.setColor(color) 

463 

464 # Should not crash 

465 assert isinstance(widget.color, QColor) 

466 

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() 

473 

474 for i in range(50): 

475 widget.setValue(i * 2) 

476 widget.setColor(f"#{i * 5:02x}{i * 3:02x}{i * 4:02x}") 

477 

478 # Should not crash 

479 assert True 

480 

481 def test_unicode_in_existing_files(self, qtbot): 

482 """Test unicode characters in existing_files.""" 

483 widget = CircularProgress() 

484 qtbot.addWidget(widget) 

485 

486 widget.existing_files = ["файл.txt", "文件.txt", "αρχείο.txt"] 

487 

488 widget.setValue(50) 

489 

490 # Should not crash 

491 assert len(widget.existing_files) == 3 

492 

493 

494class TestIntegration: 

495 """Integration tests.""" 

496 

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() 

503 

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) 

510 

511 assert widget.value == 100 

512 

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() 

519 

520 # 0–50%: blue 

521 for i in range(0, 51, 10): 

522 widget.setValue(i) 

523 

524 # Switch to green 

525 widget.setColor("#00FF00") 

526 

527 # 51–100%: green 

528 for i in range(51, 102, 10): 

529 widget.setValue(i) 

530 

531 assert widget.value == 100 

532 assert widget.color == QColor("#00FF00") 

533 

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() 

540 

541 # 3 complete cycles 

542 for cycle in range(3): 

543 for i in range(0, 101, 25): 

544 widget.setValue(i) 

545 

546 # Reset 

547 widget.setValue(0) 

548 

549 assert widget.value == 0 

550 

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) 

556 

557 widget = CircularProgress() 

558 widget.setParent(parent) 

559 parent.show() 

560 

561 widget.setValue(50) 

562 

563 # Verify sizeHint uses parent's size 

564 assert widget.sizeHint() == parent.size() 

565 

566 

567class TestRenderingCalculations: 

568 """Test for rendering calculations.""" 

569 

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() 

576 

577 widget.setValue(50) 

578 

579 width = widget.width() 

580 height = widget.height() 

581 size = min(width, height) 

582 

583 assert size == 200 # Con widget quadrato il minimo è 200 

584 

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() 

591 

592 widget.setValue(50) 

593 

594 width = widget.width() 

595 height = widget.height() 

596 size = min(width, height) 

597 

598 assert size == 200 

599 

600 def test_pen_width_calculation(self, qtbot): 

601 """Test pen width calculation.""" 

602 widget = CircularProgress() 

603 qtbot.addWidget(widget) 

604 

605 sizes = [50, 100, 200, 500] 

606 

607 for s in sizes: 

608 widget.resize(s, s) 

609 widget.show() 

610 

611 size = min(widget.width(), widget.height()) 

612 expected_pen_width = max(5, int(size / 12)) 

613 

614 assert expected_pen_width >= 5 

615 

616 def test_font_size_calculation(self, qtbot): 

617 """Test font size calculation.""" 

618 widget = CircularProgress() 

619 qtbot.addWidget(widget) 

620 

621 sizes = [50, 100, 200, 500] 

622 

623 for s in sizes: 

624 widget.resize(s, s) 

625 widget.show() 

626 

627 size = min(widget.width(), widget.height()) 

628 expected_font_size = max(5, int(size / 9)) 

629 

630 assert expected_font_size >= 5 

631 

632 

633class TestValueBoundaries: 

634 """Test for value boundaries.""" 

635 

636 def test_boundary_value_zero(self, qtbot): 

637 """Test for 0.""" 

638 widget = CircularProgress() 

639 qtbot.addWidget(widget) 

640 

641 widget.setValue(-1) 

642 assert widget.value == 0 

643 

644 widget.setValue(0) 

645 assert widget.value == 0 

646 

647 def test_boundary_value_hundred(self, qtbot): 

648 """Test for 100.""" 

649 widget = CircularProgress() 

650 qtbot.addWidget(widget) 

651 

652 widget.setValue(100) 

653 assert widget.value == 100 

654 

655 widget.setValue(101) 

656 assert widget.value == 100 

657 

658 def test_all_integer_values(self, qtbot): 

659 """Test for every value between 0 and 100.""" 

660 widget = CircularProgress() 

661 qtbot.addWidget(widget) 

662 

663 for i in range(101): 

664 widget.setValue(i) 

665 assert widget.value == i 

666 

667 

668class TestColorFormats: 

669 """Test for color formats.""" 

670 

671 def test_hex_color_3_digits(self, qtbot): 

672 """Test color 3 hex digits.""" 

673 widget = CircularProgress() 

674 qtbot.addWidget(widget) 

675 

676 widget.setColor("#F00") 

677 

678 assert isinstance(widget.color, QColor) 

679 

680 def test_hex_color_6_digits(self, qtbot): 

681 """Test color 6 hex digits.""" 

682 widget = CircularProgress() 

683 qtbot.addWidget(widget) 

684 

685 widget.setColor("#FF0000") 

686 

687 assert widget.color == QColor("#FF0000") 

688 

689 def test_named_colors(self, qtbot): 

690 """Test color with names.""" 

691 widget = CircularProgress() 

692 qtbot.addWidget(widget) 

693 

694 named_colors = ["red", "green", "blue", "yellow", "cyan", "magenta", "black", "white"] 

695 

696 for color_name in named_colors: 

697 widget.setColor(color_name) 

698 assert isinstance(widget.color, QColor) 

699 

700 def test_qcolor_from_rgb(self, qtbot): 

701 """Test QColor with RGB color""" 

702 widget = CircularProgress() 

703 qtbot.addWidget(widget) 

704 

705 color = QColor(255, 128, 64) 

706 widget.setColor(color) 

707 

708 assert widget.color == color 

709 

710 

711class TestMemoryAndPerformance: 

712 """Test for memory and performace.""" 

713 

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() 

720 

721 for _ in range(1000): 

722 widget.setValue(50) 

723 

724 assert widget.value == 50 

725 

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() 

732 

733 for i in range(100): 

734 widget.setValue(i % 101) 

735 widget.repaint() 

736 

737 assert True