Coverage for tests/components/test_file_selector_widget.py: 100%

483 statements  

« prev     ^ index     » next       coverage.py v7.15.0, created at 2026-07-03 15:51 +0000

1import os 

2import pytest 

3from unittest.mock import Mock, patch, MagicMock 

4from PyQt6.QtWidgets import QListWidgetItem 

5from PyQt6.QtCore import Qt, pyqtSignal 

6 

7from main.components.file_selector_widget import FileSelectorWidget 

8 

9 

10@pytest.fixture 

11def mock_context_selector(signal_emitter): 

12 """Context per FileSelectorWidget.""" 

13 context = { 

14 "selected_files_signal": signal_emitter.selected_files, 

15 "update_main_buttons": Mock(), 

16 "language_changed": signal_emitter.language_changed, 

17 "workspace_path": "/tmp" 

18 } 

19 return context 

20 

21 

22@pytest.fixture 

23def mock_has_existing_function(): 

24 """Mock per has_existing_function.""" 

25 return Mock(return_value=False) 

26 

27 

28@pytest.fixture 

29def test_nifti_files(temp_workspace): 

30 """Create test NIfTI file.""" 

31 files = [] 

32 

33 # File .nii 

34 nii_file = os.path.join(temp_workspace, "test1.nii") 

35 with open(nii_file, "w") as f: 

36 f.write("nifti data") 

37 files.append(nii_file) 

38 

39 # File .nii.gz 

40 nii_gz_file = os.path.join(temp_workspace, "test2.nii.gz") 

41 with open(nii_gz_file, "w") as f: 

42 f.write("compressed nifti data") 

43 files.append(nii_gz_file) 

44 

45 # File non NIfTI 

46 txt_file = os.path.join(temp_workspace, "test.txt") 

47 with open(txt_file, "w") as f: 

48 f.write("text data") 

49 files.append(txt_file) 

50 

51 return files 

52 

53 

54class TestFileSelectorWidgetInitialization: 

55 """Test for initialization if FileSelectorWidget.""" 

56 

57 def test_initialization_basic(self, qtbot, mock_context_selector, mock_has_existing_function): 

58 """Test base initialization.""" 

59 widget = FileSelectorWidget( 

60 context=mock_context_selector, 

61 has_existing_function=mock_has_existing_function, 

62 label="test", 

63 allow_multiple=True 

64 ) 

65 qtbot.addWidget(widget) 

66 

67 assert widget.context == mock_context_selector 

68 assert widget.label == "test" 

69 assert widget.allow_multiple is True 

70 assert widget.has_existing_function == mock_has_existing_function 

71 assert widget.selected_files is None 

72 

73 def test_initialization_single_file_mode(self, qtbot, mock_context_selector, mock_has_existing_function): 

74 """Test initialization single file modality.""" 

75 widget = FileSelectorWidget( 

76 context=mock_context_selector, 

77 has_existing_function=mock_has_existing_function, 

78 label="single", 

79 allow_multiple=False 

80 ) 

81 qtbot.addWidget(widget) 

82 

83 assert widget.allow_multiple is False 

84 

85 def test_initialization_with_processing_signal(self, qtbot, mock_context_selector, mock_has_existing_function): 

86 """Test initialization with signal processing.""" 

87 processing_signal = Mock() 

88 processing_signal.connect = Mock() 

89 

90 widget = FileSelectorWidget( 

91 context=mock_context_selector, 

92 has_existing_function=mock_has_existing_function, 

93 label="test", 

94 allow_multiple=True, 

95 processing=processing_signal 

96 ) 

97 qtbot.addWidget(widget) 

98 

99 # Verifica che il signal sia stato connesso 

100 processing_signal.connect.assert_called_once() 

101 

102 def test_initialization_with_forced_filters(self, qtbot, mock_context_selector, mock_has_existing_function): 

103 """Test initialization with forced_filters.""" 

104 filters = {"type": "mask"} 

105 

106 widget = FileSelectorWidget( 

107 context=mock_context_selector, 

108 has_existing_function=mock_has_existing_function, 

109 label="test", 

110 allow_multiple=True, 

111 forced_filters=filters 

112 ) 

113 qtbot.addWidget(widget) 

114 

115 assert widget.forced_filters == filters 

116 

117 def test_ui_elements_created(self, qtbot, mock_context_selector, mock_has_existing_function): 

118 """Test creation UI elements""" 

119 widget = FileSelectorWidget( 

120 context=mock_context_selector, 

121 has_existing_function=mock_has_existing_function, 

122 label="test", 

123 allow_multiple=True 

124 ) 

125 qtbot.addWidget(widget) 

126 

127 assert widget.file_list_widget is not None 

128 assert widget.file_button is not None 

129 assert widget.clear_button is not None 

130 

131 def test_clear_button_initially_disabled(self, qtbot, mock_context_selector, mock_has_existing_function): 

132 """Test clear button initially disabled""" 

133 widget = FileSelectorWidget( 

134 context=mock_context_selector, 

135 has_existing_function=mock_has_existing_function, 

136 label="test", 

137 allow_multiple=True 

138 ) 

139 qtbot.addWidget(widget) 

140 

141 assert not widget.clear_button.isEnabled() 

142 

143 def test_file_list_widget_config(self, qtbot, mock_context_selector, mock_has_existing_function): 

144 """Test configuration file_list_widget.""" 

145 widget = FileSelectorWidget( 

146 context=mock_context_selector, 

147 has_existing_function=mock_has_existing_function, 

148 label="test", 

149 allow_multiple=True 

150 ) 

