123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639 |
- from typing import Dict, List
- import pytest
- from pynecone.base import Base
- from pynecone.event import Event
- from pynecone.state import State
- from pynecone.var import BaseVar, ComputedVar
- @pytest.fixture
- def TestObject():
- class TestObject(Base):
- """A test object fixture."""
- prop1: int = 42
- prop2: str = "hello"
- return TestObject
- @pytest.fixture
- def TestState(TestObject):
- class TestState(State):
- """A test state."""
- num1: int
- num2: float = 3.14
- key: str
- array: List[float] = [1, 2, 3.14]
- mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
- obj: TestObject = TestObject()
- complex: Dict[int, TestObject] = {1: TestObject(), 2: TestObject()}
- @ComputedVar
- def sum(self) -> float:
- """Dynamically sum the numbers.
- Returns:
- The sum of the numbers.
- """
- return self.num1 + self.num2
- @ComputedVar
- def upper(self) -> str:
- """Uppercase the key.
- Returns:
- The uppercased key.
- """
- return self.key.upper()
- def do_something(self):
- """Do something."""
- pass
- return TestState
- @pytest.fixture
- def ChildState(TestState):
- class ChildState(TestState):
- """A child state fixture."""
- value: str
- count: int = 23
- def change_both(self, value: str, count: int):
- """Change both the value and count.
- Args:
- value: The new value.
- count: The new count.
- """
- self.value = value.upper()
- self.count = count * 2
- return ChildState
- @pytest.fixture
- def ChildState2(TestState):
- class ChildState2(TestState):
- """A child state fixture."""
- value: str
- return ChildState2
- @pytest.fixture
- def GrandchildState(ChildState):
- class GrandchildState(ChildState):
- """A grandchild state fixture."""
- value2: str
- return GrandchildState
- @pytest.fixture
- def state(TestState) -> State:
- """A state.
- Args:
- TestState: The state class.
- Returns:
- A test state.
- """
- return TestState() # type: ignore
- def test_base_class_vars(state):
- """Test that the class vars are set correctly.
- Args:
- state: A state.
- """
- fields = state.get_fields()
- cls = type(state)
- for field in fields:
- if field in (
- "parent_state",
- "substates",
- "dirty_vars",
- "dirty_substates",
- ):
- continue
- prop = getattr(cls, field)
- assert isinstance(prop, BaseVar)
- assert prop.name == field
- assert cls.num1.type_ == int
- assert cls.num2.type_ == float
- assert cls.key.type_ == str
- def test_computed_class_var(state):
- """Test that the class computed vars are set correctly.
- Args:
- state: A state.
- """
- cls = type(state)
- vars = [(prop.name, prop.type_) for prop in cls.computed_vars.values()]
- assert ("sum", float) in vars
- assert ("upper", str) in vars
- def test_class_vars(state):
- """Test that the class vars are set correctly.
- Args:
- state: A state.
- """
- cls = type(state)
- assert set(cls.vars.keys()) == {
- "num1",
- "num2",
- "key",
- "array",
- "mapping",
- "obj",
- "complex",
- "sum",
- "upper",
- }
- def test_default_value(state):
- """Test that the default value of a var is correct.
- Args:
- state: A state.
- """
- assert state.num1 == 0
- assert state.num2 == 3.14
- assert state.key == ""
- assert state.sum == 3.14
- assert state.upper == ""
- def test_computed_vars(state):
- """Test that the computed var is computed correctly.
- Args:
- state: A state.
- """
- state.num1 = 1
- state.num2 = 4
- assert state.sum == 5
- state.key = "hello world"
- assert state.upper == "HELLO WORLD"
- def test_dict(state):
- """Test that the dict representation of a state is correct.
- Args:
- state: A state.
- """
- assert set(state.dict().keys()) == set(state.vars.keys())
- assert set(state.dict(include_computed=False).keys()) == set(state.base_vars)
- def test_default_setters(TestState):
- """Test that we can set default values.
- Args:
- TestState: The state class.
- """
- state = TestState()
- for prop_name in state.base_vars:
- # Each base var should have a default setter.
- assert hasattr(state, f"set_{prop_name}")
- def test_class_indexing_with_vars(TestState):
- """Test that we can index into a state var with another var.
- Args:
- TestState: The state class.
- """
- prop = TestState.array[TestState.num1]
- assert str(prop) == "{test_state.array[test_state.num1]}"
- prop = TestState.mapping["a"][TestState.num1]
- assert str(prop) == '{test_state.mapping["a"][test_state.num1]}'
- def test_class_attributes(TestState):
- """Test that we can get class attributes.
- Args:
- TestState: The state class.
- """
- prop = TestState.obj.prop1
- assert str(prop) == "{test_state.obj.prop1}"
- prop = TestState.complex[1].prop1
- assert str(prop) == "{test_state.complex[1].prop1}"
- def test_get_parent_state(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the parent state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_parent_state() is None
- assert ChildState.get_parent_state() == TestState
- assert ChildState2.get_parent_state() == TestState
- assert GrandchildState.get_parent_state() == ChildState
- def test_get_substates(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the substates.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_substates() == {ChildState, ChildState2}
- assert ChildState.get_substates() == {GrandchildState}
- assert ChildState2.get_substates() == set()
- assert GrandchildState.get_substates() == set()
- def test_get_name(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the name of a state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_name() == "test_state"
- assert ChildState.get_name() == "child_state"
- assert ChildState2.get_name() == "child_state2"
- assert GrandchildState.get_name() == "grandchild_state"
- def test_get_full_name(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the full name.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_full_name() == "test_state"
- assert ChildState.get_full_name() == "test_state.child_state"
- assert ChildState2.get_full_name() == "test_state.child_state2"
- assert GrandchildState.get_full_name() == "test_state.child_state.grandchild_state"
- def test_get_class_substate(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the substate of a class.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_class_substate(("child_state",)) == ChildState
- assert TestState.get_class_substate(("child_state2",)) == ChildState2
- assert ChildState.get_class_substate(("grandchild_state",)) == GrandchildState
- assert (
- TestState.get_class_substate(("child_state", "grandchild_state"))
- == GrandchildState
- )
- with pytest.raises(ValueError):
- TestState.get_class_substate(("invalid_child",))
- with pytest.raises(ValueError):
- TestState.get_class_substate(
- (
- "child_state",
- "invalid_child",
- )
- )
- def test_get_class_var(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the var of a class.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- assert TestState.get_class_var(("num1",)) == TestState.num1
- assert TestState.get_class_var(("num2",)) == TestState.num2
- assert ChildState.get_class_var(("value",)) == ChildState.value
- assert GrandchildState.get_class_var(("value2",)) == GrandchildState.value2
- assert TestState.get_class_var(("child_state", "value")) == ChildState.value
- assert (
- TestState.get_class_var(("child_state", "grandchild_state", "value2"))
- == GrandchildState.value2
- )
- assert (
- ChildState.get_class_var(("grandchild_state", "value2"))
- == GrandchildState.value2
- )
- with pytest.raises(ValueError):
- TestState.get_class_var(("invalid_var",))
- with pytest.raises(ValueError):
- TestState.get_class_var(
- (
- "child_state",
- "invalid_var",
- )
- )
- def test_set_class_var(TestState):
- """Test setting the var of a class.
- Args:
- TestState: The state class.
- """
- with pytest.raises(AttributeError):
- TestState.num3
- TestState._set_var(BaseVar(name="num3", type_=int).set_state(TestState))
- var = TestState.num3
- assert var.name == "num3"
- assert var.type_ == int
- assert var.state == TestState.get_full_name()
- def test_set_parent_and_substates(TestState, ChildState, ChildState2, GrandchildState):
- """Test setting the parent and substates.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- assert len(test_state.substates) == 2
- assert set(test_state.substates) == {"child_state", "child_state2"}
- child_state = test_state.substates["child_state"]
- assert child_state.parent_state == test_state
- assert len(child_state.substates) == 1
- assert set(child_state.substates) == {"grandchild_state"}
- grandchild_state = child_state.substates["grandchild_state"]
- assert grandchild_state.parent_state == child_state
- assert len(grandchild_state.substates) == 0
- def test_get_child_attribute(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the attribute of a state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- child_state = test_state.get_substate(["child_state"])
- child_state2 = test_state.get_substate(["child_state2"])
- grandchild_state = child_state.get_substate(["grandchild_state"])
- assert test_state.num1 == 0
- assert child_state.value == ""
- assert child_state2.value == ""
- assert child_state.count == 23
- assert grandchild_state.value2 == ""
- with pytest.raises(AttributeError):
- test_state.invalid
- with pytest.raises(AttributeError):
- test_state.child_state.invalid
- with pytest.raises(AttributeError):
- test_state.child_state.grandchild_state.invalid
- def test_set_child_attribute(TestState, ChildState, ChildState2, GrandchildState):
- """Test setting the attribute of a state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- child_state = test_state.get_substate(["child_state"])
- grandchild_state = child_state.get_substate(["grandchild_state"])
- test_state.num1 = 10
- assert test_state.num1 == 10
- child_state.value = "test"
- assert child_state.value == "test"
- grandchild_state.value2 = "test2"
- assert grandchild_state.value2 == "test2"
- def test_get_substate(TestState, ChildState, ChildState2, GrandchildState):
- """Test getting the substate of a state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- child_state = test_state.substates["child_state"]
- child_state2 = test_state.substates["child_state2"]
- grandchild_state = child_state.substates["grandchild_state"]
- assert test_state.get_substate(("child_state",)) == child_state
- assert test_state.get_substate(("child_state2",)) == child_state2
- assert (
- test_state.get_substate(("child_state", "grandchild_state")) == grandchild_state
- )
- assert child_state.get_substate(("grandchild_state",)) == grandchild_state
- with pytest.raises(ValueError):
- test_state.get_substate(("invalid",))
- with pytest.raises(ValueError):
- test_state.get_substate(("child_state", "invalid"))
- with pytest.raises(ValueError):
- test_state.get_substate(("child_state", "grandchild_state", "invalid"))
- def test_set_dirty_var(TestState):
- """Test changing state vars marks the value as dirty.
- Args:
- TestState: The state class.
- """
- test_state = TestState()
- # Initially there should be no dirty vars.
- assert test_state.dirty_vars == set()
- # Setting a var should mark it as dirty.
- test_state.num1 = 1
- assert test_state.dirty_vars == {"num1"}
- # Setting another var should mark it as dirty.
- test_state.num2 = 2
- assert test_state.dirty_vars == {"num1", "num2"}
- # Cleaning the state should remove all dirty vars.
- test_state.clean()
- assert test_state.dirty_vars == set()
- def test_set_dirty_substate(TestState, ChildState, ChildState2, GrandchildState):
- """Test changing substate vars marks the value as dirty.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- ChildState2: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- child_state = test_state.get_substate(["child_state"])
- child_state2 = test_state.get_substate(["child_state2"])
- grandchild_state = child_state.get_substate(["grandchild_state"])
- # Initially there should be no dirty vars.
- assert test_state.dirty_vars == set()
- assert child_state.dirty_vars == set()
- assert child_state2.dirty_vars == set()
- assert grandchild_state.dirty_vars == set()
- # Setting a var should mark it as dirty.
- child_state.value = "test"
- assert child_state.dirty_vars == {"value"}
- assert test_state.dirty_substates == {"child_state"}
- assert child_state.dirty_substates == set()
- # Cleaning the parent state should remove the dirty substate.
- test_state.clean()
- assert test_state.dirty_substates == set()
- assert child_state.dirty_vars == set()
- # Setting a var on the grandchild should bubble up.
- grandchild_state.value2 = "test2"
- assert child_state.dirty_substates == {"grandchild_state"}
- assert test_state.dirty_substates == {"child_state"}
- # Cleaning the middle state should keep the parent state dirty.
- child_state.clean()
- assert test_state.dirty_substates == {"child_state"}
- assert child_state.dirty_substates == set()
- assert grandchild_state.dirty_vars == set()
- def test_reset(TestState, ChildState):
- """Test resetting the state.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- """
- test_state = TestState()
- child_state = test_state.get_substate(["child_state"])
- # Set some values.
- test_state.num1 = 1
- test_state.num2 = 2
- child_state.value = "test"
- # Reset the state.
- test_state.reset()
- # The values should be reset.
- assert test_state.num1 == 0
- assert test_state.num2 == 3.14
- assert child_state.value == ""
- # The dirty vars should be reset.
- assert test_state.dirty_vars == set()
- assert child_state.dirty_vars == set()
- # The dirty substates should be reset.
- assert test_state.dirty_substates == set()
- @pytest.mark.asyncio
- async def test_process_event_simple(TestState):
- """Test processing an event.
- Args:
- TestState: The state class.
- """
- test_state = TestState()
- assert test_state.num1 == 0
- event = Event(token="t", name="set_num1", payload={"value": 69})
- update = await test_state.process(event)
- # The event should update the value.
- assert test_state.num1 == 69
- # The delta should contain the changes, including computed vars.
- assert update.delta == {"test_state": {"num1": 69, "sum": 72.14, "upper": ""}}
- assert update.events == []
- @pytest.mark.asyncio
- async def test_process_event_substate(TestState, ChildState, GrandchildState):
- """Test processing an event on a substate.
- Args:
- TestState: The state class.
- ChildState: The child state class.
- GrandchildState: The grandchild state class.
- """
- test_state = TestState()
- child_state = test_state.get_substate(["child_state"])
- grandchild_state = child_state.get_substate(["grandchild_state"])
- # Events should bubble down to the substate.
- assert child_state.value == ""
- assert child_state.count == 23
- event = Event(
- token="t", name="child_state.change_both", payload={"value": "hi", "count": 12}
- )
- update = await test_state.process(event)
- assert child_state.value == "HI"
- assert child_state.count == 24
- assert update.delta == {
- "test_state.child_state": {"value": "HI", "count": 24},
- "test_state": {"sum": 3.14, "upper": ""},
- }
- test_state.clean()
- # Test with the granchild state.
- assert grandchild_state.value2 == ""
- event = Event(
- token="t",
- name="child_state.grandchild_state.set_value2",
- payload={"value": "new"},
- )
- update = await test_state.process(event)
- assert grandchild_state.value2 == "new"
- assert update.delta == {
- "test_state.child_state.grandchild_state": {"value2": "new"},
- "test_state": {"sum": 3.14, "upper": ""},
- }
|