浏览代码

components as literal vars (#4223)

* component as literal vars

* fix pyi

* use render

* fix pyi

* only render once

* add type ignore

* fix upload default value

* remove testcases if you don't pass them

* improve behavior

* fix render

* that's not how icon buttons work

* upgrade to next js 15 and remove babel and enable turbo

* upload is a silly guy

* woops

* how did this work before

* set env variable

* lower it even more

* lower it even more

* lower it even more

* only do literals as component vars
Khaleel Al-Adhami 7 月之前
父节点
当前提交
24363170d3

+ 4 - 8
reflex/.templates/jinja/web/pages/utils.js.jinja2

@@ -36,14 +36,10 @@
 {#     component: component dictionary #}
 {#     component: component dictionary #}
 {% macro render_tag(component) %}
 {% macro render_tag(component) %}
 <{{component.name}} {{- render_props(component.props) }}>
 <{{component.name}} {{- render_props(component.props) }}>
-{%- if component.args is not none -%}
-  {{- render_arg_content(component) }}
-{%- else -%}
-  {{ component.contents }}
-  {% for child in component.children %}
-  {{ render(child) }}
-  {% endfor %}
-{%- endif -%}
+{{ component.contents }}
+{% for child in component.children %}
+{{ render(child) }}
+{% endfor %}
 </{{component.name}}>
 </{{component.name}}>
 {%- endmacro %}
 {%- endmacro %}
 
 

+ 1 - 3
reflex/.templates/web/utils/state.js

@@ -15,7 +15,6 @@ import {
 } from "$/utils/context.js";
 } from "$/utils/context.js";
 import debounce from "$/utils/helpers/debounce";
 import debounce from "$/utils/helpers/debounce";
 import throttle from "$/utils/helpers/throttle";
 import throttle from "$/utils/helpers/throttle";
-import * as Babel from "@babel/standalone";
 
 
 // Endpoint URLs.
 // Endpoint URLs.
 const EVENTURL = env.EVENT;
 const EVENTURL = env.EVENT;
@@ -139,8 +138,7 @@ export const evalReactComponent = async (component) => {
   if (!window.React && window.__reflex) {
   if (!window.React && window.__reflex) {
     window.React = window.__reflex.react;
     window.React = window.__reflex.react;
   }
   }
-  const output = Babel.transform(component, { presets: ["react"] }).code;
-  const encodedJs = encodeURIComponent(output);
+  const encodedJs = encodeURIComponent(component);
   const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
   const dataUri = "data:text/javascript;charset=utf-8," + encodedJs;
   const module = await eval(`import(dataUri)`);
   const module = await eval(`import(dataUri)`);
   return module.default;
   return module.default;

+ 72 - 3
reflex/components/base/bare.py

@@ -4,10 +4,11 @@ from __future__ import annotations
 
 
 from typing import Any, Iterator
 from typing import Any, Iterator
 
 
-from reflex.components.component import Component
+from reflex.components.component import Component, LiteralComponentVar
 from reflex.components.tags import Tag
 from reflex.components.tags import Tag
 from reflex.components.tags.tagless import Tagless
 from reflex.components.tags.tagless import Tagless
-from reflex.vars import ArrayVar, BooleanVar, ObjectVar, Var
+from reflex.utils.imports import ParsedImportDict
+from reflex.vars import BooleanVar, ObjectVar, Var
 
 
 
 
 class Bare(Component):
 class Bare(Component):
@@ -31,9 +32,77 @@ class Bare(Component):
             contents = str(contents) if contents is not None else ""
             contents = str(contents) if contents is not None else ""
         return cls(contents=contents)  # type: ignore
         return cls(contents=contents)  # type: ignore
 
 
+    def _get_all_hooks_internal(self) -> dict[str, None]:
+        """Include the hooks for the component.
+
+        Returns:
+            The hooks for the component.
+        """
+        hooks = super()._get_all_hooks_internal()
+        if isinstance(self.contents, LiteralComponentVar):
+            hooks |= self.contents._var_value._get_all_hooks_internal()
+        return hooks
+
+    def _get_all_hooks(self) -> dict[str, None]:
+        """Include the hooks for the component.
+
+        Returns:
+            The hooks for the component.
+        """
+        hooks = super()._get_all_hooks()
+        if isinstance(self.contents, LiteralComponentVar):
+            hooks |= self.contents._var_value._get_all_hooks()
+        return hooks
+
+    def _get_all_imports(self) -> ParsedImportDict:
+        """Include the imports for the component.
+
+        Returns:
+            The imports for the component.
+        """
+        imports = super()._get_all_imports()
+        if isinstance(self.contents, LiteralComponentVar):
+            var_data = self.contents._get_all_var_data()
+            if var_data:
+                imports |= {k: list(v) for k, v in var_data.imports}
+        return imports
+
+    def _get_all_dynamic_imports(self) -> set[str]:
+        """Get dynamic imports for the component.
+
+        Returns:
+            The dynamic imports.
+        """
+        dynamic_imports = super()._get_all_dynamic_imports()
+        if isinstance(self.contents, LiteralComponentVar):
+            dynamic_imports |= self.contents._var_value._get_all_dynamic_imports()
+        return dynamic_imports
+
+    def _get_all_custom_code(self) -> set[str]:
+        """Get custom code for the component.
+
+        Returns:
+            The custom code.
+        """
+        custom_code = super()._get_all_custom_code()
+        if isinstance(self.contents, LiteralComponentVar):
+            custom_code |= self.contents._var_value._get_all_custom_code()
+        return custom_code
+
+    def _get_all_refs(self) -> set[str]:
+        """Get the refs for the children of the component.
+
+        Returns:
+            The refs for the children.
+        """
+        refs = super()._get_all_refs()
+        if isinstance(self.contents, LiteralComponentVar):
+            refs |= self.contents._var_value._get_all_refs()
+        return refs
+
     def _render(self) -> Tag:
     def _render(self) -> Tag:
         if isinstance(self.contents, Var):
         if isinstance(self.contents, Var):
-            if isinstance(self.contents, (BooleanVar, ObjectVar, ArrayVar)):
+            if isinstance(self.contents, (BooleanVar, ObjectVar)):
                 return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
                 return Tagless(contents=f"{{{str(self.contents.to_string())}}}")
             return Tagless(contents=f"{{{str(self.contents)}}}")
             return Tagless(contents=f"{{{str(self.contents)}}}")
         return Tagless(contents=str(self.contents))
         return Tagless(contents=str(self.contents))

+ 210 - 1
reflex/components/component.py

@@ -3,6 +3,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import copy
 import copy
+import dataclasses
 import typing
 import typing
 from abc import ABC, abstractmethod
 from abc import ABC, abstractmethod
 from functools import lru_cache, wraps
 from functools import lru_cache, wraps
@@ -59,7 +60,15 @@ from reflex.utils.imports import (
     parse_imports,
     parse_imports,
 )
 )
 from reflex.vars import VarData
 from reflex.vars import VarData
-from reflex.vars.base import LiteralVar, Var
+from reflex.vars.base import (
+    CachedVarOperation,
+    LiteralVar,
+    Var,
+    cached_property_no_lock,
+)
+from reflex.vars.function import ArgsFunctionOperation, FunctionStringVar
+from reflex.vars.number import ternary_operation
+from reflex.vars.object import ObjectVar
 from reflex.vars.sequence import LiteralArrayVar
 from reflex.vars.sequence import LiteralArrayVar
 
 
 
 
@@ -2345,3 +2354,203 @@ class MemoizationLeaf(Component):
 
 
 
 
 load_dynamic_serializer()
 load_dynamic_serializer()
+
+
+class ComponentVar(Var[Component], python_types=BaseComponent):
+    """A Var that represents a Component."""
+
+
+def empty_component() -> Component:
+    """Create an empty component.
+
+    Returns:
+        An empty component.
+    """
+    from reflex.components.base.bare import Bare
+
+    return Bare.create("")
+
+
+def render_dict_to_var(tag: dict | Component | str, imported_names: set[str]) -> Var:
+    """Convert a render dict to a Var.
+
+    Args:
+        tag: The render dict.
+        imported_names: The names of the imported components.
+
+    Returns:
+        The Var.
+    """
+    if not isinstance(tag, dict):
+        if isinstance(tag, Component):
+            return render_dict_to_var(tag.render(), imported_names)
+        return Var.create(tag)
+
+    if "iterable" in tag:
+        function_return = Var.create(
+            [
+                render_dict_to_var(child.render(), imported_names)
+                for child in tag["children"]
+            ]
+        )
+
+        func = ArgsFunctionOperation.create(
+            (tag["arg_var_name"], tag["index_var_name"]),
+            function_return,
+        )
+
+        return FunctionStringVar.create("Array.prototype.map.call").call(
+            tag["iterable"]
+            if not isinstance(tag["iterable"], ObjectVar)
+            else tag["iterable"].items(),
+            func,
+        )
+
+    if tag["name"] == "match":
+        element = tag["cond"]
+
+        conditionals = tag["default"]
+
+        for case in tag["match_cases"][::-1]:
+            condition = case[0].to_string() == element.to_string()
+            for pattern in case[1:-1]:
+                condition = condition | (pattern.to_string() == element.to_string())
+
+            conditionals = ternary_operation(
+                condition,
+                case[-1],
+                conditionals,
+            )
+
+        return conditionals
+
+    if "cond" in tag:
+        return ternary_operation(
+            tag["cond"],
+            render_dict_to_var(tag["true_value"], imported_names),
+            render_dict_to_var(tag["false_value"], imported_names)
+            if tag["false_value"] is not None
+            else Var.create(None),
+        )
+
+    props = {}
+
+    special_props = []
+
+    for prop_str in tag["props"]:
+        if "=" not in prop_str:
+            special_props.append(Var(prop_str).to(ObjectVar))
+            continue
+        prop = prop_str.index("=")
+        key = prop_str[:prop]
+        value = prop_str[prop + 2 : -1]
+        props[key] = value
+
+    props = Var.create({Var.create(k): Var(v) for k, v in props.items()})
+
+    for prop in special_props:
+        props = props.merge(prop)
+
+    contents = tag["contents"][1:-1] if tag["contents"] else None
+
+    raw_tag_name = tag.get("name")
+    tag_name = Var(raw_tag_name or "Fragment")
+
+    tag_name = (
+        Var.create(raw_tag_name)
+        if raw_tag_name
+        and raw_tag_name.split(".")[0] not in imported_names
+        and raw_tag_name.lower() == raw_tag_name
+        else tag_name
+    )
+
+    return FunctionStringVar.create(
+        "jsx",
+    ).call(
+        tag_name,
+        props,
+        *([Var(contents)] if contents is not None else []),
+        *[render_dict_to_var(child, imported_names) for child in tag["children"]],
+    )
+
+
+@dataclasses.dataclass(
+    eq=False,
+    frozen=True,
+)
+class LiteralComponentVar(CachedVarOperation, LiteralVar, ComponentVar):
+    """A Var that represents a Component."""
+
+    _var_value: BaseComponent = dataclasses.field(default_factory=empty_component)
+
+    @cached_property_no_lock
+    def _cached_var_name(self) -> str:
+        """Get the name of the var.
+
+        Returns:
+            The name of the var.
+        """
+        var_data = self._get_all_var_data()
+        if var_data is not None:
+            # flatten imports
+            imported_names = {j.alias or j.name for i in var_data.imports for j in i[1]}
+        else:
+            imported_names = set()
+        return str(render_dict_to_var(self._var_value.render(), imported_names))
+
+    @cached_property_no_lock
+    def _cached_get_all_var_data(self) -> VarData | None:
+        """Get the VarData for the var.
+
+        Returns:
+            The VarData for the var.
+        """
+        return VarData.merge(
+            VarData(
+                imports={
+                    "@emotion/react": [
+                        ImportVar(tag="jsx"),
+                    ],
+                }
+            ),
+            VarData(
+                imports=self._var_value._get_all_imports(),
+            ),
+            VarData(
+                imports={
+                    "react": [
+                        ImportVar(tag="Fragment"),
+                    ],
+                }
+            ),
+        )
+
+    def __hash__(self) -> int:
+        """Get the hash of the var.
+
+        Returns:
+            The hash of the var.
+        """
+        return hash((self.__class__.__name__, self._js_expr))
+
+    @classmethod
+    def create(
+        cls,
+        value: Component,
+        _var_data: VarData | None = None,
+    ):
+        """Create a var from a value.
+
+        Args:
+            value: The value of the var.
+            _var_data: Additional hooks and imports associated with the Var.
+
+        Returns:
+            The var.
+        """
+        return LiteralComponentVar(
+            _js_expr="",
+            _var_type=type(value),
+            _var_data=_var_data,
+            _var_value=value,
+        )

+ 79 - 23
reflex/components/core/upload.py

@@ -5,11 +5,17 @@ from __future__ import annotations
 from pathlib import Path
 from pathlib import Path
 from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
 from typing import Any, Callable, ClassVar, Dict, List, Optional, Tuple
 
 
-from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
+from reflex.components.component import (
+    Component,
+    ComponentNamespace,
+    MemoizationLeaf,
+    StatefulComponent,
+)
 from reflex.components.el.elements.forms import Input
 from reflex.components.el.elements.forms import Input
 from reflex.components.radix.themes.layout.box import Box
 from reflex.components.radix.themes.layout.box import Box
 from reflex.config import environment
 from reflex.config import environment
 from reflex.constants import Dirs
 from reflex.constants import Dirs
+from reflex.constants.compiler import Imports
 from reflex.event import (
 from reflex.event import (
     CallableEventSpec,
     CallableEventSpec,
     EventChain,
     EventChain,
@@ -19,9 +25,10 @@ from reflex.event import (
     call_script,
     call_script,
     parse_args_spec,
     parse_args_spec,
 )
 )
+from reflex.utils import format
 from reflex.utils.imports import ImportVar
 from reflex.utils.imports import ImportVar
 from reflex.vars import VarData
 from reflex.vars import VarData
-from reflex.vars.base import CallableVar, LiteralVar, Var
+from reflex.vars.base import CallableVar, LiteralVar, Var, get_unique_variable_name
 from reflex.vars.sequence import LiteralStringVar
 from reflex.vars.sequence import LiteralStringVar
 
 
 DEFAULT_UPLOAD_ID: str = "default"
 DEFAULT_UPLOAD_ID: str = "default"
@@ -179,9 +186,7 @@ class Upload(MemoizationLeaf):
 
 
     library = "react-dropzone@14.2.10"
     library = "react-dropzone@14.2.10"
 
 
-    tag = "ReactDropzone"
-
-    is_default = True
+    tag = ""
 
 
     # The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
     # The list of accepted file types. This should be a dictionary of MIME types as keys and array of file formats as
     # values.
     # values.
@@ -201,7 +206,7 @@ class Upload(MemoizationLeaf):
     min_size: Var[int]
     min_size: Var[int]
 
 
     # Whether to allow multiple files to be uploaded.
     # Whether to allow multiple files to be uploaded.
-    multiple: Var[bool] = True  # type: ignore
+    multiple: Var[bool]
 
 
     # Whether to disable click to upload.
     # Whether to disable click to upload.
     no_click: Var[bool]
     no_click: Var[bool]
@@ -232,6 +237,8 @@ class Upload(MemoizationLeaf):
         # Mark the Upload component as used in the app.
         # Mark the Upload component as used in the app.
         cls.is_used = True
         cls.is_used = True
 
 
+        props.setdefault("multiple", True)
+
         # Apply the default classname
         # Apply the default classname
         given_class_name = props.pop("class_name", [])
         given_class_name = props.pop("class_name", [])
         if isinstance(given_class_name, str):
         if isinstance(given_class_name, str):
@@ -243,17 +250,6 @@ class Upload(MemoizationLeaf):
         upload_props = {
         upload_props = {
             key: value for key, value in props.items() if key in supported_props
             key: value for key, value in props.items() if key in supported_props
         }
         }
-        # The file input to use.
-        upload = Input.create(type="file")
-        upload.special_props = [Var(_js_expr="{...getInputProps()}", _var_type=None)]
-
-        # The dropzone to use.
-        zone = Box.create(
-            upload,
-            *children,
-            **{k: v for k, v in props.items() if k not in supported_props},
-        )
-        zone.special_props = [Var(_js_expr="{...getRootProps()}", _var_type=None)]
 
 
         # Create the component.
         # Create the component.
         upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
         upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
@@ -275,9 +271,74 @@ class Upload(MemoizationLeaf):
                     ),
                     ),
                 )
                 )
             upload_props["on_drop"] = on_drop
             upload_props["on_drop"] = on_drop