151 qtbot.addWidget(widget) 

152 

153 assert widget.file_list_widget.selectionMode() == widget.file_list_widget.SelectionMode.NoSelection 

154 assert widget.file_list_widget.focusPolicy() == Qt.FocusPolicy.NoFocus 

155 assert widget.file_list_widget.maximumHeight() == 100 

156 

157 

158class TestSetSelectedFiles: 

159 """Test selected files method.""" 

160 

161 def test_set_selected_files_multiple_valid(self, qtbot, mock_context_selector, mock_has_existing_function, 

162 test_nifti_files): 

163 """Test multiple files valid""" 

164 widget = FileSelectorWidget( 

165 context=mock_context_selector, 

166 has_existing_function=mock_has_existing_function, 

167 label="test", 

168 allow_multiple=True 

169 ) 

170 qtbot.addWidget(widget) 

171 

172 # Obly first two are .nii/.nii.gz 

173 widget.set_selected_files(test_nifti_files) 

174 

175 assert len(widget.selected_files) == 2 

176 

177 def test_many_files(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

178 """Test wuth manty files.""" 

179 files = [] 

180 for i in range(100): 

181 filepath = os.path.join(temp_workspace, f"file{i}.nii") 

182 with open(filepath, "w") as f: 

183 f.write("data") 

184 files.append(filepath) 

185 

186 widget = FileSelectorWidget( 

187 context=mock_context_selector, 

188 has_existing_function=mock_has_existing_function, 

189 label="test", 

190 allow_multiple=True 

191 ) 

192 qtbot.addWidget(widget) 

193 

194 widget.set_selected_files(files) 

195 

196 assert len(widget.selected_files) == 100 

197 assert widget.file_list_widget.count() == 100 

198 

199 def test_unicode_in_filenames(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

200 """Test with unicode chars in file name.""" 

201 unicode_files = [ 

202 os.path.join(temp_workspace, "файл.nii"), 

203 os.path.join(temp_workspace, "文件.nii"), 

204 os.path.join(temp_workspace, "αρχείο.nii.gz") 

205 ] 

206 

207 for filepath in unicode_files: 

208 with open(filepath, "w") as f: 

209 f.write("data") 

210 

211 widget = FileSelectorWidget( 

212 context=mock_context_selector, 

213 has_existing_function=mock_has_existing_function, 

214 label="test", 

215 allow_multiple=True 

216 ) 

217 qtbot.addWidget(widget) 

218 

219 widget.set_selected_files(unicode_files) 

220 

221 assert len(widget.selected_files) == 3 

222 

223 def test_mixed_nii_and_nii_gz(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

224 """Test with file .nii and .nii.gz mixed.""" 

225 files = [] 

226 

227 for i in range(5): 

228 nii_file = os.path.join(temp_workspace, f"file{i}.nii") 

229 with open(nii_file, "w") as f: 

230 f.write("data") 

231 files.append(nii_file) 

232 

233 nii_gz_file = os.path.join(temp_workspace, f"file{i}.nii.gz") 

234 with open(nii_gz_file, "w") as f: 

235 f.write("data") 

236 files.append(nii_gz_file) 

237 

238 widget = FileSelectorWidget( 

239 context=mock_context_selector, 

240 has_existing_function=mock_has_existing_function, 

241 label="test", 

242 allow_multiple=True 

243 ) 

244 qtbot.addWidget(widget) 

245 

246 widget.set_selected_files(files) 

247 

248 assert len(widget.selected_files) == 10 

249 

250 def test_duplicate_files(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

251 """Test with duplicated file in the list.""" 

252 filepath = os.path.join(temp_workspace, "duplicate.nii") 

253 with open(filepath, "w") as f: 

254 f.write("data") 

255 

256 widget = FileSelectorWidget( 

257 context=mock_context_selector, 

258 has_existing_function=mock_has_existing_function, 

259 label="test", 

260 allow_multiple=True 

261 ) 

262 qtbot.addWidget(widget) 

263 

264 # Pass two times same file 

265 widget.set_selected_files([filepath, filepath]) 

266 

267 # Should be both accepted 

268 assert len(widget.selected_files) == 2 

269 

270 

271class TestIntegration: 

272 """Test integration.""" 

273 

274 def test_full_workflow_select_clear(self, qtbot, mock_context_selector, mock_has_existing_function, 

275 test_nifti_files): 

276 """Test workflow complete: select -> clear.""" 

277 widget = FileSelectorWidget( 

278 context=mock_context_selector, 

279 has_existing_function=mock_has_existing_function, 

280 label="test", 

281 allow_multiple=True 

282 ) 

283 qtbot.addWidget(widget) 

284 

285 # Seleziona file 

286 widget.set_selected_files(test_nifti_files) 

287 

288 assert len(widget.selected_files) == 2 

289 assert widget.clear_button.isEnabled() 

290 assert widget.file_list_widget.count() == 2 

291 

292 # Pulisci 

293 widget.clear_selected_files() 

294 

295 assert widget.selected_files == [] 

296 assert not widget.clear_button.isEnabled() 

297 assert widget.file_list_widget.count() == 0 

298 

299 def test_full_workflow_multiple_selections(self, qtbot, mock_context_selector, mock_has_existing_function, 

300 test_nifti_files): 

301 """Test workflow with multiple selection.""" 

302 widget = FileSelectorWidget( 

303 context=mock_context_selector, 

304 has_existing_function=mock_has_existing_function, 

305 label="test", 

306 allow_multiple=True 

307 ) 

308 qtbot.addWidget(widget) 

309 

310 # First selection 

311 widget.set_selected_files([test_nifti_files[0]]) 

312 assert len(widget.selected_files) == 1 

313 

314 # Second selection 

315 widget.set_selected_files(test_nifti_files) 

316 assert len(widget.selected_files) == 2 

317 

318 # Clear 

319 widget.clear_selected_files() 

320 assert len(widget.selected_files) == 0 

321 

322 # Third selection 

323 widget.set_selected_files([test_nifti_files[1]]) 

324 assert len(widget.selected_files) == 1 

325 

326 def test_full_workflow_with_processing(self, qtbot, mock_context_selector, mock_has_existing_function, 

327 test_nifti_files): 

328 """Test workflow with processing mode.""" 

329 widget = FileSelectorWidget( 

330 context=mock_context_selector, 

331 has_existing_function=mock_has_existing_function, 

332 label="test", 

333 allow_multiple=True 

334 ) 

335 qtbot.addWidget(widget) 

336 

337 # Seleziona file 

338 widget.set_selected_files(test_nifti_files) 

339 assert widget.file_button.isEnabled() 

340 assert widget.clear_button.isEnabled() 

341 

342 # Avvia processing 

343 widget.set_processing_mode(True) 

344 assert not widget.file_button.isEnabled() 

345 assert not widget.clear_button.isEnabled() 

346 

347 # Termina processing 

348 widget.set_processing_mode(False) 

349 assert widget.file_button.isEnabled() 

350 assert widget.clear_button.isEnabled() 

351 

352 

353class TestContextHandling: 

354 """Test context handling.""" 

355 

356 def test_context_without_update_main_buttons(self, qtbot, signal_emitter, mock_has_existing_function, 

357 test_nifti_files): 

358 """Test context without update_main_buttons.""" 

359 context = { 

360 "selected_files_signal": signal_emitter.selected_files, 

361 "language_changed": signal_emitter.language_changed 

362 } 

363 

364 widget = FileSelectorWidget( 

365 context=context, 

366 has_existing_function=mock_has_existing_function, 

367 label="test", 

368 allow_multiple=True 

369 ) 

370 qtbot.addWidget(widget) 

371 

372 # Should not crash 

373 widget.set_selected_files(test_nifti_files) 

374 widget.clear_selected_files() 

375 

376 def test_context_without_language_changed(self, qtbot, signal_emitter, mock_has_existing_function): 

377 """Test context without language_changed.""" 

378 context = { 

379 "selected_files_signal": signal_emitter.selected_files, 

380 "update_main_buttons": Mock() 

381 } 

382 

383 widget = FileSelectorWidget( 

384 context=context, 

385 has_existing_function=mock_has_existing_function, 

386 label="test", 

387 allow_multiple=True 

388 ) 

389 qtbot.addWidget(widget) 

390 

391 # Non dovrebbe crashare 

392 assert widget.context == context 

393 

394 

395class TestButtonClicks: 

396 """Test for button clicks.""" 

397 

398 def test_file_button_click(self, qtbot, mock_context_selector, mock_has_existing_function): 

399 """Test click on file buttons.""" 

400 widget = FileSelectorWidget( 

401 context=mock_context_selector, 

402 has_existing_function=mock_has_existing_function, 

403 label="test", 

404 allow_multiple=True 

405 ) 

406 qtbot.addWidget(widget) 

407 

408 with patch.object(widget, 'open_tree_dialog') as mock_open: 

409 widget.file_button.clicked.disconnect() 

410 widget.file_button.clicked.connect(widget.open_tree_dialog) 

411 widget.file_button.click() 

412 mock_open.assert_called_once() 

413 

414 def test_clear_button_click(self, qtbot, mock_context_selector, mock_has_existing_function, test_nifti_files): 

415 """Test click on clear button.""" 

416 widget = FileSelectorWidget( 

417 context=mock_context_selector, 

418 has_existing_function=mock_has_existing_function, 

419 label="test", 

420 allow_multiple=True 

421 ) 

422 qtbot.addWidget(widget) 

423 

424 widget.set_selected_files(test_nifti_files) 

425 

426 with patch.object(widget, 'open_tree_dialog') as mock_open: 

427 widget.clear_button.clicked.disconnect() 

428 widget.clear_button.clicked.connect(widget.open_tree_dialog) 

429 widget.clear_button.click() 

430 mock_open.assert_called_once() 

431 

432 

433class TestFileExtensions: 

434 """Tests for file extension handling.""" 

435 

436 def test_accepts_nii(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

437 """Test that it accepts .nii files.""" 

438 nii_file = os.path.join(temp_workspace, "test.nii") 

439 with open(nii_file, "w") as f: 

440 f.write("data") 

441 

442 widget = FileSelectorWidget( 

443 context=mock_context_selector, 

444 has_existing_function=mock_has_existing_function, 

445 label="test", 

446 allow_multiple=True 

447 ) 

448 qtbot.addWidget(widget) 

449 

450 widget.set_selected_files([nii_file]) 

451 

452 assert len(widget.selected_files) == 1 

453 

454 def test_accepts_nii_gz(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

455 """Test that it accepts .nii.gz files.""" 

456 nii_gz_file = os.path.join(temp_workspace, "test.nii.gz") 

457 with open(nii_gz_file, "w") as f: 

458 f.write("data") 

459 

460 widget = FileSelectorWidget( 

461 context=mock_context_selector, 

462 has_existing_function=mock_has_existing_function, 

463 label="test", 

464 allow_multiple=True 

465 ) 

466 qtbot.addWidget(widget) 

467 

468 widget.set_selected_files([nii_gz_file]) 

469 

470 assert len(widget.selected_files) == 1 

471 

472 def test_rejects_other_extensions(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

473 """Test that it rejects other extensions.""" 

474 invalid_files = [ 

475 os.path.join(temp_workspace, "test.txt"), 

476 os.path.join(temp_workspace, "test.nii.bak"), 

477 os.path.join(temp_workspace, "test.json"), 

478 os.path.join(temp_workspace, "test.nii.txt") 

479 ] 

480 

481 for filepath in invalid_files: 

482 with open(filepath, "w") as f: 

483 f.write("data") 

484 

485 widget = FileSelectorWidget( 

486 context=mock_context_selector, 

487 has_existing_function=mock_has_existing_function, 

488 label="test", 

489 allow_multiple=True 

490 ) 

491 qtbot.addWidget(widget) 

492 

493 widget.set_selected_files(invalid_files) 

494 

495 assert len(widget.selected_files) == 0 

496 

497 def test_case_sensitive_extensions(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

498 """Test case sensitivity of file extensions.""" 

499 # .nii and .nii.gz should be case sensitive 

500 upper_file = os.path.join(temp_workspace, "test.NII") 

501 with open(upper_file, "w") as f: 

502 f.write("data") 

503 

504 widget = FileSelectorWidget( 

505 context=mock_context_selector, 

506 has_existing_function=mock_has_existing_function, 

507 label="test", 

508 allow_multiple=True 

509 ) 

510 qtbot.addWidget(widget) 

511 

512 widget.set_selected_files([upper_file]) 

513 

514 # Should be rejected (case sensitive) 

515 assert len(widget.selected_files) == 0 

516 

517class TestMemoryAndPerformance: 

518 """Tests for memory usage and performance.""" 

519 

520 def test_no_memory_leak_repeated_selections(self, qtbot, mock_context_selector, mock_has_existing_function, 

521 test_nifti_files): 

522 """Test that there are no memory leaks with repeated selections.""" 

523 widget = FileSelectorWidget( 

524 context=mock_context_selector, 

525 has_existing_function=mock_has_existing_function, 

526 label="test", 

527 allow_multiple=True 

528 ) 

529 qtbot.addWidget(widget) 

530 

531 # Select and clear 100 times 

532 for _ in range(100): 

533 widget.set_selected_files(test_nifti_files) 

534 widget.clear_selected_files() 

535 

536 # Final state should be clean 

537 assert widget.selected_files == [] 

538 assert widget.file_list_widget.count() == 0 

539 

540 def test_performance_many_items(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

541 """Performance test with many items.""" 

542 files = [] 

543 for i in range(500): 

544 filepath = os.path.join(temp_workspace, f"file{i}.nii") 

545 with open(filepath, "w") as f: 

546 f.write("data") 

547 files.append(filepath) 

548 

549 widget = FileSelectorWidget( 

550 context=mock_context_selector, 

551 has_existing_function=mock_has_existing_function, 

552 label="test", 

553 allow_multiple=True 

554 ) 

555 qtbot.addWidget(widget) 

556 

557 # Should not be too slow 

558 widget.set_selected_files(files) 

559 

560 assert len(widget.selected_files) == 500 

561 

562 

563 

564class TestStateConsistency: 

565 """Tests for state consistency.""" 

566 

567 def test_state_after_set_and_get(self, qtbot, mock_context_selector, mock_has_existing_function, test_nifti_files): 

568 """Test state consistency after set and get.""" 

569 widget = FileSelectorWidget( 

570 context=mock_context_selector, 

571 has_existing_function=mock_has_existing_function, 

572 label="test", 

573 allow_multiple=True 

574 ) 

575 qtbot.addWidget(widget) 

576 

577 widget.set_selected_files(test_nifti_files) 

578 

579 retrieved_files = widget.get_selected_files() 

580 

581 assert retrieved_files == widget.selected_files 

582 assert len(retrieved_files) == len(widget.selected_files) 

583 

584 def test_ui_state_consistency(self, qtbot, mock_context_selector, mock_has_existing_function, test_nifti_files): 

585 """Test UI state consistency.""" 

586 widget = FileSelectorWidget( 

587 context=mock_context_selector, 

588 has_existing_function=mock_has_existing_function, 

589 label="test", 

590 allow_multiple=True 

591 ) 

592 qtbot.addWidget(widget) 

593 

594 # Initial state 

595 assert widget.selected_files is None 

596 assert widget.file_list_widget.count() == 0 

597 assert not widget.clear_button.isEnabled() 

598 

599 # After selection 

600 widget.set_selected_files(test_nifti_files) 

601 assert widget.selected_files is not None 

602 assert widget.file_list_widget.count() == len(widget.selected_files) 

603 assert widget.clear_button.isEnabled() 

604 

605 # After clearing 

606 widget.clear_selected_files() 

607 assert widget.selected_files == [] 

608 assert widget.file_list_widget.count() == 0 

609 assert not widget.clear_button.isEnabled() 

610 

611 

612class TestAccessibility: 

613 """Tests for accessibility.""" 

614 

615 def test_buttons_have_text(self, qtbot, mock_context_selector, mock_has_existing_function): 

616 """Test that the buttons have text.""" 

617 widget = FileSelectorWidget( 

618 context=mock_context_selector, 

619 has_existing_function=mock_has_existing_function, 

620 label="test", 

621 allow_multiple=True 

622 ) 

623 qtbot.addWidget(widget) 

624 

625 assert len(widget.file_button.text()) > 0 

626 assert len(widget.clear_button.text()) > 0 

627 

628 def test_list_items_have_tooltips(self, qtbot, mock_context_selector, mock_has_existing_function, test_nifti_files): 

629 """Test that the items have tooltips.""" 

630 widget = FileSelectorWidget( 

631 context=mock_context_selector, 

632 has_existing_function=mock_has_existing_function, 

633 label="test", 

634 allow_multiple=True 

635 ) 

636 qtbot.addWidget(widget) 

637 

638 widget.set_selected_files(test_nifti_files) 

639 

640 for i in range(widget.file_list_widget.count()): 

641 item = widget.file_list_widget.item(i) 

642 tooltip = item.toolTip() 

643 assert len(tooltip) > 0 

644 assert tooltip in test_nifti_files 

645 assert all(f.endswith(('.nii', '.nii.gz')) for f in widget.selected_files) 

646 

647 def test_set_selected_files_single_mode(self, qtbot, mock_context_selector, mock_has_existing_function, 

648 test_nifti_files): 

649 """Test setting files in single mode.""" 

650 widget = FileSelectorWidget( 

651 context=mock_context_selector, 

652 has_existing_function=mock_has_existing_function, 

653 label="test", 

654 allow_multiple=False 

655 ) 

656 qtbot.addWidget(widget) 

657 

658 widget.set_selected_files(test_nifti_files) 

659 

660 # Should keep only the last file 

661 assert len(widget.selected_files) == 1 

662 

663 def test_set_selected_files_filters_non_nifti(self, qtbot, mock_context_selector, mock_has_existing_function, 

664 temp_workspace): 

665 """Test that non-NIfTI files are filtered out.""" 

666 widget = FileSelectorWidget( 

667 context=mock_context_selector, 

668 has_existing_function=mock_has_existing_function, 

669 label="test", 

670 allow_multiple=True 

671 ) 

672 qtbot.addWidget(widget) 

673 

674 # Create mixed files 

675 nifti_file = os.path.join(temp_workspace, "valid.nii") 

676 txt_file = os.path.join(temp_workspace, "invalid.txt") 

677 

678 with open(nifti_file, "w") as f: 

679 f.write("nifti") 

680 with open(txt_file, "w") as f: 

681 f.write("text") 

682 

683 widget.set_selected_files([nifti_file, txt_file]) 

684 

685 # Only the .nii file should be selected 

686 assert len(widget.selected_files) == 1 

687 assert widget.selected_files[0] == nifti_file 

688 

689 def test_set_selected_files_filters_directories(self, qtbot, mock_context_selector, mock_has_existing_function, 

690 temp_workspace): 

691 """Test that directories are filtered out.""" 

692 widget = FileSelectorWidget( 

693 context=mock_context_selector, 

694 has_existing_function=mock_has_existing_function, 

695 label="test", 

696 allow_multiple=True 

697 ) 

698 qtbot.addWidget(widget) 

699 

700 # Create a directory 

701 dir_path = os.path.join(temp_workspace, "test_dir.nii") 

702 os.makedirs(dir_path) 

703 

704 widget.set_selected_files([dir_path]) 

705 

706 # Directory should not be accepted 

707 assert len(widget.selected_files) == 0 

708 

709 def test_set_selected_files_filters_nonexistent(self, qtbot, mock_context_selector, mock_has_existing_function): 

710 """Test that nonexistent files are filtered out.""" 

711 widget = FileSelectorWidget( 

712 context=mock_context_selector, 

713 has_existing_function=mock_has_existing_function, 

714 label="test", 

715 allow_multiple=True 

716 ) 

717 qtbot.addWidget(widget) 

718 

719 widget.set_selected_files(["/nonexistent/file.nii"]) 

720 

721 assert len(widget.selected_files) == 0 

722 

723 def test_set_selected_files_updates_list_widget(self, qtbot, mock_context_selector, mock_has_existing_function, 

724 test_nifti_files): 

725 """Test that the list widget is updated.""" 

726 widget = FileSelectorWidget( 

727 context=mock_context_selector, 

728 has_existing_function=mock_has_existing_function, 

729 label="test", 

730 allow_multiple=True 

731 ) 

732 qtbot.addWidget(widget) 

733 

734 widget.set_selected_files(test_nifti_files) 

735 

736 # There should be 2 items (only .nii/.nii.gz) 

737 assert widget.file_list_widget.count() == 2 

738 

739 def test_set_selected_files_enables_clear_button(self, qtbot, mock_context_selector, mock_has_existing_function, 

740 test_nifti_files): 

741 """Test that the clear button is enabled.""" 

742 widget = FileSelectorWidget( 

743 context=mock_context_selector, 

744 has_existing_function=mock_has_existing_function, 

745 label="test", 

746 allow_multiple=True 

747 ) 

748 qtbot.addWidget(widget) 

749 

750 assert not widget.clear_button.isEnabled() 

751 

752 widget.set_selected_files(test_nifti_files) 

753 

754 assert widget.clear_button.isEnabled() 

755 

756 def test_set_selected_files_emits_has_file_signal(self, qtbot, mock_context_selector, mock_has_existing_function, 

757 test_nifti_files): 

758 """Test that the has_file signal is emitted.""" 

759 widget = FileSelectorWidget( 

760 context=mock_context_selector, 

761 has_existing_function=mock_has_existing_function, 

762 label="test", 

763 allow_multiple=True 

764 ) 

765 qtbot.addWidget(widget) 

766 

767 with qtbot.waitSignal(widget.has_file) as blocker: 

768 widget.set_selected_files(test_nifti_files) 

769 

770 # Signal should be True (files exist) 

771 assert blocker.args[0] is True 

772 

773 def test_set_selected_files_calls_update_main_buttons(self, qtbot, mock_context_selector, 

774 mock_has_existing_function, test_nifti_files): 

775 """Test that update_main_buttons is called.""" 

776 widget = FileSelectorWidget( 

777 context=mock_context_selector, 

778 has_existing_function=mock_has_existing_function, 

779 label="test", 

780 allow_multiple=True 

781 ) 

782 qtbot.addWidget(widget) 

783 

784 widget.set_selected_files(test_nifti_files) 

785 

786 mock_context_selector["update_main_buttons"].assert_called() 

787 

788 def test_set_selected_files_with_tooltips(self, qtbot, mock_context_selector, mock_has_existing_function, 

789 test_nifti_files): 

790 """Test that tooltips are set.""" 

791 widget = FileSelectorWidget( 

792 context=mock_context_selector, 

793 has_existing_function=mock_has_existing_function, 

794 label="test", 

795 allow_multiple=True 

796 ) 

797 qtbot.addWidget(widget) 

798 

799 widget.set_selected_files(test_nifti_files) 

800 

801 # Verify tooltips 

802 for i in range(widget.file_list_widget.count()): 

803 item = widget.file_list_widget.item(i) 

804 assert item.toolTip() != "" 

805 

806 

807class TestClearSelectedFiles: 

808 """Tests for the clear_selected_files method.""" 

809 

810 def test_clear_selected_files_basic(self, qtbot, mock_context_selector, mock_has_existing_function, 

811 test_nifti_files): 

812 """Basic clear files test.""" 

813 widget = FileSelectorWidget( 

814 context=mock_context_selector, 

815 has_existing_function=mock_has_existing_function, 

816 label="test", 

817 allow_multiple=True 

818 ) 

819 qtbot.addWidget(widget) 

820 

821 # First select files 

822 widget.set_selected_files(test_nifti_files) 

823 assert len(widget.selected_files) > 0 

824 

825 # Then clear 

826 widget.clear_selected_files() 

827 

828 assert widget.selected_files == [] 

829 assert widget.file_list_widget.count() == 0 

830 assert not widget.clear_button.isEnabled() 

831 

832 def test_clear_selected_files_emits_signal(self, qtbot, mock_context_selector, mock_has_existing_function, 

833 test_nifti_files): 

834 """Test that it emits has_file signal with False.""" 

835 widget = FileSelectorWidget( 

836 context=mock_context_selector, 

837 has_existing_function=mock_has_existing_function, 

838 label="test", 

839 allow_multiple=True 

840 ) 

841 qtbot.addWidget(widget) 

842 

843 widget.set_selected_files(test_nifti_files) 

844 

845 with qtbot.waitSignal(widget.has_file) as blocker: 

846 widget.clear_selected_files() 

847 

848 assert blocker.args[0] is False 

849 

850 def test_clear_selected_files_calls_update_main_buttons(self, qtbot, mock_context_selector, 

851 mock_has_existing_function, test_nifti_files): 

852 """Test that it calls update_main_buttons.""" 

853 widget = FileSelectorWidget( 

854 context=mock_context_selector, 

855 has_existing_function=mock_has_existing_function, 

856 label="test", 

857 allow_multiple=True 

858 ) 

859 qtbot.addWidget(widget) 

860 

861 widget.set_selected_files(test_nifti_files) 

862 mock_context_selector["update_main_buttons"].reset_mock() 

863 

864 widget.clear_selected_files() 

865 

866 mock_context_selector["update_main_buttons"].assert_called_once() 

867 

868 def test_clear_when_already_empty(self, qtbot, mock_context_selector, mock_has_existing_function): 

869 """Test clear when already empty.""" 

870 widget = FileSelectorWidget( 

871 context=mock_context_selector, 

872 has_existing_function=mock_has_existing_function, 

873 label="test", 

874 allow_multiple=True 

875 ) 

876 qtbot.addWidget(widget) 

877 

878 # Clear without having selected files 

879 widget.clear_selected_files() 

880 

881 assert widget.selected_files == [] 

882 

883 

884class TestGetSelectedFiles: 

885 """Tests for the get_selected_files method.""" 

886 

887 def test_get_selected_files_with_files(self, qtbot, mock_context_selector, mock_has_existing_function, 

888 test_nifti_files): 

889 """Test get files when files are selected.""" 

890 widget = FileSelectorWidget( 

891 context=mock_context_selector, 

892 has_existing_function=mock_has_existing_function, 

893 label="test", 

894 allow_multiple=True 

895 ) 

896 qtbot.addWidget(widget) 

897 

898 widget.set_selected_files(test_nifti_files) 

899 

900 files = widget.get_selected_files() 

901 

902 assert files == widget.selected_files 

903 assert len(files) == 2 

904 

905 def test_get_selected_files_empty(self, qtbot, mock_context_selector, mock_has_existing_function): 

906 """Test get files with no selection.""" 

907 widget = FileSelectorWidget( 

908 context=mock_context_selector, 

909 has_existing_function=mock_has_existing_function, 

910 label="test", 

911 allow_multiple=True 

912 ) 

913 qtbot.addWidget(widget) 

914 

915 files = widget.get_selected_files() 

916 

917 assert files is None 

918 

919 

920class TestOpenTreeDialog: 

921 """Tests for the open_tree_dialog method.""" 

922 

923 def test_open_tree_dialog_called(self, qtbot, mock_context_selector, mock_has_existing_function): 

924 """Test that open_tree_dialog calls NiftiFileDialog.""" 

925 widget = FileSelectorWidget( 

926 context=mock_context_selector, 

927 has_existing_function=mock_has_existing_function, 

928 label="test", 

929 allow_multiple=True 

930 ) 

931 qtbot.addWidget(widget) 

932 

933 with patch("main.components.file_selector_widget.NiftiFileDialog") as mock_dialog: 

934 mock_dialog.get_files = Mock(return_value=None) 

935 

936 widget.open_tree_dialog() 

937 

938 mock_dialog.get_files.assert_called_once() 

939 

940 def test_open_tree_dialog_with_results_multiple(self, qtbot, mock_context_selector, mock_has_existing_function, 

941 test_nifti_files): 

942 """Test open_tree_dialog with multiple results.""" 

943 widget = FileSelectorWidget( 

944 context=mock_context_selector, 

945 has_existing_function=mock_has_existing_function, 

946 label="test", 

947 allow_multiple=True 

948 ) 

949 qtbot.addWidget(widget) 

950 

951 with patch("main.components.file_selector_widget.NiftiFileDialog") as mock_dialog: 

952 mock_dialog.get_files = Mock(return_value=test_nifti_files[:2]) 

953 

954 widget.open_tree_dialog() 

955 

956 assert widget.selected_files == test_nifti_files[:2] 

957 

958 def test_open_tree_dialog_with_results_single(self, qtbot, mock_context_selector, mock_has_existing_function, 

959 test_nifti_files): 

960 """Test open_tree_dialog with results in single mode.""" 

961 widget = FileSelectorWidget( 

962 context=mock_context_selector, 

963 has_existing_function=mock_has_existing_function, 

964 label="test", 

965 allow_multiple=False 

966 ) 

967 qtbot.addWidget(widget) 

968 

969 with patch("main.components.file_selector_widget.NiftiFileDialog") as mock_dialog: 

970 mock_dialog.get_files = Mock(return_value=test_nifti_files[:2]) 

971 

972 widget.open_tree_dialog() 

973 

974 # Should take only the first file 

975 assert len(widget.selected_files) == 1 

976 

977 def test_open_tree_dialog_parameters(self, qtbot, mock_context_selector, mock_has_existing_function): 

978 """Test that open_tree_dialog passes the correct parameters.""" 

979 widget = FileSelectorWidget( 

980 context=mock_context_selector, 

981 has_existing_function=mock_has_existing_function, 

982 label="mylabel", 

983 allow_multiple=True, 

984 forced_filters={"type": "mask"} 

985 ) 

986 qtbot.addWidget(widget) 

987 

988 with patch("main.components.file_selector_widget.NiftiFileDialog") as mock_dialog: 

989 mock_dialog.get_files = Mock(return_value=None) 

990 

991 widget.open_tree_dialog() 

992 

993 call_kwargs = mock_dialog.get_files.call_args[1] 

994 assert call_kwargs['context'] == mock_context_selector 

995 assert call_kwargs['allow_multiple'] is True 

996 assert call_kwargs['has_existing_func'] == mock_has_existing_function 

997 assert call_kwargs['label'] == "mylabel" 

998 assert call_kwargs['forced_filters'] == {"type": "mask"} 

999 

1000 

1001class TestSetProcessingMode: 

1002 """Tests for the set_processing_mode method.""" 

1003 

1004 def test_set_processing_mode_true(self, qtbot, mock_context_selector, mock_has_existing_function): 

1005 """Test set_processing_mode True.""" 

1006 widget = FileSelectorWidget( 

1007 context=mock_context_selector, 

1008 has_existing_function=mock_has_existing_function, 

1009 label="test", 

1010 allow_multiple=True 

1011 ) 

1012 qtbot.addWidget(widget) 

1013 

1014 widget.set_processing_mode(True) 

1015 

1016 assert not widget.file_button.isEnabled() 

1017 assert not widget.clear_button.isEnabled() 

1018 

1019 def test_set_processing_mode_false_with_files(self, qtbot, mock_context_selector, mock_has_existing_function, 

1020 test_nifti_files): 

1021 """Test set_processing_mode False with files.""" 

1022 widget = FileSelectorWidget( 

1023 context=mock_context_selector, 

1024 has_existing_function=mock_has_existing_function, 

1025 label="test", 

1026 allow_multiple=True 

1027 ) 

1028 qtbot.addWidget(widget) 

1029 

1030 widget.set_selected_files(test_nifti_files) 

1031 widget.set_processing_mode(False) 

1032 

1033 assert widget.file_button.isEnabled() 

1034 assert widget.clear_button.isEnabled() 

1035 

1036 def test_set_processing_mode_false_without_files(self, qtbot, mock_context_selector, mock_has_existing_function): 

1037 """Test set_processing_mode False without files.""" 

1038 widget = FileSelectorWidget( 

1039 context=mock_context_selector, 

1040 has_existing_function=mock_has_existing_function, 

1041 label="test", 

1042 allow_multiple=True 

1043 ) 

1044 qtbot.addWidget(widget) 

1045 

1046 widget.set_processing_mode(False) 

1047 

1048 assert widget.file_button.isEnabled() 

1049 assert not widget.clear_button.isEnabled() 

1050 

1051 def test_set_processing_mode_toggle(self, qtbot, mock_context_selector, mock_has_existing_function, 

1052 test_nifti_files): 

1053 """Test toggle processing mode.""" 

1054 widget = FileSelectorWidget( 

1055 context=mock_context_selector, 

1056 has_existing_function=mock_has_existing_function, 

1057 label="test", 

1058 allow_multiple=True 

1059 ) 

1060 qtbot.addWidget(widget) 

1061 

1062 widget.set_selected_files(test_nifti_files) 

1063 

1064 # Enable processing 

1065 widget.set_processing_mode(True) 

1066 assert not widget.file_button.isEnabled() 

1067 

1068 # Disable processing 

1069 widget.set_processing_mode(False) 

1070 assert widget.file_button.isEnabled() 

1071 

1072 

1073class TestSignals: 

1074 """Tests for signals.""" 

1075 

1076 def test_has_file_signal_exists(self, qtbot, mock_context_selector, mock_has_existing_function): 

1077 """Test that the has_file signal exists.""" 

1078 widget = FileSelectorWidget( 

1079 context=mock_context_selector, 

1080 has_existing_function=mock_has_existing_function, 

1081 label="test", 

1082 allow_multiple=True 

1083 ) 

1084 qtbot.addWidget(widget) 

1085 

1086 assert hasattr(widget, 'has_file') 

1087 

1088 def test_selected_files_signal_connection(self, qtbot, mock_context_selector, mock_has_existing_function): 

1089 """Test that the selected_files_signal is connected.""" 

1090 widget = FileSelectorWidget( 

1091 context=mock_context_selector, 

1092 has_existing_function=mock_has_existing_function, 

1093 label="test", 

1094 allow_multiple=True 

1095 ) 

1096 qtbot.addWidget(widget) 

1097 

1098 # Verify that the signal is connected (by emitting) 

1099 test_files = ["/fake/file.nii"] 

1100 mock_context_selector["selected_files_signal"].emit(test_files) 

1101 

1102 # The widget should have received the signal 

1103 

1104 

1105class TestTranslations: 

1106 """Tests for translations.""" 

1107 

1108 def test_translate_ui_called_on_init(self, qtbot, mock_context_selector, mock_has_existing_function): 

1109 """Test that _translate_ui is called during init.""" 

1110 with patch.object(FileSelectorWidget, '_translate_ui') as mock_translate: 

1111 widget = FileSelectorWidget( 

1112 context=mock_context_selector, 

1113 has_existing_function=mock_has_existing_function, 

1114 label="test", 

1115 allow_multiple=True 

1116 ) 

1117 qtbot.addWidget(widget) 

1118 

1119 mock_translate.assert_called() 

1120 

1121 def test_translate_ui_updates_buttons(self, qtbot, mock_context_selector, mock_has_existing_function): 

1122 """Test that _translate_ui updates the buttons.""" 

1123 widget = FileSelectorWidget( 

1124 context=mock_context_selector, 

1125 has_existing_function=mock_has_existing_function, 

1126 label="test", 

1127 allow_multiple=True 

1128 ) 

1129 qtbot.addWidget(widget) 

1130 

1131 widget._translate_ui() 

1132 

1133 assert widget.file_button.text() != "" 

1134 assert widget.clear_button.text() != "" 

1135 

1136 def test_language_changed_signal(self, qtbot, mock_context_selector, signal_emitter, mock_has_existing_function): 

1137 """Test that the language_changed signal updates the UI.""" 

1138 widget = FileSelectorWidget( 

1139 context=mock_context_selector, 

1140 has_existing_function=mock_has_existing_function, 

1141 label="test", 

1142 allow_multiple=True 

1143 ) 

1144 qtbot.addWidget(widget) 

1145 

1146 with patch.object(widget, '_translate_ui') as mock_translate: 

1147 mock_context_selector["language_changed"].connect(mock_translate) 

1148 mock_context_selector["language_changed"].emit("it") 

1149 

1150 mock_translate.assert_called() 

1151 

1152 

1153class TestEdgeCases: 

1154 """Tests for edge cases.""" 

1155 

1156 def test_empty_file_list(self, qtbot, mock_context_selector, mock_has_existing_function): 

1157 """Test with empty file list.""" 

1158 widget = FileSelectorWidget( 

1159 context=mock_context_selector, 

1160 has_existing_function=mock_has_existing_function, 

1161 label="test", 

1162 allow_multiple=True 

1163 ) 

1164 qtbot.addWidget(widget) 

1165 

1166 widget.set_selected_files([]) 

1167 

1168 assert widget.selected_files == [] 

1169 assert widget.file_list_widget.count() == 0 

1170 

1171 def test_very_long_file_paths(self, qtbot, mock_context_selector, mock_has_existing_function, temp_workspace): 

1172 """Test with very long file paths.""" 

1173 long_path = os.path.join(temp_workspace, "a" * 200 + ".nii") 

1174 with open(long_path, "w") as f: 

1175 f.write("data") 

1176 

1177 widget = FileSelectorWidget( 

1178 context=mock_context_selector, 

1179 has_existing_function=mock_has_existing_function, 

1180 label="test", 

1181 allow_multiple=True 

1182 ) 

1183 qtbot.addWidget(widget) 

1184 

1185 widget.set_selected_files([long_path]) 

1186 

1187 assert len(widget.selected_files) == 1 

1188 assert widget.file_list_widget.count() == 1