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"
[tool.codespell]
skip = "docs/*,*.html,examples/*, *.pyi"
skip = "docs/*,*.html,examples/*, *.pyi, *.lock"
ignore-words-list = "te, TreeE"

View File

@ -300,10 +300,7 @@ export const applyEvent = async (event, socket) => {
// Send the event to the server.
if (socket) {
socket.emit(
"event",
event,
);
socket.emit("event", event);
return true;
}
@ -410,7 +407,7 @@ export const connect = async (
autoUnref: false,
});
// 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() {
if (document.visibilityState === "visible") {
@ -488,7 +485,7 @@ export const uploadFiles = async (
return false;
}
const upload_ref_name = `__upload_controllers_${upload_id}`
const upload_ref_name = `__upload_controllers_${upload_id}`;
if (refs[upload_ref_name]) {
console.log("Upload already in progress for ", upload_id);
@ -924,6 +921,18 @@ export const atSlice = (arrayLike, slice) => {
.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.
* @param ref The ref to get the value from.

View File

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

View File

@ -930,6 +930,7 @@ class Component(BaseComponent, ABC):
children: The children of the component.
"""
from reflex.components.base.bare import Bare
from reflex.components.base.fragment import Fragment
from reflex.components.core.cond import Cond
from reflex.components.core.foreach import Foreach
@ -960,6 +961,16 @@ class Component(BaseComponent, ABC):
validate_child(child.comp1)
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):
for cases in child.match_cases:
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"
)
if self._valid_children and child_name not in [
*self._valid_children,
*allowed_components,
]:
valid_children = 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(
[f"`{v_child}`" for v_child in self._valid_children]
)

View File

@ -175,6 +175,8 @@ class ConnectionBanner(Component):
Returns:
The connection banner component.
"""
from reflex.components.base.bare import Bare
if not comp:
comp = Flex.create(
Text.create(
@ -189,7 +191,7 @@ class ConnectionBanner(Component):
position="fixed",
)
return cond(has_connection_errors, comp)
return Bare.create(cond(has_connection_errors, comp))
class ConnectionModal(Component):
@ -205,18 +207,22 @@ class ConnectionModal(Component):
Returns:
The connection banner component.
"""
from reflex.components.base.bare import Bare
if not comp:
comp = Text.create(*default_connection_error())
return cond(
has_too_many_connection_errors,
DialogRoot.create(
DialogContent.create(
DialogTitle.create("Connection Error"),
comp,
return Bare.create(
cond(
has_too_many_connection_errors,
DialogRoot.create(
DialogContent.create(
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.types import safe_issubclass
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
_IS_TRUE_IMPORT: ImportDict = {
@ -150,12 +151,23 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
if c2 is None:
raise ValueError("For conditional vars, the second argument must be set.")
c1 = Var.create(c1)
c2 = Var.create(c2)
# Create the conditional var.
return ternary_operation(
cond_var.bool(),
c1,
c2,
)
ArgsFunctionOperation.create(
(),
c1,
_var_type=ReflexCallable[[], c1._var_type],
),
ArgsFunctionOperation.create(
(),
c2,
_var_type=ReflexCallable[[], c2._var_type],
),
).call()
@overload

View File

@ -184,11 +184,17 @@ class Match(MemoizationLeaf):
return_type = Var
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(
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" 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

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
# 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
console.deprecate(
feature_name="theme prop as string",

View File

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

View File

@ -850,6 +850,22 @@ def safe_issubclass(cls: Any, class_or_tuple: Any, /) -> bool:
) 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:
"""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.base import Base
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 (
VarAttributeError,
VarDependencyError,
@ -72,6 +72,7 @@ from reflex.utils.types import (
_isinstance,
get_origin,
has_args,
infallible_issubclass,
typehint_issubclass,
unionize,
)
@ -125,8 +126,25 @@ def unwrap_reflex_callalbe(
"""
if callable_type is ReflexCallable:
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
args = get_args(callable_type)
if not args or len(args) != 2:
return Ellipsis, Any
@ -143,6 +161,7 @@ class VarSubclassEntry:
var_subclass: Type[Var]
to_var_subclass: Type[ToOperation]
python_types: Tuple[GenericType, ...]
is_subclass: Callable[[GenericType], bool] | None
_var_subclasses: List[VarSubclassEntry] = []
@ -208,7 +227,7 @@ class VarData:
object.__setattr__(self, "imports", immutable_imports)
object.__setattr__(self, "hooks", tuple(hooks or {}))
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, "position", position or None)
@ -444,6 +463,7 @@ class Var(Generic[VAR_TYPE]):
cls,
python_types: Tuple[GenericType, ...] | GenericType = types.Unset(),
default_type: GenericType = types.Unset(),
is_subclass: Callable[[GenericType], bool] | types.Unset = types.Unset(),
**kwargs,
):
"""Initialize the subclass.
@ -451,11 +471,12 @@ class Var(Generic[VAR_TYPE]):
Args:
python_types: The python types that the var represents.
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.
"""
super().__init_subclass__(**kwargs)
if python_types or default_type:
if python_types or default_type or is_subclass:
python_types = (
(python_types if isinstance(python_types, tuple) else (python_types,))
if python_types
@ -480,7 +501,14 @@ class Var(Generic[VAR_TYPE]):
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):
"""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.
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)
if fixed_output_type is None:
@ -801,12 +834,13 @@ class Var(Generic[VAR_TYPE]):
Raises:
TypeError: If the type is not supported for guessing.
"""
from .number import NumberVar
from .object import ObjectVar
var_type = self._var_type
if var_type is None:
return self.to(None)
if types.is_optional(var_type):
var_type = types.get_args(var_type)[0]
@ -818,10 +852,15 @@ class Var(Generic[VAR_TYPE]):
if fixed_type in types.UnionTypes:
inner_types = get_args(var_type)
if all(
inspect.isclass(t) and issubclass(t, (int, float)) for t in inner_types
):
return self.to(NumberVar, self._var_type)
for var_subclass in _var_subclasses:
if all(
(
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):
return self.to(ObjectVar, self._var_type)
@ -839,7 +878,9 @@ class Var(Generic[VAR_TYPE]):
return self.to(None)
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)
if can_use_in_object_var(fixed_type):
@ -1799,6 +1840,7 @@ def var_operation(
),
function_name=func_name,
type_computer=custom_operation_return._type_computer,
_raw_js_function=custom_operation_return._raw_js_function,
_var_type=ReflexCallable[
tuple(
arg_python_type
@ -2541,15 +2583,17 @@ RETURN = TypeVar("RETURN")
class CustomVarOperationReturn(Var[RETURN]):
"""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
def create(
cls,
js_expression: str,
_var_type: Type[RETURN] | None = None,
_type_computer: Optional[TypeComputer] = None,
_type_computer: TypeComputer | None = None,
_var_data: VarData | None = None,
_raw_js_function: str | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Create a CustomVarOperation.
@ -2558,6 +2602,7 @@ class CustomVarOperationReturn(Var[RETURN]):
_var_type: The type of the var.
_type_computer: A function to compute the type of the var given the arguments.
_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:
The CustomVarOperation.
@ -2567,6 +2612,7 @@ class CustomVarOperationReturn(Var[RETURN]):
_var_type=_var_type or Any,
_type_computer=_type_computer,
_var_data=_var_data,
_raw_js_function=_raw_js_function,
)
@ -2575,6 +2621,7 @@ def var_operation_return(
var_type: Type[RETURN] | None = None,
type_computer: Optional[TypeComputer] = None,
var_data: VarData | None = None,
_raw_js_function: str | None = None,
) -> CustomVarOperationReturn[RETURN]:
"""Shortcut for creating a CustomVarOperationReturn.
@ -2583,6 +2630,7 @@ def var_operation_return(
var_type: The type of the var.
type_computer: A function to compute the type of the var given the arguments.
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:
The CustomVarOperationReturn.
@ -2592,6 +2640,7 @@ def var_operation_return(
_var_type=var_type,
_type_computer=type_computer,
_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."""
@overload
@ -304,15 +320,27 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
if arg_len is not None:
if len(args) < required_arg_len:
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:
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))
self._pre_check(*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()
def chain(
@ -412,7 +440,7 @@ class FunctionVar(Var[CALLABLE_TYPE], default_type=ReflexCallable[Any, Any]):
Returns:
True if the function can be called with the given arguments.
"""
return tuple()
return ()
@overload
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())
+ (
f" = {str(default_value.default)}"
f" = {default_value.default!s}"
if i < len(self._default_values)
and not isinstance(
(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
if arg_name is not None:
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(
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) :]
@ -679,6 +707,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
_function_name: str = dataclasses.field(default="")
_type_computer: Optional[TypeComputer] = dataclasses.field(default=None)
_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)
@ -698,6 +727,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
function_name: str = "",
explicit_return: bool = False,
type_computer: Optional[TypeComputer] = None,
_raw_js_function: str | None = None,
_var_type: GenericType = Callable,
_var_data: VarData | None = None,
):
@ -712,6 +742,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
function_name: The name of the function.
explicit_return: Whether to use explicit return syntax.
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_data: Additional hooks and imports associated with the Var.
@ -723,6 +754,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar[CALLABLE_TYPE]):
_var_type=_var_type,
_var_data=_var_data,
_args=FunctionArgs(args=tuple(args_names), rest=rest),
_raw_js_function=_raw_js_function,
_default_values=tuple(default_values),
_function_name=function_name,
_validators=tuple(validators),
@ -753,6 +785,7 @@ class ArgsFunctionOperationBuilder(
_function_name: str = dataclasses.field(default="")
_type_computer: Optional[TypeComputer] = dataclasses.field(default=None)
_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)
@ -772,6 +805,7 @@ class ArgsFunctionOperationBuilder(
function_name: str = "",
explicit_return: bool = False,
type_computer: Optional[TypeComputer] = None,
_raw_js_function: str | None = None,
_var_type: GenericType = Callable,
_var_data: VarData | None = None,
):
@ -788,6 +822,7 @@ class ArgsFunctionOperationBuilder(
type_computer: A function to compute the return type.
_var_type: The type of 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:
The function var.
@ -797,6 +832,7 @@ class ArgsFunctionOperationBuilder(
_var_type=_var_type,
_var_data=_var_data,
_args=FunctionArgs(args=tuple(args_names), rest=rest),
_raw_js_function=_raw_js_function,
_default_values=tuple(default_values),
_function_name=function_name,
_validators=tuple(validators),

View File

@ -530,7 +530,9 @@ def number_abs_operation(
The number absolute operation.
"""
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:
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
@ -763,7 +769,9 @@ def boolean_to_number_operation(value: Var[bool]):
Returns:
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(
@ -1002,6 +1010,10 @@ _AT_SLICE_IMPORT: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSlice")],
}
_AT_SLICE_OR_INDEX: ImportDict = {
f"$/{Dirs.STATE_PATH}": [ImportVar(tag="atSliceOrIndex")],
}
_RANGE_IMPORT: ImportDict = {
f"$/{Dirs.UTILS}/helpers/range": [ImportVar(tag="range", is_default=True)],
}
@ -1021,6 +1033,7 @@ def boolify(value: Var):
js_expression=f"isTrue({value})",
var_type=bool,
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(
js_expression=f"Object.keys({value})",
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()],
),
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()]],
),
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.utils.exceptions import VarTypeError
from reflex.utils.types import GenericType, get_origin
from .base import (
from reflex.vars.base import (
CachedVarOperation,
CustomVarOperationReturn,
LiteralVar,
@ -51,8 +50,10 @@ from .base import (
var_operation,
var_operation_return,
)
from .number import (
_AT_SLICE_IMPORT,
_AT_SLICE_OR_INDEX,
_IS_TRUE_IMPORT,
_RANGE_IMPORT,
LiteralNumberVar,
@ -88,7 +89,7 @@ def string_lt_operation(lhs: Var[str], rhs: Var[str]):
Returns:
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
@ -102,7 +103,7 @@ def string_gt_operation(lhs: Var[str], rhs: Var[str]):
Returns:
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
@ -116,7 +117,7 @@ def string_le_operation(lhs: Var[str], rhs: Var[str]):
Returns:
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
@ -130,7 +131,7 @@ def string_ge_operation(lhs: Var[str], rhs: Var[str]):
Returns:
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
@ -143,7 +144,11 @@ def string_lower_operation(string: Var[str]):
Returns:
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
@ -156,7 +161,11 @@ def string_upper_operation(string: Var[str]):
Returns:
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
@ -169,7 +178,11 @@ def string_strip_operation(string: Var[str]):
Returns:
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
@ -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)
@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
def string_replace_operation(
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.
"""
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(
ReflexCallable[[Sequence, Union[int, slice]], Any],
ReflexCallable[[Union[int, slice]], Any],
@ -465,7 +532,7 @@ def array_item_or_slice_operation(
),
),
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
__getitem__ = string_item_operation
__getitem__ = string_index_or_slice_operation
at = string_item_operation
slice = string_slice_operation
lower = string_lower_operation

View File

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

View File

@ -846,7 +846,7 @@ def test_component_event_trigger_arbitrary_args():
assert comp.render()["props"][0] == (
"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], ({ }))))}"
)

View File

@ -432,12 +432,15 @@ def test_default_setters(test_state):
def test_class_indexing_with_vars():
"""Test that we can index into a state var with another var."""
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]
assert (
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]

View File

@ -307,30 +307,65 @@ def test_basic_operations(TestObj):
Args:
TestObj: The test object.
"""
assert str(v(1) == v(2)) == "(1 === 2)"
assert str(v(1) != v(2)) == "(1 !== 2)"
assert str(LiteralNumberVar.create(1) < 2) == "(1 < 2)"
assert str(LiteralNumberVar.create(1) <= 2) == "(1 <= 2)"
assert str(LiteralNumberVar.create(1) > 2) == "(1 > 2)"
assert str(LiteralNumberVar.create(1) >= 2) == "(1 >= 2)"
assert str(LiteralNumberVar.create(1) + 2) == "(1 + 2)"
assert str(LiteralNumberVar.create(1) - 2) == "(1 - 2)"
assert str(LiteralNumberVar.create(1) * 2) == "(1 * 2)"
assert str(LiteralNumberVar.create(1) / 2) == "(1 / 2)"
assert str(LiteralNumberVar.create(1) // 2) == "Math.floor(1 / 2)"
assert str(LiteralNumberVar.create(1) % 2) == "(1 % 2)"
assert str(LiteralNumberVar.create(1) ** 2) == "(1 ** 2)"
assert str(LiteralNumberVar.create(1) & v(2)) == "(1 && 2)"
assert str(LiteralNumberVar.create(1) | v(2)) == "(1 || 2)"
assert str(LiteralArrayVar.create([1, 2, 3])[0]) == "[1, 2, 3].at(0)"
assert str(v(1) == v(2)) == "(((_lhs, _rhs) => (_lhs === _rhs))(1, 2))"
assert str(v(1) != v(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) => (_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) => (_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 (
str(LiteralObjectVar.create({"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 (
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"))
assert (
@ -338,33 +373,39 @@ def test_basic_operations(TestObj):
Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar
== LiteralVar.create("bar")
)
== '(state.foo["bar"] === "bar")'
== '(((_lhs, _rhs) => (_lhs === _rhs))(state.foo["bar"], "bar"))'
)
assert (
str(Var(_js_expr="foo").to(ObjectVar, TestObj)._var_set_state("state").bar)
== 'state.foo["bar"]'
)
assert str(abs(LiteralNumberVar.create(1))) == "Math.abs(1)"
assert str(LiteralArrayVar.create([1, 2, 3]).length()) == "[1, 2, 3].length"
assert str(abs(LiteralNumberVar.create(1))) == "(Math.abs(1))"
assert (
str(LiteralArrayVar.create([1, 2, 3]).length())
== "(((...args) => (((_array) => _array.length)([1, 2, 3], ...args)))())"
)
assert (
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
assert (
str(LiteralArrayVar.create([1, 2, 3]).reverse())
== "[1, 2, 3].slice().reverse()"
== "(((...args) => (((_array) => _array.slice().reverse())([1, 2, 3], ...args)))())"
)
assert (
str(LiteralArrayVar.create(["1", "2", "3"]).reverse())
== '["1", "2", "3"].slice().reverse()'
== '(((...args) => (((_array) => _array.slice().reverse())(["1", "2", "3"], ...args)))())'
)
assert (
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))"
@ -389,14 +430,32 @@ def test_basic_operations(TestObj):
],
)
def test_list_tuple_contains(var, expected):
assert str(var.contains(1)) == f"{expected}.includes(1)"
assert str(var.contains("1")) == f'{expected}.includes("1")'
assert str(var.contains(v(1))) == f"{expected}.includes(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("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_var = Var(_js_expr="other", _var_type=str)
assert str(var.contains(other_state_var)) == f"{expected}.includes(state.other)"
assert str(var.contains(other_var)) == f"{expected}.includes(other)"
assert (
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):
@ -446,15 +505,27 @@ def test_var_types(var, var_type):
],
)
def test_str_contains(var, expected):
assert str(var.contains("1")) == f'{expected}.includes("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"))'
)
other_state_var = Var(_js_expr="other")._var_set_state("state").to(str)
other_var = Var(_js_expr="other").to(str)
assert str(var.contains(other_state_var)) == f"{expected}.includes(state.other)"
assert str(var.contains(other_var)) == f"{expected}.includes(other)"
assert (
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 (
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):
assert str(var.contains(1)) == f"{expected}.hasOwnProperty(1)"
assert str(var.contains("1")) == f'{expected}.hasOwnProperty("1")'
assert str(var.contains(v(1))) == f"{expected}.hasOwnProperty(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("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_var = Var(_js_expr="other").to(str)
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(
@ -484,7 +571,6 @@ def test_dict_contains(var, expected):
[
Var(_js_expr="list", _var_type=List[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):
@ -494,11 +580,20 @@ def test_var_indexing_lists(var):
var : The str, list or tuple base var.
"""
# Test basic indexing.
assert str(var[0]) == f"{var._js_expr}.at(0)"
assert str(var[1]) == f"{var._js_expr}.at(1)"
assert (
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.
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(
@ -532,11 +627,20 @@ def test_var_indexing_str():
assert str_var[0]._var_type is str
# Test basic indexing.
assert str(str_var[0]) == "str.at(0)"
assert str(str_var[1]) == "str.at(1)"
assert (
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.
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(
@ -651,9 +755,18 @@ def test_var_list_slicing(var):
Args:
var : The str, list or tuple base var.
"""
assert str(var[:1]) == f"{var._js_expr}.slice(undefined, 1)"
assert str(var[1:]) == f"{var._js_expr}.slice(1, undefined)"
assert str(var[:]) == f"{var._js_expr}.slice(undefined, undefined)"
assert (
str(var[:1])
== 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():
@ -665,16 +778,40 @@ def test_str_var_slicing():
assert str_var[:1]._var_type is str
# Test basic slicing.
assert str(str_var[:1]) == 'str.split("").slice(undefined, 1).join("")'
assert str(str_var[1:]) == 'str.split("").slice(1, undefined).join("")'
assert str(str_var[:]) == 'str.split("").slice(undefined, undefined).join("")'
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)))([null, 1, null]))"
)
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.
assert str(str_var[:-1]) == 'str.split("").slice(undefined, -1).join("")'
assert str(str_var[-1:]) == 'str.split("").slice(-1, undefined).join("")'
assert str(str_var[:-2]) == 'str.split("").slice(undefined, -2).join("")'
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)))([null, -1, null]))"
)
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():
@ -966,8 +1103,8 @@ def test_var_operation():
def add(a: Var[int], b: Var[int]):
return var_operation_return(js_expression=f"({a} + {b})", var_type=int)
assert str(add(1, 2)) == "(1 + 2)"
assert str(add(4, -9)) == "(4 + -9)"
assert str(add(1, 2)) == "(((_a, _b) => (_a + _b))(1, 2))"
assert str(add(4, -9)) == "(((_a, _b) => (_a + _b))(4, -9))"
five = LiteralNumberVar.create(5)
seven = add(2, five)
@ -978,13 +1115,29 @@ def test_var_operation():
def test_string_operations():
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 (
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 (
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 = ~(
@ -1004,14 +1157,20 @@ def test_all_number_operations():
assert (
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 str(LiteralBooleanVar.create(False) < 5) == "(Number(false) < 5)"
assert (
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 (
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(True), "true"),
(Var.create("false"), 'isTrue("false")'),
(Var.create([1, 2, 3]), "isTrue([1, 2, 3])"),
(Var.create({"a": 1, "b": 2}), 'isTrue(({ ["a"] : 1, ["b"] : 2 }))'),
(Var("mysterious_var"), "isTrue(mysterious_var)"),
(Var.create("false"), '(isTrue("false"))'),
(Var.create([1, 2, 3]), "(isTrue([1, 2, 3]))"),
(Var.create({"a": 1, "b": 2}), '(isTrue(({ ["a"] : 1, ["b"] : 2 })))'),
(Var("mysterious_var"), "(isTrue(mysterious_var))"),
],
)
def test_boolify_operations(var, expected):
@ -1032,18 +1191,30 @@ def test_boolify_operations(var, expected):
def test_index_operation():
array_var = LiteralArrayVar.create([1, 2, 3, 4, 5])
assert str(array_var[0]) == "[1, 2, 3, 4, 5].at(0)"
assert str(array_var[1:2]) == "[1, 2, 3, 4, 5].slice(1, 2)"
assert (
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 (
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 (
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(
@ -1065,24 +1236,33 @@ def test_inf_and_nan(var, expected_js):
def test_array_operations():
array_var = LiteralArrayVar.create([1, 2, 3, 4, 5])
assert str(array_var.length()) == "[1, 2, 3, 4, 5].length"
assert str(array_var.contains(3)) == "[1, 2, 3, 4, 5].includes(3)"
assert str(array_var.reverse()) == "[1, 2, 3, 4, 5].slice().reverse()"
assert (
str(array_var.length())
== "(((...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 (
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 (
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 (
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 (
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})
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 (
str(object_var.values())
== 'Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 }))'
== '(Object.values(({ ["a"] : 1, ["b"] : 2, ["c"] : 3 })))'
)
assert (
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.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 (
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 (
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 (
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():
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 (
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 (
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("5"),
[
"+",
"-",
"/",
"//",
"*",
"%",
"**",
">",
"<",
"<=",
">=",
"^",
"<<",
">>",