Repository URL to install this package:
|
Version:
6.4.1 ▾
|
# (C) Copyright 2005-2022 Enthought, Inc., Austin, TX
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in LICENSE.txt and may be redistributed only under
# the conditions described in the aforementioned license. The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
#
# Thanks for using Enthought open source!
import unittest
from unittest import mock
from traits.api import HasTraits, Instance, Int, List
from traits.observation._list_item_observer import ListItemObserver
from traits.observation._testing import (
call_add_or_remove_notifiers,
create_graph,
)
from traits.trait_list_object import TraitList, TraitListObject
class TestListItemObserverEqualHash(unittest.TestCase):
def test_not_equal_notify(self):
observer1 = ListItemObserver(notify=False, optional=False)
observer2 = ListItemObserver(notify=True, optional=False)
self.assertNotEqual(observer1, observer2)
def test_not_equal_optional(self):
observer1 = ListItemObserver(notify=True, optional=True)
observer2 = ListItemObserver(notify=True, optional=False)
self.assertNotEqual(observer1, observer2)
def test_not_equal_different_type(self):
observer1 = ListItemObserver(notify=False, optional=False)
imposter = mock.Mock()
imposter.notify = False
imposter.optional = False
self.assertNotEqual(observer1, imposter)
def test_equal_observers(self):
observer1 = ListItemObserver(notify=False, optional=False)
observer2 = ListItemObserver(notify=False, optional=False)
self.assertEqual(observer1, observer2)
self.assertEqual(hash(observer1), hash(observer2))
def test_slots(self):
observer = ListItemObserver(notify=True, optional=False)
with self.assertRaises(AttributeError):
observer.__dict__
with self.assertRaises(AttributeError):
observer.__weakref__
def test_eval_repr_roundtrip(self):
observer = ListItemObserver(notify=True, optional=False)
self.assertEqual(eval(repr(observer)), observer)
class CustomList(list):
pass
class CustomTraitList(TraitList):
pass
class ClassWithList(HasTraits):
values = List()
not_a_trait_list = Instance(CustomList)
number = Int()
custom_trait_list = Instance(CustomTraitList)
class ClassWithListOfList(HasTraits):
list_of_list = List(List())
class TestListItemObserverIterObservable(unittest.TestCase):
""" Test ListItemObserver.iter_observables """
def test_trait_list_iter_observables(self):
instance = ClassWithList()
instance.values = [1, 2, 3]
observer = ListItemObserver(notify=True, optional=False)
# In the expected scenario, the ListItemObserver will
# follow another observer whose iter_objects will yield
# the trait value
actual_item, = list(observer.iter_observables(instance.values))
self.assertIs(actual_item, instance.values)
def test_trait_list_iter_observables_with_default_list(self):
instance = ClassWithList()
observer = ListItemObserver(notify=True, optional=False)
# In the expected scenario, the ListItemObserver will
# follow another observer whose iter_objects will yield
# the trait value
actual_item, = list(observer.iter_observables(instance.values))
self.assertIsInstance(actual_item, TraitListObject)
def test_trait_list_iter_observables_accept_custom_trait_list(self):
# An extension of TraitList can be used with ListItemObserver
instance = ClassWithList()
instance.custom_trait_list = CustomTraitList([1, 2, 3])
observer = ListItemObserver(notify=True, optional=False)
actual_item, = list(
observer.iter_observables(instance.custom_trait_list))
self.assertIs(actual_item, instance.custom_trait_list)
def test_trait_list_iter_observables_error(self):
# If the user chains a ListItemObserver after an observer that
# does not produce a TraitList, raise an error
instance = ClassWithList()
instance.not_a_trait_list = CustomList()
observer = ListItemObserver(notify=True, optional=False)
with self.assertRaises(ValueError) as exception_context:
next(observer.iter_observables(instance.not_a_trait_list))
self.assertIn(
"Expected a TraitList to be observed",
str(exception_context.exception)
)
def test_trait_list_iter_observables_not_a_trait_list_optional(self):
# Test when the given object is a list but not an IObservable
instance = ClassWithList()
observer = ListItemObserver(notify=True, optional=True)
self.assertIsNone(instance.not_a_trait_list)
actual = list(observer.iter_observables(instance.not_a_trait_list))
self.assertEqual(actual, [])
instance.not_a_trait_list = CustomList()
actual = list(observer.iter_observables(instance.not_a_trait_list))
self.assertEqual(actual, [])
def test_trait_list_iter_observables_not_a_list_error(self):
# Test when the given object is not a list
instance = ClassWithList()
observer = ListItemObserver(notify=True, optional=False)
with self.assertRaises(ValueError) as exception_context:
list(observer.iter_observables(instance.number))
self.assertIn(
"Expected a TraitList to be observed",
str(exception_context.exception))
class TestListItemObserverIterObjects(unittest.TestCase):
""" Test ListItemObserver.iter_objects """
def test_trait_list_iter_objects(self):
instance = ClassWithList()
item1 = mock.Mock()
item2 = mock.Mock()
instance.values = [item1, item2]
observer = ListItemObserver(notify=True, optional=False)
# the observer does not filter if the values are observables or not.
actual = list(observer.iter_objects(instance.values))
self.assertEqual(actual, [item1, item2])
def test_trait_list_iter_object_accept_custom_trait_list(self):
# An extension of TraitList can be used with ListItemObserver
instance = ClassWithList()
instance.custom_trait_list = CustomTraitList([1, 2, 3])
observer = ListItemObserver(notify=True, optional=False)
actual = list(
observer.iter_objects(instance.custom_trait_list))
self.assertEqual(actual, [1, 2, 3])
def test_trait_list_iter_objects_complain_not_list(self):
observer = ListItemObserver(notify=True, optional=False)
with self.assertRaises(ValueError) as exception_cm:
next(observer.iter_objects(set([1])))
self.assertIn(
"Expected a TraitList to be observed", str(exception_cm.exception))
def test_trait_list_iter_objects_ignore_if_optional_and_not_list(self):
observer = ListItemObserver(notify=True, optional=True)
actual = list(observer.iter_objects(set([1])))
self.assertEqual(actual, [])
# ----------------------------------
# Integration tests with notifiers
# ----------------------------------
class TestListTraitObserverNotifications(unittest.TestCase):
def test_notifier_list_change(self):
instance = ClassWithList(values=[])
graph = create_graph(
ListItemObserver(notify=True, optional=False),
)
handler = mock.Mock()
call_add_or_remove_notifiers(
object=instance.values,
graph=graph,
handler=handler,
)
# when
instance.values.append(1)
# then
((event, ), _), = handler.call_args_list
self.assertIs(event.object, instance.values)
self.assertEqual(event.added, [1])
self.assertEqual(event.removed, [])
self.assertEqual(event.index, 0)
def test_notifier_custom_trait_list_change(self):
# Test compatibility with any extension of TraitList, not just
# TraitListObject
instance = ClassWithList()
instance.custom_trait_list = CustomTraitList()
graph = create_graph(
ListItemObserver(notify=True, optional=False),
)
handler = mock.Mock()
call_add_or_remove_notifiers(
object=instance.custom_trait_list,
graph=graph,
handler=handler,
)
# when
instance.custom_trait_list.append(1)
# then
((event, ), _), = handler.call_args_list
self.assertIs(event.object, instance.custom_trait_list)
self.assertEqual(event.added, [1])
self.assertEqual(event.removed, [])
self.assertEqual(event.index, 0)
def test_maintain_notifier(self):
# Test maintaining downstream notifier by
# observing list of list
instance = ClassWithListOfList()
graph = create_graph(
ListItemObserver(notify=False, optional=False),
ListItemObserver(notify=True, optional=False),
)
handler = mock.Mock()
call_add_or_remove_notifiers(
object=instance.list_of_list,
graph=graph,
handler=handler,
)
# when
instance.list_of_list.append([])
# then
# the first ListItemObserver has notify=False
self.assertEqual(handler.call_count, 0)
# but the second ListItemObserver is given to the nested list
nested_list = instance.list_of_list[0]
# when
nested_list.append(1)
# then
((event, ), _), = handler.call_args_list
self.assertIs(event.object, nested_list)
self.assertEqual(event.added, [1])
self.assertEqual(event.removed, [])
self.assertEqual(event.index, 0)
handler.reset_mock()
# when
# the list is removed, it is not observed
instance.list_of_list.pop()
nested_list.append(1)
# then
self.assertEqual(handler.call_count, 0)
def test_optional_observers(self):
# ListItemObserver.optional is true, meaning it will ignore
# incompatible incoming object.
instance = ClassWithList()
graph = create_graph(
ListItemObserver(notify=True, optional=True),
)
handler = mock.Mock()
call_add_or_remove_notifiers(
object=instance.not_a_trait_list,
graph=graph,
handler=handler,
)
instance.not_a_trait_list = CustomList()
instance.not_a_trait_list.append(1)
self.assertEqual(handler.call_count, 0)