feat: implement new grid creation with inline rename in DataGridsManager

- Add new_grid() method to create empty DataGrid under selected folder/leaf parent
  - Generate unique sheet names (Sheet1, Sheet2, ...) with _generate_unique_sheet_name()
  - Auto-select and open new node in edit mode for immediate renaming
  - Fix TreeView to cancel edit mode when selecting any node
  - Wire "New grid" icon to new_grid() instead of clear_tree()
  - Add 14 unit tests covering new_grid() scenarios and TreeView behavior
This commit is contained in:
2026-02-20 21:50:47 +01:00
parent 8f3b2e795e
commit 40a90c7ff5
4 changed files with 368 additions and 10 deletions

View File

@@ -0,0 +1,261 @@
"""Unit tests for DataGridsManager component."""
import shutil
import pytest
from myfasthtml.controls.DataGridsManager import DataGridsManager
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.TreeView import TreeNode
from myfasthtml.core.instances import InstancesManager
from .conftest import root_instance
@pytest.fixture(autouse=True)
def cleanup_db():
shutil.rmtree(".myFastHtmlDb", ignore_errors=True)
@pytest.fixture
def datagrid_manager(root_instance):
"""Create a DataGridsManager instance for testing."""
InstancesManager.reset()
TabsManager(root_instance) # just define it
return DataGridsManager(root_instance)
class TestDataGridsManagerBehaviour:
"""Tests for DataGridsManager behavior and logic."""
def test_i_can_create_new_grid_with_nothing_selected(self, datagrid_manager):
"""Test creating a new grid when no node is selected.
Verifies that:
- Grid is created under "Untitled" folder
- Name is "Sheet1"
- Node is selected and in edit mode
- Document definition is created
"""
result = datagrid_manager.new_grid()
# Verify tree structure
tree = datagrid_manager._tree
assert len(tree._state.items) == 2, "Should have Untitled folder + Sheet1 node"
# Find the Untitled folder and Sheet1 node
nodes = list(tree._state.items.values())
untitled = [n for n in nodes if n.label == "Untitled"][0]
sheet = [n for n in nodes if n.label == "Sheet1"][0]
# Verify hierarchy
assert untitled.parent is None, "Untitled should be root"
assert sheet.parent == untitled.id, "Sheet1 should be under Untitled"
# Verify selection and edit mode
assert tree._state.selected == sheet.id, "Sheet1 should be selected"
assert tree._state.editing == sheet.id, "Sheet1 should be in edit mode"
# Verify document definition
assert len(datagrid_manager._state.elements) == 1, "Should have one document"
doc = datagrid_manager._state.elements[0]
assert doc.namespace == "Untitled"
assert doc.name == "Sheet1"
assert doc.type == "excel"
def test_i_can_create_new_grid_under_selected_folder(self, datagrid_manager):
"""Test creating a new grid when a folder is selected.
Verifies that:
- Grid is created under the selected folder
- Namespace matches folder name
"""
# Create a folder and select it
folder_id = datagrid_manager._tree.ensure_path("MyFolder")
datagrid_manager._tree._select_node(folder_id)
result = datagrid_manager.new_grid()
# Verify the new grid is under MyFolder
tree = datagrid_manager._tree
nodes = list(tree._state.items.values())
sheet = [n for n in nodes if n.label == "Sheet1"][0]
assert sheet.parent == folder_id, "Sheet1 should be under MyFolder"
# Verify document definition
doc = datagrid_manager._state.elements[0]
assert doc.namespace == "MyFolder"
assert doc.name == "Sheet1"
def test_i_can_create_new_grid_under_selected_leaf_parent(self, datagrid_manager):
"""Test creating a new grid when a leaf node is selected.
Verifies that:
- Grid is created under the parent of the selected leaf
- Not under the leaf itself
"""
# Create a folder with a leaf
folder_id = datagrid_manager._tree.ensure_path("MyFolder")
leaf = TreeNode(label="ExistingSheet", type="excel", parent=folder_id)
datagrid_manager._tree.add_node(leaf, parent_id=folder_id)
# Select the leaf
datagrid_manager._tree._select_node(leaf.id)
result = datagrid_manager.new_grid()
# Verify the new grid is under MyFolder (not under ExistingSheet)
tree = datagrid_manager._tree
nodes = list(tree._state.items.values())
new_sheet = [n for n in nodes if n.label == "Sheet1"][0]
assert new_sheet.parent == folder_id, "Sheet1 should be under MyFolder (leaf's parent)"
assert new_sheet.parent != leaf.id, "Sheet1 should not be under the leaf"
def test_new_grid_generates_unique_sheet_names(self, datagrid_manager):
"""Test that new_grid generates unique sequential sheet names.
Verifies Sheet1, Sheet2, Sheet3... generation.
"""
# Create first grid
datagrid_manager.new_grid()
assert datagrid_manager._state.elements[0].name == "Sheet1"
# Create second grid
datagrid_manager.new_grid()
assert datagrid_manager._state.elements[1].name == "Sheet2"
# Create third grid
datagrid_manager.new_grid()
assert datagrid_manager._state.elements[2].name == "Sheet3"
def test_new_grid_expands_parent_folder(self, datagrid_manager):
"""Test that creating a new grid automatically expands the parent folder.
Verifies parent is added to tree._state.opened.
"""
result = datagrid_manager.new_grid()
tree = datagrid_manager._tree
nodes = list(tree._state.items.values())
untitled = [n for n in nodes if n.label == "Untitled"][0]
# Verify parent is expanded
assert untitled.id in tree._state.opened, "Parent folder should be expanded"
def test_new_grid_selects_and_edits_new_node(self, datagrid_manager):
"""Test that new grid node is both selected and in edit mode.
Verifies _state.selected and _state.editing are set to new node.
"""
result = datagrid_manager.new_grid()
tree = datagrid_manager._tree
nodes = list(tree._state.items.values())
sheet = [n for n in nodes if n.label == "Sheet1"][0]
# Verify selection
assert tree._state.selected == sheet.id, "New node should be selected"
# Verify edit mode
assert tree._state.editing == sheet.id, "New node should be in edit mode"
def test_new_grid_creates_document_definition(self, datagrid_manager):
"""Test that new_grid creates a DocumentDefinition with correct fields.
Verifies document_id, namespace, name, type, tab_id, datagrid_id.
"""
result = datagrid_manager.new_grid()
# Verify document was created
assert len(datagrid_manager._state.elements) == 1, "Should have one document"
doc = datagrid_manager._state.elements[0]
# Verify all fields
assert doc.document_id is not None, "Should have document_id"
assert isinstance(doc.document_id, str), "document_id should be string"
assert doc.namespace == "Untitled", "namespace should match parent folder"
assert doc.name == "Sheet1", "name should be Sheet1"
assert doc.type == "excel", "type should be excel"
assert doc.tab_id is not None, "Should have tab_id"
assert doc.datagrid_id is not None, "Should have datagrid_id"
def test_new_grid_creates_datagrid_and_registers(self, datagrid_manager):
"""Test that new_grid creates a DataGrid and registers it.
Verifies DataGrid exists and is in registry with namespace.name.
"""
result = datagrid_manager.new_grid()
doc = datagrid_manager._state.elements[0]
# Verify DataGrid is registered
tables = datagrid_manager._registry.get_all_tables()
assert "Untitled.Sheet1" in tables, "DataGrid should be registered as Untitled.Sheet1"
# Verify DataGrid exists in InstancesManager
from myfasthtml.core.instances import InstancesManager
datagrid = InstancesManager.get(datagrid_manager._session, doc.datagrid_id, None)
assert datagrid is not None, "DataGrid instance should exist"
def test_new_grid_creates_tab_with_datagrid(self, datagrid_manager):
"""Test that new_grid creates a tab with correct label and content.
Verifies tab is created via TabsManager with DataGrid as content.
"""
result = datagrid_manager.new_grid()
doc = datagrid_manager._state.elements[0]
tabs_manager = datagrid_manager._tabs_manager
# Verify tab exists in TabsManager
assert doc.tab_id in tabs_manager._state.tabs, "Tab should exist in TabsManager"
# Verify tab label
tab_metadata = tabs_manager._state.tabs[doc.tab_id]
assert tab_metadata['label'] == "Sheet1", "Tab label should be Sheet1"
def test_generate_unique_sheet_name_with_no_children(self, datagrid_manager):
"""Test _generate_unique_sheet_name on an empty folder.
Verifies it returns "Sheet1" when no children exist.
"""
folder_id = datagrid_manager._tree.ensure_path("EmptyFolder")
name = datagrid_manager._generate_unique_sheet_name(folder_id)
assert name == "Sheet1", "Should generate Sheet1 for empty folder"
def test_generate_unique_sheet_name_with_existing_sheets(self, datagrid_manager):
"""Test _generate_unique_sheet_name with existing sheets.
Verifies it generates the next sequential number.
"""
folder_id = datagrid_manager._tree.ensure_path("MyFolder")
# Add Sheet1 and Sheet2 manually
sheet1 = TreeNode(label="Sheet1", type="excel", parent=folder_id)
sheet2 = TreeNode(label="Sheet2", type="excel", parent=folder_id)
datagrid_manager._tree.add_node(sheet1, parent_id=folder_id)
datagrid_manager._tree.add_node(sheet2, parent_id=folder_id)
name = datagrid_manager._generate_unique_sheet_name(folder_id)
assert name == "Sheet3", "Should generate Sheet3 when Sheet1 and Sheet2 exist"
def test_generate_unique_sheet_name_skips_gaps(self, datagrid_manager):
"""Test _generate_unique_sheet_name fills gaps in sequence.
Verifies it generates Sheet2 when Sheet1 and Sheet3 exist (missing Sheet2).
"""
folder_id = datagrid_manager._tree.ensure_path("MyFolder")
# Add Sheet1 and Sheet3 (skip Sheet2)
sheet1 = TreeNode(label="Sheet1", type="excel", parent=folder_id)
sheet3 = TreeNode(label="Sheet3", type="excel", parent=folder_id)
datagrid_manager._tree.add_node(sheet1, parent_id=folder_id)
datagrid_manager._tree.add_node(sheet3, parent_id=folder_id)
name = datagrid_manager._generate_unique_sheet_name(folder_id)
assert name == "Sheet2", "Should generate Sheet2 to fill the gap"

