浏览代码

Enable real app AppHarness tests to not specify `state=` (#2358)

Masen Furer 1 年之前
父节点
当前提交
87844c3f7d
共有 5 个文件被更改,包括 43 次插入3 次删除
  1. 6 1
      reflex/.templates/apps/sidebar/code/sidebar.py
  2. 18 2
      reflex/app.py
  3. 12 0
      reflex/state.py
  4. 2 0
      reflex/testing.py
  5. 5 0
      reflex/utils/prerequisites.py

+ 6 - 1
reflex/.templates/apps/sidebar/code/sidebar.py

@@ -7,5 +7,10 @@ from code.pages import *
 
 import reflex as rx
 
-# Create the app and compile it.
+
+class State(rx.State):
+    """Define empty state to allow access to rx.State.router."""
+
+
+# Create the app.
 app = rx.App(style=styles.base_style)

+ 18 - 2
reflex/app.py

@@ -63,6 +63,7 @@ from reflex.state import (
     State,
     StateManager,
     StateUpdate,
+    code_uses_state_contexts,
 )
 from reflex.utils import console, exceptions, format, prerequisites, types
 from reflex.utils.imports import ImportVar
@@ -169,7 +170,8 @@ class App(Base):
                     deprecation_version="0.3.5",
                     removal_version="0.4.0",
                 )
-            self.state = State
+            if len(State.class_subclasses) > 0:
+                self.state = State
         # Get the config
         config = get_config()
 
@@ -636,7 +638,11 @@ class App(Base):
         return
 
     def compile_(self):
-        """Compile the app and output it to the pages folder."""
+        """Compile the app and output it to the pages folder.
+
+        Raises:
+            RuntimeError: When any page uses state, but no rx.State subclass is defined.
+        """
         # add the pages before the compile check so App know onload methods
         for render, kwargs in DECORATED_PAGES:
             self.add_page(render, **kwargs)
@@ -701,6 +707,16 @@ class App(Base):
                 stateful_components_code,
                 page_components,
             ) = compiler.compile_stateful_components(self.pages.values())
+
+            # Catch "static" apps (that do not define a rx.State subclass) which are trying to access rx.State.
+            if (
+                code_uses_state_contexts(stateful_components_code)
+                and self.state is None
+            ):
+                raise RuntimeError(
+                    "To access rx.State in frontend components, at least one "
+                    "subclass of rx.State must be defined in the app."
+                )
             compile_results.append((stateful_components_path, stateful_components_code))
 
             result_futures = []

+ 12 - 0
reflex/state.py

@@ -2181,3 +2181,15 @@ class ImmutableMutableProxy(MutableProxy):
         return super()._mark_dirty(
             wrapped=wrapped, instance=instance, args=args, kwargs=kwargs
         )
+
+
+def code_uses_state_contexts(javascript_code: str) -> bool:
+    """Check if the rendered Javascript uses state contexts.
+
+    Args:
+        javascript_code: The Javascript code to check.
+
+    Returns:
+        True if the code attempts to access a member of StateContexts.
+    """
+    return bool("useContext(StateContexts" in javascript_code)

+ 2 - 0
reflex/testing.py

@@ -209,6 +209,8 @@ class AppHarness:
             reflex.config.get_config(reload=True)
             # reset rx.State subclasses
             State.class_subclasses.clear()
+            # Ensure the AppHarness test does not skip State assignment due to running via pytest
+            os.environ.pop(reflex.constants.PYTEST_CURRENT_TEST, None)
             # self.app_module.app.
             self.app_module = reflex.utils.prerequisites.get_compiled_app(reload=True)
         self.app_instance = self.app_module.app

+ 5 - 0
reflex/utils/prerequisites.py

@@ -159,6 +159,11 @@ def get_app(reload: bool = False) -> ModuleType:
     sys.path.insert(0, os.getcwd())
     app = __import__(module, fromlist=(constants.CompileVars.APP,))
     if reload:
+        from reflex.state import State
+
+        # Reset rx.State subclasses to avoid conflict when reloading.
+        State.class_subclasses.clear()
+        # Reload the app module.
         importlib.reload(app)
 
     return app