mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-06 22:49:16 +00:00
@@ -1511,6 +1511,27 @@ def test_issue_90_space_merge_remaps_internal_badge_marker_link(tmp_path: Path)
|
||||
assert details["dropped_marker_links"] == 0
|
||||
|
||||
|
||||
def test_issue_378_space_merge_remaps_internal_value_face_marker_link(tmp_path: Path) -> None:
|
||||
document = _document(tmp_path, "space")
|
||||
markers = document["payload"]["config"]["markers"]
|
||||
markers[0]["name"] = "Controller"
|
||||
markers[0]["value_source"] = {
|
||||
"kind": "derived_marker_state", "ref": "marker:dumb",
|
||||
}
|
||||
markers.append({
|
||||
"id": "dumb", "binding": "virtual", "space": "ground",
|
||||
"room_id": "living", "name": "Dumb lamp", "is_light": True,
|
||||
})
|
||||
merged, _layout, details = build_space_merge(
|
||||
document, {"spaces": [], "markers": [], "settings": {}}, {}, "skip",
|
||||
)
|
||||
imported = [m for m in merged["markers"] if m.get("space") == details["space_id"]]
|
||||
by_name = {m.get("name"): m for m in imported}
|
||||
assert by_name["Controller"]["value_source"]["ref"] == \
|
||||
"marker:" + by_name["Dumb lamp"]["id"]
|
||||
assert details["dropped_marker_links"] == 0
|
||||
|
||||
|
||||
def test_space_export_drops_marker_links_outside_selection(tmp_path: Path) -> None:
|
||||
config = _config()
|
||||
config["markers"][0]["controls"] = ["marker:outside", "light.keep"]
|
||||
@@ -1549,6 +1570,47 @@ def test_issue_90_space_export_disables_badge_link_outside_selection(tmp_path: P
|
||||
assert document["transfer"]["dropped_marker_links"] == 1
|
||||
|
||||
|
||||
def test_issue_378_space_export_drops_value_face_link_outside_selection(tmp_path: Path) -> None:
|
||||
config = _config()
|
||||
config["markers"][0]["value_source"] = {
|
||||
"kind": "derived_marker_state", "ref": "marker:outside",
|
||||
}
|
||||
config["markers"].append({
|
||||
"id": "outside", "binding": "virtual", "space": "other",
|
||||
"name": "Other lamp", "is_light": True,
|
||||
})
|
||||
document, _ = create_export(
|
||||
SimpleNamespace(instance_id="instance-a"), {"config": config},
|
||||
{"layout": {"lamp": {"s": "ground", "x": 0.5, "y": 0.5}}},
|
||||
kind="space", space_id="ground", card_version="1.61.0", config_root=tmp_path,
|
||||
)
|
||||
marker = document["payload"]["config"]["markers"][0]
|
||||
assert "value_source" not in marker
|
||||
assert document["transfer"]["dropped_marker_links"] == 1
|
||||
|
||||
|
||||
def test_issue_378_virtualized_duplicate_drops_value_face_link(tmp_path: Path) -> None:
|
||||
document = _document(tmp_path, "space")
|
||||
controller = document["payload"]["config"]["markers"][0]
|
||||
controller["name"] = "Controller"
|
||||
controller["value_source"] = {
|
||||
"kind": "derived_marker_state", "ref": "marker:dumb",
|
||||
}
|
||||
document["payload"]["config"]["markers"].append({
|
||||
"id": "dumb", "binding": "virtual", "space": "ground", "is_light": True,
|
||||
})
|
||||
current = {
|
||||
"spaces": [], "markers": [{"id": "existing", "binding": controller["binding"]}],
|
||||
}
|
||||
merged, _layout, details = build_space_merge(document, current, {}, "virtual")
|
||||
imported_controller = next(
|
||||
marker for marker in merged["markers"]
|
||||
if marker.get("name") == controller.get("name") and marker.get("binding") == "virtual"
|
||||
)
|
||||
assert "value_source" not in imported_controller
|
||||
assert details["dropped_marker_links"] == 1
|
||||
|
||||
|
||||
def test_space_merge_drops_link_when_target_is_virtualized_duplicate(tmp_path: Path) -> None:
|
||||
document = _document(tmp_path, "space")
|
||||
controller = document["payload"]["config"]["markers"][0]
|
||||
|
||||
@@ -2400,3 +2400,75 @@ def test_issue_90_marker_value_badge_reference_uses_canonical_ref():
|
||||
with pytest.raises(v.MarkerControlError) as invalid:
|
||||
v.validate_marker_value_badges(config, {"markers": []})
|
||||
assert invalid.value.code == "invalid_value_badge_source"
|
||||
|
||||
|
||||
def test_issue_378_value_face_source_is_lossless_and_strict_only_when_changed():
|
||||
source = {
|
||||
"kind": "entity_attribute", "entity_id": "cover.awning",
|
||||
"attribute": "current_position", "future_source": {"kept": True},
|
||||
}
|
||||
config = {"markers": [{
|
||||
"id": "awning", "binding": "entity:cover.awning", "display": "value",
|
||||
"value_source": source,
|
||||
}]}
|
||||
v.validate_marker_value_badges(config, {"markers": []})
|
||||
stored = v.MARKER_SCHEMA(config["markers"][0])
|
||||
assert stored["value_source"]["future_source"] == {"kept": True}
|
||||
|
||||
future = {"markers": [{
|
||||
"id": "future", "binding": "entity:sensor.future", "display": "value",
|
||||
"value_source": {"kind": "future_source", "payload": {"kept": True}},
|
||||
}]}
|
||||
v.validate_marker_value_badges(future, future)
|
||||
with pytest.raises(v.MarkerControlError) as changed:
|
||||
v.validate_marker_value_badges(future, {"markers": []})
|
||||
assert changed.value.code == "invalid_value_source"
|
||||
with pytest.raises(v.MarkerControlError) as imported:
|
||||
v.validate_marker_value_badges(future, validate_all=True)
|
||||
assert imported.value.code == "invalid_value_source"
|
||||
|
||||
invalid_attr = {"markers": [{
|
||||
"id": "bad", "binding": "entity:sensor.x", "display": "value",
|
||||
"value_source": {
|
||||
"kind": "entity_attribute", "entity_id": "sensor.x",
|
||||
"attribute": "entity_picture",
|
||||
},
|
||||
}]}
|
||||
with pytest.raises(v.MarkerControlError) as attribute:
|
||||
v.validate_marker_value_badges(invalid_attr, {"markers": []})
|
||||
assert attribute.value.code == "invalid_value_source_attribute"
|
||||
|
||||
|
||||
def test_issue_378_value_face_marker_reference_and_id_rename_are_delta_safe():
|
||||
config = {"markers": [
|
||||
{"id": "lamp", "binding": "virtual", "is_light": True},
|
||||
{"id": "controller", "binding": "device:controller", "display": "value",
|
||||
"value_source": {"kind": "derived_marker_state", "ref": "marker:lamp"}},
|
||||
]}
|
||||
v.validate_marker_value_badges(config, {"markers": []})
|
||||
|
||||
missing = {"markers": [
|
||||
config["markers"][0],
|
||||
{**config["markers"][1], "value_source": {
|
||||
"kind": "derived_marker_state", "ref": "marker:gone",
|
||||
}},
|
||||
]}
|
||||
with pytest.raises(v.MarkerControlError) as absent:
|
||||
v.validate_marker_value_badges(missing, config)
|
||||
assert absent.value.code == "value_source_marker_missing"
|
||||
|
||||
not_light = {"markers": [
|
||||
{**config["markers"][0], "is_light": False}, config["markers"][1],
|
||||
]}
|
||||
with pytest.raises(v.MarkerControlError) as target:
|
||||
v.validate_marker_value_badges(not_light, {"markers": []})
|
||||
assert target.value.code == "value_source_marker_not_light"
|
||||
|
||||
# Stable binding identifies a renamed marker, so dormant unchanged source data
|
||||
# remains old data instead of becoming a newly-invalid write.
|
||||
future_old = {"markers": [{
|
||||
"id": "old", "binding": "device:controller", "display": "value",
|
||||
"value_source": {"kind": "future_source", "payload": 1},
|
||||
}]}
|
||||
future_renamed = {"markers": [{**future_old["markers"][0], "id": "new"}]}
|
||||
v.validate_marker_value_badges(future_renamed, future_old)
|
||||
|
||||
Reference in New Issue
Block a user