diff --git a/src/spellbind/int_values.py b/src/spellbind/int_values.py index 2d674e8..3a1fd87 100644 --- a/src/spellbind/int_values.py +++ b/src/spellbind/int_values.py @@ -116,6 +116,9 @@ def __rmod__(self, other: int) -> IntValue: def __abs__(self) -> IntValue: return AbsIntValue(self) + def equals(self, other: FloatLike) -> BoolValue: + return CompareNumbersValues(self, other, operator.eq) + def __lt__(self, other: FloatLike) -> BoolValue: return CompareNumbersValues(self, other, operator.lt) diff --git a/src/spellbind/observable_collections.py b/src/spellbind/observable_collections.py index f177d26..6a15977 100644 --- a/src/spellbind/observable_collections.py +++ b/src/spellbind/observable_collections.py @@ -7,6 +7,7 @@ from typing_extensions import override from spellbind.actions import CollectionAction, DeltaAction, DeltasAction, ClearAction +from spellbind.bool_values import BoolValue from spellbind.deriveds import Derived from spellbind.event import BiEvent from spellbind.int_values import IntValue @@ -37,6 +38,10 @@ def length_value(self) -> IntValue: ... def __len__(self) -> int: return self.length_value.value + @property + def is_empty(self) -> BoolValue: + return self.length_value.equals(0) + def combine(self, combiner: Callable[[Iterable[_S_co]], _S]) -> Value[_S]: return CombinedValue(self, combiner=combiner) diff --git a/src/spellbind/observable_sequences.py b/src/spellbind/observable_sequences.py index 610aa78..9c04deb 100644 --- a/src/spellbind/observable_sequences.py +++ b/src/spellbind/observable_sequences.py @@ -752,7 +752,7 @@ def _to_value(value: _S | Value[_S], if checker(value): return constant_factory(value) else: - return value + return value # type: ignore[return-value] # mypy raises an error since mypy 1.18.1, but that seems to be a bug https://github.com/python/mypy/issues/20330 def _to_values(values: Iterable[_S | Value[_S]], diff --git a/tests/test_collections/test_observable_lists/test_observable_list_is_empty.py b/tests/test_collections/test_observable_lists/test_observable_list_is_empty.py new file mode 100644 index 0000000..0f9d3e0 --- /dev/null +++ b/tests/test_collections/test_observable_lists/test_observable_list_is_empty.py @@ -0,0 +1,56 @@ +from conftest import OneParameterObserver +from spellbind.observable_sequences import ObservableList + + +def test_empty_is_empty(): + assert ObservableList([]).is_empty.value is True + + +def test_one_element_is_not_empty(): + assert ObservableList([1]).is_empty.value is False + + +def test_two_elements_is_not_empty(): + assert ObservableList([1, 2]).is_empty.value is False + + +def test_adding_one_element_to_empty_list_makes_it_not_empty(): + observable_list = ObservableList([]) + is_empty_value = observable_list.is_empty + observer = OneParameterObserver() + is_empty_value.observe(observer) + observable_list.append(42) + assert is_empty_value.value is False + observer.assert_called_once_with(False) + + +def test_removing_only_element_makes_list_empty(): + observable_list = ObservableList([42]) + is_empty_value = observable_list.is_empty + observer = OneParameterObserver() + is_empty_value.observe(observer) + del observable_list[0] + assert is_empty_value.value is True + observer.assert_called_once_with(True) + + +def test_removing_one_element_from_two_element_list_remains_non_empty(): + observable_list = ObservableList([1, 2]) + is_empty_value = observable_list.is_empty + observer = OneParameterObserver() + is_empty_value.observe(observer) + del observable_list[0] + assert is_empty_value.value is False + observer.assert_not_called() + + +def test_add_and_remove_from_empty_list(): + observable_list = ObservableList([]) + is_empty_value = observable_list.is_empty + observer = OneParameterObserver() + is_empty_value.observe(observer) + observable_list.append(1) + assert is_empty_value.value is False + observable_list.remove(1) + assert is_empty_value.value is True + assert observer.calls == [False, True] diff --git a/tests/test_values/test_int_values/test_equal_int_values.py b/tests/test_values/test_int_values/test_equal_int_values.py new file mode 100644 index 0000000..a6e2bcd --- /dev/null +++ b/tests/test_values/test_int_values/test_equal_int_values.py @@ -0,0 +1,54 @@ +from conftest import OneParameterObserver +from spellbind.int_values import IntVariable, IntConstant + + +def test_constant_equals_constant(): + v0 = IntConstant(0) + v1 = IntConstant(0) + equals = v0.equals(v1) + assert equals.value is True + + +def test_constant_does_not_equal_constant(): + v0 = IntConstant(0) + v1 = IntConstant(1) + equals = v0.equals(v1) + assert equals.value is False + + +def test_equal_values_become_unequal(): + v = IntVariable(0) + c = IntConstant(0) + equals = v.equals(c) + observer = OneParameterObserver() + equals.observe(observer) + assert equals.value is True + v.value = 1 + assert equals.value is False + observer.assert_called_once_with(False) + + +def test_unequal_values_become_equal(): + v = IntVariable(1) + c = IntConstant(0) + equals = v.equals(c) + observer = OneParameterObserver() + equals.observe(observer) + assert equals.value is False + v.value = 0 + assert equals.value is True + observer.assert_called_once_with(True) + + +def test_become_unequal_and_equal_again(): + v = IntVariable(0) + c = IntConstant(0) + equals = v.equals(c) + observer = OneParameterObserver() + equals.observe(observer) + assert equals.value is True + v.value = 1 + assert equals.value is False + v.value = 0 + assert equals.value is True + assert observer.calls == [False, True]