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
« 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
5from PyQt6.QtGui import QFont
6from PyQt6.QtWidgets import QMessageBox
7from PyQt6.QtCore import QCoreApplication, Qt
9from main.ui.dl_selection_page import DlNiftiSelectionPage
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)
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")
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)
39 return temp_workspace
42class TestDlNiftiSelectionPageInitialization:
43 """Tests for the page initialization."""
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)
50 assert page.context == mock_context
51 assert page.previous_page is None
52 assert page.next_page is None
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)
60 assert page.previous_page == previous
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)
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
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)
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]
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'])
88class TestHasExistingSegmentation:
89 """Tests for the has_existing_segmentation method."""
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)
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)
101 assert result is True
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")
117 page = DlNiftiSelectionPage(mock_context)
118 qtbot.addWidget(page)
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)
123 assert result is True
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)
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)
135 assert result is False
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)
142 nifti_path = os.path.join(temp_workspace, "random", "path", "file.nii")
143 result = page.has_existing_segmentation(nifti_path, temp_workspace)
145 assert result is False
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)
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)
156 assert result is False
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)
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")
175 page = DlNiftiSelectionPage(mock_context)
176 qtbot.addWidget(page)
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)
181 assert result is True
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)
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("{}")
201 page = DlNiftiSelectionPage(mock_context)
202 qtbot.addWidget(page)
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)
207 assert result is False
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")
223 page = DlNiftiSelectionPage(mock_context)
224 qtbot.addWidget(page)
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)
230 assert result is True
233class TestBackNavigation:
234 """Tests for back navigation."""
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)
242 result = page.back()
244 assert result == previous
245 previous.on_enter.assert_called_once()
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)
252 result = page.back()
254 assert result is None
257class TestNextNavigation:
258 """Tests for next navigation."""
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)
265 # Simulate file selection
266 page.file_selector_widget._selected_files = ["file1.nii", "file2.nii"]
268 result = page.next(mock_context)
270 assert page.next_page is not None
271 assert result == page.next_page
272 assert "DlExecutionPage" in str(type(page.next_page))
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)
279 selected_files = ["file1.nii", "file2.nii", "file3.nii"]
280 page.file_selector_widget._selected_files = selected_files
282 page.next(mock_context)
284 assert mock_context["selected_segmentation_files"] == selected_files
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)
291 page.file_selector_widget._selected_files = ["file1.nii"]
292 initial_history_len = len(mock_context["history"])
294 page.next(mock_context)
296 assert len(mock_context["history"]) == initial_history_len + 1
297 assert page.next_page in mock_context["history"]
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)
304 page.file_selector_widget._selected_files = ["file1.nii"]
306 # First call
307 result1 = page.next(mock_context)
308 first_next_page = page.next_page
310 # Second call
311 result2 = page.next(mock_context)
313 assert page.next_page == first_next_page
314 assert result1 == result2
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)
321 page.file_selector_widget._selected_files = ["file1.nii"]
323 with patch.object(page, 'next_page', create=True) as mock_next:
324 mock_next.on_enter = Mock()
325 page.next_page = mock_next
327 page.next(mock_context)
329 mock_next.on_enter.assert_called_once()
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)
336 page.file_selector_widget._selected_files = []
338 result = page.next(mock_context)
340 # Should still create the next page
341 assert result is not None
342 assert mock_context["selected_segmentation_files"] == []
345class TestOnEnter:
346 """Tests for the on_enter method."""
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)
353 page.status_label.setText("Some status message")
355 page.on_enter()
357 assert page.status_label.text() == ""
360class TestReadyToAdvance:
361 """Tests for is_ready_to_advance."""
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)
368 page.file_selector_widget._selected_files = ["file1.nii", "file2.nii"]
370 assert bool(page.is_ready_to_advance()) is True
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)
377 page.file_selector_widget._selected_files = []
379 assert not page.is_ready_to_advance()
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)
386 page.file_selector_widget._selected_files = ["single_file.nii"]
388 assert bool(page.is_ready_to_advance()) is True
391class TestReadyToGoBack:
392 """Tests for is_ready_to_go_back."""
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)
399 assert page.is_ready_to_go_back() is True
401 # Even with files selected
402 page.file_selector_widget._selected_files = ["file1.nii"]
403 assert page.is_ready_to_go_back() is True
406class TestResetPage:
407 """Tests for reset_page."""
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)
414 page.status_label.setText("Test status")
416 page.reset_page()
418 assert page.status_label.text() == ""
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)
425 mock_context["selected_segmentation_files"] = ["file1.nii", "file2.nii"]
427 page.reset_page()
429 assert mock_context["selected_segmentation_files"] == []
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)
436 page.status_label.setText("Test")
438 # Shouldn't crash
439 page.reset_page()
441 assert page.status_label.text() == ""
444class TestTranslation:
445 """Tests for translations."""
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)
453 mock_translate.assert_called()
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)
460 page._translate_ui()
462 assert page.title.text() is not None
463 assert len(page.title.text()) > 0
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)
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")
474 mock_translate.assert_called()
477class TestEdgeCases:
478 """Tests for edge cases."""
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)
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")
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 )
507 result = page.has_existing_segmentation(nifti_path, temp_workspace)
509 assert result is True
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)
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")
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 )
536 result = page.has_existing_segmentation(nifti_path, temp_workspace)
538 assert result is True
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)
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")
556 workspace_with_slash = temp_workspace + os.sep
557 nifti_path = os.path.join(temp_workspace, "sub-07", "anat", "file.nii")
559 result = page.has_existing_segmentation(nifti_path, workspace_with_slash)
561 assert result is True
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)
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")
580 nifti_path = os.path.join(temp_workspace, "sub-test_01", "anat", "file.nii")
581 result = page.has_existing_segmentation(nifti_path, temp_workspace)
583 assert result is True
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)
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)
600 nifti_path = os.path.join(temp_workspace, "sub-08", "anat", "file.nii")
601 result = page.has_existing_segmentation(nifti_path, temp_workspace)
603 assert result is False
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)
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")
622 nifti_path = os.path.join(temp_workspace, "sub-àèéìòù", "anat", "file.nii")
623 result = page.has_existing_segmentation(nifti_path, temp_workspace)
625 assert result is True
628class TestIntegration:
629 """Integration tests for complete flows."""
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)
636 # Simulate file selection
637 selected_files = ["file1.nii", "file2.nii", "file3.nii"]
638 page.file_selector_widget._selected_files = selected_files
640 # Verify ready to advance
641 assert bool(page.is_ready_to_advance()) is True
643 # Next
644 result = page.next(mock_context)
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
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)
656 # First selection
657 page.file_selector_widget._selected_files = ["file1.nii"]
658 page.next(mock_context)
660 assert mock_context["selected_segmentation_files"] == ["file1.nii"]
662 # Reset
663 page.reset_page()
665 assert mock_context["selected_segmentation_files"] == []
667 # New selection
668 page.file_selector_widget._selected_files = ["file2.nii", "file3.nii"]
669 page.next(mock_context)
671 assert mock_context["selected_segmentation_files"] == ["file2.nii", "file3.nii"]
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)
679 # Next
680 page.file_selector_widget._selected_files = ["file1.nii"]
681 next_page = page.next(mock_context)
683 assert next_page is not None
685 # Back
686 result = page.back()
688 assert result == previous
689 previous.on_enter.assert_called_once()
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)
696 # Initially empty
697 assert page.status_label.text() == ""
699 # Add status
700 page.status_label.setText("Processing...")
701 assert page.status_label.text() == "Processing..."
703 # on_enter clears it
704 page.on_enter()
705 assert page.status_label.text() == ""
707 # Add again
708 page.status_label.setText("Error occurred")
710 # reset_page clears it
711 page.reset_page()
712 assert page.status_label.text() == ""
715class TestContextHandling:
716 """Tests for context management."""
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)
723 # Shouldn't crash
724 page.reset_page()
726 assert page.context is None
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)
736 page.file_selector_widget._selected_files = ["file1.nii"]
738 # Shouldn't crash if history doesn't exist
739 result = page.next(context)
741 assert result is not None
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)
748 files = ["a.nii", "b.nii", "c.nii"]
749 page.file_selector_widget._selected_files = files
751 page.next(mock_context)
753 # Verify they are saved
754 assert "selected_segmentation_files" in mock_context
755 assert mock_context["selected_segmentation_files"] == files
757 # Even after reset
758 page.reset_page()
759 assert mock_context["selected_segmentation_files"] == []
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)
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"]
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"]
777class TestFileSelectorIntegration:
778 """Tests for FileSelectorWidget integration."""
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)
785 page.file_selector_widget._selected_files = ["file1.nii"]
787 with patch.object(page.file_selector_widget, 'get_selected_files', return_value=["file1.nii"]) as mock_get:
788 page.next(mock_context)
790 mock_get.assert_called()
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)
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
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
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)
811 call_kwargs = mock_file_selector_dl.call_args[1]
813 # Verify specific parameters for DL segmentation
814 assert call_kwargs['label'] == "seg"
815 assert call_kwargs['allow_multiple'] is True
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
822class TestPathHandling:
823 """Tests for path handling."""
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)
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")
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")
847 result = page.has_existing_segmentation(nifti_path, temp_workspace)
849 assert result is True
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)
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")
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)
871 assert result is True
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)
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")
890 nifti_path = os.path.join(temp_workspace, "sub-11", "anat", "file.nii")
891 result = page.has_existing_segmentation(nifti_path, temp_workspace)
893 # Should also work with real paths
894 assert result is True
897class TestUIInteraction:
898 """Tests for UI interaction."""
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)
905 font = page.title.font()
907 assert font.pointSize() == 18
908 assert font.weight() == QFont.Weight.Bold
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)
915 assert page.status_label.alignment() == Qt.AlignmentFlag.AlignCenter
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)
922 # Verify the layout is VBoxLayout
923 assert page.layout is not None
925 # Verify element order
926 assert page.layout.itemAt(0).widget() == page.title
927 assert page.layout.itemAt(1).widget() == page.file_selector_widget
930class TestErrorHandling:
931 """Tests for error handling."""
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)
939 # Path that doesn't exist
940 nifti_path = "/nonexistent/path/sub-12/anat/file.nii"
942 # Shouldn't crash
943 result = page.has_existing_segmentation(nifti_path, temp_workspace)
945 assert result is False
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)
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)
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")
962 nifti_path = os.path.join(temp_workspace, "sub-13", "anat", "file.nii")
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
974class TestBoundaryConditions:
975 """Tests for boundary conditions."""
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)
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}")
987 nifti_path = os.path.join(deep_path, "sub-14", "anat", "file.nii")
989 # Shouldn't crash
990 result = page.has_existing_segmentation(nifti_path, temp_workspace)
992 assert isinstance(result, bool)
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)
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)
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")
1014 nifti_path = os.path.join(temp_workspace, "sub-15", "anat", "file.nii")
1015 result = page.has_existing_segmentation(nifti_path, temp_workspace)
1017 # Should find at least one _seg file
1018 assert result is True
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)
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")
1036 # Subject ID in the last component
1037 nifti_path = os.path.join(temp_workspace, "data", "sub-16")
1039 result = page.has_existing_segmentation(nifti_path, temp_workspace)
1041 assert result is True
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)
1048 # Same path
1049 result = page.has_existing_segmentation(temp_workspace, temp_workspace)
1051 # Shouldn't crash
1052 assert isinstance(result, bool)
1055class TestConcurrency:
1056 """Tests for concurrent situations (simulated)."""
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)
1065 # Modify from page1
1066 page1.file_selector_widget._selected_files = ["file1.nii"]
1067 page1.next(mock_context)
1069 # Verify page2 sees the changes
1070 assert mock_context["selected_segmentation_files"] == ["file1.nii"]
1072 # Reset from page2
1073 page2.reset_page()
1075 # Verify the context is updated
1076 assert mock_context["selected_segmentation_files"] == []
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)
1084 page.file_selector_widget._selected_files = ["file1.nii"]
1086 # Rapid next and back
1087 for _ in range(10):
1088 page.next(mock_context)
1089 page.back()
1091 # Shouldn't crash
1092 assert True
1095class TestDocumentation:
1096 """Tests to verify documentation and comments."""
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)
1103 assert page.has_existing_segmentation.__doc__ is not None
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)
1110 assert page.reset_page.__doc__ is not None
1113class TestStateConsistency:
1114 """Tests to verify state consistency."""
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)
1121 # Sequence of operations
1122 page.file_selector_widget._selected_files = ["file1.nii"]
1123 assert bool(page.is_ready_to_advance()) is True
1125 page.next(mock_context)
1126 assert mock_context["selected_segmentation_files"] == ["file1.nii"]
1128 page.on_enter()
1129 assert page.status_label.text() == ""
1131 page.reset_page()
1132 assert mock_context["selected_segmentation_files"] == []
1134 # State should be consistent
1135 assert not bool(page.is_ready_to_advance())
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)
1143 qtbot.addWidget(page1)
1144 qtbot.addWidget(page2)
1146 # Modify page1
1147 page1.status_label.setText("Status 1")
1149 # page2 should have independent state
1150 assert page2.status_label.text() == ""
1152 # Resetting page1 should not affect page2
1153 page1.reset_page()
1154 page2.status_label.setText("Status 2")
1156 assert page1.status_label.text() == ""
1157 assert page2.status_label.text() == "Status 2"