Cover GDM module with tests, added FilesLabelerFactory, renamed GdmBuilder

This commit is contained in:
Vladyslav Hroshev
2025-04-13 23:50:22 +03:00
parent ca4e4d4cbe
commit 58658ff7fc
17 changed files with 775 additions and 31 deletions

View File

View File

@@ -0,0 +1,81 @@
from unittest import TestCase
from unittest.mock import Mock
from scripts.utils.global_theme.gdm import GDMTheme
from scripts.utils.global_theme.gdm_installer import GDMThemeInstaller
from scripts.utils.global_theme.gdm_preparer import GDMThemePreparer
from scripts.utils.global_theme.gdm_remover import GDMThemeRemover
class GDMTestCase(TestCase):
def setUp(self):
self.preparer = Mock(spec=GDMThemePreparer)
self.installer = Mock(spec=GDMThemeInstaller)
self.remover = Mock(spec=GDMThemeRemover)
self.gdm = GDMTheme(self.preparer, self.installer, self.remover)
def test_prepare_uses_backup_if_installed(self):
self.installer.is_installed.return_value = True
self.gdm.prepare()
self.preparer.use_backup_as_source.assert_called_once()
def test_prepare_does_not_use_backup_if_not_installed(self):
self.installer.is_installed.return_value = False
self.gdm.prepare()
self.preparer.use_backup_as_source.assert_not_called()
def test_prepare_calls_preparer_prepare_and_sets_themes(self):
mock_theme = Mock()
self.preparer.prepare.return_value = [mock_theme]
self.gdm.prepare()
self.preparer.prepare.assert_called_once()
self.assertEqual(self.gdm.themes, [mock_theme])
def test_install_correctly_passes_arguments_to_installer_compile(self):
hue = 100
name = "test_theme"
sat = 0.5
self.gdm.install(hue, name, sat)
self.installer.compile.assert_called_once_with(self.gdm.themes, hue, name, sat)
def test_install_calls_installer_backup_if_not_installed(self):
self.installer.is_installed.return_value = False
self.gdm.install(100, "test_theme")
self.installer.backup.assert_called_once()
def test_install_does_not_call_installer_backup_if_installed(self):
self.installer.is_installed.return_value = True
self.gdm.install(100, "test_theme")
self.installer.backup.assert_not_called()
def test_install_calls_installer_install(self):
self.gdm.install(100, "test_theme")
self.installer.install.assert_called_once()
def test_remove_calls_installer_remove_if_installed(self):
self.installer.is_installed.return_value = True
self.gdm.remove()
self.remover.remove.assert_called_once()
def test_remove_calls_installer_warn_if_not_installed(self):
self.installer.is_installed.return_value = False
self.gdm.remove()
self.remover.remove.assert_not_called()

View File

