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:
261
tests/controls/test_datagridmanager.py
Normal file
261
tests/controls/test_datagridmanager.py
Normal 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"
|
||||
@@ -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."""
|
||||
|
||||
Reference in New Issue
Block a user