187 lines
6.9 KiB
Python
187 lines
6.9 KiB
Python
"""Tests for Fenris theme presets (issue #80).
|
|
|
|
Covers:
|
|
- Three valid presets: Amber, Nord, High Contrast
|
|
- Chalktone is the default; Amber retains its amber graph role
|
|
- Theme roles for graph rendering (allocated, unallocated, gap, zero, partial)
|
|
- Status semantic colours/glyphs/text always win over theme
|
|
- Global action reachability and focus contrast in every preset
|
|
"""
|
|
import pytest
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
from fenris.themes import (
|
|
THEMES,
|
|
THEME_NAMES,
|
|
get_theme,
|
|
get_graph_colors,
|
|
STATUS_COLORS,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Theme registry tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestThemeRegistry:
|
|
"""All three presets are registered with correct names."""
|
|
|
|
def test_four_themes_registered(self):
|
|
"""All four presets are registered."""
|
|
assert len(THEMES) == 4
|
|
|
|
def test_theme_names(self):
|
|
"""Theme names are amber, nord, high_contrast."""
|
|
assert THEME_NAMES == {"chalktone", "amber", "nord", "high_contrast"}
|
|
|
|
def test_get_theme_valid(self):
|
|
"""get_theme returns a Theme for each valid name."""
|
|
from textual.theme import Theme
|
|
for name in THEME_NAMES:
|
|
theme = get_theme(name)
|
|
assert isinstance(theme, Theme)
|
|
|
|
def test_get_theme_invalid_returns_chalktone(self):
|
|
"""Unknown theme name returns the amber theme."""
|
|
theme = get_theme("nonexistent")
|
|
assert theme.name == "fenris-chalktone"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Amber theme tests (amber graph role)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestAmberTheme:
|
|
"""Amber is the default theme with warm tones."""
|
|
|
|
def test_amber_is_dark(self):
|
|
"""Amber is a dark theme."""
|
|
theme = get_theme("amber")
|
|
assert theme.dark is True
|
|
|
|
def test_amber_primary_is_amber(self):
|
|
"""Primary colour is warm amber."""
|
|
theme = get_theme("amber")
|
|
assert "d4a017" in theme.primary.lower() or "amber" in theme.primary.lower()
|
|
|
|
def test_amber_has_graph_colors(self):
|
|
"""Amber defines all required graph role variables."""
|
|
colors = get_graph_colors("amber")
|
|
assert "allocated" in colors
|
|
assert "unallocated" in colors
|
|
assert "gap" in colors
|
|
assert "zero" in colors
|
|
assert "partial" in colors
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Nord theme tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestNordTheme:
|
|
"""Nord uses the polar night palette."""
|
|
|
|
def test_nord_is_dark(self):
|
|
"""Nord is a dark theme."""
|
|
theme = get_theme("nord")
|
|
assert theme.dark is True
|
|
|
|
def test_nord_has_graph_colors(self):
|
|
"""Nord defines all required graph role variables."""
|
|
colors = get_graph_colors("nord")
|
|
assert "allocated" in colors
|
|
assert "unallocated" in colors
|
|
assert "gap" in colors
|
|
assert "zero" in colors
|
|
assert "partial" in colors
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# High Contrast theme tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestHighContrastTheme:
|
|
"""High Contrast for maximum readability."""
|
|
|
|
def test_high_contrast_is_dark(self):
|
|
"""High contrast is a dark theme."""
|
|
theme = get_theme("high_contrast")
|
|
assert theme.dark is True
|
|
|
|
def test_high_contrast_foreground_is_white(self):
|
|
"""Foreground is pure white for maximum contrast."""
|
|
theme = get_theme("high_contract") if False else get_theme("high_contrast")
|
|
assert theme.foreground is not None
|
|
|
|
def test_high_contrast_has_graph_colors(self):
|
|
"""High contrast defines all required graph role variables."""
|
|
colors = get_graph_colors("high_contrast")
|
|
assert "allocated" in colors
|
|
assert "unallocated" in colors
|
|
assert "gap" in colors
|
|
assert "zero" in colors
|
|
assert "partial" in colors
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Status semantic colours override theme (AC80-1)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestStatusSemanticOverride:
|
|
"""Status semantic colours/glyphs/text always win over theme styling."""
|
|
|
|
def test_status_colors_defined(self):
|
|
"""STATUS_COLORS maps each StatusState to a fixed colour."""
|
|
from fenris.status_composition import StatusState
|
|
for state in StatusState:
|
|
assert state.value in STATUS_COLORS
|
|
|
|
def test_status_colors_are_not_theme_dependent(self):
|
|
"""Status colours are the same regardless of theme."""
|
|
from fenris.status_composition import StatusState
|
|
# These are the canonical status colours — they must not change with theme
|
|
assert STATUS_COLORS[StatusState.MONITORING.value] == "green"
|
|
assert STATUS_COLORS[StatusState.ERROR.value] == "red"
|
|
assert STATUS_COLORS[StatusState.PAUSED.value] == "yellow"
|
|
assert STATUS_COLORS[StatusState.INTERRUPTED.value] == "red"
|
|
assert STATUS_COLORS[StatusState.STALE.value] == "red"
|
|
assert STATUS_COLORS[StatusState.WAITING.value] == "yellow"
|
|
assert STATUS_COLORS[StatusState.UNKNOWN.value] == "dim"
|
|
assert STATUS_COLORS[StatusState.COLLECTING.value] == "green"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Graph colour role tests (AC80-5)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class TestGraphColorRoles:
|
|
"""Theme roles for graph rendering expose distinct colours per preset."""
|
|
|
|
def test_all_themes_define_same_roles(self):
|
|
"""Every theme has the same set of graph colour roles."""
|
|
roles = None
|
|
for name in THEME_NAMES:
|
|
colors = get_graph_colors(name)
|
|
if roles is None:
|
|
roles = set(colors.keys())
|
|
else:
|
|
assert set(colors.keys()) == roles
|
|
|
|
def test_graph_roles_are_distinct_across_presets(self):
|
|
"""Different themes produce different graph colour values."""
|
|
amber = get_graph_colors("amber")
|
|
nord = get_graph_colors("nord")
|
|
hc = get_graph_colors("high_contrast")
|
|
# At least one role should differ between themes
|
|
assert amber["allocated"] != nord["allocated"] or amber["allocated"] != hc["allocated"]
|
|
|
|
def test_zero_role_is_dim(self):
|
|
"""Zero-usage markers use a dim/subtle colour in all themes."""
|
|
for name in THEME_NAMES:
|
|
colors = get_graph_colors(name)
|
|
# Zero should be distinct from allocated
|
|
assert colors["zero"] != colors["allocated"]
|