@@ -0,0 +1,155 @@
from unittest import TestCase
from unittest.mock import Mock
from scripts.types.installation_color import InstallationMode
from scripts.utils.global_theme.gdm_builder import GDMThemeBuilder
class GDMBuilderTestCase(TestCase):
def setUp(self):
self.colors_provider = Mock()
self.builder = GDMThemeBuilder(colors_provider=self.colors_provider)
def test_with_mode_sets_correct_mode(self):
self.builder._mode = None
mode: InstallationMode = "dark"
builder = self.builder.with_mode(mode)
self.assertEqual(builder._mode, mode)
def test_with_filled_sets_correct_filled_state(self):
self.builder._is_filled = False
is_filled = True
builder = self.builder.with_filled(is_filled)
self.assertEqual(builder._is_filled, is_filled)
def test_with_logger_factory_sets_specified_logger_factory(self):
logger_factory = Mock()
builder = self.builder.with_logger_factory(logger_factory)
self.assertEqual(builder._logger_factory, logger_factory)
def test_with_gresource_sets_specified_gresource(self):
gresource = Mock()
builder = self.builder.with_gresource(gresource)
self.assertEqual(builder._gresource, gresource)
def test_with_ubuntu_gdm_alternatives_updater_sets_specified_updater(self):
alternatives_updater = Mock()
builder = self.builder.with_ubuntu_gdm_alternatives_updater(alternatives_updater)
self.assertEqual(builder._ubuntu_gdm_alternatives_updater, alternatives_updater)
def test_with_preparer_sets_specified_preparer(self):
preparer = Mock()
builder = self.builder.with_preparer(preparer)
self.assertEqual(builder._preparer, preparer)
def test_with_installer_sets_specified_installer(self):
installer = Mock()
builder = self.builder.with_installer(installer)
self.assertEqual(builder._installer, installer)
def test_with_remover_sets_specified_remover(self):
remover = Mock()
builder = self.builder.with_remover(remover)
self.assertEqual(builder._remover, remover)
def test_resolve_logger_factory_initializes_logger_factory(self):
self.builder._logger_factory = None
self.builder._resolve_logger_factory()
self.assertIsNotNone(self.builder._logger_factory)
def test_resolve_gresource_initializes_gresource(self):
self.builder._logger_factory = Mock()
self.builder._gresource = None
self.builder._resolve_gresource()
self.assertIsNotNone(self.builder._gresource)
def test_builder_supports_chaining(self):
theme = self.builder.with_mode("dark").with_filled(True).build()
self.assertIsNotNone(theme)
def test_resolve_ubuntu_gdm_alternatives_updater_initializes_gresource(self):
self.builder._logger_factory = Mock()
self.builder._gresource = Mock()
self.builder._ubuntu_gdm_alternatives_updater = None
self.builder._resolve_ubuntu_gdm_alternatives_updater()
self.assertIsNotNone(self.builder._ubuntu_gdm_alternatives_updater)
def test_resolve_preparer_initializes_preparer(self):
self.builder._logger_factory = Mock()
self.builder._gresource = Mock()
self.builder._ubuntu_gdm_alternatives_updater = Mock()
self.builder._preparer = None
self.builder._resolve_preparer()
self.assertIsNotNone(self.builder._preparer)
def test_resolve_installer_initializes_installer(self):
self.builder._gresource = Mock()
self.builder._ubuntu_gdm_alternatives_updater = Mock()
self.builder._installer = None
self.builder._resolve_installer()
self.assertIsNotNone(self.builder._installer)
def test_resolve_remover_initializes_remover(self):
self.builder._gresource = Mock()
self.builder._ubuntu_gdm_alternatives_updater = Mock()
self.builder._remover = None
self.builder._resolve_remover()
self.assertIsNotNone(self.builder._remover)
def test_build_resolves_dependencies(self):
self.builder._resolve_logger_factory = Mock()
self.builder._resolve_gresource = Mock()
self.builder._resolve_ubuntu_gdm_alternatives_updater = Mock()
self.builder._resolve_preparer = Mock()
self.builder._resolve_installer = Mock()
self.builder._resolve_remover = Mock()
self.builder.build()
self.builder._resolve_logger_factory.assert_called_once()
self.builder._resolve_gresource.assert_called_once()
self.builder._resolve_ubuntu_gdm_alternatives_updater.assert_called_once()
self.builder._resolve_preparer.assert_called_once()
self.builder._resolve_installer.assert_called_once()
self.builder._resolve_remover.assert_called_once()
def test_build_correctly_builds_gdm_theme(self):
self.builder._preparer = Mock()
self.builder._installer = Mock()
self.builder._remover = Mock()
result = self.builder.build()
self.assertEqual(result.preparer, self.builder._preparer)
self.assertEqual(result.installer, self.builder._installer)
self.assertEqual(result.remover, self.builder._remover)
def test_build_with_explicit_dependencies_works_correctly(self):
preparer = Mock()
installer = Mock()
remover = Mock()
builder = (self.builder
.with_preparer(preparer)
.with_installer(installer)
.with_remover(remover))
result = builder.build()
self.assertEqual(result.preparer, preparer)
self.assertEqual(result.installer, installer)
self.assertEqual(result.remover, remover)

