Fixed command id collision. Added class support in style preset

This commit is contained in:
2026-02-08 19:50:10 +01:00
parent 3ec994d6df
commit d44e0a0c01
14 changed files with 623 additions and 3677 deletions

View File

@@ -8,6 +8,7 @@ from myfasthtml.core.formatting.dataclasses import (
FormatRule,
)
from myfasthtml.core.formatting.engine import FormattingEngine
from myfasthtml.core.formatting.style_resolver import StyleContainer
class TestApplyFormat:
@@ -18,9 +19,9 @@ class TestApplyFormat:
css, formatted = engine.apply_format(rules, cell_value=42)
assert css is not None
assert "background-color: red" in css
assert "color: white" in css
assert isinstance(css, StyleContainer)
assert "background-color: red" in css.css
assert "color: white" in css.css
assert formatted is None
def test_apply_format_with_formatter_only(self):
@@ -45,8 +46,8 @@ class TestApplyFormat:
css, formatted = engine.apply_format(rules, cell_value=42.567)
assert css is not None
assert "color: green" in css
assert isinstance(css, StyleContainer)
assert "color: green" in css.css
assert formatted == "42.57"
def test_apply_format_condition_met(self):
@@ -61,8 +62,8 @@ class TestApplyFormat:
css, formatted = engine.apply_format(rules, cell_value=-5)
assert css is not None
assert "color: red" in css
assert isinstance(css, StyleContainer)
assert "color: red" in css.css
def test_apply_format_condition_not_met(self):
"""Conditional rule does not apply when condition is not met."""
@@ -97,8 +98,8 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value="anything")
assert css is not None
assert "color: gray" in css
assert isinstance(css, StyleContainer)
assert "color: gray" in css.css
def test_multiple_unconditional_rules_last_wins(self):
"""Among unconditional rules, last one wins."""
@@ -111,9 +112,9 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=42)
assert "color: green" in css
assert "color: red" not in css
assert "color: blue" not in css
assert "color: green" in css.css
assert "color: red" not in css.css
assert "color: blue" not in css.css
def test_conditional_beats_unconditional(self):
"""Conditional rule (higher specificity) beats unconditional."""
@@ -128,8 +129,8 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=-5)
assert "color: red" in css
assert "color: gray" not in css
assert "color: red" in css.css
assert "color: gray" not in css.css
def test_conditional_not_met_falls_back_to_unconditional(self):
"""When conditional doesn't match, unconditional applies."""
@@ -144,7 +145,7 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=5) # positive, condition not met
assert "color: gray" in css
assert "color: gray" in css.css
def test_multiple_conditional_last_wins(self):
"""Among conditional rules with same specificity, last wins."""
@@ -162,8 +163,8 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=5)
assert "color: blue" in css
assert "color: red" not in css
assert "color: blue" in css.css
assert "color: red" not in css.css
def test_spec_example_value_minus_5(self):
"""
@@ -190,7 +191,7 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=-5)
assert "color: black" in css
assert "color: black" in css.css
def test_spec_example_value_minus_3(self):
"""
@@ -213,7 +214,7 @@ class TestConflictResolution:
css, _ = engine.apply_format(rules, cell_value=-3)
assert "color: red" in css
assert "color: red" in css.css
def test_style_and_formatter_fusion(self):
"""
@@ -241,8 +242,8 @@ class TestConflictResolution:
# Case 1: Condition met (value > budget)
css, formatted = engine.apply_format(rules, cell_value=150, row_data=row_data)
assert css is not None
assert "var(--color-secondary)" in css # Style from Rule 2
assert isinstance(css, StyleContainer)
assert "var(--color-secondary)" in css.css # Style from Rule 2
assert formatted == "150.00 €" # Formatter from Rule 1
# Case 2: Condition not met (value <= budget)
@@ -281,7 +282,7 @@ class TestConflictResolution:
css, formatted = engine.apply_format(rules, cell_value=-5.67)
assert "var(--color-error)" in css # Rule 3 wins for style
assert "var(--color-error)" in css.css # Rule 3 wins for style
assert formatted == "-6 €" # Rule 4 wins for formatter (precision=0)
@@ -299,7 +300,7 @@ class TestWithRowData:
css, _ = engine.apply_format(rules, cell_value=150, row_data=row_data)
assert "color: red" in css
assert "color: red" in css.css
def test_condition_with_col_parameter(self):
"""Row-level condition using col parameter."""
@@ -314,8 +315,8 @@ class TestWithRowData:
css, _ = engine.apply_format(rules, cell_value=42, row_data=row_data)
assert css is not None
assert "background-color" in css
assert isinstance(css, StyleContainer)
assert "background-color" in css.css
class TestPresets:
@@ -326,7 +327,7 @@ class TestPresets:
css, _ = engine.apply_format(rules, cell_value=42)
assert "var(--color-success)" in css
assert "var(--color-success)" in css.css
def test_formatter_preset(self):
"""Formatter preset is resolved correctly."""
@@ -347,5 +348,5 @@ class TestPresets:
css, _ = engine.apply_format(rules, cell_value=42)
assert "background-color: purple" in css
assert "color: yellow" in css
assert "background-color: purple" in css.css
assert "color: yellow" in css.css

View File

@@ -1,7 +1,7 @@
import pytest
from myfasthtml.core.formatting.dataclasses import Style
from myfasthtml.core.formatting.style_resolver import StyleResolver
from myfasthtml.core.formatting.style_resolver import StyleResolver, StyleContainer
class TestResolve:
@@ -141,3 +141,108 @@ class TestToCssString:
result = resolver.to_css_string(style)
assert result == "color: blue;"
class TestToStyleContainer:
@pytest.mark.parametrize("style_input,expected_cls,expected_css_contains", [
# CSS properties only
(
Style(background_color="red", color="white"),
None,
["background-color: red", "color: white"]
),
# Class only via preset
(
Style(preset="success"),
None, # Default presets don't have __class__
["background-color: var(--color-success)", "color: var(--color-success-content)"]
),
# Empty style
(
Style(),
None,
[]
),
# None style
(
None,
None,
[]
),
])
def test_i_can_resolve_to_style_container(self, style_input, expected_cls, expected_css_contains):
"""Test to_style_container() with various style inputs."""
resolver = StyleResolver()
result = resolver.to_style_container(style_input)
assert isinstance(result, StyleContainer)
assert result.cls == expected_cls
if expected_css_contains:
for css_part in expected_css_contains:
assert css_part in result.css
else:
assert result.css == ""
def test_i_can_resolve_preset_with_class_to_container(self):
"""Preset with __class__ key is extracted to cls attribute."""
custom_presets = {
"badge": {
"__class__": "badge badge-primary",
"background-color": "blue",
"color": "white"
}
}
resolver = StyleResolver(style_presets=custom_presets)
style = Style(preset="badge")
result = resolver.to_style_container(style)
assert result.cls == "badge badge-primary"
assert "background-color: blue" in result.css
assert "color: white" in result.css
assert "__class__" not in result.css
def test_i_can_override_preset_class_with_explicit_properties(self):
"""Explicit properties override preset but __class__ is preserved."""
custom_presets = {
"badge": {
"__class__": "badge badge-primary",
"background-color": "blue",
"color": "white"
}
}
resolver = StyleResolver(style_presets=custom_presets)
style = Style(preset="badge", color="black")
result = resolver.to_style_container(style)
assert result.cls == "badge badge-primary"
assert "background-color: blue" in result.css
assert "color: black" in result.css # Overridden
assert "color: white" not in result.css
def test_i_can_resolve_multiple_css_properties_to_container(self):
"""Multiple CSS properties are correctly formatted in container."""
resolver = StyleResolver()
style = Style(
background_color="#ff0000",
color="#ffffff",
font_weight="bold",
font_style="italic"
)
result = resolver.to_style_container(style)
assert result.cls is None
assert "background-color: #ff0000" in result.css
assert "color: #ffffff" in result.css
assert "font-weight: bold" in result.css
assert "font-style: italic" in result.css
assert result.css.endswith(";")
def test_i_can_resolve_empty_container_when_no_properties(self):
"""Empty style returns container with None cls and empty css."""
resolver = StyleResolver()
result = resolver.to_style_container(Style())
assert isinstance(result, StyleContainer)
assert result.cls is None
assert result.css == ""