+
+        input_props_unique_name = get_unique_variable_name()
+        root_props_unique_name = get_unique_variable_name()
+
+        event_var, callback_str = StatefulComponent._get_memoized_event_triggers(
+            Box.create(on_click=upload_props["on_drop"])  # type: ignore
+        )["on_click"]
+
+        upload_props["on_drop"] = event_var
+
+        upload_props = {
+            format.to_camel_case(key): value for key, value in upload_props.items()
+        }
+
+        use_dropzone_arguements = {
+            "onDrop": event_var,
+            **upload_props,
+        }
+
+        left_side = f"const {{getRootProps: {root_props_unique_name}, getInputProps: {input_props_unique_name}}} "
+        right_side = f"useDropzone({str(Var.create(use_dropzone_arguements))})"
+
+        var_data = VarData.merge(
+            VarData(
+                imports=Imports.EVENTS,
+                hooks={
+                    "const [addEvents, connectError] = useContext(EventLoopContext);": None
+                },
+            ),
+            event_var._get_all_var_data(),
+            VarData(
+                hooks={
+                    callback_str: None,
+                    f"{left_side} = {right_side};": None,
+                },
+                imports={
+                    "react-dropzone": "useDropzone",
+                    **Imports.EVENTS,
+                },
+            ),
+        )
+
+        # The file input to use.
+        upload = Input.create(type="file")
+        upload.special_props = [
+            Var(
+                _js_expr=f"{{...{input_props_unique_name}()}}",
+                _var_type=None,
+                _var_data=var_data,
+            )
+        ]
+
+        # The dropzone to use.
+        zone = Box.create(
+            upload,
+            *children,
+            **{k: v for k, v in props.items() if k not in supported_props},
+        )
+        zone.special_props = [
+            Var(
+                _js_expr=f"{{...{root_props_unique_name}()}}",
+                _var_type=None,
+                _var_data=var_data,
+            )
+        ]
+
         return super().create(
         return super().create(
             zone,
             zone,
-            **upload_props,
         )
         )
 
 
     @classmethod
     @classmethod
