fix some tests

This commit is contained in:
Khaleel Al-Adhami 2025-01-15 17:00:06 -08:00
parent 45dde0072e
commit f0f84d5410
19 changed files with 607 additions and 178 deletions

View File

@ -103,5 +103,5 @@ asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto" asyncio_mode = "auto"
[tool.codespell] [tool.codespell]
skip = "docs/*,*.html,examples/*, *.pyi" skip = "docs/*,*.html,examples/*, *.pyi, *.lock"
ignore-words-list = "te, TreeE" ignore-words-list = "te, TreeE"

View File

@ -300,10 +300,7 @@ export const applyEvent = async (event, socket) => {
// Send the event to the server. // Send the event to the server.
if (socket) { if (socket) {
socket.emit( socket.emit("event", event);
"event",
event,
);
return true; return true;
} }
@ -410,7 +407,7 @@ export const connect = async (
autoUnref: false, autoUnref: false,
}); });
// Ensure undefined fields in events are sent as null instead of removed // Ensure undefined fields in events are sent as null instead of removed
socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v) socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v);
function checkVisibility() { function checkVisibility() {
if (document.visibilityState === "visible") { if (document.visibilityState === "visible") {
@ -488,7 +485,7 @@ export const uploadFiles = async (
return false; return false;
} }
const upload_ref_name = `__upload_controllers_${upload_id}` const upload_ref_name = `__upload_controllers_${upload_id}`;
if (refs[upload_ref_name]) { if (refs[upload_ref_name]) {
console.log("Upload already in progress for ", upload_id); console.log("Upload already in progress for ", upload_id);
@ -924,6 +921,18 @@ export const atSlice = (arrayLike, slice) => {
.filter((_, i) => i % step === 0); .filter((_, i) => i % step === 0);
}; };
/**
* Get the value at a slice or index.
* @param {Array | string} arrayLike The array to get the value from.
* @param {number | [number, number, number]} sliceOrIndex The slice or index to get the value at.
* @returns The value at the slice or index.
*/
export const atSliceOrIndex = (arrayLike, sliceOrIndex) => {
return Array.isArray(sliceOrIndex)
? atSlice(arrayLike, sliceOrIndex)
: arrayLike.at(sliceOrIndex);
};
/** /**
* Get the value from a ref. * Get the value from a ref.
* @param ref The ref to get the value from. * @param ref The ref to get the value from.

View File

@ -61,13 +61,13 @@ class Bare(Component):
hooks |= component._get_all_hooks() hooks |= component._get_all_hooks()
return hooks return hooks
def _get_all_imports(self) -> ParsedImportDict: def _get_all_imports(self, collapse: bool = False) -> ParsedImportDict:
"""Include the imports for the component. """Include the imports for the component.
Returns: Returns:
The imports for the component. The imports for the component.
""" """
imports = super()._get_all_imports() imports = super()._get_all_imports(collapse=collapse)
if isinstance(self.contents, Var): if isinstance(self.contents, Var):
var_data = self.contents._get_all_var_data() var_data = self.contents._get_all_var_data()
if var_data: if var_data:

View File

@ -930,6 +930,7 @@ class Component(BaseComponent, ABC):
children: The children of the component. children: The children of the component.
""" """
from reflex.components.base.bare import Bare
from reflex.components.base.fragment import Fragment from reflex.components.base.fragment import Fragment
from reflex.components.core.cond import Cond from reflex.components.core.cond import Cond
from reflex.components.core.foreach import Foreach from reflex.components.core.foreach import Foreach
@ -960,6 +961,16 @@ class Component(BaseComponent, ABC):
validate_child(child.comp1) validate_child(child.comp1)
validate_child(child.comp2) validate_child(child.comp2)
if (
isinstance(child, Bare)
and child.contents is not None
and isinstance(child.contents, Var)
):
var_data = child.contents._get_all_var_data()
if var_data is not None:
for c in var_data.components:
validate_child(c)
if isinstance(child, Match): if isinstance(child, Match):
for cases in child.match_cases: for cases in child.match_cases:
validate_child(cases[-1]) validate_child(cases[-1])
@ -970,10 +981,23 @@ class Component(BaseComponent, ABC):
f"The component `{comp_name}` cannot have `{child_name}` as a child component" f"The component `{comp_name}` cannot have `{child_name}` as a child component"
) )
if self._valid_children and child_name not in [ valid_children = self._valid_children + allowed_components
*self._valid_children,
*allowed_components, def child_is_in_valid(child):
]: if type(child).__name__ in valid_children:
return True
if (
not isinstance(child, Bare)
or child.contents is None
or not isinstance(child.contents, Var)
or (var_data := child.contents._get_all_var_data()) is None
):
return False
return all(child_is_in_valid(c) for c in var_data.components)
if self._valid_children and not child_is_in_valid(child):
valid_child_list = ", ".join( valid_child_list = ", ".join(
[f"`{v_child}`" for v_child in self._valid_children] [f"`{v_child}`" for v_child in self._valid_children]
) )

View File

@ -175,6 +175,8 @@ class ConnectionBanner(Component):
Returns: Returns:
The connection banner component. The connection banner component.
""" """
from reflex.components.base.bare import Bare
if not comp: if not comp:
comp = Flex.create( comp = Flex.create(
Text.create( Text.create(
@ -189,7 +191,7 @@ class ConnectionBanner(Component):
position="fixed", position="fixed",
) )
return cond(has_connection_errors, comp) return Bare.create(cond(has_connection_errors, comp))
class ConnectionModal(Component): class ConnectionModal(Component):
@ -205,18 +207,22 @@ class ConnectionModal(Component):
Returns: Returns:
The connection banner component. The connection banner component.
""" """
from reflex.components.base.bare import Bare
if not comp: if not comp:
comp = Text.create(*default_connection_error()) comp = Text.create(*default_connection_error())
return cond( return Bare.create(
has_too_many_connection_errors, cond(
DialogRoot.create( has_too_many_connection_errors,
DialogContent.create( DialogRoot.create(
DialogTitle.create("Connection Error"), DialogContent.create(
comp, DialogTitle.create("Connection Error"),
comp,
),
open=has_too_many_connection_errors,
z_index=9999,
), ),
open=has_too_many_connection_errors, )
z_index=9999,
),
) )

View File

@ -12,7 +12,8 @@ from reflex.style import LIGHT_COLOR_MODE, resolved_color_mode
from reflex.utils.imports import ImportDict, ImportVar from reflex.utils.imports import ImportDict, ImportVar
from reflex.utils.types import safe_issubclass from reflex.utils.types import safe_issubclass
from reflex.vars import VarData from reflex.vars import VarData
from reflex.vars.base import LiteralVar, Var from reflex.vars.base import LiteralVar, ReflexCallable, Var
from reflex.vars.function import ArgsFunctionOperation
from reflex.vars.number import ternary_operation from reflex.vars.number import ternary_operation
_IS_TRUE_IMPORT: ImportDict = { _IS_TRUE_IMPORT: ImportDict = {
@ -150,12 +151,23 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
if c2 is None: if c2 is None:
raise ValueError("For conditional vars, the second argument must be set.") raise ValueError("For conditional vars, the second argument must be set.")
c1 = Var.create(c1)
c2 = Var.create(c2)
# Create the conditional var. # Create the conditional var.
return ternary_operation( return ternary_operation(
cond_var.bool(), cond_var.bool(),
c1, ArgsFunctionOperation.create(
c2, (),
) c1,
_var_type=ReflexCallable[[], c1._var_type],
),
ArgsFunctionOperation.create(
(),
c2,
_var_type=ReflexCallable[[], c2._var_type],
),
).call()
@overload @overload

View File

@ -184,11 +184,17 @@ class Match(MemoizationLeaf):
return_type = Var return_type = Var
for index, case in enumerate(match_cases): for index, case in enumerate(match_cases):
if not types._issubclass(type(case[-1]), return_type): if not (
types._issubclass(type(case[-1]), return_type)
or (
isinstance(case[-1], Var)
and types.typehint_issubclass(case[-1]._var_type, return_type)
)
):
raise MatchTypeError( raise MatchTypeError(
f"Match cases should have the same return types. Case {index} with return " f"Match cases should have the same return types. Case {index} with return "
f"value `{case[-1]._js_expr if isinstance(case[-1], Var) else textwrap.shorten(str(case[-1]), width=250)}`" f"value `{case[-1]._js_expr if isinstance(case[-1], Var) else textwrap.shorten(str(case[-1]), width=250)}`"
f" of type {type(case[-1])!r} is not {return_type}" f" of type {(type(case[-1]) if not isinstance(case[-1], Var) else case[-1]._var_type)!r} is not {return_type}"
) )
@classmethod @classmethod

View File

@ -447,7 +447,7 @@ class CodeBlock(Component, MarkdownComponentMap):
# react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark # react-syntax-highlighter doesn't have an explicit "light" or "dark" theme so we use one-light and one-dark
# themes respectively to ensure code compatibility. # themes respectively to ensure code compatibility.
if "theme" in props and not isinstance(props["theme"], Var): if props.get("theme") is not None and not isinstance(props["theme"], Var):
props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore props["theme"] = getattr(Theme, format.to_snake_case(props["theme"])) # type: ignore
console.deprecate( console.deprecate(
feature_name="theme prop as string", feature_name="theme prop as string",

View File

@ -113,8 +113,8 @@ class MarkdownComponentMap:
explicit_return = explicit_return or cls._explicit_return explicit_return = explicit_return or cls._explicit_return
return ArgsFunctionOperation.create( return ArgsFunctionOperation.create(
args_names=(DestructuredArg(fields=tuple(fn_args)),), (DestructuredArg(fields=tuple(fn_args)),),
return_expr=fn_body, fn_body,
explicit_return=explicit_return, explicit_return=explicit_return,
) )

View File

@ -850,6 +850,22 @@ def safe_issubclass(cls: Any, class_or_tuple: Any, /) -> bool:
) from e ) from e
def infallible_issubclass(cls: Any, class_or_tuple: Any, /) -> bool:
"""Check if a class is a subclass of another class or a tuple of classes.
Args:
cls: The class to check.
class_or_tuple: The class or tuple of classes to check against.
Returns:
Whether the class is a subclass of the other class or tuple of classes.
"""
try:
return issubclass(cls, class_or_tuple)
except TypeError:
return False
def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> bool: def typehint_issubclass(possible_subclass: Any, possible_superclass: Any) -> bool:
"""Check if a type hint is a subclass of another type hint. """Check if a type hint is a subclass of another type hint.

View File

@ -51,7 +51,7 @@ from typing_extensions import (
from reflex import constants from reflex import constants
from reflex.base import Base from reflex.base import Base
from reflex.constants.compiler import Hooks from reflex.constants.compiler import Hooks
from reflex.utils import console, exceptions, imports, serializers, types from reflex.utils import console, imports, serializers, types
from reflex.utils.exceptions import ( from reflex.utils.exceptions import (
VarAttributeError, VarAttributeError,
VarDependencyError, VarDependencyError,
@ -72,6 +72,7 @@ from reflex.utils.types import (
_isinstance, _isinstance,
get_origin, get_origin,
has_args, has_args,
infallible_issubclass,
typehint_issubclass, typehint_issubclass,
unionize, unionize,
) )
@ -125,8 +126,25 @@ def unwrap_reflex_callalbe(
""" """
if callable_type is ReflexCallable: if callable_type is ReflexCallable:
return Ellipsis, Any return Ellipsis, Any
if get_origin(callable_type) is not ReflexCallable:
origin = get_origin(callable_type)
if origin is not ReflexCallable:
if origin in types.UnionTypes:
args = get_args(callable_type)
params: List[ReflexCallableParams] = []
return_types: List[GenericType] = []
for arg in args:
param, return_type = unwrap_reflex_callalbe(arg)
if param not in params:
params.append(param)
return_types.append(return_type)
return (
Ellipsis if len(params) > 1 else params[0],
unionize(*return_types),
)
return Ellipsis, Any return Ellipsis, Any
args = get_args(callable_type) args = get_args(callable_type)
if not args or len(args) != 2: if not args or len(args) != 2:
return Ellipsis, Any return Ellipsis, Any
@ -143,6 +161,7 @@ class VarSubclassEntry:
var_subclass: Type[Var] var_subclass: Type[Var]
to_var_subclass: Type[ToOperation] to_var_subclass: Type[ToOperation]
python_types: Tuple[GenericType, ...] python_types: Tuple[GenericType, ...]
is_subclass: Callable[[GenericType], bool] | None
_var_subclasses: List[VarSubclassEntry] = [] _var_subclasses: List[VarSubclassEntry] = []
@ -208,7 +227,7 @@ class VarData:
object.__setattr__(self, "imports", immutable_imports) object.__setattr__(self, "imports", immutable_imports)
object.__setattr__(self, "hooks", tuple(hooks or {})) object.__setattr__(self, "hooks", tuple(hooks or {}))
object.__setattr__( object.__setattr__(
self, "components", tuple(components) if components is not None else tuple() self, "components", tuple(components) if components is not None else ()
) )
object.__setattr__(self, "deps", tuple(deps or [])) object.__setattr__(self, "deps", tuple(deps or []))
object.__setattr__(self, "position", position or None) object.__setattr__(self, "position", position or None)
@ -444,6 +463,7 @@ class Var(Generic[VAR_TYPE]):
cls, cls,
python_types: Tuple[GenericType, ...] | GenericType = types.Unset(), python_types: Tuple[GenericType, ...] | GenericType = types.Unset(),
default_type: GenericType = types.Unset(), default_type: GenericType = types.Unset(),
is_subclass: Callable[[GenericType], bool] | types.Unset = types.Unset(),
**kwargs, **kwargs,
): ):
"""Initialize the subclass. """Initialize the subclass.
@ -451,11 +471,12 @@ class Var(Generic[VAR_TYPE]):
Args: Args:
python_types: The python types that the var represents. python_types: The python types that the var represents.
default_type: The default type of the var. Defaults to the first python type. default_type: The default type of the var. Defaults to the first python type.
is_subclass: A function to check if a type is a subclass of the var.
**kwargs: Additional keyword arguments. **kwargs: Additional keyword arguments.
""" """
super().__init_subclass__(**kwargs) super().__init_subclass__(**kwargs)
if python_types or default_type: if python_types or default_type or is_subclass:
python_types = ( python_types = (
(python_types if isinstance(python_types, tuple) else (python_types,)) (python_types if isinstance(python_types, tuple) else (python_types,))
if python_types if python_types
@ -480,7 +501,14 @@ class Var(Generic[VAR_TYPE]):
ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation' ToVarOperation.__name__ = f'To{cls.__name__.removesuffix("Var")}Operation'
_var_subclasses.append(VarSubclassEntry(cls, ToVarOperation, python_types)) _var_subclasses.append(
VarSubclassEntry(
cls,
ToVarOperation,
python_types,
is_subclass if not isinstance(is_subclass, types.Unset) else None,
)
)
def __post_init__(self): def __post_init__(self):
"""Post-initialize the var.""" """Post-initialize the var."""
@ -726,7 +754,12 @@ class Var(Generic[VAR_TYPE]):
# If the first argument is a python type, we map it to the corresponding Var type. # If the first argument is a python type, we map it to the corresponding Var type.
for var_subclass in _var_subclasses[::-1]: for var_subclass in _var_subclasses[::-1]:
if fixed_output_type in var_subclass.python_types: if (
var_subclass.python_types
and infallible_issubclass(fixed_output_type, var_subclass.python_types)
) or (
var_subclass.is_subclass and var_subclass.is_subclass(fixed_output_type)
):
return self.to(var_subclass.var_subclass, output) return self.to(var_subclass.var_subclass, output)
if fixed_output_type is None: if fixed_output_type is None:
@ -801,12 +834,13 @@ class Var(Generic[VAR_TYPE]):
Raises: Raises:
TypeError: If the type is not supported for guessing. TypeError: If the type is not supported for guessing.
""" """
from .number import NumberVar
from .object import ObjectVar from .object import ObjectVar
var_type = self._var_type var_type = self._var_type
if var_type is None: if var_type is None:
return self.to(None) return self.to(None)
if types.is_optional(var_type): if types.is_optional(var_type):
var_type = types.get_args(var_type)[0] var_type = types.get_args(var_type)[0]
@ -818,10 +852,15 @@ class Var(Generic[VAR_TYPE]):
if fixed_type in types.UnionTypes: if fixed_type in types.UnionTypes:
inner_types = get_args(var_type) inner_types = get_args(var_type)
if all( for var_subclass in _var_subclasses:
inspect.isclass(t) and issubclass(t, (int, float)) for t in inner_types if all(
): (
return self.to(NumberVar, self._var_type) infallible_issubclass(t, var_subclass.python_types)
or (var_subclass.is_subclass and var_subclass.is_subclass(t))
)
for t in inner_types
):
return self.to(var_subclass.var_subclass, self._var_type)
if can_use_in_object_var(var_type): if can_use_in_object_var(var_type):
return self.to(ObjectVar, self._var_type) return self.to(ObjectVar, self._var_type)
@ -839,7 +878,9 @@ class Var(Generic[VAR_TYPE]):
return self.to(None) return self.to(None)
for var_subclass in _var_subclasses[::-1]: for var_subclass in _var_subclasses[::-1]:
if issubclass(fixed_type, var_subclass.python_types): if infallible_issubclass(fixed_type, var_subclass.python_types) or (
var_subclass.is_subclass and var_subclass.is_subclass(fixed_type)
):
return self.to(var_subclass.var_subclass, self._var_type) return self.to(var_subclass.var_subclass, self._var_type)
if can_use_in_object_var(fixed_type): if can_use_in_object_var(fixed_type):
@ -1799,6 +1840,7 @@ def var_operation(
), ),
function_name=func_name, function_name=func_name,
type_computer=custom_operation_return._type_computer, type_computer=custom_operation_return._type_computer,
_raw_js_function=custom_operation_return._raw_js_function,
_var_type=ReflexCallable[ _var_type=ReflexCallable[
tuple( tuple(
arg_python_type arg_python_type
@ -2541,15 +2583,17 @@ RETURN = TypeVar("RETURN")
class CustomVarOperationReturn(Var[RETURN]): class CustomVarOperationReturn(Var[RETURN]):
"""Base class for custom var operations.""" """Base class for custom var operations."""
_type_computer: Optional[TypeComputer] = dataclasses.field(default=None) _type_computer: TypeComputer | None = dataclasses.field(default=None)
_raw_js_function: str | None = dataclasses.field(default=None)
@classmethod @classmethod
def create( def create(
cls, cls,
js_expression: str, js_expression: str,
_var_type: Type[RETURN] | None = None, _var_type: Type[RETURN] | None = None,
_type_computer: Optional[TypeComputer] = None, _type_computer: TypeComputer | None = None,
_var_data: VarData | None = None, _var_data: VarData | None = None,
_raw_js_function: str | None = None,
) -> CustomVarOperationReturn[RETURN]: ) -> CustomVarOperationReturn[RETURN]:
"""Create a CustomVarOperation. """Create a CustomVarOperation.
@ -2558,6 +2602,7 @@ class CustomVarOperationReturn(Var[RETURN]):
_var_type: The type of the var. _var_type: The type of the var.
_type_computer: A function to compute the type of the var given the arguments. _type_computer: A function to compute the type of the var given the arguments.
_var_data: Additional hooks and imports associated with the Var. _var_data: Additional hooks and imports associated with the Var.
_raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
Returns: Returns:
The CustomVarOperation. The CustomVarOperation.
@ -2567,6 +2612,7 @@ class CustomVarOperationReturn(Var[RETURN]):
_var_type=_var_type or Any, _var_type=_var_type or Any,
_type_computer=_type_computer, _type_computer=_type_computer,
_var_data=_var_data, _var_data=_var_data,
_raw_js_function=_raw_js_function,
) )
@ -2575,6 +2621,7 @@ def var_operation_return(
var_type: Type[RETURN] | None = None, var_type: Type[RETURN] | None = None,
type_computer: Optional[TypeComputer] = None, type_computer: Optional[TypeComputer] = None,
var_data: VarData | None = None, var_data: VarData | None = None,
_raw_js_function: str | None = None,
) -> CustomVarOperationReturn[RETURN]: ) -> CustomVarOperationReturn[RETURN]:
"""Shortcut for creating a CustomVarOperationReturn. """Shortcut for creating a CustomVarOperationReturn.
@ -2583,6 +2630,7 @@ def var_operation_return(
var_type: The type of the var. var_type: The type of the var.
type_computer: A function to compute the type of the var given the arguments. type_computer: A function to compute the type of the var given the arguments.
var_data: Additional hooks and imports associated with the Var. var_data: Additional hooks and imports associated with the Var.
_raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
Returns: Returns:
The CustomVarOperationReturn. The CustomVarOperationReturn.
@ -2592,6 +2640,7 @@ def var_operation_return(
_var_type=var_type, _var_type=var_type,
_type_computer=type_computer, _type_computer=type_computer,
_var_data=var_data, _var_data=var_data,
_raw_js_function=_raw_js_function,
) )

View File

@ -52,7 +52,23 @@ OTHER_CALLABLE_TYPE = TypeVar(
) )
class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]): def type_is_reflex_callable(type_: Any) -> bool:
"""Check if a type is a ReflexCallable.
Args:
type_: The type to check.
Returns:
True if the type is a ReflexCallable.
"""
return type_ is ReflexCallable or get_origin(type_) is ReflexCallable
class FunctionVar(
Var[CALLABLE_TYPE],
default_type=ReflexCallable[Any, Any],
is_subclass=type_is_reflex_callable,
):
"""Base class for immutable function vars.""" """Base class for immutable function vars."""
@overload @overload
@ -304,15 +320,27 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
if arg_len is not None: if arg_len is not None:
if len(args) < required_arg_len: if len(args) < required_arg_len:
raise VarTypeError( raise VarTypeError(
f"Passed {len(args)} arguments, expected at least {required_arg_len} for {str(self)}" f"Passed {len(args)} arguments, expected at least {required_arg_len} for {self!s}"
) )
if len(args) > arg_len: if len(args) > arg_len:
raise VarTypeError( raise VarTypeError(
f"Passed {len(args)} arguments, expected at most {arg_len} for {str(self)}" f"Passed {len(args)} arguments, expected at most {arg_len} for {self!s}"
) )
args = tuple(map(LiteralVar.create, args)) args = tuple(map(LiteralVar.create, args))
self._pre_check(*args) self._pre_check(*args)
return_type = self._return_type(*args) return_type = self._return_type(*args)
if (
isinstance(self, (ArgsFunctionOperation, ArgsFunctionOperationBuilder))
and self._raw_js_function
):
return VarOperationCall.create(
FunctionStringVar.create(
self._raw_js_function, _var_type=self._var_type
),
*args,
_var_type=return_type,
).guess_type()
return VarOperationCall.create(self, *args, _var_type=return_type).guess_type() return VarOperationCall.create(self, *args, _var_type=return_type).guess_type()
def chain( def chain(
@ -412,7 +440,7 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
Returns: Returns:
True if the function can be called with the given arguments. True if the function can be called with the given arguments.
""" """
return tuple() return ()
@overload @overload
def __get__(self, instance: None, owner: Any) -> FunctionVar[CALLABLE_TYPE]: ... def __get__(self, instance: None, owner: Any) -> FunctionVar[CALLABLE_TYPE]: ...
@ -588,7 +616,7 @@ def format_args_function_operation(
[ [
(arg if isinstance(arg, str) else arg.to_javascript()) (arg if isinstance(arg, str) else arg.to_javascript())
+ ( + (
f" = {str(default_value.default)}" f" = {default_value.default!s}"
if i < len(self._default_values) if i < len(self._default_values)
and not isinstance( and not isinstance(
(default_value := self._default_values[i]), inspect.Parameter.empty (default_value := self._default_values[i]), inspect.Parameter.empty
@ -632,10 +660,10 @@ def pre_check_args(
arg_name = self._args.args[i] if i < len(self._args.args) else None arg_name = self._args.args[i] if i < len(self._args.args) else None
if arg_name is not None: if arg_name is not None:
raise VarTypeError( raise VarTypeError(
f"Invalid argument {str(arg)} provided to {arg_name} in {self._function_name or 'var operation'}. {validation_message}" f"Invalid argument {arg!s} provided to {arg_name} in {self._function_name or 'var operation'}. {validation_message}"
) )
raise VarTypeError( raise VarTypeError(
f"Invalid argument {str(arg)} provided to argument {i} in {self._function_name or 'var operation'}. {validation_message}" f"Invalid argument {arg!s} provided to argument {i} in {self._function_name or 'var operation'}. {validation_message}"
) )
return self._validators[len(args) :] return self._validators[len(args) :]
@ -679,6 +707,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
_function_name: str = dataclasses.field(default="") _function_name: str = dataclasses.field(default="")
_type_computer: Optional[TypeComputer] = dataclasses.field(default=None) _type_computer: Optional[TypeComputer] = dataclasses.field(default=None)
_explicit_return: bool = dataclasses.field(default=False) _explicit_return: bool = dataclasses.field(default=False)
_raw_js_function: str | None = dataclasses.field(default=None)
_cached_var_name = cached_property_no_lock(format_args_function_operation) _cached_var_name = cached_property_no_lock(format_args_function_operation)
@ -698,6 +727,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
function_name: str = "", function_name: str = "",
explicit_return: bool = False, explicit_return: bool = False,
type_computer: Optional[TypeComputer] = None, type_computer: Optional[TypeComputer] = None,
_raw_js_function: str | None = None,
_var_type: GenericType = Callable, _var_type: GenericType = Callable,
_var_data: VarData | None = None, _var_data: VarData | None = None,
): ):
@ -712,6 +742,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
function_name: The name of the function. function_name: The name of the function.
explicit_return: Whether to use explicit return syntax. explicit_return: Whether to use explicit return syntax.
type_computer: A function to compute the return type. type_computer: A function to compute the return type.
_raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
_var_type: The type of the var. _var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var. _var_data: Additional hooks and imports associated with the Var.
@ -723,6 +754,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
_var_type=_var_type, _var_type=_var_type,
_var_data=_var_data, _var_data=_var_data,
_args=FunctionArgs(args=tuple(args_names), rest=rest), _args=FunctionArgs(args=tuple(args_names), rest=rest),
_raw_js_function=_raw_js_function,
_default_values=tuple(default_values), _default_values=tuple(default_values),
_function_name=function_name, _function_name=function_name,
_validators=tuple(validators), _validators=tuple(validators),
@ -753,6 +785,7 @@ class ArgsFunctionOperationBuilder(
_function_name: str = dataclasses.field(default="") _function_name: str = dataclasses.field(default="")
_type_computer: Optional[TypeComputer] = dataclasses.field(default=None) _type_computer: Optional[TypeComputer] = dataclasses.field(default=None)
_explicit_return: bool = dataclasses.field(default=False) _explicit_return: bool = dataclasses.field(default=False)
_raw_js_function: str | None = dataclasses.field(default=None)
_cached_var_name = cached_property_no_lock(format_args_function_operation) _cached_var_name = cached_property_no_lock(format_args_function_operation)
@ -772,6 +805,7 @@ class ArgsFunctionOperationBuilder(
function_name: str = "", function_name: str = "",
explicit_return: bool = False, explicit_return: bool = False,
type_computer: Optional[TypeComputer] = None, type_computer: Optional[TypeComputer] = None,
_raw_js_function: str | None = None,
_var_type: GenericType = Callable, _var_type: GenericType = Callable,
_var_data: VarData | None = None, _var_data: VarData | None = None,
): ):
@ -788,6 +822,7 @@ class ArgsFunctionOperationBuilder(
type_computer: A function to compute the return type. type_computer: A function to compute the return type.
_var_type: The type of the var. _var_type: The type of the var.
_var_data: Additional hooks and imports associated with the Var. _var_data: Additional hooks and imports associated with the Var.
_raw_js_function: If provided, it will be used when the operation is being called with all of its arguments at once.
Returns: Returns:
The function var. The function var.
@ -797,6 +832,7 @@ class ArgsFunctionOperationBuilder(
_var_type=_var_type, _var_type=_var_type,
_var_data=_var_data, _var_data=_var_data,
_args=FunctionArgs(args=tuple(args_names), rest=rest), _args=FunctionArgs(args=tuple(args_names), rest=rest),
_raw_js_function=_raw_js_function,
_default_values=tuple(default_values), _default_values=tuple(default_values),
_function_name=function_name, _function_name=function_name,
_validators=tuple(validators), _validators=tuple(validators),

View File

@ -530,7 +530,9 @@ def number_abs_operation(
The number absolute operation. The number absolute operation.
""" """
return var_operation_return( return var_operation_return(
js_expression=f"Math.abs({value})", type_computer=unary_operation_type_computer js_expression=f"Math.abs({value})",
type_computer=unary_operation_type_computer,
_raw_js_function="Math.abs",
) )
@ -657,7 +659,11 @@ def number_floor_operation(value: Var[int | float]):
Returns: Returns:
The number floor operation. The number floor operation.
""" """
return var_operation_return(js_expression=f"Math.floor({value})", var_type=int) return var_operation_return(
js_expression=f"Math.floor({value})",
var_type=int,
_raw_js_function="Math.floor",
)
@var_operation @var_operation
@ -763,7 +769,9 @@ def boolean_to_number_operation(value: Var[bool]):
Returns: Returns:
The boolean to number operation. The boolean to number operation.
""" """
return var_operation_return(js_expression=f"Number({value})", var_type=int) return var_operation_return(
js_expression=f"Number({value})", var_type=int, _raw_js_function="Number"
)
def comparison_operator( def comparison_operator(
@ -1002,6 +1010,10 @@ _AT_SLICE_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSlice")], f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSlice")],
} }
_AT_SLICE_OR_INDEX: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSliceOrIndex")],
}
_RANGE_IMPORT: ImportDict = { _RANGE_IMPORT: ImportDict = {
f"$/{Dirs.UTILS}/helpers/range": [ImportVar(tag="range", is_default=True)], f"$/{Dirs.UTILS}/helpers/range": [ImportVar(tag="range", is_default=True)],
} }
@ -1021,6 +1033,7 @@ def boolify(value: Var):
js_expression=f"isTrue({value})", js_expression=f"isTrue({value})",
var_type=bool, var_type=bool,
var_data=VarData(imports=_IS_TRUE_IMPORT), var_data=VarData(imports=_IS_TRUE_IMPORT),
_raw_js_function="isTrue",
) )

View File

@ -415,6 +415,7 @@ def object_keys_operation(value: Var):
return var_operation_return( return var_operation_return(
js_expression=f"Object.keys({value})", js_expression=f"Object.keys({value})",
var_type=List[str], var_type=List[str],
_raw_js_function="Object.keys",
) )
@ -435,6 +436,7 @@ def object_values_operation(value: Var):
lambda x: List[x.to(ObjectVar)._value_type()], lambda x: List[x.to(ObjectVar)._value_type()],
), ),
var_type=List[Any], var_type=List[Any],
_raw_js_function="Object.values",
) )
@ -456,6 +458,7 @@ def object_entries_operation(value: Var):
lambda x: List[Tuple[str, x.to(ObjectVar)._value_type()]], lambda x: List[Tuple[str, x.to(ObjectVar)._value_type()]],
), ),
var_type=List[Tuple[str, Any]], var_type=List[Tuple[str, Any]],
_raw_js_function="Object.entries",
) )

View File

@ -30,8 +30,7 @@ from reflex.constants.base import REFLEX_VAR_OPENING_TAG
from reflex.constants.colors import Color from reflex.constants.colors import Color
from reflex.utils.exceptions import VarTypeError from reflex.utils.exceptions import VarTypeError
from reflex.utils.types import GenericType, get_origin from reflex.utils.types import GenericType, get_origin
from reflex.vars.base import (
from .base import (
CachedVarOperation, CachedVarOperation,
CustomVarOperationReturn, CustomVarOperationReturn,
LiteralVar, LiteralVar,
@ -51,8 +50,10 @@ from .base import (
var_operation, var_operation,
var_operation_return, var_operation_return,
) )
from .number import ( from .number import (
_AT_SLICE_IMPORT, _AT_SLICE_IMPORT,
_AT_SLICE_OR_INDEX,
_IS_TRUE_IMPORT, _IS_TRUE_IMPORT,
_RANGE_IMPORT, _RANGE_IMPORT,
LiteralNumberVar, LiteralNumberVar,
@ -88,7 +89,7 @@ def string_lt_operation(lhs: Var[str], rhs: Var[str]):
Returns: Returns:
The string less than operation. The string less than operation.
""" """
return var_operation_return(js_expression=f"{lhs} < {rhs}", var_type=bool) return var_operation_return(js_expression=f"({lhs} < {rhs})", var_type=bool)
@var_operation @var_operation
@ -102,7 +103,7 @@ def string_gt_operation(lhs: Var[str], rhs: Var[str]):
Returns: Returns:
The string greater than operation. The string greater than operation.
""" """
return var_operation_return(js_expression=f"{lhs} > {rhs}", var_type=bool) return var_operation_return(js_expression=f"({lhs} > {rhs})", var_type=bool)
@var_operation @var_operation
@ -116,7 +117,7 @@ def string_le_operation(lhs: Var[str], rhs: Var[str]):
Returns: Returns:
The string less than or equal operation. The string less than or equal operation.
""" """
return var_operation_return(js_expression=f"{lhs} <= {rhs}", var_type=bool) return var_operation_return(js_expression=f"({lhs} <= {rhs})", var_type=bool)
@var_operation @var_operation
@ -130,7 +131,7 @@ def string_ge_operation(lhs: Var[str], rhs: Var[str]):
Returns: Returns:
The string greater than or equal operation. The string greater than or equal operation.
""" """
return var_operation_return(js_expression=f"{lhs} >= {rhs}", var_type=bool) return var_operation_return(js_expression=f"({lhs} >= {rhs})", var_type=bool)
@var_operation @var_operation
@ -143,7 +144,11 @@ def string_lower_operation(string: Var[str]):
Returns: Returns:
The lowercase string. The lowercase string.
""" """
return var_operation_return(js_expression=f"{string}.toLowerCase()", var_type=str) return var_operation_return(
js_expression=f"String.prototype.toLowerCase.apply({string})",
var_type=str,
_raw_js_function="String.prototype.toLowerCase.apply",
)
@var_operation @var_operation
@ -156,7 +161,11 @@ def string_upper_operation(string: Var[str]):
Returns: Returns:
The uppercase string. The uppercase string.
""" """
return var_operation_return(js_expression=f"{string}.toUpperCase()", var_type=str) return var_operation_return(
js_expression=f"String.prototype.toUpperCase.apply({string})",
var_type=str,
_raw_js_function="String.prototype.toUpperCase.apply",
)
@var_operation @var_operation
@ -169,7 +178,11 @@ def string_strip_operation(string: Var[str]):
Returns: Returns:
The stripped string. The stripped string.
""" """
return var_operation_return(js_expression=f"{string}.trim()", var_type=str) return var_operation_return(
js_expression=f"String.prototype.trim.apply({string})",
var_type=str,
_raw_js_function="String.prototype.trim.apply",
)
@var_operation @var_operation
@ -259,6 +272,59 @@ def string_item_operation(string: Var[str], index: Var[int]):
return var_operation_return(js_expression=f"{string}.at({index})", var_type=str) return var_operation_return(js_expression=f"{string}.at({index})", var_type=str)
@var_operation
def string_slice_operation(
string: Var[str], slice: Var[slice]
) -> CustomVarOperationReturn[str]:
"""Get a slice from a string.
Args:
string: The string.
slice: The slice.
Returns:
The sliced string.
"""
return var_operation_return(
js_expression=f'atSlice({string}.split(""), {slice}).join("")',
type_computer=nary_type_computer(
ReflexCallable[[List[str], slice], str],
ReflexCallable[[slice], str],
computer=lambda args: str,
),
var_data=VarData(
imports=_AT_SLICE_IMPORT,
),
)
@var_operation
def string_index_or_slice_operation(
string: Var[str], index_or_slice: Var[Union[int, slice]]
) -> CustomVarOperationReturn[Union[str, Sequence[str]]]:
"""Get an item or slice from a string.
Args:
string: The string.
index_or_slice: The index or slice.
Returns:
The item or slice from the string.
"""
return var_operation_return(
js_expression=f"Array.prototype.join.apply(atSliceOrIndex({string}, {index_or_slice}), [''])",
_raw_js_function="atSliceOrIndex",
type_computer=nary_type_computer(
ReflexCallable[[List[str], Union[int, slice]], str],
ReflexCallable[[Union[int, slice]], str],
computer=lambda args: str,
),
var_data=VarData(
imports=_AT_SLICE_OR_INDEX,
),
)
@var_operation @var_operation
def string_replace_operation( def string_replace_operation(
string: Var[str], search_value: Var[str], new_value: Var[str] string: Var[str], search_value: Var[str], new_value: Var[str]
@ -454,7 +520,8 @@ def array_item_or_slice_operation(
The item or slice from the array. The item or slice from the array.
""" """
return var_operation_return( return var_operation_return(
js_expression=f"Array.isArray({index_or_slice}) ? at_slice({array}, {index_or_slice}) : {array}.at({index_or_slice})", js_expression=f"atSliceOrIndex({array}, {index_or_slice})",
_raw_js_function="atSliceOrIndex",
type_computer=nary_type_computer( type_computer=nary_type_computer(
ReflexCallable[[Sequence, Union[int, slice]], Any], ReflexCallable[[Sequence, Union[int, slice]], Any],
ReflexCallable[[Union[int, slice]], Any], ReflexCallable[[Union[int, slice]], Any],
@ -465,7 +532,7 @@ def array_item_or_slice_operation(
), ),
), ),
var_data=VarData( var_data=VarData(
imports=_AT_SLICE_IMPORT, imports=_AT_SLICE_OR_INDEX,
), ),
) )
@ -1073,7 +1140,11 @@ class StringVar(Var[STRING_TYPE], python_types=str):
__radd__ = reverse_string_concat_operation __radd__ = reverse_string_concat_operation
__getitem__ = string_item_operation __getitem__ = string_index_or_slice_operation
at = string_item_operation
slice = string_slice_operation
lower = string_lower_operation lower = string_lower_operation

View File

@ -25,6 +25,7 @@ def test_connection_banner():
"react", "react",
"$/utils/context", "$/utils/context",
"$/utils/state", "$/utils/state",
"@emotion/react",
RadixThemesComponent().library or "", RadixThemesComponent().library or "",
"$/env.json", "$/env.json",
) )
@ -43,6 +44,7 @@ def test_connection_modal():
"react", "react",
"$/utils/context", "$/utils/context",
"$/utils/state", "$/utils/state",
"@emotion/react",
RadixThemesComponent().library or "", RadixThemesComponent().library or "",
"$/env.json", "$/env.json",
) )

View File

@ -846,7 +846,7 @@ def test_component_event_trigger_arbitrary_args():
assert comp.render()["props"][0] == ( assert comp.render()["props"][0] == (
"onFoo={((__e, _alpha, _bravo, _charlie) => (addEvents(" "onFoo={((__e, _alpha, _bravo, _charlie) => (addEvents("
f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (_charlie["custom"] + 42) }}), ({{ }})))], ' f'[(Event("{C1State.get_full_name()}.mock_handler", ({{ ["_e"] : __e["target"]["value"], ["_bravo"] : _bravo["nested"], ["_charlie"] : (((_lhs, _rhs) => (_lhs + _rhs))(_charlie["custom"], 42)) }}), ({{ }})))], '
"[__e, _alpha, _bravo, _charlie], ({ }))))}" "[__e, _alpha, _bravo, _charlie], ({ }))))}"
) )

View File

@ -432,12 +432,15 @@ def test_default_setters(test_state):
def test_class_indexing_with_vars(): def test_class_indexing_with_vars():
"""Test that we can index into a state var with another var.""" """Test that we can index into a state var with another var."""
prop = TestState.array[TestState.num1] prop = TestState.array[TestState.num1]
assert str(prop) == f"{TestState.get_name()}.array.at({TestState.get_name()}.num1)" assert (
str(prop)
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({TestState.get_name()}.array, ...args)))({TestState.get_name()}.num1))"
)
prop = TestState.mapping["a"][TestState.num1] prop = TestState.mapping["a"][TestState.num1]
assert ( assert (
str(prop) str(prop)
== f'{TestState.get_name()}.mapping["a"].at({TestState.get_name()}.num1)' == f'(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({TestState.get_name()}.mapping["a"], ...args)))({TestState.get_name()}.num1))'
) )
prop = TestState.mapping[TestState.map_key] prop = TestState.mapping[TestState.map_key]

View File

@ -307,30 +307,65 @@ def test_basic_operations(TestObj):
Args: Args:
TestObj: The test object. TestObj: The test object.
""" """
assert str(v(1) == v(2)) == "(1 === 2)" assert str(v(1) == v(2)) == "(((_lhs, _rhs) => (_lhs === _rhs))(1, 2))"
assert str(v(1) != v(2)) == "(1 !== 2)" assert str(v(1) != v(2)) == "(((_lhs, _rhs) => (_lhs !== _rhs))(1, 2))"
assert str(LiteralNumberVar.create(1) < 2) == "(1 < 2)" assert (
assert str(LiteralNumberVar.create(1) <= 2) == "(1 <= 2)" str(LiteralNumberVar.create(1) < 2) == "(((_lhs, _rhs) => (_lhs < _rhs))(1, 2))"
assert str(LiteralNumberVar.create(1) > 2) == "(1 > 2)" )
assert str(LiteralNumberVar.create(1) >= 2) == "(1 >= 2)" assert (
assert str(LiteralNumberVar.create(1) + 2) == "(1 + 2)" str(LiteralNumberVar.create(1) <= 2)
assert str(LiteralNumberVar.create(1) - 2) == "(1 - 2)" == "(((_lhs, _rhs) => (_lhs <= _rhs))(1, 2))"
assert str(LiteralNumberVar.create(1) * 2) == "(1 * 2)" )
assert str(LiteralNumberVar.create(1) / 2) == "(1 / 2)" assert (
assert str(LiteralNumberVar.create(1) // 2) == "Math.floor(1 / 2)" str(LiteralNumberVar.create(1) > 2) == "(((_lhs, _rhs) => (_lhs > _rhs))(1, 2))"
assert str(LiteralNumberVar.create(1) % 2) == "(1 % 2)" )
assert str(LiteralNumberVar.create(1) ** 2) == "(1 ** 2)" assert (
assert str(LiteralNumberVar.create(1) & v(2)) == "(1 && 2)" str(LiteralNumberVar.create(1) >= 2)
assert str(LiteralNumberVar.create(1) | v(2)) == "(1 || 2)" == "(((_lhs, _rhs) => (_lhs >= _rhs))(1, 2))"
assert str(LiteralArrayVar.create([1, 2, 3])[0]) == "[1, 2, 3].at(0)" )
assert (
str(LiteralNumberVar.create(1) + 2) == "(((_lhs, _rhs) => (_lhs + _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) - 2) == "(((_lhs, _rhs) => (_lhs - _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) * 2) == "(((_lhs, _rhs) => (_lhs * _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) / 2) == "(((_lhs, _rhs) => (_lhs / _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) // 2)
== "(((_lhs, _rhs) => Math.floor(_lhs / _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) % 2) == "(((_lhs, _rhs) => (_lhs % _rhs))(1, 2))"
)
assert (
str(LiteralNumberVar.create(1) ** 2)
== "(((_lhs, _rhs) => (_lhs ** _rhs))(1, 2))"
)
assert str(LiteralNumberVar.create(1) & v(2)) == "(((_a, _b) => (_a && _b))(1, 2))"
assert str(LiteralNumberVar.create(1) | v(2)) == "(((_a, _b) => (_a || _b))(1, 2))"
assert (
str(LiteralArrayVar.create([1, 2, 3])[0])
== "(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3], ...args)))(0))"
)
assert ( assert (
str(LiteralObjectVar.create({"a": 1, "b": 2})["a"]) str(LiteralObjectVar.create({"a": 1, "b": 2})["a"])
== '({ ["a"] : 1, ["b"] : 2 })["a"]' == '({ ["a"] : 1, ["b"] : 2 })["a"]'
) )
assert str(v("foo") == v("bar")) == '("foo" === "bar")'
assert str(Var(_js_expr="foo") == Var(_js_expr="bar")) == "(foo === bar)"
assert ( assert (
str(LiteralVar.create("foo") == LiteralVar.create("bar")) == '("foo" === "bar")' str(v("foo") == v("bar")) == '(((_lhs, _rhs) => (_lhs === _rhs))("foo", "bar"))'
)
assert (
str(Var(_js_expr="foo") == Var(_js_expr="bar"))
== "(((_lhs, _rhs) => (_lhs === _rhs))(foo, bar))"
)
assert (
str(LiteralVar.create("foo") == LiteralVar.create("bar"))
== '(((_lhs, _rhs) => (_lhs === _rhs))("foo", "bar"))'
) )
print(Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state")) print(Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state"))
assert ( assert (
@ -338,33 +373,39 @@ def test_basic_operations(TestObj):
Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar
== LiteralVar.create("bar") == LiteralVar.create("bar")
) )
== '(state.foo["bar"] === "bar")' == '(((_lhs, _rhs) => (_lhs === _rhs))(state.foo["bar"], "bar"))'
) )
assert ( assert (
str(Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar) str(Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar)
== 'state.foo["bar"]' == 'state.foo["bar"]'
) )
assert str(abs(LiteralNumberVar.create(1))) == "Math.abs(1)" assert str(abs(LiteralNumberVar.create(1))) == "(Math.abs(1))"
assert str(LiteralArrayVar.create([1, 2, 3]).length()) == "[1, 2, 3].length" assert (
str(LiteralArrayVar.create([1, 2, 3]).length())
== "(((...args) => (((_array) => _array.length)([1, 2, 3], ...args)))())"
)
assert ( assert (
str(LiteralArrayVar.create([1, 2]) + LiteralArrayVar.create([3, 4])) str(LiteralArrayVar.create([1, 2]) + LiteralArrayVar.create([3, 4]))
== "[...[1, 2], ...[3, 4]]" == "(((...args) => (((_lhs, _rhs) => [..._lhs, ..._rhs])([1, 2], ...args)))([3, 4]))"
) )
# Tests for reverse operation # Tests for reverse operation
assert ( assert (
str(LiteralArrayVar.create([1, 2, 3]).reverse()) str(LiteralArrayVar.create([1, 2, 3]).reverse())
== "[1, 2, 3].slice().reverse()" == "(((...args) => (((_array) => _array.slice().reverse())([1, 2, 3], ...args)))())"
) )
assert ( assert (
str(LiteralArrayVar.create(["1", "2", "3"]).reverse()) str(LiteralArrayVar.create(["1", "2", "3"]).reverse())
== '["1", "2", "3"].slice().reverse()' == '(((...args) => (((_array) => _array.slice().reverse())(["1", "2", "3"], ...args)))())'
) )
assert ( assert (
str(Var(_js_expr="foo")._var_set_state("state").to(list).reverse()) str(Var(_js_expr="foo")._var_set_state("state").to(list).reverse())
== "state.foo.slice().reverse()" == "(((...args) => (((_array) => _array.slice().reverse())(state.foo, ...args)))())"
)
assert (
str(Var(_js_expr="foo").to(list).reverse())
== "(((...args) => (((_array) => _array.slice().reverse())(foo, ...args)))())"
) )
assert str(Var(_js_expr="foo").to(list).reverse()) == "foo.slice().reverse()"
assert str(Var(_js_expr="foo", _var_type=str).js_type()) == "(typeof(foo))" assert str(Var(_js_expr="foo", _var_type=str).js_type()) == "(typeof(foo))"
@ -389,14 +430,32 @@ def test_basic_operations(TestObj):
], ],
) )
def test_list_tuple_contains(var, expected): def test_list_tuple_contains(var, expected):
assert str(var.contains(1)) == f"{expected}.includes(1)" assert (
assert str(var.contains("1")) == f'{expected}.includes("1")' str(var.contains(1))
assert str(var.contains(v(1))) == f"{expected}.includes(1)" == f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(1))'
assert str(var.contains(v("1"))) == f'{expected}.includes("1")' )
assert (
str(var.contains("1"))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))("1"))'
)
assert (
str(var.contains(v(1)))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(1))'
)
assert (
str(var.contains(v("1")))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))("1"))'
)
other_state_var = Var(_js_expr="other", _var_type=str)._var_set_state("state") other_state_var = Var(_js_expr="other", _var_type=str)._var_set_state("state")
other_var = Var(_js_expr="other", _var_type=str) other_var = Var(_js_expr="other", _var_type=str)
assert str(var.contains(other_state_var)) == f"{expected}.includes(state.other)" assert (
assert str(var.contains(other_var)) == f"{expected}.includes(other)" str(var.contains(other_state_var))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(state.other))'
)
assert (
str(var.contains(other_var))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(other))'
)
class Foo(rx.Base): class Foo(rx.Base):
@ -446,15 +505,27 @@ def test_var_types(var, var_type):
], ],
) )
def test_str_contains(var, expected): def test_str_contains(var, expected):
assert str(var.contains("1")) == f'{expected}.includes("1")' assert (
assert str(var.contains(v("1"))) == f'{expected}.includes("1")' str(var.contains("1"))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))("1"))'
)
assert (
str(var.contains(v("1")))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))("1"))'
)
other_state_var = Var(_js_expr="other")._var_set_state("state").to(str) other_state_var = Var(_js_expr="other")._var_set_state("state").to(str)
other_var = Var(_js_expr="other").to(str) other_var = Var(_js_expr="other").to(str)
assert str(var.contains(other_state_var)) == f"{expected}.includes(state.other)" assert (
assert str(var.contains(other_var)) == f"{expected}.includes(other)" str(var.contains(other_state_var))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(state.other))'
)
assert (
str(var.contains(other_var))
== f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))(other))'
)
assert ( assert (
str(var.contains("1", "hello")) str(var.contains("1", "hello"))
== f'{expected}.some(obj => obj["hello"] === "1")' == f'(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))({expected!s}, ...args)))("1", "hello"))'
) )
@ -467,16 +538,32 @@ def test_str_contains(var, expected):
], ],
) )
def test_dict_contains(var, expected): def test_dict_contains(var, expected):
assert str(var.contains(1)) == f"{expected}.hasOwnProperty(1)" assert (
assert str(var.contains("1")) == f'{expected}.hasOwnProperty("1")' str(var.contains(1))
assert str(var.contains(v(1))) == f"{expected}.hasOwnProperty(1)" == f"(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, 1))"
assert str(var.contains(v("1"))) == f'{expected}.hasOwnProperty("1")' )
assert (
str(var.contains("1"))
== f'(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, "1"))'
)
assert (
str(var.contains(v(1)))
== f"(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, 1))"
)
assert (
str(var.contains(v("1")))
== f'(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, "1"))'
)
other_state_var = Var(_js_expr="other")._var_set_state("state").to(str) other_state_var = Var(_js_expr="other")._var_set_state("state").to(str)
other_var = Var(_js_expr="other").to(str) other_var = Var(_js_expr="other").to(str)
assert ( assert (
str(var.contains(other_state_var)) == f"{expected}.hasOwnProperty(state.other)" str(var.contains(other_state_var))
== f"(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, state.other))"
)
assert (
str(var.contains(other_var))
== f"(((_object, _key) => _object.hasOwnProperty(_key))({expected!s}, other))"
) )
assert str(var.contains(other_var)) == f"{expected}.hasOwnProperty(other)"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -484,7 +571,6 @@ def test_dict_contains(var, expected):
[ [
Var(_js_expr="list", _var_type=List[int]).guess_type(), Var(_js_expr="list", _var_type=List[int]).guess_type(),
Var(_js_expr="tuple", _var_type=Tuple[int, int]).guess_type(), Var(_js_expr="tuple", _var_type=Tuple[int, int]).guess_type(),
Var(_js_expr="str", _var_type=str).guess_type(),
], ],
) )
def test_var_indexing_lists(var): def test_var_indexing_lists(var):
@ -494,11 +580,20 @@ def test_var_indexing_lists(var):
var : The str, list or tuple base var. var : The str, list or tuple base var.
""" """
# Test basic indexing. # Test basic indexing.
assert str(var[0]) == f"{var._js_expr}.at(0)" assert (
assert str(var[1]) == f"{var._js_expr}.at(1)" str(var[0])
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))(0))"
)
assert (
str(var[1])
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))(1))"
)
# Test negative indexing. # Test negative indexing.
assert str(var[-1]) == f"{var._js_expr}.at(-1)" assert (
str(var[-1])
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))(-1))"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -532,11 +627,20 @@ def test_var_indexing_str():
assert str_var[0]._var_type is str assert str_var[0]._var_type is str
# Test basic indexing. # Test basic indexing.
assert str(str_var[0]) == "str.at(0)" assert (
assert str(str_var[1]) == "str.at(1)" str(str_var[0])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))(0))"
)
assert (
str(str_var[1])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))(1))"
)
# Test negative indexing. # Test negative indexing.
assert str(str_var[-1]) == "str.at(-1)" assert (
str(str_var[-1])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))(-1))"
)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -651,9 +755,18 @@ def test_var_list_slicing(var):
Args: Args:
var : The str, list or tuple base var. var : The str, list or tuple base var.
""" """
assert str(var[:1]) == f"{var._js_expr}.slice(undefined, 1)" assert (
assert str(var[1:]) == f"{var._js_expr}.slice(1, undefined)" str(var[:1])
assert str(var[:]) == f"{var._js_expr}.slice(undefined, undefined)" == f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))([null, 1, null]))"
)
assert (
str(var[1:])
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))([1, null, null]))"
)
assert (
str(var[:])
== f"(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))({var!s}, ...args)))([null, null, null]))"
)
def test_str_var_slicing(): def test_str_var_slicing():
@ -665,16 +778,40 @@ def test_str_var_slicing():
assert str_var[:1]._var_type is str assert str_var[:1]._var_type is str
# Test basic slicing. # Test basic slicing.
assert str(str_var[:1]) == 'str.split("").slice(undefined, 1).join("")' assert (
assert str(str_var[1:]) == 'str.split("").slice(1, undefined).join("")' str(str_var[:1])
assert str(str_var[:]) == 'str.split("").slice(undefined, undefined).join("")' == "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([null, 1, null]))"
assert str(str_var[1:2]) == 'str.split("").slice(1, 2).join("")' )
assert (
str(str_var[1:])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([1, null, null]))"
)
assert (
str(str_var[:])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([null, null, null]))"
)
assert (
str(str_var[1:2])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([1, 2, null]))"
)
# Test negative slicing. # Test negative slicing.
assert str(str_var[:-1]) == 'str.split("").slice(undefined, -1).join("")' assert (
assert str(str_var[-1:]) == 'str.split("").slice(-1, undefined).join("")' str(str_var[:-1])
assert str(str_var[:-2]) == 'str.split("").slice(undefined, -2).join("")' == "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([null, -1, null]))"
assert str(str_var[-2:]) == 'str.split("").slice(-2, undefined).join("")' )
assert (
str(str_var[-1:])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([-1, null, null]))"
)
assert (
str(str_var[:-2])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([null, -2, null]))"
)
assert (
str(str_var[-2:])
== "(((...args) => (((_string, _index_or_slice) => Array.prototype.join.apply(atSliceOrIndex(_string, _index_or_slice), ['']))(str, ...args)))([-2, null, null]))"
)
def test_dict_indexing(): def test_dict_indexing():
@ -966,8 +1103,8 @@ def test_var_operation():
def add(a: Var[int], b: Var[int]): def add(a: Var[int], b: Var[int]):
return var_operation_return(js_expression=f"({a} + {b})", var_type=int) return var_operation_return(js_expression=f"({a} + {b})", var_type=int)
assert str(add(1, 2)) == "(1 + 2)" assert str(add(1, 2)) == "(((_a, _b) => (_a + _b))(1, 2))"
assert str(add(4, -9)) == "(4 + -9)" assert str(add(4, -9)) == "(((_a, _b) => (_a + _b))(4, -9))"
five = LiteralNumberVar.create(5) five = LiteralNumberVar.create(5)
seven = add(2, five) seven = add(2, five)
@ -978,13 +1115,29 @@ def test_var_operation():
def test_string_operations(): def test_string_operations():
basic_string = LiteralStringVar.create("Hello, World!") basic_string = LiteralStringVar.create("Hello, World!")
assert str(basic_string.length()) == '"Hello, World!".split("").length'
assert str(basic_string.lower()) == '"Hello, World!".toLowerCase()'
assert str(basic_string.upper()) == '"Hello, World!".toUpperCase()'
assert str(basic_string.strip()) == '"Hello, World!".trim()'
assert str(basic_string.contains("World")) == '"Hello, World!".includes("World")'
assert ( assert (
str(basic_string.split(" ").join(",")) == '"Hello, World!".split(" ").join(",")' str(basic_string.length())
== '(((...args) => (((...arg) => (((_array) => _array.length)((((_string, _sep = "") => isTrue(_sep) ? _string.split(_sep) : [..._string])(...args)))))("Hello, World!", ...args)))())'
)
assert (
str(basic_string.lower())
== '(((...args) => (((_string) => String.prototype.toLowerCase.apply(_string))("Hello, World!", ...args)))())'
)
assert (
str(basic_string.upper())
== '(((...args) => (((_string) => String.prototype.toUpperCase.apply(_string))("Hello, World!", ...args)))())'
)
assert (
str(basic_string.strip())
== '(((...args) => (((_string) => String.prototype.trim.apply(_string))("Hello, World!", ...args)))())'
)
assert (
str(basic_string.contains("World"))
== '(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))("Hello, World!", ...args)))("World"))'
)
assert (
str(basic_string.split(" ").join(","))
== '(((...args) => (((_array, _sep = "") => _array.join(_sep))((((...args) => (((_string, _sep = "") => isTrue(_sep) ? _string.split(_sep) : [..._string])("Hello, World!", ...args)))(" ")), ...args)))(","))'
) )
@ -995,7 +1148,7 @@ def test_all_number_operations():
assert ( assert (
str(complicated_number) str(complicated_number)
== "((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)" == "(((_lhs, _rhs) => (_lhs ** _rhs))((((_lhs, _rhs) => (_lhs % _rhs))((((_lhs, _rhs) => Math.floor(_lhs / _rhs))((((_lhs, _rhs) => (_lhs / _rhs))((((_lhs, _rhs) => (_lhs * _rhs))((((_value) => -(_value))((((_lhs, _rhs) => (_lhs + _rhs))(-5.4, 1)))), 2)), 3)), 2)), 3)), 2))"
) )
even_more_complicated_number = ~( even_more_complicated_number = ~(
@ -1004,14 +1157,20 @@ def test_all_number_operations():
assert ( assert (
str(even_more_complicated_number) str(even_more_complicated_number)
== "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))" == "(((_value) => !(_value))((((_lhs, _rhs) => (_lhs !== _rhs))((((_a, _b) => (_a || _b))((Math.abs((Math.floor((((_lhs, _rhs) => (_lhs ** _rhs))((((_lhs, _rhs) => (_lhs % _rhs))((((_lhs, _rhs) => Math.floor(_lhs / _rhs))((((_lhs, _rhs) => (_lhs / _rhs))((((_lhs, _rhs) => (_lhs * _rhs))((((_value) => -(_value))((((_lhs, _rhs) => (_lhs + _rhs))(-5.4, 1)))), 2)), 3)), 2)), 3)), 2)))))), (((_a, _b) => (_a && _b))(2, (((_value) => Math.round(_value))((((_lhs, _rhs) => (_lhs ** _rhs))((((_lhs, _rhs) => (_lhs % _rhs))((((_lhs, _rhs) => Math.floor(_lhs / _rhs))((((_lhs, _rhs) => (_lhs / _rhs))((((_lhs, _rhs) => (_lhs * _rhs))((((_value) => -(_value))((((_lhs, _rhs) => (_lhs + _rhs))(-5.4, 1)))), 2)), 3)), 2)), 3)), 2)))))))), 0))))"
) )
assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)" assert (
assert str(LiteralBooleanVar.create(False) < 5) == "(Number(false) < 5)" str(LiteralNumberVar.create(5) > False)
== "(((_lhs, _rhs) => (_lhs > _rhs))(5, 0))"
)
assert (
str(LiteralBooleanVar.create(False) < 5)
== "(((_lhs, _rhs) => (_lhs < _rhs))((Number(false)), 5))"
)
assert ( assert (
str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True)) str(LiteralBooleanVar.create(False) < LiteralBooleanVar.create(True))
== "(Number(false) < Number(true))" == "(((_lhs, _rhs) => (_lhs < _rhs))((Number(false)), (Number(true))))"
) )
@ -1020,10 +1179,10 @@ def test_all_number_operations():
[ [
(Var.create(False), "false"), (Var.create(False), "false"),
(Var.create(True), "true"), (Var.create(True), "true"),
(Var.create("false"), 'isTrue("false")'), (Var.create("false"), '(isTrue("false"))'),
(Var.create([1, 2, 3]), "isTrue([1, 2, 3])"), (Var.create([1, 2, 3]), "(isTrue([1, 2, 3]))"),
(Var.create({"a": 1, "b": 2}), 'isTrue(({ ["a"] : 1, ["b"] : 2 }))'), (Var.create({"a": 1, "b": 2}), '(isTrue(({ ["a"] : 1, ["b"] : 2 })))'),
(Var("mysterious_var"), "isTrue(mysterious_var)"), (Var("mysterious_var"), "(isTrue(mysterious_var))"),
], ],
) )
def test_boolify_operations(var, expected): def test_boolify_operations(var, expected):
@ -1032,18 +1191,30 @@ def test_boolify_operations(var, expected):
def test_index_operation(): def test_index_operation():
array_var = LiteralArrayVar.create([1, 2, 3, 4, 5]) array_var = LiteralArrayVar.create([1, 2, 3, 4, 5])
assert str(array_var[0]) == "[1, 2, 3, 4, 5].at(0)" assert (
assert str(array_var[1:2]) == "[1, 2, 3, 4, 5].slice(1, 2)" str(array_var[0])
== "(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3, 4, 5], ...args)))(0))"
)
assert (
str(array_var[1:2])
== "(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3, 4, 5], ...args)))([1, 2, null]))"
)
assert ( assert (
str(array_var[1:4:2]) str(array_var[1:4:2])
== "[1, 2, 3, 4, 5].slice(1, 4).filter((_, i) => i % 2 === 0)" == "(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3, 4, 5], ...args)))([1, 4, 2]))"
) )
assert ( assert (
str(array_var[::-1]) str(array_var[::-1])
== "[1, 2, 3, 4, 5].slice(0, [1, 2, 3, 4, 5].length).slice().reverse().slice(undefined, undefined).filter((_, i) => i % 1 === 0)" == "(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3, 4, 5], ...args)))([null, null, -1]))"
)
assert (
str(array_var.reverse())
== "(((...args) => (((_array) => _array.slice().reverse())([1, 2, 3, 4, 5], ...args)))())"
)
assert (
str(array_var[0].to(NumberVar) + 9)
== "(((_lhs, _rhs) => (_lhs + _rhs))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))([1, 2, 3, 4, 5], ...args)))(0)), 9))"
) )
assert str(array_var.reverse()) == "[1, 2, 3, 4, 5].slice().reverse()"
assert str(array_var[0].to(NumberVar) + 9) == "([1, 2, 3, 4, 5].at(0) + 9)"
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1065,24 +1236,33 @@ def test_inf_and_nan(var, expected_js):
def test_array_operations(): def test_array_operations():
array_var = LiteralArrayVar.create([1, 2, 3, 4, 5]) array_var = LiteralArrayVar.create([1, 2, 3, 4, 5])
assert str(array_var.length()) == "[1, 2, 3, 4, 5].length" assert (
assert str(array_var.contains(3)) == "[1, 2, 3, 4, 5].includes(3)" str(array_var.length())
assert str(array_var.reverse()) == "[1, 2, 3, 4, 5].slice().reverse()" == "(((...args) => (((_array) => _array.length)([1, 2, 3, 4, 5], ...args)))())"
)
assert (
str(array_var.contains(3))
== '(((...args) => (((_haystack, _needle, _field = "") => isTrue(_field) ? _haystack.some(obj => obj[_field] === _needle) : _haystack.some(obj => obj === _needle))([1, 2, 3, 4, 5], ...args)))(3))'
)
assert (
str(array_var.reverse())
== "(((...args) => (((_array) => _array.slice().reverse())([1, 2, 3, 4, 5], ...args)))())"
)
assert ( assert (
str(ArrayVar.range(10)) str(ArrayVar.range(10))
== "Array.from({ length: (10 - 0) / 1 }, (_, i) => 0 + i * 1)" == "(((_e1, _e2 = null, _step = 1) => range(_e1, _e2, _step))(10))"
) )
assert ( assert (
str(ArrayVar.range(1, 10)) str(ArrayVar.range(1, 10))
== "Array.from({ length: (10 - 1) / 1 }, (_, i) => 1 + i * 1)" == "(((_e1, _e2 = null, _step = 1) => range(_e1, _e2, _step))(1, 10))"
) )
assert ( assert (
str(ArrayVar.range(1, 10, 2)) str(ArrayVar.range(1, 10, 2))
== "Array.from({ length: (10 - 1) / 2 }, (_, i) => 1 + i * 2)" == "(((_e1, _e2 = null, _step = 1) => range(_e1, _e2, _step))(1, 10, 2))"
) )
assert ( assert (
str(ArrayVar.range(1, 10, -1)) str(ArrayVar.range(1, 10, -1))
== "Array.from({ length: (10 - 1) / -1 }, (_, i) => 1 + i * -1)" == "(((_e1, _e2 = null, _step = 1) => range(_e1, _e2, _step))(1, 10, -1))"
) )
@ -1090,21 +1270,21 @@ def test_object_operations():
object_var = LiteralObjectVar.create({"a": 1, "b": 2, "c": 3}) object_var = LiteralObjectVar.create({"a": 1, "b": 2, "c": 3})
assert ( assert (
str(object_var.keys()) == 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' str(object_var.keys()) == '(Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })))'
) )
assert ( assert (
str(object_var.values()) str(object_var.values())
== 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' == '(Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })))'
) )
assert ( assert (
str(object_var.entries()) str(object_var.entries())
== 'Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))' == '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })))'
) )
assert str(object_var.a) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]' assert str(object_var.a) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
assert str(object_var["a"]) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]' assert str(object_var["a"]) == '({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["a"]'
assert ( assert (
str(object_var.merge(LiteralObjectVar.create({"c": 4, "d": 5}))) str(object_var.merge(LiteralObjectVar.create({"c": 4, "d": 5})))
== '({...({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }), ...({ ["c"] : 4, ["d"] : 5 })})' == '(((_lhs, _rhs) => ({..._lhs, ..._rhs}))(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }), ({ ["c"] : 4, ["d"] : 5 })))'
) )
@ -1140,23 +1320,27 @@ def test_type_chains():
) )
assert ( assert (
str(object_var.keys()[0].upper()) # type: ignore str(object_var.keys()[0].upper()) # type: ignore
== 'Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(0).toUpperCase()' == '(((...args) => (((_string) => String.prototype.toUpperCase.apply(_string))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((Object.keys(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))), ...args)))(0)), ...args)))())'
) )
assert ( assert (
str(object_var.entries()[1][1] - 1) # type: ignore str(object_var.entries()[1][1] - 1) # type: ignore
== '(Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })).at(1).at(1) - 1)' == '(((_lhs, _rhs) => (_lhs - _rhs))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((Object.entries(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))), ...args)))(1)), ...args)))(1)), 1))'
) )
assert ( assert (
str(object_var["c"] + object_var["b"]) # type: ignore str(object_var["c"] + object_var["b"]) # type: ignore
== '(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["c"] + ({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["b"])' == '(((_lhs, _rhs) => (_lhs + _rhs))(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["c"], ({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })["b"]))'
) )
def test_nested_dict(): def test_nested_dict():
arr = LiteralArrayVar.create([{"bar": ["foo", "bar"]}], List[Dict[str, List[str]]]) arr = Var.create([{"bar": ["foo", "bar"]}]).to(List[Dict[str, List[str]]])
first_dict = arr.at(0)
bar_element = first_dict["bar"]
first_bar_element = bar_element[0]
assert ( assert (
str(arr[0]["bar"][0]) == '[({ ["bar"] : ["foo", "bar"] })].at(0)["bar"].at(0)' str(first_bar_element)
== '(((...args) => (((_array, _index_or_slice) => atSliceOrIndex(_array, _index_or_slice))((((...args) => (((_array, _index) => _array.at(_index))([({ ["bar"] : ["foo", "bar"] })], ...args)))(0))["bar"], ...args)))(0))'
) )
@ -1376,7 +1560,7 @@ def test_unsupported_types_for_string_contains(other):
assert Var(_js_expr="var").to(str).contains(other) assert Var(_js_expr="var").to(str).contains(other)
assert ( assert (
err.value.args[0] err.value.args[0]
== f"Unsupported Operand type(s) for contains: ToStringOperation, {type(other).__name__}" == f"Invalid argument other provided to argument 0 in var operation. Expected <class 'str'> but got {other._var_type}."
) )
@ -1608,17 +1792,12 @@ def test_valid_var_operations(operand1_var: Var, operand2_var, operators: List[s
LiteralVar.create([10, 20]), LiteralVar.create([10, 20]),
LiteralVar.create("5"), LiteralVar.create("5"),
[ [
"+",
"-", "-",
"/", "/",
"//", "//",
"*", "*",
"%", "%",
"**", "**",
">",
"<",
"<=",
">=",
"^", "^",
"<<", "<<",
">>", ">>",