Added "table" and "tables" in the DSL

This commit is contained in:
2026-02-07 22:48:51 +01:00
parent 08c8c00e28
commit 6160e91665
18 changed files with 717 additions and 54 deletions

View File

@@ -0,0 +1,279 @@
"""
Tests for DataGrid formatting integration with table/tables scopes.
Tests the complete formatting flow: DSL → Storage → Application.
"""
import pytest
from myfasthtml.controls.DataGrid import DataGrid
from myfasthtml.controls.DataGridFormattingEditor import DataGridFormattingEditor
from myfasthtml.controls.DataGridsManager import DataGridsManager
from myfasthtml.controls.datagrid_objects import DataGridColumnState, DataGridRowState
from myfasthtml.core.constants import ColumnType
from myfasthtml.core.formatting.dataclasses import FormatRule, Style
from myfasthtml.core.formatting.dsl.definition import FormattingDSL
from myfasthtml.core.instances import InstancesManager
@pytest.fixture
def manager(root_instance):
"""Create a DataGridsManager instance."""
mgr = DataGridsManager(root_instance, _id="test-manager")
yield mgr
InstancesManager.reset()
@pytest.fixture
def datagrid(manager):
"""Create a DataGrid instance."""
from myfasthtml.controls.DataGrid import DatagridConf
conf = DatagridConf(namespace="app", name="products", id="test-grid")
grid = DataGrid(manager, conf=conf, save_state=False, _id="test-datagrid")
# Add some columns
grid._state.columns = [
DataGridColumnState(col_id="amount", col_index=0, title="Amount", type=ColumnType.Number, visible=True),
DataGridColumnState(col_id="status", col_index=1, title="Status", type=ColumnType.Text, visible=True),
]
# Add some rows
grid._state.rows = [
DataGridRowState(0),
DataGridRowState(1),
]
yield grid
InstancesManager.reset()
@pytest.fixture
def editor(datagrid):
return DataGridFormattingEditor(datagrid, FormattingDSL())
# =============================================================================
# _get_format_rules() Hierarchy Tests
# =============================================================================
class TestFormatRulesHierarchy:
"""Tests for format rules hierarchy (cell > row > column > table > tables)."""
def test_i_can_get_cell_level_rules(self, datagrid):
"""Test that cell-level rules have highest priority."""
# Setup rules at different levels
cell_rules = [FormatRule(style=Style(preset="error"))]
column_rules = [FormatRule(style=Style(preset="success"))]
table_rules = [FormatRule(style=Style(preset="info"))]
datagrid._state.cell_formats["tcell_test-datagrid-0-0"] = cell_rules
datagrid._state.columns[0].format = column_rules
datagrid._state.table_format = table_rules
# Get rules for cell (0, 0)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
# Should return cell rules (highest priority)
assert rules == cell_rules
def test_i_can_get_row_level_rules(self, datagrid):
"""Test that row-level rules have second priority."""
# Setup rules at different levels
row_rules = [FormatRule(style=Style(preset="warning"))]
column_rules = [FormatRule(style=Style(preset="success"))]
table_rules = [FormatRule(style=Style(preset="info"))]
datagrid._state.rows[0].format = row_rules
datagrid._state.columns[0].format = column_rules
datagrid._state.table_format = table_rules
# Get rules for row 0 (no cell-level rules)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
# Should return row rules
assert rules == row_rules
def test_i_can_get_column_level_rules(self, datagrid):
"""Test that column-level rules have third priority."""
# Setup rules at different levels
column_rules = [FormatRule(style=Style(preset="success"))]
table_rules = [FormatRule(style=Style(preset="info"))]
datagrid._state.columns[0].format = column_rules
datagrid._state.table_format = table_rules
# Get rules for column (no cell or row rules)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
# Should return column rules
assert rules == column_rules
def test_i_can_get_table_level_rules(self, datagrid, manager):
"""Test that table-level rules have fourth priority."""
# Setup rules at different levels
table_rules = [FormatRule(style=Style(preset="info"))]
tables_rules = [FormatRule(style=Style(preset="neutral"))]
datagrid._state.table_format = table_rules
manager.all_tables_formats = tables_rules
# Get rules for cell (no higher level rules)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
# Should return table rules
assert rules == table_rules
def test_i_can_get_tables_level_rules(self, datagrid, manager):
"""Test that tables-level rules have lowest priority."""
# Setup global rules
tables_rules = [FormatRule(style=Style(preset="neutral"))]
manager.all_tables_formats = tables_rules
# Get rules for cell (no other rules)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
# Should return global tables rules
assert rules == tables_rules
def test_i_get_none_when_no_rules(self, datagrid):
"""Test that None is returned when no rules are defined."""
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
assert rules == []
@pytest.mark.parametrize("level,setup_func,expected_preset", [
("cell", lambda dg: dg._state.cell_formats.__setitem__("tcell_test-datagrid-0-0",
[FormatRule(style=Style(preset="error"))]), "error"),
("row", lambda dg: setattr(dg._state.rows[0], "format",
[FormatRule(style=Style(preset="warning"))]), "warning"),
("column", lambda dg: setattr(dg._state.columns[0], "format",
[FormatRule(style=Style(preset="success"))]), "success"),
("table", lambda dg: setattr(dg._state, "table_format",
[FormatRule(style=Style(preset="info"))]), "info"),
])
def test_hierarchy_priority(self, datagrid, level, setup_func, expected_preset):
"""Test that each level has correct priority in the hierarchy."""
setup_func(datagrid)
rules = datagrid._get_format_rules(0, 0, datagrid._state.columns[0])
assert rules is not None
assert len(rules) == 1
assert rules[0].style.preset == expected_preset
# =============================================================================
# DataGridFormattingEditor Integration Tests
# =============================================================================
class TestFormattingEditorIntegration:
"""Tests for DataGridFormattingEditor with table/tables scopes."""
def test_i_can_dispatch_table_rules(self, datagrid, editor):
"""Test that table rules are dispatched to DatagridState.table_format."""
dsl = '''
table "products":
style("info")
'''
editor.set_content(dsl)
editor.on_content_changed()
# Check that table_format is populated
assert len(datagrid._state.table_format) == 1
assert datagrid._state.table_format[0].style.preset == "info"
def test_i_cannot_use_wrong_table_name(self, datagrid, editor):
"""Test that wrong table name is rejected."""
dsl = '''
table "wrong_name":
style("error")
'''
editor.set_content(dsl)
editor.on_content_changed()
# Rules should not be applied (wrong table name)
assert len(datagrid._state.table_format) == 0
def test_i_can_dispatch_tables_rules(self, manager, datagrid, editor):
"""Test that tables rules are dispatched to DataGridsManager."""
dsl = '''
tables:
style("neutral")
format.number(precision=2)
'''
editor.set_content(dsl)
editor.on_content_changed()
# Check that manager.all_tables_formats is populated
assert len(manager.all_tables_formats) == 2
assert manager.all_tables_formats[0].style.preset == "neutral"
assert manager.all_tables_formats[1].formatter.precision == 2
def test_i_can_combine_all_scope_types(self, manager, datagrid, editor):
"""Test that all 5 scope types can be used together."""
dsl = '''
tables:
style(font_size="14px")
table "products":
format.number(precision=2)
column amount:
style("success") if value > 0
row 0:
style("neutral", bold=True)
cell (amount, 1):
style("error")
'''
editor.set_content(dsl)
editor.on_content_changed()
# Check all levels are populated
assert len(manager.all_tables_formats) == 1
assert len(datagrid._state.table_format) == 1
assert len(datagrid._state.columns[0].format) == 1
assert len(datagrid._state.rows[0].format) == 1
assert len(datagrid._state.cell_formats) == 1
def test_i_can_clear_table_format(self, datagrid, editor):
"""Test that table_format is cleared when DSL changes."""
# First set table rules
dsl = '''
table "products":
style("info")
'''
editor.set_content(dsl)
editor.on_content_changed()
assert len(datagrid._state.table_format) == 1
# Then remove them
editor.set_content("")
editor.on_content_changed()
assert len(datagrid._state.table_format) == 0
@pytest.mark.parametrize("table_name,should_apply", [
("products", True), # Correct name
("PRODUCTS", False), # Case sensitive
("product", False), # Partial match not allowed
("app.products", False), # Namespace not included
("other", False), # Completely wrong
])
def test_table_name_validation(self, datagrid, editor, table_name, should_apply):
"""Test that table name validation is case-sensitive and exact."""
dsl = f'''
table "{table_name}":
style("info")
'''
editor.set_content(dsl)
editor.on_content_changed()
if should_apply:
assert len(datagrid._state.table_format) == 1
else:
assert len(datagrid._state.table_format) == 0