Working on Treeview unit tests

This commit is contained in:
2025-11-30 23:12:12 +01:00
parent 93cb477c21
commit e34d675e38
2 changed files with 249 additions and 18 deletions

View File

@@ -37,15 +37,17 @@ This is only one instance per session.
## High Level Hierarchical Structure ## High Level Hierarchical Structure
``` ```
MyFastHtml Div(id="layout")
├── src ├── Header
│ ├── myfasthtml/ # Main library code │ ├── Div(id="layout_hl")
│ │ ├── core/commands.py # Command definitions │ │ ├── Icon # Left drawer icon button
│ │ ── controls/button.py # Control helpers │ │ ── Div # Left content for the header
│ └── pages/LoginPage.py # Predefined Login page │ └── Div(id="layout_hr")
└── ... ├── Div # Right content for the header
├── tests # Unit and integration tests │ └── UserProfile # user profile icon button
├── LICENSE # License file (MIT) ├── Div # Left Drawer
├── README.md # Project documentation ├── Main # Main content
── pyproject.toml # Build configuration ── Div # Right Drawer
├── Footer # Footer
└── Script # To initialize the resizing
``` ```

View File

@@ -6,7 +6,7 @@ from fasthtml.components import *
from myfasthtml.controls.Keyboard import Keyboard from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.TreeView import TreeView, TreeNode from myfasthtml.controls.TreeView import TreeView, TreeNode
from myfasthtml.test.matcher import matches, TestObject, TestCommand from myfasthtml.test.matcher import matches, TestObject, TestCommand, TestIcon, find_one, find, Contains, DoesNotContain
from .conftest import root_instance from .conftest import root_instance
@@ -382,17 +382,246 @@ class TestTreeViewRender:
"""Tests for TreeView HTML rendering.""" """Tests for TreeView HTML rendering."""
def test_empty_treeview_is_rendered(self, root_instance): def test_empty_treeview_is_rendered(self, root_instance):
"""Test that TreeView generates correct HTML structure.""" """Test that empty TreeView generates correct HTML structure.
Why these elements matter:
- TestObject Keyboard: Essential for keyboard shortcuts (Escape to cancel rename)
- _id: Required for HTMX targeting and component identification
- cls "mf-treeview": Root CSS class for TreeView styling
"""
# Step 1: Create empty TreeView
tree_view = TreeView(root_instance) tree_view = TreeView(root_instance)
# Step 2: Define expected structure
expected = Div( expected = Div(
TestObject(Keyboard, combinations={"esc": TestCommand("CancelRename")}), TestObject(Keyboard, combinations={"esc": TestCommand("CancelRename")}),
_id=tree_view.get_id(), _id=tree_view.get_id(),
cls="mf-treeview" cls="mf-treeview"
) )
# Step 3: Compare
assert matches(tree_view.__ft__(), expected) assert matches(tree_view.__ft__(), expected)
def test_node_action_buttons_are_rendered(self): @pytest.fixture
"""Test that action buttons are present in rendered HTML.""" def tree_view(self, root_instance):
# Signature only - implementation later return TreeView(root_instance)
pass
def test_node_with_children_collapsed_is_rendered(self, tree_view):
"""Test that a collapsed node with children renders correctly.
Why these elements matter:
- TestIcon chevron_right: Indicates visually that the node is collapsed
- Span with label: Displays the node's text content
- Action buttons (add_child, edit, delete): Enable user interactions
- cls "mf-treenode": Required CSS class for node styling
- data_node_id: Essential for identifying the node in DOM operations
- No children in container: Verifies children are hidden when collapsed
"""
parent = TreeNode(label="Parent", type="folder")
child = TreeNode(label="Child", type="file")
tree_view.add_node(parent)
tree_view.add_node(child, parent_id=parent.id)
# Step 1: Extract the node element to test
rendered = tree_view.render()
node_container = find_one(rendered, Div(data_node_id=parent.id))
# Step 2: Define expected structure
expected = Div(
TestIcon("chevron_right20_regular"), # Collapsed toggle icon
Span("Parent"), # Label
Div( # Action buttons
TestIcon("add_circle20_regular"),
TestIcon("edit20_regular"),
TestIcon("delete20_regular"),
cls=Contains("mf-treenode-actions")
),
cls=Contains("mf-treenode"),
data_node_id=parent.id
)
# Step 3: Compare
assert matches(node_container, expected)
# Verify no children are rendered (collapsed)
child_containers = find(node_container, Div(data_node_id=child.id))
assert len(child_containers) == 0, "Children should not be rendered when node is collapsed"
def test_node_with_children_expanded_is_rendered(self, tree_view):
"""Test that an expanded node with children renders correctly.
Why these elements matter:
- TestIcon chevron_down: Indicates visually that the node is expanded
- Children rendered: Verifies that child nodes are visible when parent is expanded
- Child has its own node structure: Ensures recursive rendering works correctly
"""
parent = TreeNode(label="Parent", type="folder")
child = TreeNode(label="Child", type="file")
tree_view.add_node(parent)
tree_view.add_node(child, parent_id=parent.id)
tree_view._toggle_node(parent.id) # Expand the parent
# Step 1: Extract the parent node element to test
rendered = tree_view.render()
parent_node = find_one(rendered, Div(data_node_id=parent.id))
# Step 2: Define expected structure for toggle icon
expected = Div(
TestIcon("chevron_down20_regular"), # Expanded toggle icon
cls=Contains("mf-treenode")
)
# Step 3: Compare
assert matches(parent_node, expected)
# Verify children ARE rendered (expanded)
child_containers = find(rendered, Div(data_node_id=child.id))
assert len(child_containers) == 1, "Child should be rendered when parent is expanded"
# Verify child has proper node structure
child_node = child_containers[0]
child_expected = Div(
Span("Child"),
cls=Contains("mf-treenode"),
data_node_id=child.id
)
assert matches(child_node, child_expected)
def test_leaf_node_is_rendered(self, tree_view):
"""Test that a leaf node (no children) renders without toggle icon.
Why these elements matter:
- No toggle icon (or empty space): Leaf nodes don't need expand/collapse functionality
- Span with label: Displays the node's text content
- Action buttons present: Even leaf nodes can be edited, deleted, or receive children
"""
leaf = TreeNode(label="Leaf Node", type="file")
tree_view.add_node(leaf)
# Step 1: Extract the leaf node element to test
rendered = tree_view.render()
leaf_node = find_one(rendered, Div(data_node_id=leaf.id))
# Step 2: Define expected structure
expected = Div(
Span("Leaf Node"), # Label
Div( # Action buttons still present
TestIcon("add_circle20_regular"),
TestIcon("edit20_regular"),
TestIcon("delete20_regular"),
cls=Contains("mf-treenode-actions")
),
cls=Contains("mf-treenode"),
data_node_id=leaf.id
)
# Step 3: Compare
assert matches(leaf_node, expected)
def test_selected_node_has_selected_class(self, tree_view):
"""Test that a selected node has the 'selected' CSS class.
Why these elements matter:
- cls Contains "selected": Enables visual highlighting of the selected node
- data_node_id: Required for identifying which node is selected
"""
node = TreeNode(label="Selected Node", type="file")
tree_view.add_node(node)
tree_view._select_node(node.id)
# Step 1: Extract the selected node element to test
rendered = tree_view.render()
selected_node = find_one(rendered, Div(data_node_id=node.id))
# Step 2: Define expected structure
expected = Div(
cls=Contains("mf-treenode", "selected"),
data_node_id=node.id
)
# Step 3: Compare
assert matches(selected_node, expected)
def test_node_in_editing_mode_shows_input(self, tree_view):
"""Test that a node in editing mode renders an Input instead of Span.
Why these elements matter:
- Input element: Enables user to modify the node label inline
- cls "mf-treenode-input": Required CSS class for input field styling
- name "node_label": Essential for form data submission
- value with current label: Pre-fills the input with existing text
- cls does NOT contain "selected": Avoids double highlighting during editing
"""
node = TreeNode(label="Edit Me", type="file")
tree_view.add_node(node)
tree_view._start_rename(node.id)
# Step 1: Extract the editing node element to test
rendered = tree_view.render()
editing_node = find_one(rendered, Div(data_node_id=node.id))
# Step 2: Define expected structure
expected = Div(
Input(
name="node_label",
value="Edit Me",
cls=Contains("mf-treenode-input")
),
cls=Contains("mf-treenode"),
data_node_id=node.id
)
# Step 3: Compare
assert matches(editing_node, expected)
# Verify "selected" class is NOT present
no_selected = Div(
cls=DoesNotContain("selected")
)
assert matches(editing_node, no_selected)
def test_node_indentation_increases_with_level(self, tree_view):
"""Test that node indentation increases correctly with hierarchy level.
Why these elements matter:
- style Contains "padding-left: 0px": Root node has no indentation
- style Contains "padding-left: 20px": Child is indented by 20px
- style Contains "padding-left: 40px": Grandchild is indented by 40px
- Progressive padding: Creates the visual hierarchy of the tree structure
"""
root = TreeNode(label="Root", type="folder")
child = TreeNode(label="Child", type="folder")
grandchild = TreeNode(label="Grandchild", type="file")
tree_view.add_node(root)
tree_view.add_node(child, parent_id=root.id)
tree_view.add_node(grandchild, parent_id=child.id)
# Expand all to make hierarchy visible
tree_view._toggle_node(root.id)
tree_view._toggle_node(child.id)
rendered = tree_view.render()
# Step 1 & 2 & 3: Test root node (level 0)
root_node = find_one(rendered, Div(data_node_id=root.id))
root_expected = Div(
style=Contains("padding-left: 0px")
)
assert matches(root_node, root_expected)
# Step 1 & 2 & 3: Test child node (level 1)
child_node = find_one(rendered, Div(data_node_id=child.id))
child_expected = Div(
style=Contains("padding-left: 20px")
)
assert matches(child_node, child_expected)
# Step 1 & 2 & 3: Test grandchild node (level 2)
grandchild_node = find_one(rendered, Div(data_node_id=grandchild.id))
grandchild_expected = Div(
style=Contains("padding-left: 40px")
)
assert matches(grandchild_node, grandchild_expected)