View File

@@ -583,6 +583,44 @@ class TestTreeviewBehaviour:
assert len(tree_view._state.items) == 1, "Node should not have been added to items"
assert tree_view._state.items[node1.id] == node2, "Node should not have been replaced"
def test_selecting_node_cancels_edit_mode(self, root_instance):
"""Test that selecting a node cancels any active edit mode."""
tree_view = TreeView(root_instance)
node1 = TreeNode(label="Node 1", type="folder")
node2 = TreeNode(label="Node 2", type="folder")
tree_view.add_node(node1)
tree_view.add_node(node2)
# Start editing node1
tree_view._start_rename(node1.id)
assert tree_view._state.editing == node1.id
# Select node2
tree_view._select_node(node2.id)
# Edit mode should be cancelled
assert tree_view._state.editing is None
assert tree_view._state.selected == node2.id
def test_selecting_same_editing_node_cancels_edit_mode(self, root_instance):
"""Test that selecting the same node being edited cancels edit mode."""
tree_view = TreeView(root_instance)
node = TreeNode(label="Node", type="folder")
tree_view.add_node(node)
# Start editing the node
tree_view._start_rename(node.id)
assert tree_view._state.editing == node.id
# Select the same node
tree_view._select_node(node.id)
# Edit mode should be cancelled
assert tree_view._state.editing is None
assert tree_view._state.selected == node.id
class TestTreeViewRender:
"""Tests for TreeView HTML rendering."""