test_hydrate_middleware.py 4.7 KB

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