View File

@@ -0,0 +1,87 @@
import os.path
from unittest import TestCase
from unittest.mock import MagicMock
from scripts import config
from scripts.utils.global_theme.gdm_installer import GDMThemeInstaller
class GDMInstallerTestCase(TestCase):
def setUp(self):
self.temp_folder = os.path.join(config.temp_tests_folder, "gdm_installer")
self.gresource = MagicMock()
self.gresource.temp_folder = self.temp_folder
self.alternatives_updater = MagicMock()
self.gdm_installer = GDMThemeInstaller(
gresource=self.gresource,
alternatives_updater=self.alternatives_updater
)
def test_is_installed_return_the_same_value_as_gresource(self):
self.gresource.has_trigger.return_value = True
result = self.gdm_installer.is_installed()
self.assertTrue(result)
self.gresource.has_trigger.assert_called_once()
def test_compile_does_not_call_label_theme_if_label_is_none(self):
theme_prepare = MagicMock()
theme_prepare.label = None
theme_prepare.label_theme = MagicMock()
self.gdm_installer.compile(themes=[theme_prepare], hue=0, color="red", sat=None)
theme_prepare.label_theme.assert_not_called()
def test_compile_calls_label_theme_if_label_is_set(self):
theme_prepare = MagicMock()
theme_prepare.label = "dark"
theme_prepare.label_theme = MagicMock()
self.gdm_installer.compile(themes=[theme_prepare], hue=0, color="red", sat=None)
theme_prepare.label_theme.assert_called_once()
def test_compile_calls_removes_keywords_and_properties_and_prepends_source_styles(self):
theme_prepare = MagicMock()
theme_prepare.remove_keywords = MagicMock()
theme_prepare.remove_properties = MagicMock()
theme_prepare.prepend_source_styles = MagicMock()
self.gdm_installer.compile(themes=[theme_prepare], hue=0, color="red", sat=None)
theme_prepare.remove_keywords.assert_called_once()
theme_prepare.remove_properties.assert_called_once()
theme_prepare.prepend_source_styles.assert_called_once()
def test_compile_installs_themes_with_correct_parameters(self):
theme_prepare = MagicMock()
theme_prepare.install = MagicMock()
themes = [theme_prepare]
hue = 0
color = "red"
sat = None
self.gdm_installer.compile(themes, hue, color, sat)
theme_prepare.install.assert_called_once()
theme_prepare.install.assert_called_with(hue, color, sat, destination=self.temp_folder)
def test_compile_calls_gresource_compile(self):
self.gdm_installer.compile([], 0, "red", None)
self.gresource.compile.assert_called_once()
def test_backup_calls_gresource_backup(self):
self.gdm_installer.backup()
self.gresource.backup.assert_called_once()
def test_install_calls_gresource_move_and_alternatives_updater_install_and_set(self):
self.gdm_installer.install()
self.gresource.move.assert_called_once()
self.alternatives_updater.install_and_set.assert_called_once()

View File

