649 lines
21 KiB
Python
649 lines
21 KiB
Python
from myfasthtml.core.network_utils import from_nested_dict, from_tree_with_metadata, from_parent_child_list
|
|
|
|
|
|
class TestFromNestedDict:
|
|
def test_i_can_convert_single_root_node(self):
|
|
"""Test conversion of a single root node without children."""
|
|
trees = [{"root": {}}]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert len(nodes) == 1
|
|
assert nodes[0] == {"id": 1, "label": "root"}
|
|
assert len(edges) == 0
|
|
|
|
def test_i_can_convert_tree_with_one_level_children(self):
|
|
"""Test conversion with direct children, verifying edge creation."""
|
|
trees = [{"root": {"child1": {}, "child2": {}}}]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert len(nodes) == 3
|
|
assert nodes[0] == {"id": 1, "label": "root"}
|
|
assert nodes[1] == {"id": 2, "label": "child1"}
|
|
assert nodes[2] == {"id": 3, "label": "child2"}
|
|
|
|
assert len(edges) == 2
|
|
assert {"from": 1, "to": 2} in edges
|
|
assert {"from": 1, "to": 3} in edges
|
|
|
|
def test_i_can_convert_tree_with_multiple_levels(self):
|
|
"""Test recursive conversion with multiple levels of nesting."""
|
|
trees = [
|
|
{
|
|
"root": {
|
|
"child1": {
|
|
"grandchild1": {},
|
|
"grandchild2": {}
|
|
},
|
|
"child2": {}
|
|
}
|
|
}
|
|
]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert len(nodes) == 5
|
|
assert len(edges) == 4
|
|
|
|
# Verify hierarchy
|
|
assert {"from": 1, "to": 2} in edges # root -> child1
|
|
assert {"from": 1, "to": 5} in edges # root -> child2
|
|
assert {"from": 2, "to": 3} in edges # child1 -> grandchild1
|
|
assert {"from": 2, "to": 4} in edges # child1 -> grandchild2
|
|
|
|
def test_i_can_generate_auto_incremented_ids(self):
|
|
"""Test that IDs start at 1 and increment correctly."""
|
|
trees = [{"a": {"b": {"c": {}}}}]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
ids = [node["id"] for node in nodes]
|
|
assert ids == [1, 2, 3]
|
|
|
|
def test_i_can_use_dict_keys_as_labels(self):
|
|
"""Test that dictionary keys become node labels."""
|
|
trees = [{"RootNode": {"ChildNode": {}}}]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert nodes[0]["label"] == "RootNode"
|
|
assert nodes[1]["label"] == "ChildNode"
|
|
|
|
def test_i_can_convert_empty_list(self):
|
|
"""Test that empty list returns empty nodes and edges."""
|
|
trees = []
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert nodes == []
|
|
assert edges == []
|
|
|
|
def test_i_can_convert_multiple_root_nodes(self):
|
|
"""Test conversion with multiple independent trees."""
|
|
trees = [
|
|
{"root1": {"child1": {}}},
|
|
{"root2": {"child2": {}}}
|
|
]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
assert len(nodes) == 4
|
|
assert nodes[0] == {"id": 1, "label": "root1"}
|
|
assert nodes[1] == {"id": 2, "label": "child1"}
|
|
assert nodes[2] == {"id": 3, "label": "root2"}
|
|
assert nodes[3] == {"id": 4, "label": "child2"}
|
|
|
|
# Verify edges connect within trees, not across
|
|
assert len(edges) == 2
|
|
assert {"from": 1, "to": 2} in edges
|
|
assert {"from": 3, "to": 4} in edges
|
|
|
|
def test_i_can_maintain_id_sequence_across_multiple_trees(self):
|
|
"""Test that ID counter continues across multiple trees."""
|
|
trees = [
|
|
{"tree1": {}},
|
|
{"tree2": {}},
|
|
{"tree3": {}}
|
|
]
|
|
nodes, edges = from_nested_dict(trees)
|
|
|
|
ids = [node["id"] for node in nodes]
|
|
assert ids == [1, 2, 3]
|
|
|
|
|
|
class TestFromTreeWithMetadata:
|
|
def test_i_can_convert_single_node_with_metadata(self):
|
|
"""Test basic conversion with explicit id and label."""
|
|
trees = [{"id": "root", "label": "Root Node"}]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert len(nodes) == 1
|
|
assert nodes[0] == {"id": "root", "label": "Root Node"}
|
|
assert len(edges) == 0
|
|
|
|
def test_i_can_preserve_string_ids_from_metadata(self):
|
|
"""Test that string IDs from the tree are preserved."""
|
|
trees = [
|
|
{
|
|
"id": "root_id",
|
|
"label": "Root",
|
|
"children": [
|
|
{"id": "child_id", "label": "Child"}
|
|
]
|
|
}
|
|
]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert nodes[0]["id"] == "root_id"
|
|
assert nodes[1]["id"] == "child_id"
|
|
assert edges[0] == {"from": "root_id", "to": "child_id"}
|
|
|
|
def test_i_can_auto_increment_when_id_is_missing(self):
|
|
"""Test fallback to auto-increment when ID is not provided."""
|
|
trees = [
|
|
{
|
|
"label": "Root",
|
|
"children": [
|
|
{"label": "Child1"},
|
|
{"id": "child2_id", "label": "Child2"}
|
|
]
|
|
}
|
|
]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert nodes[0]["id"] == 1 # auto-incremented
|
|
assert nodes[1]["id"] == 2 # auto-incremented
|
|
assert nodes[2]["id"] == "child2_id" # preserved
|
|
|
|
def test_i_can_convert_tree_with_children(self):
|
|
"""Test handling of children list."""
|
|
trees = [
|
|
{
|
|
"id": "root",
|
|
"label": "Root",
|
|
"children": [
|
|
{
|
|
"id": "child1",
|
|
"label": "Child 1",
|
|
"children": [
|
|
{"id": "grandchild", "label": "Grandchild"}
|
|
]
|
|
},
|
|
{"id": "child2", "label": "Child 2"}
|
|
]
|
|
}
|
|
]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert len(nodes) == 4
|
|
assert len(edges) == 3
|
|
assert {"from": "root", "to": "child1"} in edges
|
|
assert {"from": "root", "to": "child2"} in edges
|
|
assert {"from": "child1", "to": "grandchild"} in edges
|
|
|
|
def test_i_can_use_custom_id_getter(self):
|
|
"""Test custom callback for extracting node ID."""
|
|
trees = [
|
|
{
|
|
"node_id": "custom_root",
|
|
"label": "Root"
|
|
}
|
|
]
|
|
|
|
def custom_id_getter(node):
|
|
return node.get("node_id")
|
|
|
|
nodes, edges = from_tree_with_metadata(
|
|
trees,
|
|
id_getter=custom_id_getter
|
|
)
|
|
|
|
assert nodes[0]["id"] == "custom_root"
|
|
|
|
def test_i_can_use_custom_label_getter(self):
|
|
"""Test custom callback for extracting node label."""
|
|
trees = [
|
|
{
|
|
"id": "root",
|
|
"name": "Custom Label"
|
|
}
|
|
]
|
|
|
|
def custom_label_getter(node):
|
|
return node.get("name", "")
|
|
|
|
nodes, edges = from_tree_with_metadata(
|
|
trees,
|
|
label_getter=custom_label_getter
|
|
)
|
|
|
|
assert nodes[0]["label"] == "Custom Label"
|
|
|
|
def test_i_can_use_custom_children_getter(self):
|
|
"""Test custom callback for extracting children."""
|
|
trees = [
|
|
{
|
|
"id": "root",
|
|
"label": "Root",
|
|
"kids": [
|
|
{"id": "child", "label": "Child"}
|
|
]
|
|
}
|
|
]
|
|
|
|
def custom_children_getter(node):
|
|
return node.get("kids", [])
|
|
|
|
nodes, edges = from_tree_with_metadata(
|
|
trees,
|
|
children_getter=custom_children_getter
|
|
)
|
|
|
|
assert len(nodes) == 2
|
|
assert nodes[1]["id"] == "child"
|
|
|
|
def test_i_can_handle_missing_label_with_default(self):
|
|
"""Test that missing label returns empty string."""
|
|
trees = [{"id": "root"}]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert nodes[0]["label"] == ""
|
|
|
|
def test_i_can_handle_missing_children_with_default(self):
|
|
"""Test that missing children returns empty list (no children processed)."""
|
|
trees = [{"id": "root", "label": "Root"}]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert len(nodes) == 1
|
|
assert len(edges) == 0
|
|
|
|
def test_i_can_convert_multiple_root_trees(self):
|
|
"""Test conversion with multiple independent trees with metadata."""
|
|
trees = [
|
|
{
|
|
"id": "root1",
|
|
"label": "Root 1",
|
|
"children": [
|
|
{"id": "child1", "label": "Child 1"}
|
|
]
|
|
},
|
|
{
|
|
"id": "root2",
|
|
"label": "Root 2",
|
|
"children": [
|
|
{"id": "child2", "label": "Child 2"}
|
|
]
|
|
}
|
|
]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert len(nodes) == 4
|
|
assert nodes[0]["id"] == "root1"
|
|
assert nodes[1]["id"] == "child1"
|
|
assert nodes[2]["id"] == "root2"
|
|
assert nodes[3]["id"] == "child2"
|
|
|
|
# Verify edges connect within trees, not across
|
|
assert len(edges) == 2
|
|
assert {"from": "root1", "to": "child1"} in edges
|
|
assert {"from": "root2", "to": "child2"} in edges
|
|
|
|
def test_i_can_maintain_id_counter_across_multiple_trees_when_missing_ids(self):
|
|
"""Test that auto-increment counter continues across multiple trees."""
|
|
trees = [
|
|
{"label": "Tree1"},
|
|
{"label": "Tree2"},
|
|
{"label": "Tree3"}
|
|
]
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert nodes[0]["id"] == 1
|
|
assert nodes[1]["id"] == 2
|
|
assert nodes[2]["id"] == 3
|
|
|
|
def test_i_can_convert_empty_list(self):
|
|
"""Test that empty list returns empty nodes and edges."""
|
|
trees = []
|
|
nodes, edges = from_tree_with_metadata(trees)
|
|
|
|
assert nodes == []
|
|
assert edges == []
|
|
|
|
|
|
class TestFromParentChildList:
|
|
def test_i_can_convert_single_root_node_without_parent(self):
|
|
"""Test conversion of a single root node without parent."""
|
|
items = [{"id": "root", "label": "Root"}]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 1
|
|
assert nodes[0] == {'color': '#ff9999', 'id': 'root', 'label': 'Root'}
|
|
assert len(edges) == 0
|
|
|
|
def test_i_can_convert_simple_parent_child_relationship(self):
|
|
"""Test conversion with basic parent-child relationship."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 2
|
|
assert {'color': '#ff9999', 'id': 'root', 'label': 'Root'} in nodes
|
|
assert {"id": "child", "label": "Child"} in nodes
|
|
|
|
assert len(edges) == 1
|
|
assert edges[0] == {"from": "root", "to": "child"}
|
|
|
|
def test_i_can_convert_multiple_children_with_same_parent(self):
|
|
"""Test that one parent can have multiple children."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child1", "parent": "root", "label": "Child 1"},
|
|
{"id": "child2", "parent": "root", "label": "Child 2"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 3
|
|
assert len(edges) == 2
|
|
assert {"from": "root", "to": "child1"} in edges
|
|
assert {"from": "root", "to": "child2"} in edges
|
|
|
|
def test_i_can_convert_multi_level_hierarchy(self):
|
|
"""Test conversion with multiple levels (root -> child -> grandchild)."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"},
|
|
{"id": "grandchild", "parent": "child", "label": "Grandchild"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 3
|
|
assert len(edges) == 2
|
|
assert {"from": "root", "to": "child"} in edges
|
|
assert {"from": "child", "to": "grandchild"} in edges
|
|
|
|
def test_i_can_handle_parent_none_as_root(self):
|
|
"""Test that parent=None identifies a root node."""
|
|
items = [
|
|
{"id": "root", "parent": None, "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 2
|
|
assert len(edges) == 1
|
|
assert edges[0] == {"from": "root", "to": "child"}
|
|
|
|
def test_i_can_handle_parent_empty_string_as_root(self):
|
|
"""Test that parent='' identifies a root node."""
|
|
items = [
|
|
{"id": "root", "parent": "", "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 2
|
|
assert len(edges) == 1
|
|
assert edges[0] == {"from": "root", "to": "child"}
|
|
|
|
def test_i_can_create_ghost_node_for_missing_parent(self):
|
|
"""Test automatic creation of ghost node when parent doesn't exist."""
|
|
items = [
|
|
{"id": "child", "parent": "missing_parent", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 2
|
|
# Find the ghost node
|
|
ghost_node = [n for n in nodes if n["id"] == "missing_parent"][0]
|
|
assert ghost_node is not None
|
|
|
|
assert len(edges) == 1
|
|
assert edges[0] == {"from": "missing_parent", "to": "child"}
|
|
|
|
def test_i_can_apply_ghost_color_to_missing_parent(self):
|
|
"""Test that ghost nodes have the default ghost color."""
|
|
items = [
|
|
{"id": "child", "parent": "ghost", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
ghost_node = [n for n in nodes if n["id"] == "ghost"][0]
|
|
assert "color" in ghost_node
|
|
assert ghost_node["color"] == "#cccccc"
|
|
|
|
def test_i_can_use_custom_ghost_color(self):
|
|
"""Test that custom ghost_color parameter is applied."""
|
|
items = [
|
|
{"id": "child", "parent": "ghost", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items, ghost_color="#0000ff")
|
|
|
|
ghost_node = [n for n in nodes if n["id"] == "ghost"][0]
|
|
assert ghost_node["color"] == "#0000ff"
|
|
|
|
def test_i_can_create_multiple_ghost_nodes(self):
|
|
"""Test handling of multiple missing parents."""
|
|
items = [
|
|
{"id": "child1", "parent": "ghost1", "label": "Child 1"},
|
|
{"id": "child2", "parent": "ghost2", "label": "Child 2"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 4 # 2 real + 2 ghost
|
|
ghost_ids = [n["id"] for n in nodes if "color" in n]
|
|
assert "ghost1" in ghost_ids
|
|
assert "ghost2" in ghost_ids
|
|
|
|
def test_i_can_avoid_duplicate_ghost_nodes(self):
|
|
"""Test that same missing parent creates only one ghost node."""
|
|
items = [
|
|
{"id": "child1", "parent": "ghost", "label": "Child 1"},
|
|
{"id": "child2", "parent": "ghost", "label": "Child 2"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert len(nodes) == 3 # 2 real + 1 ghost
|
|
ghost_nodes = [n for n in nodes if n["id"] == "ghost"]
|
|
assert len(ghost_nodes) == 1
|
|
|
|
assert len(edges) == 2
|
|
assert {"from": "ghost", "to": "child1"} in edges
|
|
assert {"from": "ghost", "to": "child2"} in edges
|
|
|
|
def test_i_can_use_custom_id_getter(self):
|
|
"""Test custom callback for extracting node ID."""
|
|
items = [
|
|
{"node_id": "root", "label": "Root"}
|
|
]
|
|
|
|
def custom_id_getter(item):
|
|
return item.get("node_id")
|
|
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
id_getter=custom_id_getter
|
|
)
|
|
|
|
assert nodes[0]["id"] == "root"
|
|
|
|
def test_i_can_use_custom_label_getter(self):
|
|
"""Test custom callback for extracting node label."""
|
|
items = [
|
|
{"id": "root", "name": "Custom Label"}
|
|
]
|
|
|
|
def custom_label_getter(item):
|
|
return item.get("name", "")
|
|
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
label_getter=custom_label_getter
|
|
)
|
|
|
|
assert nodes[0]["label"] == "Custom Label"
|
|
|
|
def test_i_can_use_custom_parent_getter(self):
|
|
"""Test custom callback for extracting parent ID."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent_id": "root", "label": "Child"}
|
|
]
|
|
|
|
def custom_parent_getter(item):
|
|
return item.get("parent_id")
|
|
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
parent_getter=custom_parent_getter
|
|
)
|
|
|
|
assert len(edges) == 1
|
|
assert edges[0] == {"from": "root", "to": "child"}
|
|
|
|
def test_i_can_handle_empty_list(self):
|
|
"""Test that empty list returns empty nodes and edges."""
|
|
items = []
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
assert nodes == []
|
|
assert edges == []
|
|
|
|
def test_i_can_use_id_as_label_for_ghost_nodes(self):
|
|
"""Test that ghost nodes use their ID as label by default."""
|
|
items = [
|
|
{"id": "child", "parent": "ghost_parent", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items)
|
|
|
|
ghost_node = [n for n in nodes if n["id"] == "ghost_parent"][0]
|
|
assert ghost_node["label"] == "ghost_parent"
|
|
|
|
def test_i_can_apply_root_color_to_single_root(self):
|
|
"""Test that a single root node receives the root_color."""
|
|
items = [{"id": "root", "label": "Root"}]
|
|
nodes, edges = from_parent_child_list(items, root_color="#ff0000")
|
|
|
|
assert len(nodes) == 1
|
|
assert nodes[0]["color"] == "#ff0000"
|
|
|
|
def test_i_can_apply_root_color_to_multiple_roots(self):
|
|
"""Test root_color is assigned to all nodes without parent."""
|
|
items = [
|
|
{"id": "root1", "label": "Root 1"},
|
|
{"id": "root2", "label": "Root 2"},
|
|
{"id": "child", "parent": "root1", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items, root_color="#aa0000")
|
|
|
|
root_nodes = [n for n in nodes if n["id"] in ("root1", "root2")]
|
|
assert all(n.get("color") == "#aa0000" for n in root_nodes)
|
|
|
|
# child must NOT have root_color
|
|
child_node = next(n for n in nodes if n["id"] == "child")
|
|
assert "color" not in child_node
|
|
|
|
def test_i_can_handle_root_with_parent_none(self):
|
|
"""Test that root_color is applied when parent=None."""
|
|
items = [
|
|
{"id": "r1", "parent": None, "label": "R1"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items, root_color="#112233")
|
|
|
|
assert nodes[0]["color"] == "#112233"
|
|
|
|
def test_i_can_handle_root_with_parent_empty_string(self):
|
|
"""Test that root_color is applied when parent=''."""
|
|
items = [
|
|
{"id": "r1", "parent": "", "label": "R1"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items, root_color="#334455")
|
|
|
|
assert nodes[0]["color"] == "#334455"
|
|
|
|
def test_i_do_not_apply_root_color_to_non_roots(self):
|
|
"""Test that only real roots receive root_color."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(items, root_color="#ff0000")
|
|
|
|
# Only one root → only this one has the color
|
|
root_node = next(n for n in nodes if n["id"] == "root")
|
|
assert root_node["color"] == "#ff0000"
|
|
|
|
child_node = next(n for n in nodes if n["id"] == "child")
|
|
assert "color" not in child_node
|
|
|
|
def test_i_do_not_override_ghost_color_with_root_color(self):
|
|
"""Ghost nodes must keep ghost_color, not root_color."""
|
|
items = [
|
|
{"id": "child", "parent": "ghost_parent", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
root_color="#ff0000",
|
|
ghost_color="#00ff00"
|
|
)
|
|
|
|
ghost_node = next(n for n in nodes if n["id"] == "ghost_parent")
|
|
assert ghost_node["color"] == "#00ff00"
|
|
|
|
# child is not root → no color
|
|
child_node = next(n for n in nodes if n["id"] == "child")
|
|
assert "color" not in child_node
|
|
|
|
def test_i_can_use_custom_root_color(self):
|
|
"""Test that a custom root_color is applied instead of default."""
|
|
items = [{"id": "root", "label": "Root"}]
|
|
nodes, edges = from_parent_child_list(items, root_color="#123456")
|
|
|
|
assert nodes[0]["color"] == "#123456"
|
|
|
|
def test_i_can_mix_root_nodes_and_ghost_nodes(self):
|
|
"""Ensure root_color applies only to roots and ghost nodes keep ghost_color."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent": "ghost_parent", "label": "Child"}
|
|
]
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
root_color="#ff0000",
|
|
ghost_color="#00ff00"
|
|
)
|
|
|
|
root_node = next(n for n in nodes if n["id"] == "root")
|
|
ghost_node = next(n for n in nodes if n["id"] == "ghost_parent")
|
|
|
|
assert root_node["color"] == "#ff0000"
|
|
assert ghost_node["color"] == "#00ff00"
|
|
|
|
def test_i_do_not_mark_node_as_root_if_parent_field_exists(self):
|
|
"""Node with parent key but non-empty value should NOT get root_color."""
|
|
items = [
|
|
{"id": "root", "label": "Root"},
|
|
{"id": "child", "parent": "root", "label": "Child"},
|
|
{"id": "other", "parent": "unknown_parent", "label": "Other"}
|
|
]
|
|
nodes, edges = from_parent_child_list(
|
|
items,
|
|
root_color="#ff0000",
|
|
ghost_color="#00ff00"
|
|
)
|
|
|
|
# "root" is the only real root
|
|
root_node = next(n for n in nodes if n["id"] == "root")
|
|
assert root_node["color"] == "#ff0000"
|
|
|
|
# "other" is NOT root, even though its parent is missing
|
|
other_node = next(n for n in nodes if n["id"] == "other")
|
|
assert "color" not in other_node
|
|
|
|
# ghost parent must have ghost_color
|
|
ghost_node = next(n for n in nodes if n["id"] == "unknown_parent")
|
|
assert ghost_node["color"] == "#00ff00"
|
|
|
|
def test_i_do_no_add_root_color_when_its_none(self):
|
|
"""Test that a single root node receives the root_color."""
|
|
items = [{"id": "root", "label": "Root"}]
|
|
nodes, edges = from_parent_child_list(items, root_color=None)
|
|
|
|
assert len(nodes) == 1
|
|
assert "color" not in nodes[0]
|