"""Tests for BindableFloat / BindableColor update semantics. The key behaviour pinned here: when ``apply_update`` receives a plain primitive (number for float, ``[R,G,B]`` list for color) it must clear ``source_id`` -- that is how the client signals "unbind, use the static value now". Previously the implementation preserved ``source_id`` on plain-primitive updates, which silently dropped unbind requests. """ from ledgrab.storage.bindable import BindableColor, BindableFloat class TestBindableFloatApplyUpdate: def test_plain_number_unbinds_when_previously_bound(self): bf = BindableFloat(value=0.5, source_id="vs_abc") updated = bf.apply_update(0.8) assert updated.value == 0.8 assert updated.source_id == "" assert not updated.is_bound def test_plain_number_leaves_unbound_unbound(self): bf = BindableFloat(value=0.5, source_id="") updated = bf.apply_update(0.8) assert updated.value == 0.8 assert updated.source_id == "" def test_dict_with_source_id_binds(self): bf = BindableFloat(value=0.5, source_id="") updated = bf.apply_update({"value": 0.8, "source_id": "vs_new"}) assert updated.value == 0.8 assert updated.source_id == "vs_new" assert updated.is_bound def test_dict_with_empty_source_id_unbinds(self): bf = BindableFloat(value=0.5, source_id="vs_abc") updated = bf.apply_update({"value": 0.8, "source_id": ""}) assert updated.value == 0.8 assert updated.source_id == "" def test_none_leaves_unchanged(self): bf = BindableFloat(value=0.5, source_id="vs_abc") updated = bf.apply_update(None) assert updated is bf class TestBindableColorApplyUpdate: def test_plain_list_unbinds_when_previously_bound(self): bc = BindableColor(color=[10, 20, 30], source_id="vs_abc") updated = bc.apply_update([100, 150, 200]) assert updated.color == [100, 150, 200] assert updated.source_id == "" assert not updated.is_bound def test_plain_list_leaves_unbound_unbound(self): bc = BindableColor(color=[10, 20, 30], source_id="") updated = bc.apply_update([100, 150, 200]) assert updated.color == [100, 150, 200] assert updated.source_id == "" def test_dict_with_source_id_binds(self): bc = BindableColor(color=[10, 20, 30], source_id="") updated = bc.apply_update({"color": [100, 150, 200], "source_id": "vs_new"}) assert updated.color == [100, 150, 200] assert updated.source_id == "vs_new" assert updated.is_bound def test_dict_with_empty_source_id_unbinds(self): bc = BindableColor(color=[10, 20, 30], source_id="vs_abc") updated = bc.apply_update({"color": [100, 150, 200], "source_id": ""}) assert updated.color == [100, 150, 200] assert updated.source_id == "" def test_none_leaves_unchanged(self): bc = BindableColor(color=[10, 20, 30], source_id="vs_abc") updated = bc.apply_update(None) assert updated is bc