@@ -0,0 +1,124 @@
import os
import shutil
import unittest
from unittest.mock import MagicMock, patch
from scripts import config
from scripts.types.theme_base import ThemeBase
from scripts.utils.global_theme.gdm_preparer import GDMThemePreparer
class DummyTheme(ThemeBase):
def __init__(self):
super().__init__()
self.temp_folder = None
self.main_styles = None
def prepare(self):
pass
def install(self, hue: int, name: str, sat: float | None = None):
pass
class TestGDMThemePreparer(unittest.TestCase):
def setUp(self):
self.temp_folder = os.path.join(config.temp_tests_folder, "gdm_preparer")
self.gresource = self._mock_gresource(self.temp_folder)
self.theme_builder = self._mock_builder()
self.mock_logger = MagicMock()
self.logger_factory = MagicMock()
self.logger_factory.create_logger.return_value = self.mock_logger
self.preparer = GDMThemePreparer(
temp_folder=self.temp_folder,
default_mode="light",
is_filled=True,
gresource=self.gresource,
theme_builder=self.theme_builder,
logger_factory=self.logger_factory,
files_labeler_factory=MagicMock(),
)
@staticmethod
def _mock_gresource(temp_folder):
gresource = MagicMock()
gresource.temp_folder = temp_folder
gresource.extract = MagicMock()
gresource.use_backup_gresource = MagicMock()
return gresource
@staticmethod
def _mock_builder():
theme_builder = MagicMock()
theme_builder.with_temp_folder.return_value = theme_builder
theme_builder.with_theme_name.return_value = theme_builder
theme_builder.with_mode.return_value = theme_builder
theme_builder.filled.return_value = theme_builder
theme_builder.with_logger_factory.return_value = theme_builder
theme_builder.with_reset_dependencies.return_value = theme_builder
theme_builder.build.return_value = DummyTheme()
return theme_builder
def tearDown(self):
shutil.rmtree(self.temp_folder, ignore_errors=True)
def test_use_backup_as_source(self):
self.preparer.use_backup_as_source()
self.gresource.use_backup_gresource.assert_called_once()
@patch("os.listdir")
def test_preparer_extracts_gresource(self, mock_listdir):
mock_listdir.return_value = ["gnome-shell.css"]
self.preparer.prepare()
self.gresource.extract.assert_called_once()
@patch("os.listdir")
def test_preparer_scans_correct_directory(self, mock_listdir):
mock_listdir.return_value = ["gnome-shell.css"]
self.preparer.prepare()
mock_listdir.assert_called_once_with(self.gresource.temp_folder)
@patch("os.listdir")
def test_preparer_filters_valid_css_files(self, mock_listdir):
valid_files = ["gnome-shell-dark.css", "gnome-shell-light.css", "gnome-shell.css"]
invalid_files = ["other.css", "readme.txt"]
mock_listdir.return_value = valid_files + invalid_files
themes = self.preparer.prepare()
self.assertEqual(len(themes), len(valid_files))
@patch("os.listdir")
def test_preparer_assigns_correct_labels(self, mock_listdir):
test_files = ["gnome-shell-dark.css", "gnome-shell-light.css", "gnome-shell.css"]
mock_listdir.return_value = test_files
themes = self.preparer.prepare()
expected_labels = {
"gnome-shell-dark.css": "dark",
"gnome-shell-light.css": "light",
"gnome-shell.css": "light" # Uses default_mode
}
for theme_obj in themes:
file_name = os.path.basename(theme_obj.theme_file)
self.assertEqual(theme_obj.label, expected_labels[file_name])
@patch("os.listdir")
def test_preparer_configures_theme_builder_correctly(self, mock_listdir):
mock_listdir.return_value = ["gnome-shell-dark.css", "gnome-shell.css"]
self.preparer.prepare()
self.theme_builder.with_theme_name.assert_any_call("gnome-shell-dark")
self.theme_builder.with_theme_name.assert_any_call("gnome-shell")

View File

@@ -0,0 +1,44 @@
from unittest import TestCase
from unittest.mock import MagicMock
from scripts.utils.global_theme.gdm_remover import GDMThemeRemover
from scripts.utils.gresource import GresourceBackupNotFoundError
class GDMRemoverTestCase(TestCase):
def setUp(self):
self.gresource = MagicMock()
self.alternatives_updater = MagicMock()
self.logger = MagicMock()
self.logger_factory = MagicMock(return_value=self.logger)
self.remover = GDMThemeRemover(
gresource=self.gresource,
alternatives_updater=self.alternatives_updater,
logger_factory=self.logger_factory
)
self.remover.remover_logger = MagicMock()
def test_remove_logs_start_message(self):
self.remover.remove()
self.remover.remover_logger.start_removing.assert_called_once()
def test_remove_calls_gresource_restore_and_alternatives_remove(self):
self.remover.remove()
self.gresource.restore.assert_called_once()
self.alternatives_updater.remove.assert_called_once()
def test_remove_logs_success_message(self):
self.remover.remove()
self.remover.remover_logger.success_removing.assert_called_once()
def test_remove_logs_error_message_when_backup_not_found(self):
self.gresource.restore.side_effect = GresourceBackupNotFoundError()
self.remover.remove()
self.remover.remover_logger.error_removing.assert_called_once()