@@ -295,11 +356,6 @@ class Upload(MemoizationLeaf):
             return (arg_value[0], placeholder)
             return (arg_value[0], placeholder)
         return arg_value
         return arg_value
 
 
-    def _render(self):
-        out = super()._render()
-        out.args = ("getRootProps", "getInputProps")
-        return out
-
     @staticmethod
     @staticmethod
     def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
     def _get_app_wrap_components() -> dict[tuple[int, str], Component]:
         return {
         return {

+ 5 - 1
reflex/components/core/upload.pyi

@@ -6,7 +6,11 @@
 from pathlib import Path
 from pathlib import Path
 from typing import Any, ClassVar, Dict, List, Optional, Union, overload
 from typing import Any, ClassVar, Dict, List, Optional, Union, overload
 
 
-from reflex.components.component import Component, ComponentNamespace, MemoizationLeaf
+from reflex.components.component import (
+    Component,
+    ComponentNamespace,
+    MemoizationLeaf,
+)
 from reflex.constants import Dirs
 from reflex.constants import Dirs
 from reflex.event import (
 from reflex.event import (
     CallableEventSpec,
     CallableEventSpec,

+ 6 - 2
reflex/components/dynamic.py

@@ -63,6 +63,9 @@ def load_dynamic_serializer():
         """
         """
         # Causes a circular import, so we import here.
         # Causes a circular import, so we import here.
         from reflex.compiler import templates, utils
         from reflex.compiler import templates, utils
+        from reflex.components.base.bare import Bare
+
+        component = Bare.create(Var.create(component))
 
 
         rendered_components = {}
         rendered_components = {}
         # Include dynamic imports in the shared component.
         # Include dynamic imports in the shared component.
@@ -127,14 +130,15 @@ def load_dynamic_serializer():
                 module_code_lines[ix] = line.replace(
                 module_code_lines[ix] = line.replace(
                     "export function", "export default function", 1
                     "export function", "export default function", 1
                 )
                 )
+            line_stripped = line.strip()
+            if line_stripped.startswith("{") and line_stripped.endswith("}"):
+                module_code_lines[ix] = line_stripped[1:-1]
 
 
         module_code_lines.insert(0, "const React = window.__reflex.react;")
         module_code_lines.insert(0, "const React = window.__reflex.react;")
 
 
         return "\n".join(
         return "\n".join(
             [
             [
                 "//__reflex_evaluate",
                 "//__reflex_evaluate",
-                "/** @jsx jsx */",
-                "const { jsx } = window.__reflex['@emotion/react']",
                 *module_code_lines,
                 *module_code_lines,
             ]
             ]
         )
         )

+ 1 - 4
reflex/components/tags/tag.py

@@ -3,7 +3,7 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
 import dataclasses
 import dataclasses
-from typing import Any, Dict, List, Optional, Tuple, Union
+from typing import Any, Dict, List, Optional, Union
 
 
 from reflex.event import EventChain
 from reflex.event import EventChain
 from reflex.utils import format, types
 from reflex.utils import format, types
@@ -23,9 +23,6 @@ class Tag:
     # The inner contents of the tag.
     # The inner contents of the tag.
     contents: str = ""
     contents: str = ""
 
 
-    # Args to pass to the tag.
-    args: Optional[Tuple[str, ...]] = None
-
     # Special props that aren't key value pairs.
     # Special props that aren't key value pairs.
     special_props: List[Var] = dataclasses.field(default_factory=list)
     special_props: List[Var] = dataclasses.field(default_factory=list)
 
 

+ 1 - 1
tests/integration/test_form_submit.py

@@ -121,7 +121,7 @@ def FormSubmitName(form_component):
                         on_change=rx.console_log,
                         on_change=rx.console_log,
                     ),
                     ),
                     rx.button("Submit", type_="submit"),
                     rx.button("Submit", type_="submit"),
-                    rx.icon_button(FormState.val, icon=rx.icon(tag="plus")),
+                    rx.icon_button(rx.icon(tag="plus")),
                 ),
                 ),
                 on_submit=FormState.form_submit,
                 on_submit=FormState.form_submit,
                 custom_attrs={"action": "/invalid"},
                 custom_attrs={"action": "/invalid"},

