Coverage for tests/ui/test_dl_selection_page.py: 99%

568 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 

4 

5from PyQt6.QtGui import QFont 

6from PyQt6.QtWidgets import QMessageBox 

7from PyQt6.QtCore import QCoreApplication, Qt 

8 

9from main.ui.dl_selection_page import DlNiftiSelectionPage 

10 

11 

12@pytest.fixture 

13def segmentation_workspace(temp_workspace): 

14 """Creates workspace with existing segmentations.""" 

15 # Create directory with existing segmentation 

16 seg_dir = os.path.join( 

17 temp_workspace, 

18 "derivatives", 

19 "deep_learning_seg", 

20 "sub-01", 

21 "anat" 

22 ) 

23 os.makedirs(seg_dir, exist_ok=True) 

24 

25 # Create segmentation file 

26 with open(os.path.join(seg_dir, "sub-01_T1w_seg.nii.gz"), "w") as f: 

27 f.write("segmentation data") 

28 

29 # Create patient without segmentation 

30 no_seg_dir = os.path.join( 

31 temp_workspace, 

32 "derivatives", 

33 "deep_learning_seg", 

34 "sub-02", 

35 "anat" 

36 ) 

37 os.makedirs(no_seg_dir, exist_ok=True) 

38 

39 return temp_workspace 

40 

41 

42class TestDlNiftiSelectionPageInitialization: 

43 """Tests for the page initialization.""" 

44 

45 def test_initialization_basic(self, qtbot, mock_context, mock_file_selector_dl): 

46 """Test basic initialization.""" 

47 page = DlNiftiSelectionPage(mock_context) 

48 qtbot.addWidget(page) 

49 

50 assert page.context == mock_context 

51 assert page.previous_page is None 

52 assert page.next_page is None 

53 

54 def test_initialization_with_previous_page(self, qtbot, mock_context, mock_file_selector_dl): 

55 """Test initialization with previous page.""" 

56 previous = Mock() 

57 page = DlNiftiSelectionPage(mock_context, previous_page=previous) 

58 qtbot.addWidget(page) 

59 

60 assert page.previous_page == previous 

61 

62 def test_ui_elements_created(self, qtbot, mock_context, mock_file_selector_dl): 

63 """Test that all UI elements are created.""" 

64 page = DlNiftiSelectionPage(mock_context) 

65 qtbot.addWidget(page) 

66 

67 assert page.title is not None 

68 assert page.file_selector_widget is not None 

69 assert page.status_label is not None 

70 assert page.layout is not None 

71 

72 def test_file_selector_widget_configured(self, qtbot, mock_context, mock_file_selector_dl): 

73 """Test that FileSelectorWidget is configured correctly.""" 

74 page = DlNiftiSelectionPage(mock_context) 

75 qtbot.addWidget(page) 

76 

77 # Verify the widget was created with the correct parameters 

78 mock_file_selector_dl.assert_called_once() 

79 call_kwargs = mock_file_selector_dl.call_args[1] 

80 

81 assert call_kwargs['parent'] == page 

82 assert call_kwargs['context'] == mock_context 

83 assert call_kwargs['label'] == "seg" 

84 assert call_kwargs['allow_multiple'] is True 

85 assert callable(call_kwargs['has_existing_function']) 

86 

87 

88class TestHasExistingSegmentation: 

89 """Tests for the has_existing_segmentation method.""" 

90 

91 def test_has_existing_segmentation_exists_nii_gz(self, qtbot, mock_context, segmentation_workspace, 

92 mock_file_selector_dl): 

93 """Test when .nii.gz segmentation exists.""" 

94 mock_context["workspace_path"] = segmentation_workspace 

95 page = DlNiftiSelectionPage(mock_context) 

96 qtbot.addWidget(page) 

97 

98 nifti_path = os.path.join(segmentation_workspace, "sub-01", "anat", "sub-01_T1w.nii") 

99 result = page.has_existing_segmentation(nifti_path, segmentation_workspace) 

100 

101 assert result is True 

102 