View File

@@ -0,0 +1,118 @@
import os.path
import shutil
from unittest import TestCase
from unittest.mock import MagicMock
from scripts import config
from scripts.utils.global_theme.gdm_theme_prepare import GDMThemePrepare
from ..._helpers import create_dummy_file, try_remove_file
class GDMThemePrepareTestCase(TestCase):
def setUp(self):
self.temp_folder = os.path.join(config.temp_tests_folder, "gdm_theme_prepare")
self.main_styles = os.path.join(self.temp_folder, "gnome-shell.css")
self.theme = MagicMock()
self.theme.add_to_start.return_value = None
self.theme.temp_folder = self.temp_folder
self.theme.main_styles = self.main_styles
self.main_styles_destination = os.path.join(self.temp_folder, "gnome-shell-result.css")
create_dummy_file(self.main_styles_destination, "body { background-color: #000; }")
self.files_labeler = MagicMock()
self.theme_prepare = GDMThemePrepare(
theme=self.theme,
theme_file=self.main_styles_destination,
label=None,
files_labeler=self.files_labeler,
)
def tearDown(self):
shutil.rmtree(self.temp_folder, ignore_errors=True)
def test_label_files_calls_labeler(self):
self.theme_prepare.label = "dark"
self.theme_prepare.label_theme()
self.files_labeler.append_label.assert_called_once_with("dark")
def test_label_files_raises_value_error_if_label_none(self):
self.theme_prepare.label = None
with self.assertRaises(ValueError):
self.theme_prepare.label_theme()
def test_remove_keywords_removes_destination_keywords(self):
try_remove_file(self.main_styles_destination)
expected_content = "body { background-color: #000; }"
create_dummy_file(self.main_styles_destination, "body {keyword1 background-color: #000 !important; }")
keywords = ["keyword1", " !important"]
self.theme_prepare.remove_keywords(*keywords)
with open(self.main_styles_destination, 'r') as file:
content = file.read()
self.assertEqual(content, expected_content)
try_remove_file(self.main_styles_destination)
def test_remove_properties_removes_destination_properties(self):
try_remove_file(self.main_styles_destination)
expected_content = "body {\n}\n"
create_dummy_file(self.main_styles_destination, "body {\nbackground-color: #000;\n}")
properties = ["background-color"]
self.theme_prepare.remove_properties(*properties)
with open(self.main_styles_destination, 'r') as file:
actual_content = file.read()
self.assertEqual(expected_content, actual_content)
try_remove_file(self.main_styles_destination)
def test_remove_properties_removes_one_line_properties(self):
try_remove_file(self.main_styles_destination)
expected_content = ""
create_dummy_file(self.main_styles_destination, "body { background-color: #000; }")
properties = ["background-color"]
self.theme_prepare.remove_properties(*properties)
with open(self.main_styles_destination, 'r') as file:
actual_content = file.read()
self.assertEqual(expected_content, actual_content)
try_remove_file(self.main_styles_destination)
def test_prepend_source_styles_prepends_destination_styles(self):
try_remove_file(self.main_styles_destination)
expected_content = "body { background-color: #000; }\n"
create_dummy_file(self.main_styles_destination, "body { background-color: #000; }")
self.theme_prepare.prepend_source_styles("")
called_content: str = self.theme.add_to_start.call_args[0][0]
self.assertTrue(called_content.startswith(expected_content))
try_remove_file(self.main_styles_destination)
def test_prepend_source_styles_adds_trigger(self):
try_remove_file(self.main_styles_destination)
expected_content = "\ntrigger\n"
create_dummy_file(self.main_styles_destination)
trigger = "trigger"
self.theme_prepare.prepend_source_styles(trigger)
called_content: str = self.theme.add_to_start.call_args[0][0]
self.assertTrue(expected_content in called_content)
try_remove_file(self.main_styles_destination)
def test_install_passes_arguments_to_theme(self):
hue = 0
color = "#000000"
sat = 100
destination = os.path.join(self.temp_folder, "destination")
self.theme_prepare.install(hue, color, sat, destination)
self.theme.install.assert_called_once_with(hue, color, sat, destination=destination)

