257 lines
8.4 KiB
Python
257 lines
8.4 KiB
Python
"""Unit tests for TreeView component."""
|
|
import shutil
|
|
|
|
import pytest
|
|
|
|
from myfasthtml.controls.TreeView import TreeView, TreeNode
|
|
from .conftest import root_instance
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def cleanup_db():
|
|
shutil.rmtree(".myFastHtmlDb", ignore_errors=True)
|
|
|
|
|
|
class TestTreeviewBehaviour:
|
|
"""Tests for TreeView behavior and logic."""
|
|
|
|
def test_i_can_create_tree_node_with_auto_generated_id(self):
|
|
"""Test that TreeNode generates UUID automatically."""
|
|
node = TreeNode(label="Test Node", type="folder")
|
|
|
|
assert node.id is not None
|
|
assert isinstance(node.id, str)
|
|
assert len(node.id) > 0
|
|
|
|
def test_i_can_create_tree_node_with_default_values(self):
|
|
"""Test that TreeNode has correct default values."""
|
|
node = TreeNode()
|
|
|
|
assert node.label == ""
|
|
assert node.type == "default"
|
|
assert node.parent is None
|
|
assert node.children == []
|
|
|
|
def test_i_can_initialize_tree_view_state(self, root_instance):
|
|
"""Test that TreeViewState initializes with default values."""
|
|
tree_view = TreeView(root_instance)
|
|
state = tree_view._state
|
|
|
|
assert isinstance(state.items, dict)
|
|
assert len(state.items) == 0
|
|
assert state.opened == []
|
|
assert state.selected is None
|
|
assert state.editing is None
|
|
assert state.icon_config == {}
|
|
|
|
def test_i_can_create_empty_treeview(self, root_instance):
|
|
"""Test creating an empty TreeView."""
|
|
tree_view = TreeView(root_instance)
|
|
|
|
assert tree_view is not None
|
|
assert len(tree_view._state.items) == 0
|
|
|
|
def test_i_can_add_node_to_treeview(self, root_instance):
|
|
"""Test adding a root node to the TreeView."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Root Node", type="folder")
|
|
|
|
tree_view.add_node(node)
|
|
|
|
assert node.id in tree_view._state.items
|
|
assert tree_view._state.items[node.id].label == "Root Node"
|
|
assert tree_view._state.items[node.id].parent is None
|
|
|
|
def test_i_can_add_child_node(self, root_instance):
|
|
"""Test adding a child node to a parent."""
|
|
tree_view = TreeView(root_instance)
|
|
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)
|
|
|
|
assert child.id in tree_view._state.items
|
|
assert child.id in parent.children
|
|
assert child.parent == parent.id
|
|
|
|
def test_i_can_set_icon_config(self, root_instance):
|
|
"""Test setting icon configuration."""
|
|
tree_view = TreeView(root_instance)
|
|
config = {
|
|
"folder": "fluent.folder",
|
|
"file": "fluent.document"
|
|
}
|
|
|
|
tree_view.set_icon_config(config)
|
|
|
|
assert tree_view._state.icon_config == config
|
|
assert tree_view._state.icon_config["folder"] == "fluent.folder"
|
|
|
|
def test_i_can_toggle_node(self, root_instance):
|
|
"""Test expand/collapse a node."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Node", type="folder")
|
|
tree_view.add_node(node)
|
|
|
|
# Initially closed
|
|
assert node.id not in tree_view._state.opened
|
|
|
|
# Toggle to open
|
|
tree_view._toggle_node(node.id)
|
|
assert node.id in tree_view._state.opened
|
|
|
|
# Toggle to close
|
|
tree_view._toggle_node(node.id)
|
|
assert node.id not in tree_view._state.opened
|
|
|
|
def test_i_can_expand_all_nodes(self, root_instance):
|
|
"""Test that expand_all opens all nodes with children."""
|
|
tree_view = TreeView(root_instance)
|
|
|
|
# Create hierarchy: root -> child1 -> grandchild
|
|
# -> child2 (leaf)
|
|
root = TreeNode(label="Root", type="folder")
|
|
child1 = TreeNode(label="Child 1", type="folder")
|
|
grandchild = TreeNode(label="Grandchild", type="file")
|
|
child2 = TreeNode(label="Child 2", type="file")
|
|
|
|
tree_view.add_node(root)
|
|
tree_view.add_node(child1, parent_id=root.id)
|
|
tree_view.add_node(grandchild, parent_id=child1.id)
|
|
tree_view.add_node(child2, parent_id=root.id)
|
|
|
|
# Initially all closed
|
|
assert len(tree_view._state.opened) == 0
|
|
|
|
# Expand all
|
|
tree_view.expand_all()
|
|
|
|
# Nodes with children should be opened
|
|
assert root.id in tree_view._state.opened
|
|
assert child1.id in tree_view._state.opened
|
|
|
|
# Leaf nodes should not be in opened list
|
|
assert grandchild.id not in tree_view._state.opened
|
|
assert child2.id not in tree_view._state.opened
|
|
|
|
def test_i_can_select_node(self, root_instance):
|
|
"""Test selecting a node."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Node", type="folder")
|
|
tree_view.add_node(node)
|
|
|
|
tree_view._select_node(node.id)
|
|
|
|
assert tree_view._state.selected == node.id
|
|
|
|
def test_i_can_start_rename_node(self, root_instance):
|
|
"""Test starting rename mode for a node."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Old Name", type="folder")
|
|
tree_view.add_node(node)
|
|
|
|
tree_view._start_rename(node.id)
|
|
|
|
assert tree_view._state.editing == node.id
|
|
|
|
def test_i_can_save_rename_node(self, root_instance):
|
|
"""Test saving renamed node with new label."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Old Name", type="folder")
|
|
tree_view.add_node(node)
|
|
tree_view._start_rename(node.id)
|
|
|
|
tree_view._save_rename(node.id, "New Name")
|
|
|
|
assert tree_view._state.items[node.id].label == "New Name"
|
|
assert tree_view._state.editing is None
|
|
|
|
def test_i_can_cancel_rename_node(self, root_instance):
|
|
"""Test canceling rename operation."""
|
|
tree_view = TreeView(root_instance)
|
|
node = TreeNode(label="Name", type="folder")
|
|
tree_view.add_node(node)
|
|
tree_view._start_rename(node.id)
|
|
|
|
tree_view._cancel_rename()
|
|
|
|
assert tree_view._state.editing is None
|
|
assert tree_view._state.items[node.id].label == "Name"
|
|
|
|
def test_i_can_delete_leaf_node(self, root_instance):
|
|
"""Test deleting a node without children."""
|
|
tree_view = TreeView(root_instance)
|
|
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)
|
|
|
|
# Delete child (leaf node)
|
|
tree_view._delete_node(child.id)
|
|
|
|
assert child.id not in tree_view._state.items
|
|
assert child.id not in parent.children
|
|
|
|
def test_i_can_add_sibling_node(self, root_instance):
|
|
"""Test adding a sibling node."""
|
|
tree_view = TreeView(root_instance)
|
|
parent = TreeNode(label="Parent", type="folder")
|
|
child1 = TreeNode(label="Child 1", type="file")
|
|
|
|
tree_view.add_node(parent)
|
|
tree_view.add_node(child1, parent_id=parent.id)
|
|
|
|
# Add sibling to child1
|
|
tree_view._add_sibling(child1.id, new_label="Child 2")
|
|
|
|
assert len(parent.children) == 2
|
|
# Sibling should be after child1
|
|
assert parent.children.index(child1.id) < len(parent.children) - 1
|
|
|
|
def test_i_cannot_delete_node_with_children(self, root_instance):
|
|
"""Test that deleting a node with children raises an error."""
|
|
tree_view = TreeView(root_instance)
|
|
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)
|
|
|
|
# Try to delete parent (has children)
|
|
with pytest.raises(ValueError, match="Cannot delete node.*with children"):
|
|
tree_view._delete_node(parent.id)
|
|
|
|
def test_i_cannot_add_sibling_to_root(self, root_instance):
|
|
"""Test that adding sibling to root node raises an error."""
|
|
tree_view = TreeView(root_instance)
|
|
root = TreeNode(label="Root", type="folder")
|
|
tree_view.add_node(root)
|
|
|
|
# Try to add sibling to root (no parent)
|
|
with pytest.raises(ValueError, match="Cannot add sibling to root node"):
|
|
tree_view._add_sibling(root.id)
|
|
|
|
def test_i_cannot_select_nonexistent_node(self, root_instance):
|
|
"""Test that selecting a nonexistent node raises an error."""
|
|
tree_view = TreeView(root_instance)
|
|
|
|
# Try to select node that doesn't exist
|
|
with pytest.raises(ValueError, match="Node.*does not exist"):
|
|
tree_view._select_node("nonexistent_id")
|
|
|
|
|
|
class TestTreeViewRender:
|
|
"""Tests for TreeView HTML rendering."""
|
|
|
|
def test_treeview_renders_correctly(self):
|
|
"""Test that TreeView generates correct HTML structure."""
|
|
# Signature only - implementation later
|
|
pass
|
|
|
|
def test_node_action_buttons_are_rendered(self):
|
|
"""Test that action buttons are present in rendered HTML."""
|
|
# Signature only - implementation later
|
|
pass
|