test_theme_manager.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. """
  2. Tests for the Theme Manager UI component (Story 7.11).
  3. """
  4. import pytest
  5. from pathlib import Path
  6. import tempfile
  7. from PyQt6.QtWidgets import QApplication
  8. from src.ui.theme_manager import (
  9. ThemeMode,
  10. ThemeColors,
  11. ThemeManager,
  12. LIGHT_COLORS,
  13. DARK_COLORS,
  14. get_theme_manager,
  15. )
  16. @pytest.fixture
  17. def app(qtbot):
  18. """Create QApplication for tests."""
  19. return QApplication.instance() or QApplication([])
  20. @pytest.fixture
  21. def clean_manager():
  22. """Create a fresh ThemeManager for testing."""
  23. # Reset singleton
  24. import src.ui.theme_manager as tm
  25. tm._theme_manager_instance = None
  26. manager = ThemeManager()
  27. return manager
  28. class TestThemeMode:
  29. """Tests for ThemeMode enum."""
  30. def test_theme_mode_values(self):
  31. """Test ThemeMode enum values."""
  32. assert ThemeMode.LIGHT.value == "light"
  33. assert ThemeMode.DARK.value == "dark"
  34. assert ThemeMode.AUTO.value == "auto"
  35. class TestThemeColors:
  36. """Tests for ThemeColors dataclass."""
  37. def test_light_colors_structure(self):
  38. """Test light colors have all required fields."""
  39. assert hasattr(LIGHT_COLORS, "window_bg")
  40. assert hasattr(LIGHT_COLORS, "window_text")
  41. assert hasattr(LIGHT_COLORS, "highlight")
  42. assert hasattr(LIGHT_COLORS, "success")
  43. assert hasattr(LIGHT_COLORS, "warning")
  44. assert hasattr(LIGHT_COLORS, "error")
  45. def test_dark_colors_structure(self):
  46. """Test dark colors have all required fields."""
  47. assert hasattr(DARK_COLORS, "window_bg")
  48. assert hasattr(DARK_COLORS, "window_text")
  49. assert hasattr(DARK_COLORS, "highlight")
  50. assert hasattr(DARK_COLORS, "success")
  51. assert hasattr(DARK_COLORS, "warning")
  52. assert hasattr(DARK_COLORS, "error")
  53. def test_light_and_dark_differ(self):
  54. """Test that light and dark themes are different."""
  55. assert LIGHT_COLORS.window_bg != DARK_COLORS.window_bg
  56. assert LIGHT_COLORS.window_text != DARK_COLORS.window_text
  57. class TestThemeManager:
  58. """Tests for ThemeManager."""
  59. def test_initialization(self, clean_manager):
  60. """Test manager initialization."""
  61. assert clean_manager.current_mode == ThemeMode.LIGHT
  62. def test_set_theme_light(self, clean_manager):
  63. """Test setting light theme."""
  64. clean_manager.set_theme(ThemeMode.LIGHT)
  65. assert clean_manager.current_mode == ThemeMode.LIGHT
  66. assert clean_manager.colors == LIGHT_COLORS
  67. def test_set_theme_dark(self, clean_manager):
  68. """Test setting dark theme."""
  69. clean_manager.set_theme(ThemeMode.DARK)
  70. assert clean_manager.current_mode == ThemeMode.DARK
  71. assert clean_manager.colors == DARK_COLORS
  72. def test_toggle_theme(self, clean_manager):
  73. """Test toggling between themes."""
  74. clean_manager.set_theme(ThemeMode.LIGHT)
  75. clean_manager.toggle_theme()
  76. assert clean_manager.current_mode == ThemeMode.DARK
  77. clean_manager.toggle_theme()
  78. assert clean_manager.current_mode == ThemeMode.LIGHT
  79. def test_generate_stylesheet(self, clean_manager):
  80. """Test stylesheet generation."""
  81. stylesheet = clean_manager.generate_stylesheet()
  82. assert isinstance(stylesheet, str)
  83. assert len(stylesheet) > 0
  84. assert "QWidget" in stylesheet
  85. assert "QPushButton" in stylesheet
  86. assert "QLineEdit" in stylesheet
  87. def test_stylesheet_contains_colors(self, clean_manager):
  88. """Test that stylesheet contains current theme colors."""
  89. clean_manager.set_theme(ThemeMode.DARK)
  90. stylesheet = clean_manager.generate_stylesheet()
  91. assert DARK_COLORS.window_bg in stylesheet
  92. assert DARK_COLORS.highlight in stylesheet
  93. def test_apply_to_application(self, app, clean_manager):
  94. """Test applying theme to application."""
  95. clean_manager.set_theme(ThemeMode.DARK)
  96. clean_manager.apply_to_application(app)
  97. # Stylesheet should be set
  98. assert app.styleSheet() != ""
  99. def test_get_color(self, clean_manager):
  100. """Test getting individual colors."""
  101. clean_manager.set_theme(ThemeMode.LIGHT)
  102. highlight = clean_manager.get_color("highlight")
  103. assert highlight == LIGHT_COLORS.highlight
  104. window_bg = clean_manager.get_color("window_bg")
  105. assert window_bg == LIGHT_COLORS.window_bg
  106. def test_adjust_color(self, clean_manager):
  107. """Test color adjustment."""
  108. # Lighten a color
  109. result = clean_manager._adjust_color("#3498db", 20)
  110. assert result.startswith("#")
  111. assert len(result) == 7
  112. # Darken a color
  113. result = clean_manager._adjust_color("#3498db", -20)
  114. assert result.startswith("#")
  115. assert len(result) == 7
  116. def test_custom_stylesheet(self, clean_manager, tmp_path):
  117. """Test loading custom stylesheet."""
  118. css_file = tmp_path / "custom.css"
  119. css_file.write_text("QPushButton { background-color: red; }")
  120. result = clean_manager.load_stylesheet_from_file(css_file)
  121. assert result is True
  122. assert "red" in clean_manager._custom_stylesheet
  123. def test_load_nonexistent_stylesheet(self, clean_manager):
  124. """Test loading non-existent stylesheet file."""
  125. result = clean_manager.load_stylesheet_from_file(Path("/nonexistent/file.css"))
  126. assert result is False
  127. def test_theme_changed_signal(self, app, clean_manager, qtbot):
  128. """Test that theme_changed signal is emitted."""
  129. signals = []
  130. def on_theme_changed(mode):
  131. signals.append(mode)
  132. clean_manager.theme_changed.connect(on_theme_changed)
  133. clean_manager.set_theme(ThemeMode.DARK)
  134. assert len(signals) == 1
  135. assert signals[0] == ThemeMode.DARK
  136. def test_set_custom_stylesheet(self, clean_manager):
  137. """Test setting custom stylesheet."""
  138. custom_css = "QPushButton { background: red; }"
  139. clean_manager.set_custom_stylesheet(custom_css)
  140. assert clean_manager._custom_stylesheet == custom_css
  141. class TestThemeManagerSingleton:
  142. """Tests for the singleton pattern."""
  143. def test_get_singleton(self):
  144. """Test getting singleton instance."""
  145. manager = get_theme_manager()
  146. assert manager is not None
  147. assert isinstance(manager, ThemeManager)
  148. def test_singleton_persistence(self):
  149. """Test that singleton returns same instance."""
  150. manager1 = get_theme_manager()
  151. manager2 = get_theme_manager()
  152. assert manager1 is manager2