1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
|
"""
test_gui_duplicate_prevention.py
Test suite for GUI duplicate prevention features.
Tests cover:
- Tools menu action creation
- Duplicate prevention toggle
- Settings persistence in GUI
- Status messages
"""
from unittest.mock import MagicMock, patch
import pytest
# Mock Qt before importing GUI modules
with patch("tidal_dl_ng.gui.QtWidgets"), patch("tidal_dl_ng.gui.QtGui"), patch("tidal_dl_ng.gui.QtCore"):
pass
class TestToolsMenuIntegration:
"""Test Tools menu integration for duplicate prevention."""
@patch("tidal_dl_ng.gui.QtWidgets.QMenuBar")
@patch("tidal_dl_ng.gui.QtGui.QAction")
def test_tools_menu_created_if_not_exists(self, mock_action, mock_menubar):
"""Test that Tools menu is created if it doesn't exist."""
# This test verifies the logic in _init_menu_actions
# We're testing the structure, not the actual Qt implementation
mock_menubar_instance = MagicMock()
mock_menubar_instance.actions.return_value = [] # No existing Tools menu
mock_menubar_instance.addMenu = MagicMock()
# Simulate the logic from _init_menu_actions
tools_menu = None
for action in mock_menubar_instance.actions():
if action.text() == "Tools":
tools_menu = action.menu()
break
if not tools_menu:
tools_menu = mock_menubar_instance.addMenu("Tools")
# Verify addMenu was called
assert mock_menubar_instance.addMenu.called
@patch("tidal_dl_ng.gui.QtWidgets.QMenuBar")
@patch("tidal_dl_ng.gui.QtGui.QAction")
def test_tools_menu_found_if_exists(self, mock_action, mock_menubar):
"""Test that existing Tools menu is found and reused."""
mock_menubar_instance = MagicMock()
# Create mock existing Tools menu
mock_tools_action = MagicMock()
mock_tools_action.text.return_value = "Tools"
mock_tools_menu = MagicMock()
mock_tools_action.menu.return_value = mock_tools_menu
mock_menubar_instance.actions.return_value = [mock_tools_action]
mock_menubar_instance.addMenu = MagicMock()
# Simulate the logic
tools_menu = None
for action in mock_menubar_instance.actions():
if action.text() == "Tools":
tools_menu = action.menu()
break
# Should have found existing menu
assert tools_menu is mock_tools_menu
# Should NOT have called addMenu
assert not mock_menubar_instance.addMenu.called
class TestDuplicatePreventionAction:
"""Test the duplicate prevention menu action."""
def test_action_is_checkable(self):
"""Test that the duplicate prevention action is checkable."""
# This verifies the action configuration
# In the actual code: self.a_toggle_duplicate_prevention.setCheckable(True)
mock_action = MagicMock()
mock_action.setCheckable = MagicMock()
# Simulate action creation
mock_action.setCheckable(True)
mock_action.setCheckable.assert_called_once_with(True)
def test_action_initial_state_from_settings(self):
"""Test that action initial state reflects settings."""
mock_history_service = MagicMock()
mock_history_service.get_settings.return_value = {"preventDuplicates": True}
# Simulate getting initial state
is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
assert is_preventing is True
def test_action_connected_to_handler(self):
"""Test that action triggered signal connects to handler."""
mock_action = MagicMock()
mock_handler = MagicMock()
# Simulate connection
mock_action.triggered.connect(mock_handler)
mock_action.triggered.connect.assert_called_once_with(mock_handler)
class TestToggleDuplicatePreventionHandler:
"""Test the on_toggle_duplicate_prevention handler method."""
def test_handler_updates_settings_when_enabled(self):
"""Test that handler updates settings to True when enabled."""
mock_history_service = MagicMock()
# Simulate the handler logic
enabled = True
mock_history_service.update_settings(preventDuplicates=enabled)
mock_history_service.update_settings.assert_called_once_with(preventDuplicates=True)
def test_handler_updates_settings_when_disabled(self):
"""Test that handler updates settings to False when disabled."""
mock_history_service = MagicMock()
# Simulate the handler logic
enabled = False
mock_history_service.update_settings(preventDuplicates=enabled)
mock_history_service.update_settings.assert_called_once_with(preventDuplicates=False)
def test_handler_emits_status_message_enabled(self):
"""Test that handler emits status message when enabled."""
# Simulate the handler logic
enabled = True
status_msg = "enabled" if enabled else "disabled"
assert status_msg == "enabled"
def test_handler_emits_status_message_disabled(self):
"""Test that handler emits status message when disabled."""
enabled = False
status_msg = "enabled" if enabled else "disabled"
assert status_msg == "disabled"
def test_handler_logs_change(self):
"""Test that handler logs the settings change."""
from unittest.mock import MagicMock
mock_logger = MagicMock()
# Simulate handler logging
enabled = True
status_msg = "enabled" if enabled else "disabled"
mock_logger.info(f"Duplicate download prevention {status_msg}")
mock_logger.info.assert_called_once()
call_args = str(mock_logger.info.call_args)
assert "enabled" in call_args
class TestSettingsPersistence:
"""Test that GUI settings changes persist."""
def test_toggle_persists_to_json(self):
"""Test that toggling the option persists to the JSON file."""
mock_history_service = MagicMock()
# Simulate toggle
mock_history_service.update_settings(preventDuplicates=False)
# Verify update_settings was called (which triggers save)
mock_history_service.update_settings.assert_called_with(preventDuplicates=False)
def test_settings_restored_on_gui_restart(self):
"""Test that settings are restored when GUI restarts."""
mock_history_service = MagicMock()
mock_history_service.get_settings.return_value = {"preventDuplicates": False}
# Simulate GUI init reading settings
is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
assert is_preventing is False
class TestStatusBarMessages:
"""Test status bar message display."""
def test_status_message_format_enabled(self):
"""Test status message format when enabled."""
from tidal_dl_ng.model.gui_data import StatusbarMessage
enabled = True
status_msg = "enabled" if enabled else "disabled"
message = StatusbarMessage(message=f"Duplicate prevention {status_msg}.", timeout=2500)
assert "Duplicate prevention enabled" in message.message
assert message.timeout == 2500
def test_status_message_format_disabled(self):
"""Test status message format when disabled."""
from tidal_dl_ng.model.gui_data import StatusbarMessage
enabled = False
status_msg = "enabled" if enabled else "disabled"
message = StatusbarMessage(message=f"Duplicate prevention {status_msg}.", timeout=2500)
assert "Duplicate prevention disabled" in message.message
assert message.timeout == 2500
class TestViewHistoryAction:
"""Test the View Download History action."""
def test_view_history_action_exists(self):
"""Test that View History action is created."""
mock_action = MagicMock()
mock_handler = MagicMock()
# Simulate action creation
mock_action.setText("View Download History...")
mock_action.triggered.connect(mock_handler)
assert "View Download History" in str(mock_action.setText.call_args)
def test_view_history_opens_dialog(self):
"""Test that View History action opens the dialog."""
mock_dialog_class = MagicMock()
mock_history_service = MagicMock()
# Simulate dialog opening
mock_dialog_class(history_service=mock_history_service, parent=None)
mock_dialog_class.assert_called_once()
class TestMenuSeparator:
"""Test that separator is added between menu items."""
def test_separator_added_between_items(self):
"""Test that a separator is added between View History and Prevent Duplicates."""
mock_menu = MagicMock()
# Simulate adding items
mock_menu.addAction(MagicMock()) # View History
mock_menu.addSeparator()
mock_menu.addAction(MagicMock()) # Prevent Duplicates
# Verify separator was called
mock_menu.addSeparator.assert_called_once()
class TestIntegrationWithDownloadQueue:
"""Test integration with download queue."""
def test_prevention_affects_queue_downloads(self):
"""Test that prevention setting affects downloads from queue."""
mock_history_service = MagicMock()
mock_history_service.should_skip_download.return_value = True
# Simulate queue download check
track_id = "123456"
should_skip = mock_history_service.should_skip_download(track_id)
assert should_skip is True
def test_prevention_disabled_allows_queue_downloads(self):
"""Test that disabling prevention allows queue downloads."""
mock_history_service = MagicMock()
mock_history_service.should_skip_download.return_value = False
# Simulate queue download check
track_id = "123456"
should_skip = mock_history_service.should_skip_download(track_id)
assert should_skip is False
class TestActionStateSync:
"""Test that action state stays synced with settings."""
def test_action_checked_when_prevention_enabled(self):
"""Test that action appears checked when prevention is enabled."""
mock_action = MagicMock()
mock_history_service = MagicMock()
mock_history_service.get_settings.return_value = {"preventDuplicates": True}
# Simulate setting initial state
is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
mock_action.setChecked(is_preventing)
mock_action.setChecked.assert_called_once_with(True)
def test_action_unchecked_when_prevention_disabled(self):
"""Test that action appears unchecked when prevention is disabled."""
mock_action = MagicMock()
mock_history_service = MagicMock()
mock_history_service.get_settings.return_value = {"preventDuplicates": False}
# Simulate setting initial state
is_preventing = mock_history_service.get_settings().get("preventDuplicates", True)
mock_action.setChecked(is_preventing)
mock_action.setChecked.assert_called_once_with(False)
class TestErrorHandling:
"""Test error handling in GUI actions."""
def test_handler_continues_on_settings_error(self):
"""Test that handler continues gracefully if settings update fails."""
mock_history_service = MagicMock()
mock_history_service.update_settings.side_effect = Exception("Settings error")
# Simulate handler with try/except (if implemented)
try:
mock_history_service.update_settings(preventDuplicates=True)
success = True
except Exception:
success = False
# In current implementation, error would propagate
# This test documents current behavior
assert success is False
if __name__ == "__main__":
pytest.main([__file__, "-v"])
|