103 def test_has_existing_segmentation_exists_nii(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

104 """Test when .nii segmentation exists.""" 

105 # Create .nii segmentation (uncompressed) 

106 seg_dir = os.path.join( 

107 temp_workspace, 

108 "derivatives", 

109 "deep_learning_seg", 

110 "sub-03", 

111 "anat" 

112 ) 

113 os.makedirs(seg_dir, exist_ok=True) 

114 with open(os.path.join(seg_dir, "sub-03_T1w_seg.nii"), "w") as f: 

115 f.write("seg data") 

116 

117 page = DlNiftiSelectionPage(mock_context) 

118 qtbot.addWidget(page) 

119 

120 nifti_path = os.path.join(temp_workspace, "sub-03", "anat", "sub-03_T1w.nii") 

121 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

122 

123 assert result is True 

124 

125 def test_has_existing_segmentation_not_exists(self, qtbot, mock_context, segmentation_workspace, 

126 mock_file_selector_dl): 

127 """Test when segmentation does not exist.""" 

128 mock_context["workspace_path"] = segmentation_workspace 

129 page = DlNiftiSelectionPage(mock_context) 

130 qtbot.addWidget(page) 

131 

132 nifti_path = os.path.join(segmentation_workspace, "sub-02", "anat", "sub-02_T1w.nii") 

133 result = page.has_existing_segmentation(nifti_path, segmentation_workspace) 

134 

135 assert result is False 

136 

137 def test_has_existing_segmentation_no_subject_id(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

138 """Test when the path does not contain a subject ID.""" 

139 page = DlNiftiSelectionPage(mock_context) 

140 qtbot.addWidget(page) 

141 

142 nifti_path = os.path.join(temp_workspace, "random", "path", "file.nii") 

143 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

144 

145 assert result is False 

146 

147 def test_has_existing_segmentation_directory_not_exists(self, qtbot, mock_context, temp_workspace, 

148 mock_file_selector_dl): 

149 """Test when the segmentation directory does not exist.""" 

150 page = DlNiftiSelectionPage(mock_context) 

151 qtbot.addWidget(page) 

152 

153 nifti_path = os.path.join(temp_workspace, "sub-99", "anat", "sub-99_T1w.nii") 

154 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

155 

156 assert result is False 

157 

158 def test_has_existing_segmentation_multiple_files(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

159 """Test when there are multiple segmentation files.""" 

160 seg_dir = os.path.join( 

161 temp_workspace, 

162 "derivatives", 

163 "deep_learning_seg", 

164 "sub-04", 

165 "anat" 

166 ) 

167 os.makedirs(seg_dir, exist_ok=True) 

168 

169 # Create multiple seg files 

170 with open(os.path.join(seg_dir, "sub-04_T1w_seg.nii.gz"), "w") as f: 

171 f.write("seg1") 

172 with open(os.path.join(seg_dir, "sub-04_flair_seg.nii"), "w") as f: 

173 f.write("seg2") 

174 

175 page = DlNiftiSelectionPage(mock_context) 

176 qtbot.addWidget(page) 

177 

178 nifti_path = os.path.join(temp_workspace, "sub-04", "anat", "sub-04_T1w.nii") 

179 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

180 

181 assert result is True 

182 

183 def test_has_existing_segmentation_ignores_non_seg_files(self, qtbot, mock_context, temp_workspace, 

184 mock_file_selector_dl): 

185 """Test that it ignores non-segmentation files.""" 

186 seg_dir = os.path.join( 

187 temp_workspace, 

188 "derivatives", 

189 "deep_learning_seg", 

190 "sub-05", 

191 "anat" 

192 ) 

193 os.makedirs(seg_dir, exist_ok=True) 

194 

195 # Create files that are not segmentations 

196 with open(os.path.join(seg_dir, "sub-05_T1w.nii.gz"), "w") as f: 

197 f.write("not seg") 

198 with open(os.path.join(seg_dir, "metadata.json"), "w") as f: 

199 f.write("{}") 

200 

201 page = DlNiftiSelectionPage(mock_context) 

202 qtbot.addWidget(page) 

203 

204 nifti_path = os.path.join(temp_workspace, "sub-05", "anat", "sub-05_T1w.nii") 

205 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

206 

207 assert result is False 

208 

209 def test_has_existing_segmentation_nested_subject_path(self, qtbot, mock_context, temp_workspace, 

210 mock_file_selector_dl): 

211 """Test with nested path (e.g., with session).""" 

212 seg_dir = os.path.join( 

213 temp_workspace, 

214 "derivatives", 

215 "deep_learning_seg", 

216 "sub-06", 

217 "anat" 

218 ) 

219 os.makedirs(seg_dir, exist_ok=True) 

220 with open(os.path.join(seg_dir, "sub-06_T1w_seg.nii.gz"), "w") as f: 

221 f.write("seg") 

222 

223 page = DlNiftiSelectionPage(mock_context) 

224 qtbot.addWidget(page) 

225 

226 # Path with session 

227 nifti_path = os.path.join(temp_workspace, "sub-06", "ses-01", "anat", "sub-06_T1w.nii") 

228 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

229 

230 assert result is True 

231 

232 

233class TestBackNavigation: 

234 """Tests for back navigation.""" 

235 

236 def test_back_with_previous_page(self, qtbot, mock_context, mock_file_selector_dl): 

237 """Test back with previous_page.""" 

238 previous = Mock() 

239 page = DlNiftiSelectionPage(mock_context, previous_page=previous) 

240 qtbot.addWidget(page) 

241 

242 result = page.back() 

243 

244 assert result == previous 

245 previous.on_enter.assert_called_once() 

246 

247 def test_back_no_previous_page(self, qtbot, mock_context, mock_file_selector_dl): 

248 """Test back without previous_page.""" 

249 page = DlNiftiSelectionPage(mock_context) 

250 qtbot.addWidget(page) 

251 

252 result = page.back() 

253 

254 assert result is None 

255 

256 

257class TestNextNavigation: 

258 """Tests for next navigation.""" 

259 

260 def test_next_creates_dl_execution_page(self, qtbot, mock_context, mock_file_selector_dl): 

261 """Test that next creates DlExecutionPage.""" 

262 page = DlNiftiSelectionPage(mock_context) 

263 qtbot.addWidget(page) 

264 

265 # Simulate file selection 

266 page.file_selector_widget._selected_files = ["file1.nii", "file2.nii"] 

267 

268 result = page.next(mock_context) 

269 

270 assert page.next_page is not None 

271 assert result == page.next_page 

272 assert "DlExecutionPage" in str(type(page.next_page)) 

273 

274 def test_next_saves_selected_files_to_context(self, qtbot, mock_context, mock_file_selector_dl): 

275 """Test that next saves selected files to the context.""" 

276 page = DlNiftiSelectionPage(mock_context) 

277 qtbot.addWidget(page) 

278 

279 selected_files = ["file1.nii", "file2.nii", "file3.nii"] 

280 page.file_selector_widget._selected_files = selected_files 

281 

282 page.next(mock_context) 

283 

284 assert mock_context["selected_segmentation_files"] == selected_files 

285 

286 def test_next_adds_to_history(self, qtbot, mock_context, mock_file_selector_dl): 

287 """Test that next adds to history.""" 

288 page = DlNiftiSelectionPage(mock_context) 

289 qtbot.addWidget(page) 

290 

291 page.file_selector_widget._selected_files = ["file1.nii"] 

292 initial_history_len = len(mock_context["history"]) 

293 

294 page.next(mock_context) 

295 

296 assert len(mock_context["history"]) == initial_history_len + 1 

297 assert page.next_page in mock_context["history"] 

298 

299 def test_next_reuses_existing_next_page(self, qtbot, mock_context, mock_file_selector_dl): 

300 """Test that next reuses the existing next_page.""" 

301 page = DlNiftiSelectionPage(mock_context) 

302 qtbot.addWidget(page) 

303 

304 page.file_selector_widget._selected_files = ["file1.nii"] 

305 

306 # First call 

307 result1 = page.next(mock_context) 

308 first_next_page = page.next_page 

309 

310 # Second call 

311 result2 = page.next(mock_context) 

312 

313 assert page.next_page == first_next_page 

314 assert result1 == result2 

315 

316 def test_next_calls_on_enter(self, qtbot, mock_context, mock_file_selector_dl): 

317 """Test that next calls on_enter on the next_page.""" 

318 page = DlNiftiSelectionPage(mock_context) 

319 qtbot.addWidget(page) 

320 

321 page.file_selector_widget._selected_files = ["file1.nii"] 

322 

323 with patch.object(page, 'next_page', create=True) as mock_next: 

324 mock_next.on_enter = Mock() 

325 page.next_page = mock_next 

326 

327 page.next(mock_context) 

328 

329 mock_next.on_enter.assert_called_once() 

330 

331 def test_next_with_empty_selection(self, qtbot, mock_context, mock_file_selector_dl): 

332 """Test next with empty selection.""" 

333 page = DlNiftiSelectionPage(mock_context) 

334 qtbot.addWidget(page) 

335 

336 page.file_selector_widget._selected_files = [] 

337 

338 result = page.next(mock_context) 

339 

340 # Should still create the next page 

341 assert result is not None 

342 assert mock_context["selected_segmentation_files"] == [] 

343 

344 

345class TestOnEnter: 

346 """Tests for the on_enter method.""" 

347 

348 def test_on_enter_clears_status(self, qtbot, mock_context, mock_file_selector_dl): 

349 """Test that on_enter clears the status label.""" 

350 page = DlNiftiSelectionPage(mock_context) 

351 qtbot.addWidget(page) 

352 

353 page.status_label.setText("Some status message") 

354 

355 page.on_enter() 

356 

357 assert page.status_label.text() == "" 

358 

359 

360class TestReadyToAdvance: 

361 """Tests for is_ready_to_advance.""" 

362 

363 def test_is_ready_to_advance_with_files(self, qtbot, mock_context, mock_file_selector_dl): 

364 """Test when files are selected.""" 

365 page = DlNiftiSelectionPage(mock_context) 

366 qtbot.addWidget(page) 

367 

368 page.file_selector_widget._selected_files = ["file1.nii", "file2.nii"] 

369 

370 assert bool(page.is_ready_to_advance()) is True 

371 

372 def test_is_ready_to_advance_without_files(self, qtbot, mock_context, mock_file_selector_dl): 

373 """Test when no files are selected.""" 

374 page = DlNiftiSelectionPage(mock_context) 

375 qtbot.addWidget(page) 

376 

377 page.file_selector_widget._selected_files = [] 

378 

379 assert not page.is_ready_to_advance() 

380 

381 def test_is_ready_to_advance_single_file(self, qtbot, mock_context, mock_file_selector_dl): 

382 """Test with a single file selected.""" 

383 page = DlNiftiSelectionPage(mock_context) 

384 qtbot.addWidget(page) 

385 

386 page.file_selector_widget._selected_files = ["single_file.nii"] 

387 

388 assert bool(page.is_ready_to_advance()) is True 

389 

390 

391class TestReadyToGoBack: 

392 """Tests for is_ready_to_go_back.""" 

393 

394 def test_is_ready_to_go_back_always_true(self, qtbot, mock_context, mock_file_selector_dl): 

395 """Test that is_ready_to_go_back always returns True.""" 

396 page = DlNiftiSelectionPage(mock_context) 

397 qtbot.addWidget(page) 

398 

399 assert page.is_ready_to_go_back() is True 

400 

401 # Even with files selected 

402 page.file_selector_widget._selected_files = ["file1.nii"] 

403 assert page.is_ready_to_go_back() is True 

404 

405 

406class TestResetPage: 

407 """Tests for reset_page.""" 

408 

409 def test_reset_page_clears_status(self, qtbot, mock_context, mock_file_selector_dl): 

410 """Test that reset_page clears the status.""" 

411 page = DlNiftiSelectionPage(mock_context) 

412 qtbot.addWidget(page) 

413 

414 page.status_label.setText("Test status") 

415 

416 page.reset_page() 

417 

418 assert page.status_label.text() == "" 

419 

420 def test_reset_page_clears_context(self, qtbot, mock_context, mock_file_selector_dl): 

421 """Test that reset_page clears the context.""" 

422 page = DlNiftiSelectionPage(mock_context) 

423 qtbot.addWidget(page) 

424 

425 mock_context["selected_segmentation_files"] = ["file1.nii", "file2.nii"] 

426 

427 page.reset_page() 

428 

429 assert mock_context["selected_segmentation_files"] == [] 

430 

431 def test_reset_page_without_context(self, qtbot, mock_file_selector_dl): 

432 """Test reset_page without context.""" 

433 page = DlNiftiSelectionPage(context=None) 

434 qtbot.addWidget(page) 

435 

436 page.status_label.setText("Test") 

437 

438 # Shouldn't crash 

439 page.reset_page() 

440 

441 assert page.status_label.text() == "" 

442 

443 

444class TestTranslation: 

445 """Tests for translations.""" 

446 

447 def test_translate_ui_called_on_init(self, qtbot, mock_context, mock_file_selector_dl): 

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

449 with patch.object(DlNiftiSelectionPage, '_translate_ui') as mock_translate: 

450 page = DlNiftiSelectionPage(mock_context) 

451 qtbot.addWidget(page) 

452 

453 mock_translate.assert_called() 

454 

455 def test_translate_ui_updates_title(self, qtbot, mock_context, mock_file_selector_dl): 

456 """Test that _translate_ui updates the title.""" 

457 page = DlNiftiSelectionPage(mock_context) 

458 qtbot.addWidget(page) 

459 

460 page._translate_ui() 

461 

462 assert page.title.text() is not None 

463 assert len(page.title.text()) > 0 

464 

465 def test_language_changed_signal(self, qtbot, mock_context, signal_emitter, mock_file_selector_dl): 

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

467 page = DlNiftiSelectionPage(mock_context) 

468 qtbot.addWidget(page) 

469 

470 with patch.object(page, '_translate_ui') as mock_translate: 

471 mock_context["language_changed"].connect(mock_translate) 

472 mock_context["language_changed"].emit("it") 

473 

474 mock_translate.assert_called() 

475 

476 

477class TestEdgeCases: 

478 """Tests for edge cases.""" 

479 

480 def test_subject_id_extraction_complex_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

481 """Test subject ID extraction with complex paths.""" 

482 page = DlNiftiSelectionPage(mock_context) 

483 qtbot.addWidget(page) 

484 

485 # Create segmentation 

486 seg_dir = os.path.join( 

487 temp_workspace, 

488 "derivatives", 

489 "deep_learning_seg", 

490 "sub-complex123", 

491 "anat" 

492 ) 

493 os.makedirs(seg_dir, exist_ok=True) 

494 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

495 f.write("seg") 

496 

497 # Path with many levels 

498 nifti_path = os.path.join( 

499 temp_workspace, 

500 "rawdata", 

501 "sub-complex123", 

502 "ses-01", 

503 "anat", 

504 "file.nii" 

505 ) 

506 

507 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

508 

509 assert result is True 

510 

511 def test_multiple_sub_in_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

512 """Test with multiple 'sub-' in path (should take the first).""" 

513 page = DlNiftiSelectionPage(mock_context) 

514 qtbot.addWidget(page) 

515 

516 # Create segmentation for sub-01 

517 seg_dir = os.path.join( 

518 temp_workspace, 

519 "derivatives", 

520 "deep_learning_seg", 

521 "sub-01", 

522 "anat" 

523 ) 

524 os.makedirs(seg_dir, exist_ok=True) 

525 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

526 f.write("seg") 

527 

528 # Path containing 'sub-' in both directory name and file name 

529 nifti_path = os.path.join( 

530 temp_workspace, 

531 "sub-01", 

532 "anat", 

533 "sub-01_T1w.nii" # 'sub-' also appears here 

534 ) 

535 

536 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

537 

538 assert result is True 

539 

540 def test_workspace_path_with_trailing_slash(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

541 """Test with workspace_path that has a trailing slash.""" 

542 page = DlNiftiSelectionPage(mock_context) 

543 qtbot.addWidget(page) 

544 

545 seg_dir = os.path.join( 

546 temp_workspace, 

547 "derivatives", 

548 "deep_learning_seg", 

549 "sub-07", 

550 "anat" 

551 ) 

552 os.makedirs(seg_dir, exist_ok=True) 

553 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

554 f.write("seg") 

555 

556 workspace_with_slash = temp_workspace + os.sep 

557 nifti_path = os.path.join(temp_workspace, "sub-07", "anat", "file.nii") 

558 

559 result = page.has_existing_segmentation(nifti_path, workspace_with_slash) 

560 

561 assert result is True 

562 

563 def test_special_characters_in_paths(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

564 """Test with special characters in paths.""" 

565 page = DlNiftiSelectionPage(mock_context) 

566 qtbot.addWidget(page) 

567 

568 # Subject with special characters 

569 seg_dir = os.path.join( 

570 temp_workspace, 

571 "derivatives", 

572 "deep_learning_seg", 

573 "sub-test_01", 

574 "anat" 

575 ) 

576 os.makedirs(seg_dir, exist_ok=True) 

577 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

578 f.write("seg") 

579 

580 nifti_path = os.path.join(temp_workspace, "sub-test_01", "anat", "file.nii") 

581 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

582 

583 assert result is True 

584 

585 def test_empty_segmentation_directory(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

586 """Test with empty segmentation directory.""" 

587 page = DlNiftiSelectionPage(mock_context) 

588 qtbot.addWidget(page) 

589 

590 # Create directory but without files 

591 seg_dir = os.path.join( 

592 temp_workspace, 

593 "derivatives", 

594 "deep_learning_seg", 

595 "sub-08", 

596 "anat" 

597 ) 

598 os.makedirs(seg_dir, exist_ok=True) 

599 

600 nifti_path = os.path.join(temp_workspace, "sub-08", "anat", "file.nii") 

601 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

602 

603 assert result is False 

604 

605 def test_unicode_in_paths(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

606 """Test with unicode characters in paths.""" 

607 page = DlNiftiSelectionPage(mock_context) 

608 qtbot.addWidget(page) 

609 

610 # Subject with unicode 

611 seg_dir = os.path.join( 

612 temp_workspace, 

613 "derivatives", 

614 "deep_learning_seg", 

615 "sub-àèéìòù", 

616 "anat" 

617 ) 

618 os.makedirs(seg_dir, exist_ok=True) 

619 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

620 f.write("seg") 

621 

622 nifti_path = os.path.join(temp_workspace, "sub-àèéìòù", "anat", "file.nii") 

623 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

624 

625 assert result is True 

626 

627 

628class TestIntegration: 

629 """Integration tests for complete flows.""" 

630 

631 def test_full_selection_flow(self, qtbot, mock_context, mock_file_selector_dl): 

632 """Test full flow: selection -> next.""" 

633 page = DlNiftiSelectionPage(mock_context) 

634 qtbot.addWidget(page) 

635 

636 # Simulate file selection 

637 selected_files = ["file1.nii", "file2.nii", "file3.nii"] 

638 page.file_selector_widget._selected_files = selected_files 

639 

640 # Verify ready to advance 

641 assert bool(page.is_ready_to_advance()) is True 

642 

643 # Next 

644 result = page.next(mock_context) 

645 

646 # Verify 

647 assert result is not None 

648 assert mock_context["selected_segmentation_files"] == selected_files 

649 assert page.next_page is not None 

650 

651 def test_reset_and_reselect_flow(self, qtbot, mock_context, mock_file_selector_dl): 

652 """Test flow: selection -> reset -> new selection.""" 

653 page = DlNiftiSelectionPage(mock_context) 

654 qtbot.addWidget(page) 

655 

656 # First selection 

657 page.file_selector_widget._selected_files = ["file1.nii"] 

658 page.next(mock_context) 

659 

660 assert mock_context["selected_segmentation_files"] == ["file1.nii"] 

661 

662 # Reset 

663 page.reset_page() 

664 

665 assert mock_context["selected_segmentation_files"] == [] 

666 

667 # New selection 

668 page.file_selector_widget._selected_files = ["file2.nii", "file3.nii"] 

669 page.next(mock_context) 

670 

671 assert mock_context["selected_segmentation_files"] == ["file2.nii", "file3.nii"] 

672 

673 def test_back_and_forth_navigation(self, qtbot, mock_context, mock_file_selector_dl): 

674 """Test back and forth navigation.""" 

675 previous = Mock() 

676 page = DlNiftiSelectionPage(mock_context, previous_page=previous) 

677 qtbot.addWidget(page) 

678 

679 # Next 

680 page.file_selector_widget._selected_files = ["file1.nii"] 

681 next_page = page.next(mock_context) 

682 

683 assert next_page is not None 

684 

685 # Back 

686 result = page.back() 

687 

688 assert result == previous 

689 previous.on_enter.assert_called_once() 

690 

691 def test_status_label_lifecycle(self, qtbot, mock_context, mock_file_selector_dl): 

692 """Test status label lifecycle.""" 

693 page = DlNiftiSelectionPage(mock_context) 

694 qtbot.addWidget(page) 

695 

696 # Initially empty 

697 assert page.status_label.text() == "" 

698 

699 # Add status 

700 page.status_label.setText("Processing...") 

701 assert page.status_label.text() == "Processing..." 

702 

703 # on_enter clears it 

704 page.on_enter() 

705 assert page.status_label.text() == "" 

706 

707 # Add again 

708 page.status_label.setText("Error occurred") 

709 

710 # reset_page clears it 

711 page.reset_page() 

712 assert page.status_label.text() == "" 

713 

714 

715class TestContextHandling: 

716 """Tests for context management.""" 

717 

718 def test_context_none_handling(self, qtbot, mock_file_selector_dl): 

719 """Test handling None context.""" 

720 page = DlNiftiSelectionPage(context=None) 

721 qtbot.addWidget(page) 

722 

723 # Shouldn't crash 

724 page.reset_page() 

725 

726 assert page.context is None 

727 

728 def test_context_without_history(self, qtbot, mock_file_selector_dl): 

729 """Test context without history.""" 

730 context = { 

731 "workspace_path": "/fake/path" 

732 } 

733 page = DlNiftiSelectionPage(context) 

734 qtbot.addWidget(page) 

735 

736 page.file_selector_widget._selected_files = ["file1.nii"] 

737 

738 # Shouldn't crash if history doesn't exist 

739 result = page.next(context) 

740 

741 assert result is not None 

742 

743 def test_selected_files_persisted_in_context(self, qtbot, mock_context, mock_file_selector_dl): 

744 """Test that selected files persist in context.""" 

745 page = DlNiftiSelectionPage(mock_context) 

746 qtbot.addWidget(page) 

747 

748 files = ["a.nii", "b.nii", "c.nii"] 

749 page.file_selector_widget._selected_files = files 

750 

751 page.next(mock_context) 

752 

753 # Verify they are saved 

754 assert "selected_segmentation_files" in mock_context 

755 assert mock_context["selected_segmentation_files"] == files 

756 

757 # Even after reset 

758 page.reset_page() 

759 assert mock_context["selected_segmentation_files"] == [] 

760 

761 def test_multiple_next_calls_update_context(self, qtbot, mock_context, mock_file_selector_dl): 

762 """Test that multiple calls to next update the context.""" 

763 page = DlNiftiSelectionPage(mock_context) 

764 qtbot.addWidget(page) 

765 

766 # First call 

767 page.file_selector_widget._selected_files = ["file1.nii"] 

768 page.next(mock_context) 

769 assert mock_context["selected_segmentation_files"] == ["file1.nii"] 

770 

771 # Second call with different files 

772 page.file_selector_widget._selected_files = ["file2.nii", "file3.nii"] 

773 page.next(mock_context) 

774 assert mock_context["selected_segmentation_files"] == ["file2.nii", "file3.nii"] 

775 

776 

777class TestFileSelectorIntegration: 

778 """Tests for FileSelectorWidget integration.""" 

779 

780 def test_file_selector_get_selected_files_called(self, qtbot, mock_context, mock_file_selector_dl): 

781 """Test that get_selected_files is called.""" 

782 page = DlNiftiSelectionPage(mock_context) 

783 qtbot.addWidget(page) 

784 

785 page.file_selector_widget._selected_files = ["file1.nii"] 

786 

787 with patch.object(page.file_selector_widget, 'get_selected_files', return_value=["file1.nii"]) as mock_get: 

788 page.next(mock_context) 

789 

790 mock_get.assert_called() 

791 

792 def test_is_ready_to_advance_uses_file_selector(self, qtbot, mock_context, mock_file_selector_dl): 

793 """Test that is_ready_to_advance uses the file selector.""" 

794 page = DlNiftiSelectionPage(mock_context) 

795 qtbot.addWidget(page) 

796 

797 # Mock get_selected_files 

798 with patch.object(page.file_selector_widget, 'get_selected_files', return_value=["file1.nii"]): 

799 result = page.is_ready_to_advance() 

800 assert bool(result) is True 

801 

802 with patch.object(page.file_selector_widget, 'get_selected_files', return_value=[]): 

803 result = page.is_ready_to_advance() 

804 assert bool(result) is False 

805 

806 def test_file_selector_receives_correct_parameters(self, qtbot, mock_context, mock_file_selector_dl): 

807 """Test that FileSelectorWidget receives correct parameters.""" 

808 page = DlNiftiSelectionPage(mock_context) 

809 qtbot.addWidget(page) 

810 

811 call_kwargs = mock_file_selector_dl.call_args[1] 

812 

813 # Verify specific parameters for DL segmentation 

814 assert call_kwargs['label'] == "seg" 

815 assert call_kwargs['allow_multiple'] is True 

816 

817 # Verify has_existing_function is the correct function 

818 has_existing_func = call_kwargs['has_existing_function'] 

819 assert has_existing_func == page.has_existing_segmentation 

820 

821 

822class TestPathHandling: 

823 """Tests for path handling.""" 

824 

825 def test_path_normalization(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

826 """Test path normalization with mixed separators.""" 

827 page = DlNiftiSelectionPage(mock_context) 

828 qtbot.addWidget(page) 

829 

830 seg_dir = os.path.join( 

831 temp_workspace, 

832 "derivatives", 

833 "deep_learning_seg", 

834 "sub-09", 

835 "anat" 

836 ) 

837 os.makedirs(seg_dir, exist_ok=True) 

838 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

839 f.write("seg") 

840 

841 # Path with mixed separators (if on Windows) 

842 if os.sep == '\\': 

843 nifti_path = temp_workspace + "/sub-09/anat/file.nii" 

844 else: 

845 nifti_path = os.path.join(temp_workspace, "sub-09", "anat", "file.nii") 

846 

847 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

848 

849 assert result is True 

850 

851 def test_relative_vs_absolute_paths(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

852 """Test with relative vs absolute paths.""" 

853 page = DlNiftiSelectionPage(mock_context) 

854 qtbot.addWidget(page) 

855 

856 seg_dir = os.path.join( 

857 temp_workspace, 

858 "derivatives", 

859 "deep_learning_seg", 

860 "sub-10", 

861 "anat" 

862 ) 

863 os.makedirs(seg_dir, exist_ok=True) 

864 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

865 f.write("seg") 

866 

867 # Absolute path 

868 nifti_path = os.path.abspath(os.path.join(temp_workspace, "sub-10", "anat", "file.nii")) 

869 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

870 

871 assert result is True 

872 

873 def test_symlinks_in_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

874 """Test with symlinks in path (if supported by system).""" 

875 page = DlNiftiSelectionPage(mock_context) 

876 qtbot.addWidget(page) 

877 

878 # Create segmentation 

879 seg_dir = os.path.join( 

880 temp_workspace, 

881 "derivatives", 

882 "deep_learning_seg", 

883 "sub-11", 

884 "anat" 

885 ) 

886 os.makedirs(seg_dir, exist_ok=True) 

887 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

888 f.write("seg") 

889 

890 nifti_path = os.path.join(temp_workspace, "sub-11", "anat", "file.nii") 

891 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

892 

893 # Should also work with real paths 

894 assert result is True 

895 

896 

897class TestUIInteraction: 

898 """Tests for UI interaction.""" 

899 

900 def test_title_styling(self, qtbot, mock_context, mock_file_selector_dl): 

901 """Test that the title has the correct style.""" 

902 page = DlNiftiSelectionPage(mock_context) 

903 qtbot.addWidget(page) 

904 

905 font = page.title.font() 

906 

907 assert font.pointSize() == 18 

908 assert font.weight() == QFont.Weight.Bold 

909 

910 def test_status_label_alignment(self, qtbot, mock_context, mock_file_selector_dl): 

911 """Test that the status label is centered.""" 

912 page = DlNiftiSelectionPage(mock_context) 

913 qtbot.addWidget(page) 

914 

915 assert page.status_label.alignment() == Qt.AlignmentFlag.AlignCenter 

916 

917 def test_layout_structure(self, qtbot, mock_context, mock_file_selector_dl): 

918 """Test layout structure.""" 

919 page = DlNiftiSelectionPage(mock_context) 

920 qtbot.addWidget(page) 

921 

922 # Verify the layout is VBoxLayout 

923 assert page.layout is not None 

924 

925 # Verify element order 

926 assert page.layout.itemAt(0).widget() == page.title 

927 assert page.layout.itemAt(1).widget() == page.file_selector_widget 

928 

929 

930class TestErrorHandling: 

931 """Tests for error handling.""" 

932 

933 def test_has_existing_segmentation_permission_error(self, qtbot, mock_context, temp_workspace, 

934 mock_file_selector_dl): 

935 """Test permission error handling (simulated).""" 

936 page = DlNiftiSelectionPage(mock_context) 

937 qtbot.addWidget(page) 

938 

939 # Path that doesn't exist 

940 nifti_path = "/nonexistent/path/sub-12/anat/file.nii" 

941 

942 # Shouldn't crash 

943 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

944 

945 assert result is False 

946 

947 def test_has_existing_segmentation_with_corrupted_directory(self, qtbot, mock_context, temp_workspace, 

948 mock_file_selector_dl): 

949 """Test with corrupted directory (simulated with file instead of directory).""" 

950 page = DlNiftiSelectionPage(mock_context) 

951 qtbot.addWidget(page) 

952 

953 # Create a file instead of a directory 

954 derivatives_path = os.path.join(temp_workspace, "derivatives") 

955 os.makedirs(derivatives_path, exist_ok=True) 

956 

957 # File instead of directory 

958 fake_dir = os.path.join(derivatives_path, "deep_learning_seg") 

959 with open(fake_dir, "w") as f: 

960 f.write("this is a file, not a directory") 

961 

962 nifti_path = os.path.join(temp_workspace, "sub-13", "anat", "file.nii") 

963 

964 # Shouldn't crash 

965 try: 

966 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

967 # Could be True or False, the important thing is it doesn't crash 

968 assert isinstance(result, bool) 

969 except (OSError, NotADirectoryError): 

970 # Some OSes might raise exceptions 

971 pass 

972 

973 

974class TestBoundaryConditions: 

975 """Tests for boundary conditions.""" 

976 

977 def test_very_long_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

978 """Test with very long path.""" 

979 page = DlNiftiSelectionPage(mock_context) 

980 qtbot.addWidget(page) 

981 

982 # Create deeply nested path 

983 deep_path = temp_workspace 

984 for i in range(10): 

985 deep_path = os.path.join(deep_path, f"level{i}") 

986 

987 nifti_path = os.path.join(deep_path, "sub-14", "anat", "file.nii") 

988 

989 # Shouldn't crash 

990 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

991 

992 assert isinstance(result, bool) 

993 

994 def test_many_files_in_directory(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

995 """Test with many files in the segmentation directory.""" 

996 page = DlNiftiSelectionPage(mock_context) 

997 qtbot.addWidget(page) 

998 

999 seg_dir = os.path.join( 

1000 temp_workspace, 

1001 "derivatives", 

1002 "deep_learning_seg", 

1003 "sub-15", 

1004 "anat" 

1005 ) 

1006 os.makedirs(seg_dir, exist_ok=True) 

1007 

1008 # Create many files 

1009 for i in range(100): 

1010 filename = f"file{i}.nii" if i < 50 else f"file{i}_seg.nii.gz" 

1011 with open(os.path.join(seg_dir, filename), "w") as f: 

1012 f.write("data") 

1013 

1014 nifti_path = os.path.join(temp_workspace, "sub-15", "anat", "file.nii") 

1015 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

1016 

1017 # Should find at least one _seg file 

1018 assert result is True 

1019 

1020 def test_subject_id_at_end_of_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

1021 """Test when subject ID is at the end of the path.""" 

1022 page = DlNiftiSelectionPage(mock_context) 

1023 qtbot.addWidget(page) 

1024 

1025 seg_dir = os.path.join( 

1026 temp_workspace, 

1027 "derivatives", 

1028 "deep_learning_seg", 

1029 "sub-16", 

1030 "anat" 

1031 ) 

1032 os.makedirs(seg_dir, exist_ok=True) 

1033 with open(os.path.join(seg_dir, "ok_seg.nii.gz"), "w") as f: 

1034 f.write("seg") 

1035 

1036 # Subject ID in the last component 

1037 nifti_path = os.path.join(temp_workspace, "data", "sub-16") 

1038 

1039 result = page.has_existing_segmentation(nifti_path, temp_workspace) 

1040 

1041 assert result is True 

1042 

1043 def test_workspace_path_equals_nifti_path(self, qtbot, mock_context, temp_workspace, mock_file_selector_dl): 

1044 """Test when workspace_path equals nifti_path.""" 

1045 page = DlNiftiSelectionPage(mock_context) 

1046 qtbot.addWidget(page) 

1047 

1048 # Same path 

1049 result = page.has_existing_segmentation(temp_workspace, temp_workspace) 

1050 

1051 # Shouldn't crash 

1052 assert isinstance(result, bool) 

1053 

1054 

1055class TestConcurrency: 

1056 """Tests for concurrent situations (simulated).""" 

1057 

1058 def test_multiple_pages_same_context(self, qtbot, mock_context, mock_file_selector_dl): 

1059 """Test with multiple pages sharing the same context.""" 

1060 page1 = DlNiftiSelectionPage(mock_context) 

1061 page2 = DlNiftiSelectionPage(mock_context) 

1062 qtbot.addWidget(page1) 

1063 qtbot.addWidget(page2) 

1064 

1065 # Modify from page1 

1066 page1.file_selector_widget._selected_files = ["file1.nii"] 

1067 page1.next(mock_context) 

1068 

1069 # Verify page2 sees the changes 

1070 assert mock_context["selected_segmentation_files"] == ["file1.nii"] 

1071 

1072 # Reset from page2 

1073 page2.reset_page() 

1074 

1075 # Verify the context is updated 

1076 assert mock_context["selected_segmentation_files"] == [] 

1077 

1078 def test_rapid_next_back_navigation(self, qtbot, mock_context, mock_file_selector_dl): 

1079 """Test rapid next-back navigation.""" 

1080 previous = Mock() 

1081 page = DlNiftiSelectionPage(mock_context, previous_page=previous) 

1082 qtbot.addWidget(page) 

1083 

1084 page.file_selector_widget._selected_files = ["file1.nii"] 

1085 

1086 # Rapid next and back 

1087 for _ in range(10): 

1088 page.next(mock_context) 

1089 page.back() 

1090 

1091 # Shouldn't crash 

1092 assert True 

1093 

1094 

1095class TestDocumentation: 

1096 """Tests to verify documentation and comments.""" 

1097 

1098 def test_has_existing_segmentation_docstring(self, qtbot, mock_context, mock_file_selector_dl): 

1099 """Test that has_existing_segmentation has a docstring.""" 

1100 page = DlNiftiSelectionPage(mock_context) 

1101 qtbot.addWidget(page) 

1102 

1103 assert page.has_existing_segmentation.__doc__ is not None 

1104 

1105 def test_reset_page_docstring(self, qtbot, mock_context, mock_file_selector_dl): 

1106 """Test that reset_page has a docstring.""" 

1107 page = DlNiftiSelectionPage(mock_context) 

1108 qtbot.addWidget(page) 

1109 

1110 assert page.reset_page.__doc__ is not None 

1111 

1112 

1113class TestStateConsistency: 

1114 """Tests to verify state consistency.""" 

1115 

1116 def test_state_after_multiple_operations(self, qtbot, mock_context, mock_file_selector_dl): 

1117 """Test state after multiple operations.""" 

1118 page = DlNiftiSelectionPage(mock_context) 

1119 qtbot.addWidget(page) 

1120 

1121 # Sequence of operations 

1122 page.file_selector_widget._selected_files = ["file1.nii"] 

1123 assert bool(page.is_ready_to_advance()) is True 

1124 

1125 page.next(mock_context) 

1126 assert mock_context["selected_segmentation_files"] == ["file1.nii"] 

1127 

1128 page.on_enter() 

1129 assert page.status_label.text() == "" 

1130 

1131 page.reset_page() 

1132 assert mock_context["selected_segmentation_files"] == [] 

1133 

1134 # State should be consistent 

1135 assert not bool(page.is_ready_to_advance()) 

1136 

1137 def test_state_independence_from_previous_page(self, qtbot, mock_context, mock_file_selector_dl): 

1138 """Test that state is independent of previous_page.""" 

1139 previous = Mock() 

1140 page1 = DlNiftiSelectionPage(mock_context, previous_page=previous) 

1141 page2 = DlNiftiSelectionPage(mock_context, previous_page=None) 

1142 

1143 qtbot.addWidget(page1) 

1144 qtbot.addWidget(page2) 

1145 

1146 # Modify page1 

1147 page1.status_label.setText("Status 1") 

1148 

1149 # page2 should have independent state 

1150 assert page2.status_label.text() == "" 

1151 

1152 # Resetting page1 should not affect page2 

1153 page1.reset_page() 

1154 page2.status_label.setText("Status 2") 

1155 

1156 assert page1.status_label.text() == "" 

1157 assert page2.status_label.text() == "Status 2"