View File

@@ -0,0 +1,47 @@
from unittest import TestCase
from unittest.mock import MagicMock
from scripts.utils.alternatives_updater import AlternativesUpdater
from scripts.utils.global_theme.ubuntu_alternatives_updater import UbuntuGDMAlternativesUpdater
class UbuntuGDMUpdateAlternativesTestCase(TestCase):
def setUp(self):
self.updater = MagicMock(spec=AlternativesUpdater)
self.ubuntu_updater = UbuntuGDMAlternativesUpdater(
alternatives_updater=self.updater
)
def test_custom_destination_updates_correctly(self):
custom_destination_dir = "/custom/path"
custom_destination_file = "custom_file.gresource"
self.ubuntu_updater.with_custom_destination(
custom_destination_dir, custom_destination_file
)
self.assertEqual(
self.ubuntu_updater.destination_dir, custom_destination_dir
)
self.assertEqual(
self.ubuntu_updater.destination_file, custom_destination_file
)
def test_install_and_set_calls_updater_correctly(self):
priority = 100
self.ubuntu_updater.install_and_set(priority)
self.updater.install_and_set.assert_called_once_with(
link=self.ubuntu_updater.ubuntu_gresource_path,
name=self.ubuntu_updater.ubuntu_gresource_link_name,
path=self.ubuntu_updater.gnome_gresource_path,
priority=priority
)
def test_remove_calls_updater_correctly(self):
self.ubuntu_updater.remove()
self.updater.remove.assert_called_once_with(
name=self.ubuntu_updater.ubuntu_gresource_link_name,
path=self.ubuntu_updater.gnome_gresource_path
)

View File

@@ -0,0 +1,57 @@
import os.path
import shutil
from unittest import TestCase
from scripts import config
from scripts.utils.files_labeler import FilesLabelerFactoryImpl
from .._helpers import create_dummy_file
class FilesLabelerTestCase(TestCase):
def setUp(self):
self.temp_folder = os.path.join(config.temp_tests_folder, "labeler")
self.files = ["file1.svg", "file2.png", "file3.svg"]
self.styles_file = os.path.join(self.temp_folder, "styles-test.css") # styles files are already labeled
self.original_styles_content = f"body {{ background: url('./{self.files[0]}'); }}"
self.factory = FilesLabelerFactoryImpl()
def _generate_test_files(self):
self.tearDown()
for filename in self.files:
create_dummy_file(os.path.join(self.temp_folder, filename))
create_dummy_file(self.styles_file, self.original_styles_content)
def tearDown(self):
shutil.rmtree(self.temp_folder, ignore_errors=True)
def test_append_label_correctly_labels_files(self):
self._generate_test_files()
label = "test"
labeled_files = [(f, f.replace(".", f"-{label}.")) for f in self.files]
labeler = self.factory.create(self.temp_folder)
labeler.append_label(label)
for original, labeled in labeled_files:
labeled_path = os.path.join(self.temp_folder, labeled)
original_path = os.path.join(self.temp_folder, original)
self.assertTrue(os.path.exists(labeled_path))
self.assertFalse(os.path.exists(original_path))
def test_append_label_correctly_updates_references(self):
self._generate_test_files()
label = "test"
replaced_file = self.files[0].replace('.', f'-{label}.')
expected_content = f"body {{ background: url('./{replaced_file}'); }}"
labeler = self.factory.create(self.temp_folder, self.styles_file)
labeler.append_label(label)
with open(self.styles_file, 'r') as file:
actual_content = file.read()
self.assertNotEqual(actual_content, self.original_styles_content)
self.assertEqual(actual_content, expected_content)