Fixed command id collision. Added class support in style preset
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 == ""
|
||||
|
||||
Reference in New Issue
Block a user