+ 2 - 2
tests/integration/test_var_operations.py

@@ -793,8 +793,8 @@ def test_var_operations(driver, var_operations: AppHarness):
         ("foreach_list_ix", "1\n2"),
         ("foreach_list_ix", "1\n2"),
         ("foreach_list_nested", "1\n1\n2"),
         ("foreach_list_nested", "1\n1\n2"),
         # rx.memo component with state
         # rx.memo component with state
-        ("memo_comp", "[1,2]10"),
-        ("memo_comp_nested", "[3,4]5"),
+        ("memo_comp", "1210"),
+        ("memo_comp_nested", "345"),
         # foreach in a match
         # foreach in a match
         ("foreach_in_match", "first\nsecond\nthird"),
         ("foreach_in_match", "first\nsecond\nthird"),
     ]
     ]

+ 0 - 205
tests/units/components/forms/test_uploads.py

@@ -1,205 +0,0 @@
-import pytest
-
-import reflex as rx
-
-
-@pytest.fixture
-def upload_root_component():
-    """A test upload component function.
-
-    Returns:
-        A test upload component function.
-    """
-
-    def upload_root_component():
-        return rx.upload.root(
-            rx.button("select file"),
-            rx.text("Drag and drop files here or click to select files"),
-            border="1px dotted black",
-        )
-
-    return upload_root_component()
-
-
-@pytest.fixture
-def upload_component():
-    """A test upload component function.
-
-    Returns:
-        A test upload component function.
-    """
-
-    def upload_component():
-        return rx.upload(
-            rx.button("select file"),
-            rx.text("Drag and drop files here or click to select files"),
-            border="1px dotted black",
-        )
-
-    return upload_component()
-
-
-@pytest.fixture
-def upload_component_id_special():
-    def upload_component():
-        return rx.upload(
-            rx.button("select file"),
-            rx.text("Drag and drop files here or click to select files"),
-            border="1px dotted black",
-            id="#spec!`al-_98ID",
-        )
-
-    return upload_component()
-
-
-@pytest.fixture
-def upload_component_with_props():
-    """A test upload component with props function.
-
-    Returns:
-        A test upload component with props function.
-    """
-
-    def upload_component_with_props():
-        return rx.upload(
-            rx.button("select file"),
-            rx.text("Drag and drop files here or click to select files"),
-            border="1px dotted black",
-            no_drag=True,
-            max_files=2,
-        )
-
-    return upload_component_with_props()
-
-
-def test_upload_root_component_render(upload_root_component):
-    """Test that the render function is set correctly.
-
-    Args:
-        upload_root_component: component fixture
-    """
-    upload = upload_root_component.render()
-
-    # upload
-    assert upload["name"] == "ReactDropzone"
-    assert upload["props"] == [
-        'id={"default"}',
-        "multiple={true}",
-        "onDrop={e => setFilesById(filesById => {\n"
-        "    const updatedFilesById = Object.assign({}, filesById);\n"
-        '    updatedFilesById["default"] = e;\n'
-        "    return updatedFilesById;\n"
-        "  })\n"
-        "    }",
-        "ref={ref_default}",
-    ]
-    assert upload["args"] == ("getRootProps", "getInputProps")
-
-    # box inside of upload
-    [box] = upload["children"]
-    assert box["name"] == "RadixThemesBox"
-    assert box["props"] == [
-        'className={"rx-Upload"}',
-        'css={({ ["border"] : "1px dotted black" })}',
-        "{...getRootProps()}",
-    ]
-
-    # input, button and text inside of box
-    [input, button, text] = box["children"]
-    assert input["name"] == "input"
-    assert input["props"] == ['type={"file"}', "{...getInputProps()}"]
-
-    assert button["name"] == "RadixThemesButton"
-    assert button["children"][0]["contents"] == '{"select file"}'
-
-    assert text["name"] == "RadixThemesText"
-    assert (
-        text["children"][0]["contents"]
-        == '{"Drag and drop files here or click to select files"}'
-    )
-
-
-def test_upload_component_render(upload_component):
-    """Test that the render function is set correctly.
-
-    Args:
-        upload_component: component fixture
-    """
-    upload = upload_component.render()
-
-    # upload
-    assert upload["name"] == "ReactDropzone"
-    assert upload["props"] == [
-        'id={"default"}',
-        "multiple={true}",
-        "onDrop={e => setFilesById(filesById => {\n"
-        "    const updatedFilesById = Object.assign({}, filesById);\n"
-        '    updatedFilesById["default"] = e;\n'
-        "    return updatedFilesById;\n"
-        "  })\n"
-        "    }",
-        "ref={ref_default}",
-    ]
-    assert upload["args"] == ("getRootProps", "getInputProps")
-
-    # box inside of upload
-    [box] = upload["children"]
-    assert box["name"] == "RadixThemesBox"
-    assert box["props"] == [
-        'className={"rx-Upload"}',
-        'css={({ ["border"] : "1px dotted black", ["padding"] : "5em", ["textAlign"] : "center" })}',
-        "{...getRootProps()}",
-    ]
-
-    # input, button and text inside of box
-    [input, button, text] = box["children"]
-    assert input["name"] == "input"
-    assert input["props"] == ['type={"file"}', "{...getInputProps()}"]
-
-    assert button["name"] == "RadixThemesButton"
-    assert button["children"][0]["contents"] == '{"select file"}'
-
-    assert text["name"] == "RadixThemesText"
-    assert (
-        text["children"][0]["contents"]
-        == '{"Drag and drop files here or click to select files"}'
-    )
-
-
-def test_upload_component_with_props_render(upload_component_with_props):
-    """Test that the render function is set correctly.
-
-    Args:
-        upload_component_with_props: component fixture
-    """
-    upload = upload_component_with_props.render()
-
-    assert upload["props"] == [
-        'id={"default"}',
-        "maxFiles={2}",
-        "multiple={true}",
-        "noDrag={true}",
-        "onDrop={e => setFilesById(filesById => {\n"
-        "    const updatedFilesById = Object.assign({}, filesById);\n"
-        '    updatedFilesById["default"] = e;\n'
-        "    return updatedFilesById;\n"
-        "  })\n"
-        "    }",
-        "ref={ref_default}",
-    ]
-
-
-def test_upload_component_id_with_special_chars(upload_component_id_special):
-    upload = upload_component_id_special.render()
-
-    assert upload["props"] == [
-        r'id={"#spec!`al-_98ID"}',
-        "multiple={true}",
-        "onDrop={e => setFilesById(filesById => {\n"
-        "    const updatedFilesById = Object.assign({}, filesById);\n"
-        '    updatedFilesById["#spec!`al-_98ID"] = e;\n'
-        "    return updatedFilesById;\n"
-        "  })\n"
-        "    }",
-        "ref={ref__spec_al__98ID}",
-    ]

