cara/caimira/tests/test_state.py
2022-09-09 16:57:20 +02:00

229 lines
5.5 KiB
Python

from dataclasses import dataclass
import typing
from unittest.mock import Mock
import pytest
from caimira import state
@dataclass
class DCSimple:
attr1: str
attr2: int
@dataclass
class DCAnother:
attr3: float
@dataclass
class DCSimpleSubclass(DCSimple):
attr3: float
@dataclass
class DCOverrideSubclass(DCSimple):
attr1: float # type: ignore
@dataclass
class DCClassVar(DCSimple):
a_class_var: typing.ClassVar[int]
@dataclass
class DCRecursive(DCSimple):
simple: DCSimple
@dataclass
class DCNested:
simple: DCSimple
others: typing.List[DCSimple]
vanilla: str = 'Default'
@dataclass
class DCNestedDeep:
child: DCNested
@pytest.fixture
def dc_simple():
return DCSimple
def test_DCS_construct():
s = state.DataclassInstanceState(DCSimple)
assert repr(s) == '<state for DCSimple(**{})>'
with pytest.raises(TypeError, match=r"A dataclass type must be provided, not an instance of one"):
state.DataclassInstanceState(DCSimple('', 1))
with pytest.raises(TypeError, match="The given class is not a valid dataclass"):
state.DataclassInstanceState(None)
def test_DCS_construct_nested():
s = state.DataclassInstanceState(DCNested)
assert repr(s) == "<state for DCNested(**{'simple': {}})>"
@pytest.mark.xfail
def test_DCS_subclass():
s = state.DataclassInstanceState(DCSimple)
s.dcs_set_instance_type(DCSimpleSubclass)
s.set('attr3', 3.14)
assert s._instance_kwargs() == {'attr3': 3.14}
s.dcs_set_instance_type(DCSimple)
# TODO: Make this fail.
assert s._instance_kwargs() == {}
def test_DCS_setattr():
s = state.DataclassInstanceState(DCSimple)
s.attr1 = 'Hello world'
assert s._instance_kwargs() == {'attr1': 'Hello world'}
@pytest.mark.xfail
def test_DCS_type_check():
s = state.DataclassInstanceState(DCSimple)
with pytest.raises(TypeError):
# TODO: Should we make this fail? It involves type-checking / validation.
s.attr1 = 1
def test_DCS_update_from_instance():
s = state.DataclassInstanceState(DCSimple)
s.dcs_update_from(DCSimple('a1', 2))
assert s._instance_type == DCSimple
assert s._instance_kwargs() == {'attr1': 'a1', 'attr2': 2}
def test_DCS_update_from_instance_subclass():
s = state.DataclassInstanceState(DCSimple)
s.dcs_update_from(DCSimpleSubclass('a1', 2, 3.14))
assert s._instance_type == DCSimpleSubclass
assert s._instance_kwargs() == {'attr1': 'a1', 'attr2': 2, 'attr3': 3.14}
def test_DCS_update_from_instance_nested():
s = state.DataclassInstanceState(DCNested)
nested = DCNested(DCSimpleSubclass('a1', 2, 3.14), [])
s.dcs_update_from(nested)
assert s.simple.dcs_instance() == nested.simple
assert s.dcs_instance() == nested
def test_observe_instance_nested():
top_level = Mock()
nested = Mock()
s = state.DataclassInstanceState(DCNested)
s.dcs_observe(top_level)
s.simple.dcs_observe(nested)
s.simple.attr1 = 'something new'
top_level.assert_called_with()
nested.assert_called_with()
top_level.reset_mock()
nested.reset_mock()
s.vanilla = 'something new'
top_level.assert_called_with()
nested.assert_not_called()
def test_DCS_predefined():
opt1 = DCSimple('a', 1)
opt2 = DCSimpleSubclass('b', 2, 3.14)
s = state.DataclassStatePredefined(
DCSimple, {'option 1': opt1, 'option 2': opt2}
)
assert s._selected == 'option 1'
# TODO: This should fail.
s.attr1 = 'can I set it?'
assert s.dcs_instance() == opt1
s.dcs_select('option 2')
assert s.dcs_instance() == opt2
assert repr(s) == "<state for DCSimple. 'option 2' selected>"
# TODO: This should fail too.
s.dcs_update_from(opt1)
assert s.dcs_instance() == opt2
observer = Mock()
s.dcs_observe(observer)
s.dcs_select('option 1')
observer.assert_called_once_with()
def test_DCS_named():
opt1 = DCSimpleSubclass('a', 1, 3.14)
opt2 = DCAnother(4.2)
s = state.DataclassStateNamed({
# Entirely different types possible.
'option 1': state.DataclassInstanceState(DCSimple),
'option 2': state.DataclassInstanceState(DCAnother),
})
assert s._selected == 'option 1'
with pytest.raises(ValueError):
s.dcs_select('option 3')
with pytest.raises(TypeError):
# Not initialised all the values yet...
s.dcs_instance()
opt1_observer = Mock()
s.dcs_observe(opt1_observer)
s.dcs_update_from(opt1)
assert s.dcs_instance() == opt1
opt1_observer.assert_called_once_with()
opt1_observer.reset_mock()
with pytest.raises(TypeError):
s.dcs_update_from(opt2)
s.dcs_select('option 2')
opt1_observer.assert_called_once_with()
opt1_observer.reset_mock()
s.dcs_update_from(opt2)
assert s.dcs_instance() == opt2
# We can't observe individual states directly.
opt1_observer.assert_called_once_with()
# Roll back to option 1.
s.dcs_select('option 1')
opt1_observer.reset_mock()
with s.dcs_state_transaction():
s.dcs_select('option 2')
s.dcs_update_from(opt2)
# TODO: Currently calls twice.
# opt1_observer.assert_called_once_with()
opt1_observer.reset_mock()
assert s.dcs_instance() == opt2
s.dcs_select('option 1')
assert s.dcs_instance() == opt1
# TODO: This should fail.
s.foo = 10
def test_DCS_non_dataclass_attrs():
val = DCClassVar('a', 1)
s = state.DataclassInstanceState(DCSimple)
s.dcs_update_from(val)
s.dcs_instance() == val