Added Context Manager ObservableResultCollector
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import pytest
|
||||
|
||||
from myutils.observable import NotObservableError, make_observable, bind, collect_return_values, unbind, unbind_all, \
|
||||
has_listeners, get_listener_count, add_event_listener, ObservableEvent, remove_event_listener
|
||||
has_listeners, get_listener_count, add_event_listener, ObservableEvent, remove_event_listener, \
|
||||
ObservableResultCollector
|
||||
|
||||
|
||||
# Test fixtures
|
||||
@@ -1045,3 +1046,244 @@ def test_i_can_receive_changes_in_after_property_change_event_for_all_attributes
|
||||
data.value = "new value"
|
||||
|
||||
assert on_after_change_results == [("number", 1, 5, [5, "5_1"]), ("value", "initial", "new value", ["new value"])]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ObservableResultCollector tests
|
||||
# ============================================================================
|
||||
|
||||
def test_i_can_create_collector_with_list_of_observables():
|
||||
"""
|
||||
ObservableResultCollector should accept a list of observable objects.
|
||||
"""
|
||||
data1 = make_observable(Data())
|
||||
data2 = make_observable(Data())
|
||||
|
||||
collector = ObservableResultCollector([data1, data2])
|
||||
|
||||
assert collector.observables == [data1, data2]
|
||||
assert collector.attr_name == ""
|
||||
assert collector.event == ObservableEvent.AFTER_PROPERTY_CHANGE
|
||||
assert collector.results == []
|
||||
|
||||
|
||||
def test_i_can_create_collector_with_single_observable():
|
||||
"""
|
||||
ObservableResultCollector should automatically convert a single object to a list.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
collector = ObservableResultCollector(data)
|
||||
|
||||
assert collector.observables == [data]
|
||||
assert isinstance(collector.observables, list)
|
||||
|
||||
|
||||
def test_i_can_collect_results_from_single_observable():
|
||||
"""
|
||||
Collector should collect results from a callback bound to an observable.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback(old, new):
|
||||
return f"Changed from {old} to {new}"
|
||||
|
||||
bind(data, "value", callback)
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.value = "world"
|
||||
|
||||
assert collector.results == ["Changed from initial to world"]
|
||||
|
||||
|
||||
def test_i_can_collect_results_from_multiple_callbacks():
|
||||
"""
|
||||
Collector should collect results from multiple callbacks on the same attribute.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback1(old, new):
|
||||
return f"callback1: {new}"
|
||||
|
||||
def callback2(old, new):
|
||||
return f"callback2: {new}"
|
||||
|
||||
bind(data, "value", callback1)
|
||||
bind(data, "value", callback2)
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.value = "test"
|
||||
|
||||
assert collector.results == ["callback1: test", "callback2: test"]
|
||||
|
||||
|
||||
def test_i_can_collect_results_from_multiple_observables():
|
||||
"""
|
||||
Collector should collect results from multiple observable objects.
|
||||
"""
|
||||
data1 = make_observable(Data())
|
||||
data2 = make_observable(Data())
|
||||
|
||||
def callback(old, new):
|
||||
return f"Changed to {new}"
|
||||
|
||||
bind(data1, "value", callback)
|
||||
bind(data2, "value", callback)
|
||||
|
||||
with ObservableResultCollector([data1, data2]) as collector:
|
||||
data1.value = "first"
|
||||
data2.value = "second"
|
||||
|
||||
assert collector.results == ["Changed to first", "Changed to second"]
|
||||
|
||||
|
||||
def test_i_can_collect_results_for_specific_attribute():
|
||||
"""
|
||||
Collector should only collect results for a specific attribute when specified.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def value_callback(old, new):
|
||||
return f"value: {new}"
|
||||
|
||||
def number_callback(old, new):
|
||||
return f"number: {new}"
|
||||
|
||||
bind(data, "value", value_callback)
|
||||
bind(data, "number", number_callback)
|
||||
|
||||
with ObservableResultCollector(data, attr_name="value") as collector:
|
||||
data.value = "test"
|
||||
data.number = 42
|
||||
|
||||
# Should only collect results from value changes
|
||||
assert collector.results == ["value: test"]
|
||||
|
||||
|
||||
def test_i_can_collect_results_for_all_attributes():
|
||||
"""
|
||||
Collector with attr_name="" should collect results from all attributes.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def value_callback(old, new):
|
||||
return f"value: {new}"
|
||||
|
||||
def number_callback(old, new):
|
||||
return f"number: {new}"
|
||||
|
||||
bind(data, "value", value_callback)
|
||||
bind(data, "number", number_callback)
|
||||
|
||||
with ObservableResultCollector(data, attr_name="") as collector:
|
||||
data.value = "test"
|
||||
data.number = 42
|
||||
|
||||
# Should collect results from all attributes
|
||||
assert collector.results == ["value: test", "number: 42"]
|
||||
|
||||
|
||||
def test_i_can_verify_listeners_are_removed_after_exit():
|
||||
"""
|
||||
Listeners should be automatically removed when exiting the context manager.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback(old, new):
|
||||
return f"Changed to {new}"
|
||||
|
||||
bind(data, "value", callback)
|
||||
|
||||
event_name = ObservableEvent.AFTER_PROPERTY_CHANGE.value
|
||||
|
||||
# Before entering context, no event listeners
|
||||
assert event_name not in data._listeners
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
# Inside context, event listener should be added
|
||||
assert event_name in data._listeners
|
||||
assert "" in data._listeners[event_name]
|
||||
|
||||
data.value = "test"
|
||||
|
||||
# After exiting context, event listener should be removed
|
||||
assert event_name not in data._listeners
|
||||
assert collector.results == ["Changed to test"]
|
||||
|
||||
|
||||
def test_i_can_verify_listeners_are_removed_on_exception():
|
||||
"""
|
||||
Listeners should be removed even when an exception occurs in the with block.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback(old, new):
|
||||
return f"Changed to {new}"
|
||||
|
||||
bind(data, "value", callback)
|
||||
|
||||
event_name = ObservableEvent.AFTER_PROPERTY_CHANGE.value
|
||||
|
||||
try:
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.value = "test"
|
||||
raise ValueError("Test exception")
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Listener should be removed despite exception
|
||||
assert event_name not in data._listeners
|
||||
|
||||
|
||||
def test_i_can_access_results_through_collector_object():
|
||||
"""
|
||||
The collector object returned by __enter__ should provide access to results.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback(old, new):
|
||||
return new * 2
|
||||
|
||||
bind(data, "number", callback)
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.number = 5
|
||||
# Results should be accessible during the with block
|
||||
assert collector.results == [10]
|
||||
|
||||
data.number = 10
|
||||
assert collector.results == [10, 20]
|
||||
|
||||
|
||||
def test_i_can_collect_empty_results_when_no_callbacks():
|
||||
"""
|
||||
Collector should have empty results if no callbacks are bound.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.value = "test"
|
||||
|
||||
# No callbacks were bound, so results should be empty
|
||||
assert collector.results == []
|
||||
|
||||
|
||||
def test_i_can_collect_none_values():
|
||||
"""
|
||||
Collector should collect None values returned by callbacks.
|
||||
"""
|
||||
data = make_observable(Data())
|
||||
|
||||
def callback1(old, new):
|
||||
return None
|
||||
|
||||
def callback2(old, new):
|
||||
return "not none"
|
||||
|
||||
bind(data, "value", callback1)
|
||||
bind(data, "value", callback2)
|
||||
|
||||
with ObservableResultCollector(data) as collector:
|
||||
data.value = "test"
|
||||
|
||||
assert collector.results == [None, "not none"]
|
||||
|
||||
Reference in New Issue
Block a user