+ 2 - 17
tests/units/components/test_component.py

@@ -642,21 +642,18 @@ def test_component_create_unallowed_types(children, test_component):
                 "name": "Fragment",
                 "name": "Fragment",
                 "props": [],
                 "props": [],
                 "contents": "",
                 "contents": "",
-                "args": None,
                 "special_props": [],
                 "special_props": [],
                 "children": [
                 "children": [
                     {
                     {
                         "name": "RadixThemesText",
                         "name": "RadixThemesText",
                         "props": ['as={"p"}'],
                         "props": ['as={"p"}'],
                         "contents": "",
                         "contents": "",
-                        "args": None,
                         "special_props": [],
                         "special_props": [],
                         "children": [
                         "children": [
                             {
                             {
                                 "name": "",
                                 "name": "",
                                 "props": [],
                                 "props": [],
                                 "contents": '{"first_text"}',
                                 "contents": '{"first_text"}',
-                                "args": None,
                                 "special_props": [],
                                 "special_props": [],
                                 "children": [],
                                 "children": [],
                                 "autofocus": False,
                                 "autofocus": False,
@@ -671,15 +668,12 @@ def test_component_create_unallowed_types(children, test_component):
         (
         (
             (rx.text("first_text"), rx.text("second_text")),
             (rx.text("first_text"), rx.text("second_text")),
             {
             {
-                "args": None,
                 "autofocus": False,
                 "autofocus": False,
                 "children": [
                 "children": [
                     {
                     {
-                        "args": None,
                         "autofocus": False,
                         "autofocus": False,
                         "children": [
                         "children": [
                             {
                             {
-                                "args": None,
                                 "autofocus": False,
                                 "autofocus": False,
                                 "children": [],
                                 "children": [],
                                 "contents": '{"first_text"}',
                                 "contents": '{"first_text"}',
@@ -694,11 +688,9 @@ def test_component_create_unallowed_types(children, test_component):
                         "special_props": [],
                         "special_props": [],
                     },
                     },
                     {
                     {
-                        "args": None,
                         "autofocus": False,
                         "autofocus": False,
                         "children": [
                         "children": [
                             {
                             {
-                                "args": None,
                                 "autofocus": False,
                                 "autofocus": False,
                                 "children": [],
                                 "children": [],
                                 "contents": '{"second_text"}',
                                 "contents": '{"second_text"}',
@@ -722,15 +714,12 @@ def test_component_create_unallowed_types(children, test_component):
         (
         (
             (rx.text("first_text"), rx.box((rx.text("second_text"),))),
             (rx.text("first_text"), rx.box((rx.text("second_text"),))),
             {
             {
-                "args": None,
                 "autofocus": False,
                 "autofocus": False,
                 "children": [
                 "children": [
                     {
                     {
-                        "args": None,
                         "autofocus": False,
                         "autofocus": False,
                         "children": [
                         "children": [
                             {
                             {
-                                "args": None,
                                 "autofocus": False,
                                 "autofocus": False,
                                 "children": [],
                                 "children": [],
                                 "contents": '{"first_text"}',
                                 "contents": '{"first_text"}',
@@ -745,19 +734,15 @@ def test_component_create_unallowed_types(children, test_component):
                         "special_props": [],
                         "special_props": [],
                     },
                     },
                     {
                     {
-                        "args": None,
                         "autofocus": False,
                         "autofocus": False,
                         "children": [
                         "children": [
                             {
                             {
-                                "args": None,
                                 "autofocus": False,
                                 "autofocus": False,
                                 "children": [
                                 "children": [
                                     {
                                     {
-                                        "args": None,
                                         "autofocus": False,
                                         "autofocus": False,
                                         "children": [
                                         "children": [
                                             {
                                             {
-                                                "args": None,
                                                 "autofocus": False,
                                                 "autofocus": False,
                                                 "children": [],
                                                 "children": [],
                                                 "contents": '{"second_text"}',
                                                 "contents": '{"second_text"}',
@@ -1117,10 +1102,10 @@ def test_component_with_only_valid_children(fixture, request):
 @pytest.mark.parametrize(
 @pytest.mark.parametrize(
     "component,rendered",
     "component,rendered",
     [
     [
-        (rx.text("hi"), '<RadixThemesText as={"p"}>\n  {"hi"}\n</RadixThemesText>'),
+        (rx.text("hi"), '<RadixThemesText as={"p"}>\n\n{"hi"}\n</RadixThemesText>'),
         (
         (
             rx.box(rx.heading("test", size="3")),
             rx.box(rx.heading("test", size="3")),
-            '<RadixThemesBox>\n  <RadixThemesHeading size={"3"}>\n  {"test"}\n</RadixThemesHeading>\n</RadixThemesBox>',
+            '<RadixThemesBox>\n\n<RadixThemesHeading size={"3"}>\n\n{"test"}\n</RadixThemesHeading>\n</RadixThemesBox>',
         ),
         ),
     ],
     ],
 )
 )