Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 9 additions & 12 deletions src/panel_reactflow/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,18 +1366,15 @@ def _get_children(self, data_model, doc, root, parent, comm) -> tuple[dict[str,

children: dict[str, list[UIElement] | UIElement | None] = {}
old_models: list[UIElement] = []
if views:
views, view_models = self._get_child_model(views, doc, root, parent, comm)
children["_views"] = views
old_models += view_models
if node_editors:
editor_models, editor_old = self._get_child_model(node_editors, doc, root, parent, comm)
children["_node_editor_views"] = editor_models
old_models += editor_old
if edge_editors:
edge_models, edge_old = self._get_child_model(edge_editors, doc, root, parent, comm)
children["_edge_editor_views"] = edge_models
old_models += edge_old
views, view_models = self._get_child_model(views, doc, root, parent, comm)
children["_views"] = views
old_models += view_models
editor_models, editor_old = self._get_child_model(node_editors, doc, root, parent, comm)
children["_node_editor_views"] = editor_models
old_models += editor_old
edge_models, edge_old = self._get_child_model(edge_editors, doc, root, parent, comm)
children["_edge_editor_views"] = edge_models
old_models += edge_old
for name in ("top_panel", "bottom_panel", "left_panel", "right_panel"):
panels = list(getattr(self, name, []) or [])
if panels:
Expand Down
56 changes: 0 additions & 56 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,62 +147,6 @@ def test_reactflow_add_node_with_view() -> None:
assert events[-1]["type"] == "node_added"


def test_view_idx_updates_on_remove_node(document, comm) -> None:
flow = ReactFlow()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "data": {}, "view": pn.pane.Markdown("A")})
flow.add_node({"id": "n2", "position": {"x": 1, "y": 1}, "data": {}, "view": pn.pane.Markdown("B")})
flow.add_node({"id": "n3", "position": {"x": 2, "y": 2}, "data": {}})

model = flow.get_root(document, comm=comm)

flow.remove_node("n1")

remaining = {node["id"]: node for node in model.data.nodes}
assert remaining["n2"]["data"]["view_idx"] == 0
assert remaining["n3"]["data"].get("view_idx") is None


def test_reactflow_add_node_with_viewer(document, comm) -> None:
"""Test that Viewer objects with __panel__() method work as node views."""

class MyViewer(pn.viewable.Viewer):
def __panel__(self):
return pn.pane.Markdown("Hello from Viewer!")

flow = ReactFlow()
my_viewer = MyViewer()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": my_viewer})

# This should not raise AttributeError about '_models'
_ = flow.get_root(document, comm=comm)
assert len(flow.nodes) == 1
assert flow.nodes[0]["id"] == "n1"


def test_reactflow_add_node_with_arbitrary_object(document, comm) -> None:
"""Test that arbitrary objects (e.g., HoloViews) work as node views via pn.panel().

This addresses issue #13 where objects without __panel__() method
(like HoloViews Curve objects) would raise AttributeError.
"""

class MockPlot:
"""Mock object simulating HoloViews/hvplot objects (no __panel__ method)."""

def __repr__(self):
return "MockPlot(data)"

flow = ReactFlow()
mock_plot = MockPlot()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Plot Node", "data": {}, "view": mock_plot})

# This should not raise AttributeError about '_models'
# The object should be converted via pn.panel()
_ = flow.get_root(document, comm=comm)
assert len(flow.nodes) == 1
assert flow.nodes[0]["id"] == "n1"


def test_reactflow_events_and_selection() -> None:
flow = ReactFlow()
events = []
Expand Down
83 changes: 78 additions & 5 deletions tests/test_core.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,81 @@
"""Core tests module."""
"""Tests for ReactFlow model creation."""

import panel_reactflow # noqa
from panel.pane import Markdown
from panel.viewable import Viewer

from panel_reactflow import ReactFlow

def test_example():
"""Test example."""
assert panel_reactflow

def test_reactflow_add_node_with_arbitrary_object(document, comm) -> None:
"""Test that arbitrary objects (e.g., HoloViews) work as node views via pn.panel().

This addresses issue #13 where objects without __panel__() method
(like HoloViews Curve objects) would raise AttributeError.
"""

class MockPlot:
"""Mock object simulating HoloViews/hvplot objects (no __panel__ method)."""

def __repr__(self):
return "MockPlot(data)"

flow = ReactFlow()
mock_plot = MockPlot()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Plot Node", "data": {}, "view": mock_plot})

# This should not raise AttributeError about '_models'
# The object should be converted via pn.panel()
_ = flow.get_root(document, comm=comm)
assert len(flow.nodes) == 1
assert flow.nodes[0]["id"] == "n1"


def test_view_idx_updates_on_remove_node(document, comm) -> None:
flow = ReactFlow()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "data": {}, "view": Markdown("A")})
flow.add_node({"id": "n2", "position": {"x": 1, "y": 1}, "data": {}, "view": Markdown("B")})
flow.add_node({"id": "n3", "position": {"x": 2, "y": 2}, "data": {}})

model = flow.get_root(document, comm=comm)

flow.remove_node("n1")

remaining = {node["id"]: node for node in model.data.nodes}
assert remaining["n2"]["data"]["view_idx"] == 0
assert remaining["n3"]["data"].get("view_idx") is None


def test_reactflow_add_node_with_viewer(document, comm) -> None:
"""Test that Viewer objects with __panel__() method work as node views."""

class MyViewer(Viewer):
def __panel__(self):
return Markdown("Hello from Viewer!")

flow = ReactFlow()
my_viewer = MyViewer()
flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": my_viewer})

# This should not raise AttributeError about '_models'
_ = flow.get_root(document, comm=comm)
assert len(flow.nodes) == 1
assert flow.nodes[0]["id"] == "n1"


def test_reactflow_add_node_dynamically_creates_views(document, comm):
flow = ReactFlow()
model = flow.get_root(document, comm=comm)
assert model.children == [
"_views",
"_node_editor_views",
"_edge_editor_views",
"top_panel",
"bottom_panel",
"left_panel",
"right_panel",
]

flow.add_node({"id": "n1", "position": {"x": 0, "y": 0}, "label": "Viewer Node", "data": {}, "view": Markdown("foo")})

assert len(model.data._views) == 1
assert len(model.data._node_editor_views) == 1
Loading