import pytest from myfasthtml.core.formatting.dataclasses import ( Condition, Style, Formatter, NumberFormatter, FormatRule, ) from myfasthtml.core.formatting.engine import FormattingEngine class TestApplyFormat: def test_apply_format_with_style_only(self): """Rule with style only returns CSS string.""" engine = FormattingEngine() rules = [FormatRule(style=Style(background_color="red", color="white"))] 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 formatted is None def test_apply_format_with_formatter_only(self): """Rule with formatter only returns formatted value.""" engine = FormattingEngine() rules = [FormatRule(formatter=NumberFormatter(precision=2, suffix=" €"))] css, formatted = engine.apply_format(rules, cell_value=1234.5) assert css is None assert formatted == "1234.50 €" def test_apply_format_with_style_and_formatter(self): """Rule with both style and formatter returns both.""" engine = FormattingEngine() rules = [ FormatRule( style=Style(color="green"), formatter=NumberFormatter(precision=2) ) ] css, formatted = engine.apply_format(rules, cell_value=42.567) assert css is not None assert "color: green" in css assert formatted == "42.57" def test_apply_format_condition_met(self): """Conditional rule applies when condition is met.""" engine = FormattingEngine() rules = [ FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") ) ] css, formatted = engine.apply_format(rules, cell_value=-5) assert css is not None assert "color: red" in css def test_apply_format_condition_not_met(self): """Conditional rule does not apply when condition is not met.""" engine = FormattingEngine() rules = [ FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") ) ] css, formatted = engine.apply_format(rules, cell_value=5) assert css is None assert formatted is None def test_empty_rules_returns_none(self): """Empty rules list returns (None, None).""" engine = FormattingEngine() css, formatted = engine.apply_format([], cell_value=42) assert css is None assert formatted is None class TestConflictResolution: def test_unconditional_rule_always_applies(self): """Unconditional rule (no condition) always applies.""" engine = FormattingEngine() rules = [FormatRule(style=Style(color="gray"))] css, _ = engine.apply_format(rules, cell_value="anything") assert css is not None assert "color: gray" in css def test_multiple_unconditional_rules_last_wins(self): """Among unconditional rules, last one wins.""" engine = FormattingEngine() rules = [ FormatRule(style=Style(color="red")), FormatRule(style=Style(color="blue")), FormatRule(style=Style(color="green")), ] 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 def test_conditional_beats_unconditional(self): """Conditional rule (higher specificity) beats unconditional.""" engine = FormattingEngine() rules = [ FormatRule(style=Style(color="gray")), # unconditional, specificity=0 FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") # conditional, specificity=1 ), ] css, _ = engine.apply_format(rules, cell_value=-5) assert "color: red" in css assert "color: gray" not in css def test_conditional_not_met_falls_back_to_unconditional(self): """When conditional doesn't match, unconditional applies.""" engine = FormattingEngine() rules = [ FormatRule(style=Style(color="gray")), # unconditional FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") # doesn't match ), ] css, _ = engine.apply_format(rules, cell_value=5) # positive, condition not met assert "color: gray" in css def test_multiple_conditional_last_wins(self): """Among conditional rules with same specificity, last wins.""" engine = FormattingEngine() rules = [ FormatRule( condition=Condition(operator="<", value=10), style=Style(color="red") ), FormatRule( condition=Condition(operator="<", value=10), style=Style(color="blue") ), ] css, _ = engine.apply_format(rules, cell_value=5) assert "color: blue" in css assert "color: red" not in css def test_spec_example_value_minus_5(self): """ Example from spec: value=-5 Rule 1: unconditional gray Rule 2: <0 -> red Rule 3: ==-5 -> black Both rule 2 and 3 match with same specificity (1). Rule 3 is last, so black wins. """ engine = FormattingEngine() rules = [ FormatRule(style=Style(color="gray")), FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") ), FormatRule( condition=Condition(operator="==", value=-5), style=Style(color="black") ), ] css, _ = engine.apply_format(rules, cell_value=-5) assert "color: black" in css def test_spec_example_value_minus_3(self): """ Same rules as above but value=-3. Rule 3 (==-5) doesn't match. Rule 2 (<0) matches and beats rule 1 (unconditional). """ engine = FormattingEngine() rules = [ FormatRule(style=Style(color="gray")), FormatRule( condition=Condition(operator="<", value=0), style=Style(color="red") ), FormatRule( condition=Condition(operator="==", value=-5), style=Style(color="black") ), ] css, _ = engine.apply_format(rules, cell_value=-3) assert "color: red" in css class TestWithRowData: def test_condition_with_column_reference(self): """Condition can reference another column.""" engine = FormattingEngine() rules = [ FormatRule( condition=Condition(operator=">", value={"col": "budget"}), style=Style(color="red") ) ] row_data = {"budget": 100, "actual": 150} css, _ = engine.apply_format(rules, cell_value=150, row_data=row_data) assert "color: red" in css def test_condition_with_col_parameter(self): """Row-level condition using col parameter.""" engine = FormattingEngine() rules = [ FormatRule( condition=Condition(operator="==", value="error", col="status"), style=Style(preset="error") ) ] row_data = {"status": "error", "value": 42} css, _ = engine.apply_format(rules, cell_value=42, row_data=row_data) assert css is not None assert "background-color" in css class TestPresets: def test_style_preset(self): """Style preset is resolved correctly.""" engine = FormattingEngine() rules = [FormatRule(style=Style(preset="success"))] css, _ = engine.apply_format(rules, cell_value=42) assert "var(--color-success)" in css def test_formatter_preset(self): """Formatter preset is resolved correctly.""" engine = FormattingEngine() rules = [FormatRule(formatter=Formatter(preset="EUR"))] _, formatted = engine.apply_format(rules, cell_value=1234.56) assert formatted == "1 234,56 €" def test_custom_presets(self): """Custom presets can be injected.""" custom_style_presets = { "custom": {"background-color": "purple", "color": "yellow"} } engine = FormattingEngine(style_presets=custom_style_presets) rules = [FormatRule(style=Style(preset="custom"))] css, _ = engine.apply_format(rules, cell_value=42) assert "background-color: purple" in css assert "color: yellow" in css