test_hydrate_middleware.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. from typing import Any, Dict
  2. import pytest
  3. from reflex.app import App
  4. from reflex.constants import IS_HYDRATED
  5. from reflex.middleware.hydrate_middleware import HydrateMiddleware
  6. from reflex.state import State, StateUpdate
  7. def exp_is_hydrated(state: State) -> Dict[str, Any]:
  8. """Expected IS_HYDRATED delta that would be emitted by HydrateMiddleware.
  9. Args:
  10. state: the State that is hydrated
  11. Returns:
  12. dict similar to that returned by `State.get_delta` with IS_HYDRATED: True
  13. """
  14. return {state.get_name(): {IS_HYDRATED: True}}
  15. class TestState(State):
  16. """A test state with no return in handler."""
  17. __test__ = False
  18. num: int = 0
  19. def test_handler(self):
  20. """Test handler."""
  21. self.num += 1
  22. class TestState2(State):
  23. """A test state with return in handler."""
  24. __test__ = False
  25. num: int = 0
  26. name: str
  27. def test_handler(self):
  28. """Test handler that calls another handler.
  29. Returns:
  30. Chain of EventHandlers
  31. """
  32. self.num += 1
  33. return self.change_name
  34. def change_name(self):
  35. """Test handler to change name."""
  36. self.name = "random"
  37. class TestState3(State):
  38. """A test state with async handler."""
  39. __test__ = False
  40. num: int = 0
  41. async def test_handler(self):
  42. """Test handler."""
  43. self.num += 1
  44. @pytest.fixture
  45. def hydrate_middleware() -> HydrateMiddleware:
  46. """Fixture creates an instance of HydrateMiddleware per test case.
  47. Returns:
  48. instance of HydrateMiddleware
  49. """
  50. return HydrateMiddleware()
  51. @pytest.mark.asyncio
  52. @pytest.mark.parametrize(
  53. "test_state, expected, event_fixture",
  54. [
  55. (TestState, {"test_state": {"num": 1}}, "event1"),
  56. (TestState2, {"test_state2": {"num": 1}}, "event2"),
  57. (TestState3, {"test_state3": {"num": 1}}, "event3"),
  58. ],
  59. )
  60. async def test_preprocess(
  61. test_state, hydrate_middleware, request, event_fixture, expected
  62. ):
  63. """Test that a state hydrate event is processed correctly.
  64. Args:
  65. test_state: State to process event.
  66. hydrate_middleware: Instance of HydrateMiddleware.
  67. request: Pytest fixture request.
  68. event_fixture: The event fixture(an Event).
  69. expected: Expected delta.
  70. """
  71. app = App(state=test_state, load_events={"index": [test_state.test_handler]})
  72. state = test_state()
  73. update = await hydrate_middleware.preprocess(
  74. app=app, event=request.getfixturevalue(event_fixture), state=state
  75. )
  76. assert isinstance(update, StateUpdate)
  77. assert update.delta == {state.get_name(): state.dict()}
  78. events = update.events
  79. assert len(events) == 2
  80. # Apply the on_load event.
  81. update = await state._process(events[0]).__anext__()
  82. assert update.delta == expected
  83. # Apply the hydrate event.
  84. update = await state._process(events[1]).__anext__()
  85. assert update.delta == exp_is_hydrated(state)
  86. @pytest.mark.asyncio
  87. async def test_preprocess_multiple_load_events(hydrate_middleware, event1):
  88. """Test that a state hydrate event for multiple on-load events is processed correctly.
  89. Args:
  90. hydrate_middleware: Instance of HydrateMiddleware
  91. event1: An Event.
  92. """
  93. app = App(
  94. state=TestState,
  95. load_events={"index": [TestState.test_handler, TestState.test_handler]},
  96. )
  97. state = TestState()
  98. update = await hydrate_middleware.preprocess(app=app, event=event1, state=state)
  99. assert isinstance(update, StateUpdate)
  100. assert update.delta == {"test_state": state.dict()}
  101. assert len(update.events) == 3
  102. # Apply the events.
  103. events = update.events
  104. update = await state._process(events[0]).__anext__()
  105. assert update.delta == {"test_state": {"num": 1}}
  106. update = await state._process(events[1]).__anext__()
  107. assert update.delta == {"test_state": {"num": 2}}
  108. update = await state._process(events[2]).__anext__()
  109. assert update.delta == exp_is_hydrated(state)
  110. @pytest.mark.asyncio
  111. async def test_preprocess_no_events(hydrate_middleware, event1):
  112. """Test that app without on_load is processed correctly.
  113. Args:
  114. hydrate_middleware: Instance of HydrateMiddleware
  115. event1: An Event.
  116. """
  117. state = TestState()
  118. update = await hydrate_middleware.preprocess(
  119. app=App(state=TestState),
  120. event=event1,
  121. state=state,
  122. )
  123. assert isinstance(update, StateUpdate)
  124. assert update.delta == {"test_state": state.dict()}
  125. assert len(update.events) == 1
  126. assert isinstance(update, StateUpdate)
  127. update = await state._process(update.events[0]).__anext__()
  128. assert update.delta == exp_is_hydrated(state)