diagrams-opus-qt/tests/test_model.py
Ilya 62780c66be feat(model): domain model, node library, and SVG symbols
Add UI-agnostic model layer: typed grouped properties (string/int/float/
bool/enum/color/catalogue item(s)/value list), catalogue registry, port
relations with color schemes and compatibility rules, ports, node/edge
models, and DiagramDocument with change signals and JSON round-trip.
Include a starter node library (source/tank/pump/valve/junction/consumer/
transformer) with matching SVG symbols, plus model+document tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 23:13:18 +02:00

93 lines
3.7 KiB
Python

"""Domain-model tests: properties, catalogue, relations, ports, serialization."""
from pipeline_editor.model.properties import (
Property, PropertyBag, PropertyGroup, PropertyType,
)
from pipeline_editor.model.catalogue import default_registry as default_catalogues
from pipeline_editor.model.relations import default_registry as default_relations
from pipeline_editor.model.port import Port, PortSide, PortDirection
from pipeline_editor.model.node_library import default_library
def test_property_coercion_and_bounds():
p = Property("d", "Diameter", PropertyType.FLOAT, "50", minimum=0, maximum=100)
assert p.value == 50.0 and isinstance(p.value, float)
assert p.set_value(150) is True
assert p.value == 100.0 # clamped to max
assert p.set_value(100) is False # unchanged
assert p.set_value(-5) is True and p.value == 0.0 # clamped to min
def test_property_on_change_callback():
seen = []
p = Property("t", "Title", PropertyType.STRING, "a")
p.on_change = lambda prop: seen.append(prop.value)
p.set_value("b")
assert seen == ["b"]
def test_property_bag_lookup_and_roundtrip():
bag = PropertyBag([
PropertyGroup("Presentation", [Property("title", "Title", PropertyType.STRING, "N1")]),
PropertyGroup("Physics", [Property("len", "Length", PropertyType.FLOAT, 12.5, unit="m")]),
])
assert bag.value("title") == "N1"
assert bag.set_value("len", 20) is True
restored = PropertyBag.from_dict(bag.to_dict())
assert restored.value("len") == 20.0
assert restored.group("Physics").get("len").unit == "m"
def test_all_property_types_roundtrip():
bag = PropertyBag([PropertyGroup("G", [
Property("s", "S", PropertyType.STRING, "x"),
Property("i", "I", PropertyType.INT, 3),
Property("f", "F", PropertyType.FLOAT, 1.5),
Property("b", "B", PropertyType.BOOL, True),
Property("e", "E", PropertyType.ENUM, "a", options=["a", "b"]),
Property("c", "C", PropertyType.COLOR, "#ff0000"),
Property("ci", "CI", PropertyType.CATALOGUE_ITEM, "DN50", catalogue="pipe_classes"),
Property("cis", "CIS", PropertyType.CATALOGUE_ITEMS, ["DN50", "DN100"], catalogue="pipe_classes"),
Property("vl", "VL", PropertyType.VALUE_LIST, [1, 2, 3]),
])])
r = PropertyBag.from_dict(bag.to_dict())
assert r.value("i") == 3 and r.value("b") is True
assert r.value("cis") == ["DN50", "DN100"]
assert r.value("vl") == [1, 2, 3]
def test_catalogue_resolution():
reg = default_catalogues()
item = reg.resolve("pipe_classes", "DN100")
assert item is not None and item.attr("diameter_mm") == 100
assert reg.resolve("pipe_classes", "NOPE") is None
def test_relation_compatibility_and_color_scheme():
reg = default_relations()
assert reg.can_connect("water", "water") is True
assert reg.can_connect("water", "hot_water") is True # hot_water compatible with water
assert reg.can_connect("water", "power") is False
# default color, then switch scheme
assert reg.color("water") == "#1e88e5"
reg.set_active_scheme("high_contrast")
assert reg.color("water") == "#0000ff"
def test_port_geometry():
p = Port("out", "water", PortSide.RIGHT, 0.5, direction=PortDirection.OUTPUT)
assert p.local_point(80, 40) == (80, 20)
assert p.normal() == (1.0, 0.0)
top = Port("t", "water", PortSide.TOP, 0.25)
assert top.local_point(100, 100) == (25, 0)
def test_library_instantiation():
lib = default_library()
tmpl = lib.get("pump")
node = tmpl.instantiate("n1", 10, 20)
assert node.title == "Pump"
assert node.properties.group("Physics").get("head").value == 30.0
assert {p.port_id for p in node.ports} == {"in", "out"}
# groups exposed for the panel
assert "Equipment" in lib.groups()