2025-11-30 15:37:32 +01:00
|
|
|
"""Tests for the refactored settings dialog UI."""
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
from PySide6 import QtCore, QtWidgets
|
|
|
|
|
|
|
|
|
|
from tidal_dl_ng.dialog import DialogPreferences
|
|
|
|
|
from tidal_dl_ng.ui.dialog_settings import Ui_DialogSettings
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def qapp():
|
|
|
|
|
"""Create QApplication instance for tests."""
|
|
|
|
|
app = QtWidgets.QApplication.instance()
|
|
|
|
|
if app is None:
|
|
|
|
|
app = QtWidgets.QApplication([])
|
|
|
|
|
yield app
|
|
|
|
|
# Cleanup is handled by pytest-qt if available, otherwise by QApplication
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestUiDialogSettings:
|
|
|
|
|
"""Test the generated UI class."""
|
|
|
|
|
|
|
|
|
|
def test_ui_setupui_creates_all_widgets(self, qapp):
|
|
|
|
|
"""Test that setupUi creates all expected widgets."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
# Check main structural widgets
|
|
|
|
|
assert hasattr(ui, "lw_categories")
|
|
|
|
|
assert hasattr(ui, "sw_categories")
|
|
|
|
|
assert hasattr(ui, "bb_dialog")
|
|
|
|
|
|
|
|
|
|
# Check pages exist
|
|
|
|
|
assert hasattr(ui, "page_flags")
|
|
|
|
|
assert hasattr(ui, "page_quality")
|
|
|
|
|
assert hasattr(ui, "page_numbers")
|
|
|
|
|
assert hasattr(ui, "page_paths")
|
|
|
|
|
|
|
|
|
|
def test_ui_list_widget_properties(self, qapp):
|
|
|
|
|
"""Test that the categories list widget has correct properties."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
assert ui.lw_categories is not None
|
|
|
|
|
assert isinstance(ui.lw_categories, QtWidgets.QListWidget)
|
|
|
|
|
assert ui.lw_categories.minimumWidth() == 150
|
|
|
|
|
assert ui.lw_categories.maximumWidth() == 200
|
|
|
|
|
|
|
|
|
|
def test_ui_stacked_widget_has_all_pages(self, qapp):
|
|
|
|
|
"""Test that the stacked widget contains all 4 pages."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
assert ui.sw_categories is not None
|
|
|
|
|
assert isinstance(ui.sw_categories, QtWidgets.QStackedWidget)
|
2025-12-03 11:41:18 +01:00
|
|
|
assert ui.sw_categories.count() == 5
|
2025-11-30 15:37:32 +01:00
|
|
|
|
|
|
|
|
def test_ui_flags_page_widgets(self, qapp):
|
|
|
|
|
"""Test that the Flags page contains all expected checkboxes."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
# Test flags group box exists
|
|
|
|
|
assert hasattr(ui, "gb_flags")
|
|
|
|
|
assert ui.gb_flags is not None
|
|
|
|
|
|
|
|
|
|
# Test all checkboxes exist
|
|
|
|
|
checkboxes = [
|
|
|
|
|
"cb_video_download",
|
|
|
|
|
"cb_video_convert_mp4",
|
|
|
|
|
"cb_lyrics_embed",
|
|
|
|
|
"cb_lyrics_file",
|
|
|
|
|
"cb_download_delay",
|
|
|
|
|
"cb_extract_flac",
|
|
|
|
|
"cb_metadata_cover_embed",
|
|
|
|
|
"cb_cover_album_file",
|
|
|
|
|
"cb_skip_existing",
|
|
|
|
|
"cb_symlink_to_track",
|
|
|
|
|
"cb_playlist_create",
|
|
|
|
|
"cb_mark_explicit",
|
|
|
|
|
"cb_use_primary_album_artist",
|
|
|
|
|
"cb_download_dolby_atmos",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for cb_name in checkboxes:
|
|
|
|
|
assert hasattr(ui, cb_name), f"Missing checkbox: {cb_name}"
|
|
|
|
|
checkbox = getattr(ui, cb_name)
|
|
|
|
|
assert isinstance(checkbox, QtWidgets.QCheckBox)
|
|
|
|
|
|
|
|
|
|
def test_ui_quality_page_widgets(self, qapp):
|
|
|
|
|
"""Test that the Quality page contains all expected combo boxes."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
# Test choices group box exists
|
|
|
|
|
assert hasattr(ui, "gb_choices")
|
|
|
|
|
assert ui.gb_choices is not None
|
|
|
|
|
|
|
|
|
|
# Test all combo boxes exist
|
|
|
|
|
combos = [
|
|
|
|
|
"c_quality_audio",
|
|
|
|
|
"c_quality_video",
|
|
|
|
|
"c_metadata_cover_dimension",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for combo_name in combos:
|
|
|
|
|
assert hasattr(ui, combo_name), f"Missing combo box: {combo_name}"
|
|
|
|
|
combo = getattr(ui, combo_name)
|
|
|
|
|
assert isinstance(combo, QtWidgets.QComboBox)
|
|
|
|
|
|
|
|
|
|
def test_ui_numbers_page_widgets(self, qapp):
|
|
|
|
|
"""Test that the Numbers page contains all expected spin boxes."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
# Test numbers group box exists
|
|
|
|
|
assert hasattr(ui, "gb_numbers")
|
|
|
|
|
assert ui.gb_numbers is not None
|
|
|
|
|
|
|
|
|
|
# Test all spin boxes exist
|
|
|
|
|
spinboxes = [
|
|
|
|
|
"sb_album_track_num_pad_min",
|
|
|
|
|
"sb_downloads_concurrent_max",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for sb_name in spinboxes:
|
|
|
|
|
assert hasattr(ui, sb_name), f"Missing spin box: {sb_name}"
|
|
|
|
|
spinbox = getattr(ui, sb_name)
|
|
|
|
|
assert isinstance(spinbox, QtWidgets.QSpinBox)
|
|
|
|
|
|
|
|
|
|
def test_ui_paths_page_widgets(self, qapp):
|
|
|
|
|
"""Test that the Paths page contains all expected line edits and buttons."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
# Test path group box exists
|
|
|
|
|
assert hasattr(ui, "gb_path")
|
|
|
|
|
assert ui.gb_path is not None
|
|
|
|
|
|
|
|
|
|
# Test all line edits exist
|
|
|
|
|
line_edits = [
|
|
|
|
|
"le_download_base_path",
|
|
|
|
|
"le_format_track",
|
|
|
|
|
"le_format_video",
|
|
|
|
|
"le_format_album",
|
|
|
|
|
"le_format_playlist",
|
|
|
|
|
"le_format_mix",
|
|
|
|
|
"le_path_binary_ffmpeg",
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for le_name in line_edits:
|
|
|
|
|
assert hasattr(ui, le_name), f"Missing line edit: {le_name}"
|
|
|
|
|
line_edit = getattr(ui, le_name)
|
|
|
|
|
assert isinstance(line_edit, QtWidgets.QLineEdit)
|
|
|
|
|
|
|
|
|
|
# Test buttons exist
|
|
|
|
|
assert hasattr(ui, "pb_download_base_path")
|
|
|
|
|
assert hasattr(ui, "pb_path_binary_ffmpeg")
|
|
|
|
|
|
|
|
|
|
def test_ui_dialog_button_box(self, qapp):
|
|
|
|
|
"""Test that the dialog has OK and Cancel buttons."""
|
|
|
|
|
dialog = QtWidgets.QDialog()
|
|
|
|
|
ui = Ui_DialogSettings()
|
|
|
|
|
ui.setupUi(dialog)
|
|
|
|
|
|
|
|
|
|
assert ui.bb_dialog is not None
|
|
|
|
|
assert isinstance(ui.bb_dialog, QtWidgets.QDialogButtonBox)
|
|
|
|
|
# Check that standard buttons include OK and Cancel
|
|
|
|
|
buttons = ui.bb_dialog.standardButtons()
|
|
|
|
|
assert QtWidgets.QDialogButtonBox.StandardButton.Ok in buttons
|
|
|
|
|
assert QtWidgets.QDialogButtonBox.StandardButton.Cancel in buttons
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestDialogPreferencesCategories:
|
|
|
|
|
"""Test the DialogPreferences category initialization and navigation."""
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def mock_settings(self):
|
|
|
|
|
"""Create a mock Settings object."""
|
|
|
|
|
settings = Mock()
|
|
|
|
|
settings.data = Mock()
|
|
|
|
|
|
|
|
|
|
# Set default values for all parameters
|
|
|
|
|
settings.data.lyrics_embed = False
|
|
|
|
|
settings.data.lyrics_file = False
|
|
|
|
|
settings.data.use_primary_album_artist = False
|
|
|
|
|
settings.data.video_download = False
|
|
|
|
|
settings.data.download_dolby_atmos = False
|
|
|
|
|
settings.data.download_delay = False
|
|
|
|
|
settings.data.video_convert_mp4 = False
|
|
|
|
|
settings.data.extract_flac = False
|
|
|
|
|
settings.data.metadata_cover_embed = False
|
|
|
|
|
settings.data.mark_explicit = False
|
|
|
|
|
settings.data.cover_album_file = False
|
|
|
|
|
settings.data.skip_existing = False
|
|
|
|
|
settings.data.symlink_to_track = False
|
|
|
|
|
settings.data.playlist_create = False
|
|
|
|
|
|
|
|
|
|
settings.data.download_base_path = str(Path.home())
|
|
|
|
|
settings.data.format_album = "{artist}/{album}"
|
|
|
|
|
settings.data.format_playlist = "Playlists/{name}"
|
|
|
|
|
settings.data.format_mix = "Mixes/{name}"
|
|
|
|
|
settings.data.format_track = "{title}"
|
|
|
|
|
settings.data.format_video = "{title}"
|
|
|
|
|
settings.data.path_binary_ffmpeg = "ffmpeg"
|
|
|
|
|
|
|
|
|
|
settings.data.album_track_num_pad_min = 2
|
|
|
|
|
settings.data.downloads_concurrent_max = 3
|
|
|
|
|
|
|
|
|
|
settings.data.quality_audio = Mock()
|
|
|
|
|
settings.data.quality_video = Mock()
|
|
|
|
|
settings.data.metadata_cover_dimension = Mock()
|
|
|
|
|
|
|
|
|
|
return settings
|
|
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_init_categories_adds_all_items(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that _init_categories adds all 4 categories to the list."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Check that all categories were added
|
2025-12-03 11:41:18 +01:00
|
|
|
assert dialog.ui.lw_categories.count() == 5
|
2025-11-30 15:37:32 +01:00
|
|
|
assert dialog.ui.lw_categories.item(0).text() == "Flags"
|
|
|
|
|
assert dialog.ui.lw_categories.item(1).text() == "Quality"
|
|
|
|
|
assert dialog.ui.lw_categories.item(2).text() == "Numbers"
|
|
|
|
|
assert dialog.ui.lw_categories.item(3).text() == "Paths & Formats"
|
2025-12-03 11:41:18 +01:00
|
|
|
assert dialog.ui.lw_categories.item(4).text() == "Delimiters"
|
2025-11-30 15:37:32 +01:00
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_init_categories_sets_first_selected(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that the first category is selected by default."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Check that first item is selected
|
|
|
|
|
assert dialog.ui.lw_categories.currentRow() == 0
|
|
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_category_change_switches_page(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that changing category switches the stacked widget page."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Initially on page 0
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 0
|
|
|
|
|
|
|
|
|
|
# Change to category 1 (Quality)
|
|
|
|
|
dialog.ui.lw_categories.setCurrentRow(1)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 1
|
|
|
|
|
|
|
|
|
|
# Change to category 2 (Numbers)
|
|
|
|
|
dialog.ui.lw_categories.setCurrentRow(2)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 2
|
|
|
|
|
|
|
|
|
|
# Change to category 3 (Paths & Formats)
|
|
|
|
|
dialog.ui.lw_categories.setCurrentRow(3)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 3
|
|
|
|
|
|
|
|
|
|
# Back to category 0 (Flags)
|
|
|
|
|
dialog.ui.lw_categories.setCurrentRow(0)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 0
|
|
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_on_category_changed_method(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that _on_category_changed method works correctly."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Test method directly
|
|
|
|
|
dialog._on_category_changed(0)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 0
|
|
|
|
|
|
|
|
|
|
dialog._on_category_changed(1)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 1
|
|
|
|
|
|
|
|
|
|
dialog._on_category_changed(2)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 2
|
|
|
|
|
|
|
|
|
|
dialog._on_category_changed(3)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestDialogPreferencesIntegration:
|
|
|
|
|
"""Integration tests for the complete dialog functionality."""
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
def mock_settings(self):
|
|
|
|
|
"""Create a mock Settings object with all required attributes."""
|
|
|
|
|
settings = Mock()
|
|
|
|
|
settings.data = Mock()
|
|
|
|
|
|
|
|
|
|
# Set all required attributes
|
|
|
|
|
for attr in [
|
|
|
|
|
"lyrics_embed",
|
|
|
|
|
"lyrics_file",
|
|
|
|
|
"use_primary_album_artist",
|
|
|
|
|
"video_download",
|
|
|
|
|
"download_dolby_atmos",
|
|
|
|
|
"download_delay",
|
|
|
|
|
"video_convert_mp4",
|
|
|
|
|
"extract_flac",
|
|
|
|
|
"metadata_cover_embed",
|
|
|
|
|
"mark_explicit",
|
|
|
|
|
"cover_album_file",
|
|
|
|
|
"skip_existing",
|
|
|
|
|
"symlink_to_track",
|
|
|
|
|
"playlist_create",
|
|
|
|
|
]:
|
|
|
|
|
setattr(settings.data, attr, False)
|
|
|
|
|
|
|
|
|
|
for attr in [
|
|
|
|
|
"download_base_path",
|
|
|
|
|
"format_album",
|
|
|
|
|
"format_playlist",
|
|
|
|
|
"format_mix",
|
|
|
|
|
"format_track",
|
|
|
|
|
"format_video",
|
|
|
|
|
"path_binary_ffmpeg",
|
|
|
|
|
]:
|
|
|
|
|
setattr(settings.data, attr, f"/test/{attr}")
|
|
|
|
|
|
|
|
|
|
settings.data.album_track_num_pad_min = 2
|
|
|
|
|
settings.data.downloads_concurrent_max = 3
|
|
|
|
|
|
|
|
|
|
settings.data.quality_audio = Mock()
|
|
|
|
|
settings.data.quality_video = Mock()
|
|
|
|
|
settings.data.metadata_cover_dimension = Mock()
|
|
|
|
|
|
|
|
|
|
return settings
|
|
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_dialog_structure_is_correct(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that the complete dialog structure is correctly set up."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Check dialog has UI
|
|
|
|
|
assert hasattr(dialog, "ui")
|
|
|
|
|
assert isinstance(dialog.ui, Ui_DialogSettings)
|
|
|
|
|
|
|
|
|
|
# Check categories list is populated
|
2025-12-03 11:41:18 +01:00
|
|
|
assert dialog.ui.lw_categories.count() == 5
|
2025-11-30 15:37:32 +01:00
|
|
|
|
2025-12-03 11:41:18 +01:00
|
|
|
# Check stacked widget has 5 pages
|
|
|
|
|
assert dialog.ui.sw_categories.count() == 5
|
2025-11-30 15:37:32 +01:00
|
|
|
|
|
|
|
|
# Check all pages are accessible
|
2025-12-03 11:41:18 +01:00
|
|
|
for i in range(5):
|
2025-11-30 15:37:32 +01:00
|
|
|
page = dialog.ui.sw_categories.widget(i)
|
|
|
|
|
assert page is not None
|
|
|
|
|
|
|
|
|
|
@patch.object(DialogPreferences, "exec")
|
|
|
|
|
def test_navigation_between_all_categories(self, mock_exec, mock_settings, qapp):
|
|
|
|
|
"""Test that navigation works correctly between all categories."""
|
|
|
|
|
settings_save = QtCore.Signal()
|
|
|
|
|
|
|
|
|
|
with patch.object(DialogPreferences, "gui_populate"):
|
|
|
|
|
dialog = DialogPreferences(mock_settings, settings_save)
|
|
|
|
|
|
|
|
|
|
# Navigate through all categories
|
|
|
|
|
for i in range(4):
|
|
|
|
|
dialog.ui.lw_categories.setCurrentRow(i)
|
|
|
|
|
assert dialog.ui.sw_categories.currentIndex() == i
|
|
|
|
|
|
|
|
|
|
# Check that the correct page is visible
|
|
|
|
|
current_widget = dialog.ui.sw_categories.currentWidget()
|
|
|
|
|
assert current_widget is not None
|