Skip to content

Commit bfcfb4c

Browse files
authored
Ensure children can be updated if not added initially (#48)
* Ensure children can be updated if not added initially * Add tests
1 parent 880e984 commit bfcfb4c

3 files changed

Lines changed: 87 additions & 73 deletions

File tree

src/panel_reactflow/base.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1366,18 +1366,15 @@ def _get_children(self, data_model, doc, root, parent, comm) -> tuple[dict[str,
13661366

13671367
children: dict[str, list[UIElement] | UIElement | None] = {}
13681368
old_models: list[UIElement] = []
1369-
if views:
1370-
views, view_models = self._get_child_model(views, doc, root, parent, comm)
1371-
children["_views"] = views
1372-
old_models += view_models
1373-
if node_editors:
1374-
editor_models, editor_old = self._get_child_model(node_editors, doc, root, parent, comm)
1375-
children["_node_editor_views"] = editor_models
1376-
old_models += editor_old
1377-
if edge_editors:
1378-
edge_models, edge_old = self._get_child_model(edge_editors, doc, root, parent, comm)
1379-
children["_edge_editor_views"] = edge_models
1380-
old_models += edge_old
1369+
views, view_models = self._get_child_model(views, doc, root, parent, comm)
1370+
children["_views"] = views
1371+
old_models += view_models
1372+
editor_models, editor_old = self._get_child_model(node_editors, doc, root, parent, comm)
1373+
children["_node_editor_views"] = editor_models
1374+
old_models += editor_old
1375+
edge_models, edge_old = self._get_child_model(edge_editors, doc, root, parent, comm)
1376+
children["_edge_editor_views"] = edge_models
1377+
old_models += edge_old
13811378
for name in ("top_panel", "bottom_panel", "left_panel", "right_panel"):
13821379
panels = list(getattr(self, name, []) or [])
13831380
if panels:

tests/test_api.py

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -147,62 +147,6 @@ def test_reactflow_add_node_with_view() -> None:
147147
assert events[-1]["type"] == "node_added"
148148

149149

150-
def test_view_idx_updates_on_remove_node(document, comm) -> None:
151-
flow = ReactFlow()
152-
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "data": {}, "view": pn.pane.Markdown("A")})
153-
flow.add_node({"id": "n2", "position": {"x": 1, "y": 1}, "data": {}, "view": pn.pane.Markdown("B")})
154-
flow.add_node({"id": "n3", "position": {"x": 2, "y": 2}, "data": {}})
155-
156-
model = flow.get_root(document, comm=comm)
157-
158-
flow.remove_node("n1")
159-
160-
remaining = {node["id"]: node for node in model.data.nodes}
161-
assert remaining["n2"]["data"]["view_idx"] == 0
162-
assert remaining["n3"]["data"].get("view_idx") is None
163-
164-
165-
def test_reactflow_add_node_with_viewer(document, comm) -> None:
166-
"""Test that Viewer objects with __panel__() method work as node views."""
167-
168-
class MyViewer(pn.viewable.Viewer):
169-
def __panel__(self):
170-
return pn.pane.Markdown("Hello from Viewer!")
171-
172-
flow = ReactFlow()
173-
my_viewer = MyViewer()
174-
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": my_viewer})
175-
176-
# This should not raise AttributeError about '_models'
177-
_ = flow.get_root(document, comm=comm)
178-
assert len(flow.nodes) == 1
179-
assert flow.nodes[0]["id"] == "n1"
180-
181-
182-
def test_reactflow_add_node_with_arbitrary_object(document, comm) -> None:
183-
"""Test that arbitrary objects (e.g., HoloViews) work as node views via pn.panel().
184-
185-
This addresses issue #13 where objects without __panel__() method
186-
(like HoloViews Curve objects) would raise AttributeError.
187-
"""
188-
189-
class MockPlot:
190-
"""Mock object simulating HoloViews/hvplot objects (no __panel__ method)."""
191-
192-
def __repr__(self):
193-
return "MockPlot(data)"
194-
195-
flow = ReactFlow()
196-
mock_plot = MockPlot()
197-
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Plot Node", "data": {}, "view": mock_plot})
198-
199-
# This should not raise AttributeError about '_models'
200-
# The object should be converted via pn.panel()
201-
_ = flow.get_root(document, comm=comm)
202-
assert len(flow.nodes) == 1
203-
assert flow.nodes[0]["id"] == "n1"
204-
205-
206150
def test_reactflow_events_and_selection() -> None:
207151
flow = ReactFlow()
208152
events = []

tests/test_core.py

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,81 @@
1-
"""Core tests module."""
1+
"""Tests for ReactFlow model creation."""
22

3-
import panel_reactflow # noqa
3+
from panel.pane import Markdown
4+
from panel.viewable import Viewer
45

6+
from panel_reactflow import ReactFlow
57

6-
def test_example():
7-
"""Test example."""
8-
assert panel_reactflow
8+
9+
def test_reactflow_add_node_with_arbitrary_object(document, comm) -> None:
10+
"""Test that arbitrary objects (e.g., HoloViews) work as node views via pn.panel().
11+
12+
This addresses issue #13 where objects without __panel__() method
13+
(like HoloViews Curve objects) would raise AttributeError.
14+
"""
15+
16+
class MockPlot:
17+
"""Mock object simulating HoloViews/hvplot objects (no __panel__ method)."""
18+
19+
def __repr__(self):
20+
return "MockPlot(data)"
21+
22+
flow = ReactFlow()
23+
mock_plot = MockPlot()
24+
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Plot Node", "data": {}, "view": mock_plot})
25+
26+
# This should not raise AttributeError about '_models'
27+
# The object should be converted via pn.panel()
28+
_ = flow.get_root(document, comm=comm)
29+
assert len(flow.nodes) == 1
30+
assert flow.nodes[0]["id"] == "n1"
31+
32+
33+
def test_view_idx_updates_on_remove_node(document, comm) -> None:
34+
flow = ReactFlow()
35+
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "data": {}, "view": Markdown("A")})
36+
flow.add_node({"id": "n2", "position": {"x": 1, "y": 1}, "data": {}, "view": Markdown("B")})
37+
flow.add_node({"id": "n3", "position": {"x": 2, "y": 2}, "data": {}})
38+
39+
model = flow.get_root(document, comm=comm)
40+
41+
flow.remove_node("n1")
42+
43+
remaining = {node["id"]: node for node in model.data.nodes}
44+
assert remaining["n2"]["data"]["view_idx"] == 0
45+
assert remaining["n3"]["data"].get("view_idx") is None
46+
47+
48+
def test_reactflow_add_node_with_viewer(document, comm) -> None:
49+
"""Test that Viewer objects with __panel__() method work as node views."""
50+
51+
class MyViewer(Viewer):
52+
def __panel__(self):
53+
return Markdown("Hello from Viewer!")
54+
55+
flow = ReactFlow()
56+
my_viewer = MyViewer()
57+
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": my_viewer})
58+
59+
# This should not raise AttributeError about '_models'
60+
_ = flow.get_root(document, comm=comm)
61+
assert len(flow.nodes) == 1
62+
assert flow.nodes[0]["id"] == "n1"
63+
64+
65+
def test_reactflow_add_node_dynamically_creates_views(document, comm):
66+
flow = ReactFlow()
67+
model = flow.get_root(document, comm=comm)
68+
assert model.children == [
69+
"_views",
70+
"_node_editor_views",
71+
"_edge_editor_views",
72+
"top_panel",
73+
"bottom_panel",
74+
"left_panel",
75+
"right_panel",
76+
]
77+
78+
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": Markdown("foo")})
79+
80+
assert len(model.data._views) == 1
81+
assert len(model.data._node_editor_views) == 1

0 commit comments

Comments
 (0)