feat: create Theme class and registry with finalized color gradients
This commit is contained in:
60
engine/themes.py
Normal file
60
engine/themes.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""
|
||||
Theme definitions with color gradients for terminal rendering.
|
||||
|
||||
This module is data-only and does not import config or render
|
||||
to prevent circular dependencies.
|
||||
"""
|
||||
|
||||
|
||||
class Theme:
|
||||
"""Represents a color theme with two gradients."""
|
||||
|
||||
def __init__(self, name, main_gradient, message_gradient):
|
||||
"""Initialize a theme with name and color gradients.
|
||||
|
||||
Args:
|
||||
name: Theme identifier string
|
||||
main_gradient: List of 12 ANSI 256-color codes for main gradient
|
||||
message_gradient: List of 12 ANSI 256-color codes for message gradient
|
||||
"""
|
||||
self.name = name
|
||||
self.main_gradient = main_gradient
|
||||
self.message_gradient = message_gradient
|
||||
|
||||
|
||||
# ─── GRADIENT DEFINITIONS ─────────────────────────────────────────────────
|
||||
# Each gradient is 12 ANSI 256-color codes in sequence
|
||||
# Format: [light...] → [medium...] → [dark...] → [black]
|
||||
|
||||
_GREEN_MAIN = [231, 195, 123, 118, 82, 46, 40, 34, 28, 22, 22, 235]
|
||||
_GREEN_MSG = [231, 225, 219, 213, 207, 201, 165, 161, 125, 89, 89, 235]
|
||||
|
||||
_ORANGE_MAIN = [231, 215, 209, 208, 202, 166, 130, 94, 58, 94, 94, 235]
|
||||
_ORANGE_MSG = [231, 195, 33, 27, 21, 21, 21, 18, 18, 18, 18, 235]
|
||||
|
||||
_PURPLE_MAIN = [231, 225, 177, 171, 165, 135, 129, 93, 57, 57, 57, 235]
|
||||
_PURPLE_MSG = [231, 226, 226, 220, 220, 184, 184, 178, 178, 172, 172, 235]
|
||||
|
||||
|
||||
# ─── THEME REGISTRY ───────────────────────────────────────────────────────
|
||||
|
||||
THEME_REGISTRY = {
|
||||
"green": Theme("green", _GREEN_MAIN, _GREEN_MSG),
|
||||
"orange": Theme("orange", _ORANGE_MAIN, _ORANGE_MSG),
|
||||
"purple": Theme("purple", _PURPLE_MAIN, _PURPLE_MSG),
|
||||
}
|
||||
|
||||
|
||||
def get_theme(theme_id):
|
||||
"""Retrieve a theme by ID.
|
||||
|
||||
Args:
|
||||
theme_id: Theme identifier string
|
||||
|
||||
Returns:
|
||||
Theme object matching the ID
|
||||
|
||||
Raises:
|
||||
KeyError: If theme_id is not in registry
|
||||
"""
|
||||
return THEME_REGISTRY[theme_id]
|
||||
169
tests/test_themes.py
Normal file
169
tests/test_themes.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""
|
||||
Tests for engine.themes module.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from engine import themes
|
||||
|
||||
|
||||
class TestThemeConstruction:
|
||||
"""Tests for Theme class initialization."""
|
||||
|
||||
def test_theme_construction(self):
|
||||
"""Theme stores name and gradients correctly."""
|
||||
main_grad = ["color1", "color2", "color3"]
|
||||
msg_grad = ["msg1", "msg2", "msg3"]
|
||||
theme = themes.Theme("test_theme", main_grad, msg_grad)
|
||||
|
||||
assert theme.name == "test_theme"
|
||||
assert theme.main_gradient == main_grad
|
||||
assert theme.message_gradient == msg_grad
|
||||
|
||||
|
||||
class TestGradientLength:
|
||||
"""Tests for gradient length validation."""
|
||||
|
||||
def test_gradient_length_green(self):
|
||||
"""Green theme has exactly 12 colors in each gradient."""
|
||||
green = themes.THEME_REGISTRY["green"]
|
||||
assert len(green.main_gradient) == 12
|
||||
assert len(green.message_gradient) == 12
|
||||
|
||||
def test_gradient_length_orange(self):
|
||||
"""Orange theme has exactly 12 colors in each gradient."""
|
||||
orange = themes.THEME_REGISTRY["orange"]
|
||||
assert len(orange.main_gradient) == 12
|
||||
assert len(orange.message_gradient) == 12
|
||||
|
||||
def test_gradient_length_purple(self):
|
||||
"""Purple theme has exactly 12 colors in each gradient."""
|
||||
purple = themes.THEME_REGISTRY["purple"]
|
||||
assert len(purple.main_gradient) == 12
|
||||
assert len(purple.message_gradient) == 12
|
||||
|
||||
|
||||
class TestThemeRegistry:
|
||||
"""Tests for THEME_REGISTRY dictionary."""
|
||||
|
||||
def test_theme_registry_has_three_themes(self):
|
||||
"""Registry contains exactly three themes: green, orange, purple."""
|
||||
assert len(themes.THEME_REGISTRY) == 3
|
||||
assert set(themes.THEME_REGISTRY.keys()) == {"green", "orange", "purple"}
|
||||
|
||||
def test_registry_values_are_themes(self):
|
||||
"""All registry values are Theme instances."""
|
||||
for theme_id, theme in themes.THEME_REGISTRY.items():
|
||||
assert isinstance(theme, themes.Theme)
|
||||
assert theme.name == theme_id
|
||||
|
||||
|
||||
class TestGetTheme:
|
||||
"""Tests for get_theme function."""
|
||||
|
||||
def test_get_theme_valid_green(self):
|
||||
"""get_theme('green') returns correct green Theme."""
|
||||
green = themes.get_theme("green")
|
||||
assert isinstance(green, themes.Theme)
|
||||
assert green.name == "green"
|
||||
|
||||
def test_get_theme_valid_orange(self):
|
||||
"""get_theme('orange') returns correct orange Theme."""
|
||||
orange = themes.get_theme("orange")
|
||||
assert isinstance(orange, themes.Theme)
|
||||
assert orange.name == "orange"
|
||||
|
||||
def test_get_theme_valid_purple(self):
|
||||
"""get_theme('purple') returns correct purple Theme."""
|
||||
purple = themes.get_theme("purple")
|
||||
assert isinstance(purple, themes.Theme)
|
||||
assert purple.name == "purple"
|
||||
|
||||
def test_get_theme_invalid(self):
|
||||
"""get_theme with invalid ID raises KeyError."""
|
||||
with pytest.raises(KeyError):
|
||||
themes.get_theme("invalid_theme")
|
||||
|
||||
def test_get_theme_invalid_none(self):
|
||||
"""get_theme with None raises KeyError."""
|
||||
with pytest.raises(KeyError):
|
||||
themes.get_theme(None)
|
||||
|
||||
|
||||
class TestGreenTheme:
|
||||
"""Tests for green theme specific values."""
|
||||
|
||||
def test_green_theme_unchanged(self):
|
||||
"""Green theme maintains original color sequence."""
|
||||
green = themes.get_theme("green")
|
||||
|
||||
# Expected main gradient: 231→195→123→118→82→46→40→34→28→22→22(dim)→235
|
||||
expected_main = [231, 195, 123, 118, 82, 46, 40, 34, 28, 22, 22, 235]
|
||||
# Expected msg gradient: 231→225→219→213→207→201→165→161→125→89→89(dim)→235
|
||||
expected_msg = [231, 225, 219, 213, 207, 201, 165, 161, 125, 89, 89, 235]
|
||||
|
||||
assert green.main_gradient == expected_main
|
||||
assert green.message_gradient == expected_msg
|
||||
|
||||
def test_green_theme_name(self):
|
||||
"""Green theme has correct name."""
|
||||
green = themes.get_theme("green")
|
||||
assert green.name == "green"
|
||||
|
||||
|
||||
class TestOrangeTheme:
|
||||
"""Tests for orange theme specific values."""
|
||||
|
||||
def test_orange_theme_unchanged(self):
|
||||
"""Orange theme maintains original color sequence."""
|
||||
orange = themes.get_theme("orange")
|
||||
|
||||
# Expected main gradient: 231→215→209→208→202→166→130→94→58→94→94(dim)→235
|
||||
expected_main = [231, 215, 209, 208, 202, 166, 130, 94, 58, 94, 94, 235]
|
||||
# Expected msg gradient: 231→195→33→27→21→21→21→18→18→18→18(dim)→235
|
||||
expected_msg = [231, 195, 33, 27, 21, 21, 21, 18, 18, 18, 18, 235]
|
||||
|
||||
assert orange.main_gradient == expected_main
|
||||
assert orange.message_gradient == expected_msg
|
||||
|
||||
def test_orange_theme_name(self):
|
||||
"""Orange theme has correct name."""
|
||||
orange = themes.get_theme("orange")
|
||||
assert orange.name == "orange"
|
||||
|
||||
|
||||
class TestPurpleTheme:
|
||||
"""Tests for purple theme specific values."""
|
||||
|
||||
def test_purple_theme_unchanged(self):
|
||||
"""Purple theme maintains original color sequence."""
|
||||
purple = themes.get_theme("purple")
|
||||
|
||||
# Expected main gradient: 231→225→177→171→165→135→129→93→57→57→57(dim)→235
|
||||
expected_main = [231, 225, 177, 171, 165, 135, 129, 93, 57, 57, 57, 235]
|
||||
# Expected msg gradient: 231→226→226→220→220→184→184→178→178→172→172(dim)→235
|
||||
expected_msg = [231, 226, 226, 220, 220, 184, 184, 178, 178, 172, 172, 235]
|
||||
|
||||
assert purple.main_gradient == expected_main
|
||||
assert purple.message_gradient == expected_msg
|
||||
|
||||
def test_purple_theme_name(self):
|
||||
"""Purple theme has correct name."""
|
||||
purple = themes.get_theme("purple")
|
||||
assert purple.name == "purple"
|
||||
|
||||
|
||||
class TestThemeDataOnly:
|
||||
"""Tests to ensure themes module has no problematic imports."""
|
||||
|
||||
def test_themes_module_imports(self):
|
||||
"""themes module should be data-only without config/render imports."""
|
||||
import inspect
|
||||
source = inspect.getsource(themes)
|
||||
# Verify no imports of config or render (look for actual import statements)
|
||||
lines = source.split('\n')
|
||||
import_lines = [line for line in lines if line.strip().startswith('import ') or line.strip().startswith('from ')]
|
||||
# Filter out empty and comment lines
|
||||
import_lines = [line for line in import_lines if line.strip() and not line.strip().startswith('#')]
|
||||
# Should have no import lines
|
||||
assert len(import_lines) == 0, f"Found unexpected imports: {import_lines}"
|
||||
Reference in New Issue
Block a user