Added tests for Layout and Treeview

This commit is contained in:
2025-12-05 17:46:15 +01:00
parent 7c701a9116
commit 8f2528787a
21 changed files with 1161 additions and 229 deletions

View File

@@ -5,7 +5,9 @@ import pytest
from fasthtml.components import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.test.matcher import matches, find, Contains, find_one, TestIcon, TestScript
from myfasthtml.controls.UserProfile import UserProfile
from myfasthtml.test.matcher import matches, find, Contains, find_one, TestIcon, TestScript, TestObject, AnyValue, Skip, \
TestIconNotStr
from .conftest import root_instance
@@ -236,11 +238,12 @@ class TestLayoutRender:
"""Test that Layout renders with all main structural sections.
Why these elements matter:
- 6 children: Verifies all main sections are rendered (header, drawers, main, footer, script)
- 7 children: Verifies all main sections are rendered (tooltip container, header, drawers, main, footer, script)
- _id: Essential for layout identification and resizer initialization
- cls="mf-layout": Root CSS class for layout styling
"""
expected = Div(
Div(), # tooltip container
Header(),
Div(), # left drawer
Main(),
@@ -286,7 +289,7 @@ class TestLayoutRender:
expected = Header(
Div(
TestIcon("panel_right_expand20_regular"),
TestIcon("PanelLeftContract20Regular"),
cls="flex gap-1"
),
cls="mf-layout-header"
@@ -343,7 +346,7 @@ class TestLayoutRender:
expected = Div(
_id=f"{layout._id}_ld",
cls=Contains("collapsed"),
cls=Contains("mf-layout-drawer", "mf-layout-left-drawer", "collapsed"),
style=Contains("width: 0px")
)
@@ -382,7 +385,7 @@ class TestLayoutRender:
expected = Div(
_id=f"{layout._id}_rd",
cls=Contains("collapsed"),
cls=Contains("mf-layout-drawer", "mf-layout-right-drawer", "collapsed"),
style=Contains("width: 0px")
)
@@ -425,34 +428,250 @@ class TestLayoutRender:
resizers = find(drawer, Div(cls=Contains("mf-resizer-right")))
assert len(resizers) == 1, "Right drawer should contain exactly one resizer element"
def test_drawer_groups_are_separated_by_dividers(self, layout):
"""Test that multiple groups in drawer are separated by divider elements.
Why this test matters:
- Dividers provide visual separation between content groups
- At least one divider should exist when multiple groups are present
"""
layout.left_drawer.add(Div("Item 1"), group="group1")
layout.left_drawer.add(Div("Item 2"), group="group2")
drawer = find(layout.render(), Div(id=f"{layout._id}_ld"))
content_wrappers = find(drawer, Div(cls="mf-layout-drawer-content"))
assert len(content_wrappers) == 1
content = content_wrappers[0]
dividers = find(content, Div(cls="divider"))
assert len(dividers) >= 1, "Groups should be separated by dividers"
def test_resizer_script_is_included(self, layout):
"""Test that resizer initialization script is included in render.
Why this test matters:
- Script element: Required to initialize resizer functionality
- Script contains initResizer call: Ensures resizer is activated for this layout instance
- Script contains initLayout call: Ensures layout is activated for this layout instance
"""
script = find_one(layout.render(), Script())
expected = TestScript(f"initResizer('{layout._id}');")
expected = TestScript(f"initLayout('{layout._id}');")
assert matches(script, expected)
def test_left_drawer_renders_content_with_groups(self, layout):
"""Test that left drawer renders content organized by groups with proper wrappers.
Why these elements matter:
- mf-layout-drawer-content wrapper: Required container for drawer scrolling behavior
- divider elements: Visual separation between content groups
- Group count validation: Ensures all added groups are rendered
"""
layout.left_drawer.add(Div("Item 1", id="item1"), group="group1")
layout.left_drawer.add(Div("Item 2", id="item2"), group="group2")
drawer = find_one(layout.render(), Div(id=f"{layout._id}_ld"))
content_wrappers = find(drawer, Div(cls="mf-layout-drawer-content"))
assert len(content_wrappers) == 1, "Left drawer should contain exactly one content wrapper"
content = content_wrappers[0]
dividers = find(content, Div(cls="divider"))
assert len(dividers) == 1, "Two groups should be separated by exactly one divider"
def test_header_left_renders_custom_content(self, layout):
"""Test that custom content added to header_left is rendered in the left header section.
Why these elements matter:
- id="{layout._id}_hl": Essential for HTMX targeting during updates
- cls Contains "flex": Ensures horizontal layout of header items
- Icon presence: Toggle drawer icon must always be first element
- Custom content: Verifies header_left.add() correctly renders content
"""
custom_content = Div("Custom Header", id="custom_header")
layout.header_left.add(custom_content)
header_left = find_one(layout.render(), Div(id=f"{layout._id}_hl"))
expected = Div(
TestIcon(""),
Skip(None),
Div("Custom Header", id="custom_header"),
id=f"{layout._id}_hl",
cls=Contains("flex", "gap-1")
)
assert matches(header_left, expected)
def test_header_right_renders_custom_content(self, layout):
"""Test that custom content added to header_right is rendered in the right header section.
Why these elements matter:
- id="{layout._id}_hr": Essential for HTMX targeting during updates
- cls Contains "flex": Ensures horizontal layout of header items
- Custom content: Verifies header_right.add() correctly renders content
- UserProfile component: Must always be last element in right header
"""
custom_content = Div("Custom Header Right", id="custom_header_right")
layout.header_right.add(custom_content)
header_right = find_one(layout.render(), Div(id=f"{layout._id}_hr"))
expected = Div(
Skip(None),
Div("Custom Header Right", id="custom_header_right"),
TestObject(UserProfile),
id=f"{layout._id}_hr",
cls=Contains("flex", "gap-1")
)
assert matches(header_right, expected)
def test_footer_left_renders_custom_content(self, layout):
"""Test that custom content added to footer_left is rendered in the left footer section.
Why these elements matter:
- id="{layout._id}_fl": Essential for HTMX targeting during updates
- cls Contains "flex": Ensures horizontal layout of footer items
- Custom content: Verifies footer_left.add() correctly renders content
"""
custom_content = Div("Custom Footer Left", id="custom_footer_left")
layout.footer_left.add(custom_content)
footer_left = find_one(layout.render(), Div(id=f"{layout._id}_fl"))
expected = Div(
Skip(None),
Div("Custom Footer Left", id="custom_footer_left"),
id=f"{layout._id}_fl",
cls=Contains("flex", "gap-1")
)
assert matches(footer_left, expected)
def test_footer_right_renders_custom_content(self, layout):
"""Test that custom content added to footer_right is rendered in the right footer section.
Why these elements matter:
- id="{layout._id}_fr": Essential for HTMX targeting during updates
- cls Contains "flex": Ensures horizontal layout of footer items
- Custom content: Verifies footer_right.add() correctly renders content
"""
custom_content = Div("Custom Footer Right", id="custom_footer_right")
layout.footer_right.add(custom_content)
footer_right = find_one(layout.render(), Div(id=f"{layout._id}_fr"))
expected = Div(
Skip(None),
Div("Custom Footer Right", id="custom_footer_right"),
id=f"{layout._id}_fr",
cls=Contains("flex", "gap-1")
)
assert matches(footer_right, expected)
def test_left_drawer_resizer_has_command_data(self, layout):
"""Test that left drawer resizer has correct data attributes for command binding.
Why these elements matter:
- data_command_id: JavaScript uses this to trigger width update command
- data_side="left": JavaScript needs this to identify which drawer to resize
- cls Contains "mf-resizer-left": CSS uses this for left-specific positioning
"""
drawer = find_one(layout.render(), Div(id=f"{layout._id}_ld"))
resizer = find_one(drawer, Div(cls=Contains("mf-resizer-left")))
expected = Div(
cls=Contains("mf-resizer", "mf-resizer-left"),
data_command_id=AnyValue(),
data_side="left"
)
assert matches(resizer, expected)
def test_right_drawer_resizer_has_command_data(self, layout):
"""Test that right drawer resizer has correct data attributes for command binding.
Why these elements matter:
- data_command_id: JavaScript uses this to trigger width update command
- data_side="right": JavaScript needs this to identify which drawer to resize
- cls Contains "mf-resizer-right": CSS uses this for right-specific positioning
"""
drawer = find_one(layout.render(), Div(id=f"{layout._id}_rd"))
resizer = find_one(drawer, Div(cls=Contains("mf-resizer-right")))
expected = Div(
cls=Contains("mf-resizer", "mf-resizer-right"),
data_command_id=AnyValue(),
data_side="right"
)
assert matches(resizer, expected)
def test_left_drawer_icon_changes_when_closed(self, layout):
"""Test that left drawer toggle icon changes from expand to collapse when drawer is closed.
Why these elements matter:
- id="{layout._id}_ldi": Required for HTMX swap-oob updates when toggling
- Icon type: Visual feedback to user about drawer state (expand icon when closed)
- Icon change: Validates that toggle_drawer returns correct icon
"""
layout._state.left_drawer_open = False
icon_div = find_one(layout.render(), Div(id=f"{layout._id}_ldi"))
expected = Div(
TestIconNotStr("panel_left_expand20_regular"),
id=f"{layout._id}_ldi"
)
assert matches(icon_div, expected)
def test_left_drawer_icon_changes_when_opne(self, layout):
"""Test that left drawer toggle icon changes from collapse to expand when drawer is open..
Why these elements matter:
- id="{layout._id}_ldi": Required for HTMX swap-oob updates when toggling
- Icon type: Visual feedback to user about drawer state (expand icon when closed)
- Icon change: Validates that toggle_drawer returns correct icon
"""
layout._state.left_drawer_open = True
icon_div = find_one(layout.render(), Div(id=f"{layout._id}_ldi"))
expected = Div(
TestIconNotStr("panel_left_contract20_regular"),
id=f"{layout._id}_ldi"
)
assert matches(icon_div, expected)
def test_tooltip_container_is_rendered(self, layout):
"""Test that tooltip container is rendered at the top of the layout.
Why these elements matter:
- id="tt_{layout._id}": JavaScript uses this to append dynamically created tooltips
- cls Contains "mf-tooltip-container": CSS positioning for tooltip overlay layer
- Presence verification: Tooltips won't work if container is missing
"""
tooltip_container = find_one(layout.render(), Div(id=f"tt_{layout._id}"))
expected = Div(
id=f"tt_{layout._id}",
cls=Contains("mf-tooltip-container")
)
assert matches(tooltip_container, expected)
def test_header_right_contains_user_profile(self, layout):
"""Test that UserProfile component is rendered in the right header section.
Why these elements matter:
- UserProfile component: Provides authentication and user menu functionality
- Position in header right: Conventional placement for user profile controls
- Count verification: Ensures component is not duplicated
"""
header_right = find_one(layout.render(), Div(id=f"{layout._id}_hr"))
user_profiles = find(header_right, TestObject(UserProfile))
assert len(user_profiles) == 1, "Header right should contain exactly one UserProfile component"
def test_layout_initialization_script_is_included(self, layout):
"""Test that layout initialization script is included in render output.
Why these elements matter:
- Script presence: Required to initialize layout behavior (resizers, drawers)
- initLayout() call: Activates JavaScript functionality for this layout instance
- Layout ID parameter: Ensures initialization targets correct layout
"""
script = find_one(layout.render(), Script())
expected = TestScript(f"initLayout('{layout._id}');")
assert matches(script, expected)