Coverage for tests/ui/test_tool_selection_page.py: 99%
215 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 15:51 +0000
1import pytest
2import platform
3from unittest.mock import Mock, patch, MagicMock
4from PyQt6.QtCore import QSettings, pyqtSignal, QObject
5from PyQt6.QtWidgets import QMessageBox, QRadioButton
7from main.ui.tool_selection_page import ToolSelectionPage
10@pytest.fixture
11def tool_page(qtbot, mock_context):
12 """Fixture that creates a ToolSelectionPage instance."""
13 previous_page = Mock()
14 page = ToolSelectionPage(mock_context, previous_page)
15 qtbot.addWidget(page)
16 return page
19class TestToolSelectionPageSetup:
20 """Tests for ToolSelectionPage initialization."""
22 def test_page_initialization(self, tool_page):
23 """Verify correct page initialization."""
24 assert tool_page.context is not None
25 assert tool_page.previous_page is not None
26 assert tool_page.selected_option is None
28 def test_title_created(self, tool_page):
29 """Verify that the title label is created."""
30 assert tool_page.title is not None
31 assert tool_page.title.text() != ""
33 def test_radio_buttons_created(self, tool_page):
34 """Verify that all radio buttons are created."""
35 assert tool_page.radio_skull is not None
36 assert tool_page.radio_draw is not None
37 assert tool_page.radio_dl is not None
38 assert tool_page.radio_analysis is not None
40 def test_radio_group_created(self, tool_page):
41 """Verify that the button group is created."""
42 assert tool_page.radio_group is not None
43 assert len(tool_page.radio_group.buttons()) == 4
45 def test_all_buttons_unchecked_initially(self, tool_page):
46 """Verify that all radio buttons are initially unchecked."""
47 assert tool_page.radio_group.checkedId() == -1
50class TestToolSelectionPageSelection:
51 """Tests for radio button selection logic."""
53 def test_select_skull_stripping(self, tool_page, qtbot):
54 """Verify skull stripping option selection."""
55 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
56 tool_page.radio_skull.setChecked(True)
57 assert tool_page.selected_option == 0
59 def test_select_automatic_drawing(self, tool_page, qtbot):
60 """Verify automatic drawing option selection."""
61 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
62 tool_page.radio_draw.setChecked(True)
63 assert tool_page.selected_option == 1
65 def test_select_deep_learning(self, tool_page, qtbot):
66 """Verify deep learning option selection."""
67 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
68 tool_page.radio_dl.setChecked(True)
69 assert tool_page.selected_option == 2
71 def test_select_full_pipeline(self, tool_page, qtbot):
72 """Verify full pipeline option selection."""
73 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
74 tool_page.radio_analysis.setChecked(True)
75 assert tool_page.selected_option == 3
77 def test_on_selection_updates_buttons(self, tool_page, qtbot):
78 """Verify that button selection updates main buttons."""
79 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
80 tool_page.radio_skull.setChecked(True)
81 tool_page.context["update_main_buttons"].assert_called()
83 def test_change_selection(self, tool_page, qtbot):
84 """Verify that changing selection updates the state."""
85 tool_page.radio_skull.setChecked(True)
86 assert tool_page.selected_option == 0
87 tool_page.radio_draw.setChecked(True)
88 assert tool_page.selected_option == 1
91class TestToolSelectionPageReadiness:
92 """Tests for readiness logic (next/back)."""
94 def test_not_ready_without_selection(self, tool_page):
95 """Verify not ready to advance without selection."""
96 assert not tool_page.is_ready_to_advance()
98 def test_ready_with_selection(self, tool_page):
99 """Verify ready to advance when a selection is made."""
100 tool_page.radio_skull.setChecked(True)
101 assert tool_page.is_ready_to_advance()
103 def test_can_go_back(self, tool_page):
104 """Verify that the page can always go back."""
105 assert tool_page.is_ready_to_go_back()
108class TestToolSelectionPageNavigation:
109 """Tests for navigation between pages."""
111 def test_back_returns_previous_page(self, tool_page):
112 """Verify that back() returns the previous page."""
113 result = tool_page.back()
114 assert result == tool_page.previous_page
115 tool_page.previous_page.on_enter.assert_called_once()
117 @patch('main.ui.tool_selection_page.SkullStrippingPage')
118 def test_next_skull_stripping(self, MockPage, tool_page):
119 """Verify navigation to SkullStrippingPage."""
120 mock_page = Mock()
121 MockPage.return_value = mock_page
122 tool_page.radio_skull.setChecked(True)
123 result = tool_page.next(tool_page.context)
124 assert result == mock_page
125 mock_page.on_enter.assert_called_once()
127 @patch('main.ui.tool_selection_page.MaskNiftiSelectionPage')
128 def test_next_automatic_drawing(self, MockPage, tool_page):
129 """Verify navigation to MaskNiftiSelectionPage."""
130 mock_page = Mock()
131 MockPage.return_value = mock_page
132 tool_page.radio_draw.setChecked(True)
133 result = tool_page.next(tool_page.context)
134 assert result == mock_page
135 mock_page.on_enter.assert_called_once()
137 @patch('main.ui.tool_selection_page.PipelinePatientSelectionPage')
138 def test_next_full_pipeline(self, MockPage, tool_page):
139 """Verify navigation to PipelinePatientSelectionPage."""
140 mock_page = Mock()
141 MockPage.return_value = mock_page
142 tool_page.radio_analysis.setChecked(True)
143 result = tool_page.next(tool_page.context)
144 assert result == mock_page
145 mock_page.on_enter.assert_called_once()
147 def test_next_caches_page(self, tool_page):
148 """Verify that pages are cached after creation."""
149 with patch('main.ui.tool_selection_page.SkullStrippingPage') as MockPage:
150 mock_page = Mock()
151 MockPage.return_value = mock_page
152 tool_page.radio_skull.setChecked(True)
153 result1 = tool_page.next(tool_page.context)
154 result2 = tool_page.next(tool_page.context)
155 assert result1 == result2
156 MockPage.assert_called_once()
159class TestToolSelectionPageDeepLearning:
160 """Tests for Deep Learning selection and GPU checks."""
162 @patch('platform.system', return_value='Linux')
163 @patch('torch.cuda.is_available', return_value=True)
164 @patch('main.ui.tool_selection_page.DlNiftiSelectionPage')
165 def test_dl_available_on_linux_with_gpu(self, MockPage, mock_cuda, mock_platform, tool_page):
166 """Verify DL page loads correctly on Linux with GPU."""
167 mock_page = Mock()
168 MockPage.return_value = mock_page
169 tool_page.radio_dl.setChecked(True)
170 result = tool_page.next(tool_page.context)
171 assert result == mock_page
172 mock_page.on_enter.assert_called_once()
174 @patch('platform.system', return_value='Windows')
175 @patch('torch.cuda.is_available', return_value=True)
176 def test_dl_not_available_on_windows(self, mock_cuda, mock_platform, tool_page, monkeypatch):
177 """Verify DL not available on Windows."""
178 message_shown = False
179 def mock_warning(*args, **kwargs):
180 nonlocal message_shown
181 message_shown = True
182 monkeypatch.setattr(QMessageBox, 'warning', mock_warning)
183 tool_page.radio_dl.setChecked(True)
184 result = tool_page.next(tool_page.context)
185 assert message_shown
186 assert result == tool_page
188 @patch('platform.system', return_value='Linux')
189 @patch('torch.cuda.is_available', return_value=False)
190 def test_dl_not_available_without_gpu(self, mock_cuda, mock_platform, tool_page, monkeypatch):
191 """Verify DL not available without a GPU."""
192 message_shown = False
193 def mock_warning(*args, **kwargs):
194 nonlocal message_shown
195 message_shown = True
196 monkeypatch.setattr(QMessageBox, 'warning', mock_warning)
197 tool_page.radio_dl.setChecked(True)
198 result = tool_page.next(tool_page.context)
199 assert message_shown
200 assert result == tool_page
202 @patch('platform.system', return_value='Darwin')
203 @patch('torch.cuda.is_available', return_value=False)
204 def test_dl_not_available_on_macos(self, mock_cuda, mock_platform, tool_page, monkeypatch):
205 """Verify DL not available on macOS."""
206 message_shown = False
207 def mock_warning(*args, **kwargs):
208 nonlocal message_shown
209 message_shown = True
210 monkeypatch.setattr(QMessageBox, 'warning', mock_warning)
211 tool_page.radio_dl.setChecked(True)
212 result = tool_page.next(tool_page.context)
213 assert message_shown
216class TestToolSelectionPageReset:
217 """Tests for page reset behavior."""
219 def test_reset_clears_selection(self, tool_page):
220 """Verify reset clears selected option."""
221 tool_page.radio_skull.setChecked(True)
222 assert tool_page.selected_option == 0
223 tool_page.reset_page()
224 assert tool_page.selected_option is None
225 assert tool_page.radio_group.checkedId() == -1
227 def test_reset_unchecks_all_buttons(self, tool_page):
228 """Verify reset unchecks all radio buttons."""
229 tool_page.radio_draw.setChecked(True)
230 tool_page.reset_page()
231 for button in tool_page.radio_group.buttons():
232 assert not button.isChecked()
235class TestToolSelectionPageResize:
236 """Tests for dynamic resizing behavior."""
238 def test_resize_event_updates_fonts(self, tool_page):
239 """Verify font size updates after resize."""
240 from PyQt6.QtGui import QResizeEvent
241 from PyQt6.QtCore import QSize
242 initial_font_size = tool_page.title.font().pointSize()
243 event = QResizeEvent(QSize(1200, 800), QSize(800, 600))
244 tool_page.resizeEvent(event)
245 new_font_size = tool_page.title.font().pointSize()
246 assert new_font_size >= 14
248 def test_resize_maintains_minimum_font_size(self, tool_page):
249 """Verify minimum font size is maintained."""
250 from PyQt6.QtGui import QResizeEvent
251 from PyQt6.QtCore import QSize
252 event = QResizeEvent(QSize(300, 200), QSize(800, 600))
253 tool_page.resizeEvent(event)
254 title_font_size = tool_page.title.font().pointSize()
255 assert title_font_size >= 14
256 button_font_size = tool_page.radio_skull.font().pointSize()
257 assert button_font_size >= 10
260class TestToolSelectionPageTranslation:
261 """Tests for UI translation updates."""
263 def test_translate_ui_updates_title(self, tool_page):
264 """Verify title text is updated."""
265 tool_page._translate_ui()
266 assert tool_page.title.text() != ""
268 def test_translate_ui_updates_buttons(self, tool_page):
269 """Verify radio button texts are updated."""
270 tool_page._translate_ui()
271 assert tool_page.radio_skull.text() != ""
272 assert tool_page.radio_draw.text() != ""
273 assert tool_page.radio_dl.text() != ""
274 assert tool_page.radio_analysis.text() != ""
276 def test_translate_ui_updates_group_box(self, tool_page):
277 """Verify group box title is updated."""
278 tool_page._translate_ui()
279 assert tool_page.radio_group_box.title() != ""
282class TestToolSelectionPageInfoLabel:
283 """Tests for Deep Learning info label."""
285 def test_dl_has_info_label(self, tool_page):
286 """Verify DL info label exists."""
287 assert hasattr(tool_page, 'dl_info_label')
288 assert tool_page.dl_info_label is not None
290 def test_info_label_has_tooltip(self, tool_page):
291 """Verify info label has a valid tooltip."""
292 tooltip = tool_page.dl_info_label.toolTip()
293 assert tooltip != ""
294 assert "GPU" in tooltip or "CUDA" in tooltip or tooltip == ""
297class TestToolSelectionPageIntegration:
298 """Integration tests for full workflow."""
300 def test_full_selection_workflow(self, tool_page, qtbot):
301 """Verify complete selection and readiness workflow."""
302 assert not tool_page.is_ready_to_advance()
303 with qtbot.waitSignal(tool_page.radio_group.buttonToggled, timeout=1000):
304 tool_page.radio_skull.setChecked(True)
305 assert tool_page.is_ready_to_advance()
306 assert tool_page.selected_option == 0
308 @patch('main.ui.tool_selection_page.SkullStrippingPage')
309 def test_navigation_and_cache(self, MockPage, tool_page):
310 """Verify navigation caching and repeated access."""
311 mock_page = Mock()
312 MockPage.return_value = mock_page
313 tool_page.radio_skull.setChecked(True)
314 result1 = tool_page.next(tool_page.context)
315 result2 = tool_page.next(tool_page.context)
316 assert result1 is result2
317 MockPage.assert_called_once()
318 assert mock_page.on_enter.call_count == 2
321if __name__ == "__main__":
322 pytest.main([__file__, "-v", "--tb=short"])