Add the QGraphicsView layer: NodeItem (SVG symbol + title + draggable, snap-to-grid), PortItem (relation-colored anchors with compatibility hints), EdgeItem (orthogonal routing, arc hops on crossings, line styles, head/tail decorations, animated flow dashes), and DiagramScene binding the document to items with edge-drawing interaction and a grid background. Add EditorController with a QUndoStack (add/connect/delete/move/edit) and scene interaction tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.3 KiB
Python
100 lines
3.3 KiB
Python
"""Scene/interaction tests: node items, port connect, move reroute, delete, undo."""
|
|
import pytest
|
|
|
|
from pipeline_editor.config import AppConfig
|
|
from pipeline_editor.controller import EditorController
|
|
from pipeline_editor.model.document import DiagramDocument
|
|
from pipeline_editor.model.node_library import default_library
|
|
from pipeline_editor.view.node_item import NodeItem
|
|
from pipeline_editor.view.edge_item import EdgeItem
|
|
from pipeline_editor.view.scene import DiagramScene
|
|
|
|
|
|
@pytest.fixture
|
|
def env(qapp):
|
|
doc = DiagramDocument()
|
|
lib = default_library()
|
|
ctrl = EditorController(doc, lib)
|
|
cfg = AppConfig()
|
|
scene = DiagramScene(doc, cfg, controller=ctrl)
|
|
return doc, lib, ctrl, cfg, scene
|
|
|
|
|
|
def test_add_node_creates_item(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
node = scene.create_node("pump", 100, 100)
|
|
assert node is not None
|
|
assert node.node_id in scene.node_items
|
|
assert isinstance(scene.node_items[node.node_id], NodeItem)
|
|
|
|
|
|
def test_connect_ports_creates_edge_item(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("source", 0, 100)
|
|
b = ctrl.add_node("pump", 200, 100)
|
|
src_port = scene.node_items[a.node_id].port_item("out")
|
|
dst_port = scene.node_items[b.node_id].port_item("in")
|
|
scene._begin_edge(src_port)
|
|
scene._finish_edge(dst_port)
|
|
assert len(scene.edge_items) == 1
|
|
edge = next(doc.edges())
|
|
assert edge.source_node == a.node_id and edge.target_node == b.node_id
|
|
|
|
|
|
def test_incompatible_ports_do_not_connect(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("source", 0, 100) # water
|
|
t = ctrl.add_node("transformer", 200, 100) # power
|
|
scene._begin_edge(scene.node_items[a.node_id].port_item("out"))
|
|
scene._finish_edge(scene.node_items[t.node_id].port_item("hv"))
|
|
assert len(scene.edge_items) == 0
|
|
|
|
|
|
def test_move_node_reroutes_edge(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("source", 0, 100)
|
|
b = ctrl.add_node("pump", 200, 100)
|
|
ctrl.connect(a.node_id, "out", b.node_id, "in")
|
|
edge_item = next(iter(scene.edge_items.values()))
|
|
before = edge_item.path_points()[-1]
|
|
# move target node down; edge end should follow
|
|
scene.node_items[b.node_id].setPos(200, 300)
|
|
after = edge_item.path_points()[-1]
|
|
assert after != before
|
|
assert after[1] > before[1]
|
|
|
|
|
|
def test_selection_emits_model(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("pump", 0, 0)
|
|
received = []
|
|
scene.selection_changed.connect(lambda m: received.append(m))
|
|
scene.node_items[a.node_id].setSelected(True)
|
|
assert received and received[-1] is a
|
|
|
|
|
|
def test_delete_and_undo_roundtrip(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("source", 0, 100)
|
|
b = ctrl.add_node("pump", 200, 100)
|
|
ctrl.connect(a.node_id, "out", b.node_id, "in")
|
|
assert len(scene.edge_items) == 1
|
|
|
|
ctrl.delete(node_ids=[a.node_id])
|
|
assert a.node_id not in scene.node_items
|
|
assert len(scene.edge_items) == 0 # cascaded
|
|
|
|
ctrl.undo()
|
|
assert a.node_id in scene.node_items
|
|
assert len(scene.edge_items) == 1 # edge restored
|
|
|
|
|
|
def test_move_undo(env):
|
|
doc, lib, ctrl, cfg, scene = env
|
|
a = ctrl.add_node("pump", 40, 40)
|
|
ctrl.record_move(a.node_id, (40, 40), (120, 200))
|
|
assert (a.x, a.y) == (120, 200)
|
|
ctrl.undo()
|
|
assert (a.x, a.y) == (40, 40)
|
|
assert scene.node_items[a.node_id].pos().x() == 40
|