reflex/tests/components/core/test_match.py
Khaleel Al-Adhami a5c73ad8e5
Use old serializer system in LiteralVar (#3875)
* use serializer system

* add checks for unsupported operands

* and and or are now supported

* format

* remove unnecessary call to JSON

* put base before rest

* fix failing testcase

* add hinting to get static analysis to complain

* damn

* big changes

* get typeguard from extensions

* please darglint

* dangit darglint

* remove one from vars

* add without data and use it in plotly

* DARGLINT

* change format for special props

* add pyi

* delete instances of Var.create

* modify client state to work

* fixed so much

* remove every Var.create

* delete all basevar stuff

* checkpoint

* fix pyi

* get older python to work

* dangit darglint

* add simple fix to last failing testcase

* remove var name unwrapped and put client state on immutable var

* fix older python

* fox event issues

* change forms pyi

* make test less strict

* use rx state directly

* add typeignore to page_id

* implement foreach

* delete .web states folder silly

* update reflex chakra

* fix issue when on mount or on unmount is not set

* nuke Var

* run pyi

* import immutablevar in critical location

* delete unwrap vars

* bring back array ref

* fix style props in app

* /health endpoint for K8 Liveness and Readiness probes (#3855)

* Added API Endpoint

* Added API Endpoint

* Added Unit Tests

* Added Unit Tests

* main

* Apply suggestions from Code Review

* Fix Ruff Formatting

* Update Socket Events

* Async Functions

* Update find_replace (#3886)

* [REF-3592]Promote `rx.progress` from radix themes (#3878)

* Promote `rx.progress` from radix themes

* fix pyi

* add warning when accessing `rx._x.progress`

* Use correct flexgen backend URL (#3891)

* Remove demo template (#3888)

* gitignore .web (#3885)

* update overflowY in AUTO_HEIGHT_JS from hidden to scroll (#3882)

* Retain mutability inside `async with self` block (#3884)

When emitting a state update, restore `_self_mutable` to the value it had
previously so that `yield` in the middle of `async with self` does not result
in an immutable StateProxy.

Fix #3869

* Include child imports in markdown component_map (#3883)

If a component in the markdown component_map contains children components, use
`_get_all_imports` to recursively enumerate them.

Fix #3880

* [REF-3570] Remove deprecated REDIS_URL syntax (#3892)

* mixin computed vars should only be applied to highest level state (#3833)

* improve state hierarchy validation, drop old testing special case (#3894)

* fix var dependency dicts (#3842)

* Adding array to array pluck operation. (#3868)

* fix initial state without cv fallback (#3670)

* add fragment to foreach (#3877)

* Update docker-example (#3324)

* /health endpoint for K8 Liveness and Readiness probes (#3855)

* Added API Endpoint

* Added API Endpoint

* Added Unit Tests

* Added Unit Tests

* main

* Apply suggestions from Code Review

* Fix Ruff Formatting

* Update Socket Events

* Async Functions

* /health endpoint for K8 Liveness and Readiness probes (#3855)

* Added API Endpoint

* Added API Endpoint

* Added Unit Tests

* Added Unit Tests

* main

* Apply suggestions from Code Review

* Fix Ruff Formatting

* Update Socket Events

* Async Functions

* Merge branch 'main' into use-old-serializer-in-literalvar

* [REF-3570] Remove deprecated REDIS_URL syntax (#3892)

* /health endpoint for K8 Liveness and Readiness probes (#3855)

* Added API Endpoint

* Added API Endpoint

* Added Unit Tests

* Added Unit Tests

* main

* Apply suggestions from Code Review

* Fix Ruff Formatting

* Update Socket Events

* Async Functions

* [REF-3570] Remove deprecated REDIS_URL syntax (#3892)

* remove extra var

Co-authored-by: Masen Furer <m_github@0x26.net>

* resolve typo

* write better doc for var.create

* return var value when we know it's literal var

* fix unit test

* less bloat for ToOperations

* simplify ImmutableComputedVar.__get__ (#3902)

* simplify ImmutableComputedVar.__get__

* ruff it

---------

Co-authored-by: Samarth Bhadane <samarthbhadane119@gmail.com>
Co-authored-by: Elijah Ahianyo <elijahahianyo@gmail.com>
Co-authored-by: Masen Furer <m_github@0x26.net>
Co-authored-by: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com>
Co-authored-by: Vishnu Deva <vishnu.deva12@gmail.com>
Co-authored-by: abulvenz <a.eismann@senbax.de>
2024-09-10 11:43:37 -07:00

314 lines
12 KiB
Python

from typing import Dict, List, Tuple
import pytest
import reflex as rx
from reflex.components.core.match import Match
from reflex.ivars.base import ImmutableVar
from reflex.state import BaseState
from reflex.utils.exceptions import MatchTypeError
class MatchState(BaseState):
"""A test state."""
value: int = 0
num: int = 5
string: str = "random string"
def test_match_components():
"""Test matching cases with return values as components."""
match_case_tuples = (
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2], rx.text("third value")),
("random", rx.text("fourth value")),
({"foo": "bar"}, rx.text("fifth value")),
(MatchState.num + 1, rx.text("sixth value")),
rx.text("default value"),
)
match_comp = Match.create(MatchState.value, *match_case_tuples)
match_dict = match_comp.render() # type: ignore
assert match_dict["name"] == "Fragment"
[match_child] = match_dict["children"]
assert match_child["name"] == "match"
assert str(match_child["cond"]) == f"{MatchState.get_name()}.value"
match_cases = match_child["match_cases"]
assert len(match_cases) == 6
assert match_cases[0][0]._var_name == "1"
assert match_cases[0][0]._var_type == int
first_return_value_render = match_cases[0][1].render()
assert first_return_value_render["name"] == "RadixThemesText"
assert first_return_value_render["children"][0]["contents"] == '{"first value"}'
assert match_cases[1][0]._var_name == "2"
assert match_cases[1][0]._var_type == int
assert match_cases[1][1]._var_name == "3"
assert match_cases[1][1]._var_type == int
second_return_value_render = match_cases[1][2].render()
assert second_return_value_render["name"] == "RadixThemesText"
assert second_return_value_render["children"][0]["contents"] == '{"second value"}'
assert match_cases[2][0]._var_name == "[1, 2]"
assert match_cases[2][0]._var_type == List[int]
third_return_value_render = match_cases[2][1].render()
assert third_return_value_render["name"] == "RadixThemesText"
assert third_return_value_render["children"][0]["contents"] == '{"third value"}'
assert match_cases[3][0]._var_name == '"random"'
assert match_cases[3][0]._var_type == str
fourth_return_value_render = match_cases[3][1].render()
assert fourth_return_value_render["name"] == "RadixThemesText"
assert fourth_return_value_render["children"][0]["contents"] == '{"fourth value"}'
assert match_cases[4][0]._var_name == '({ ["foo"] : "bar" })'
assert match_cases[4][0]._var_type == Dict[str, str]
fifth_return_value_render = match_cases[4][1].render()
assert fifth_return_value_render["name"] == "RadixThemesText"
assert fifth_return_value_render["children"][0]["contents"] == '{"fifth value"}'
assert match_cases[5][0]._var_name == f"({MatchState.get_name()}.num + 1)"
assert match_cases[5][0]._var_type == int
fifth_return_value_render = match_cases[5][1].render()
assert fifth_return_value_render["name"] == "RadixThemesText"
assert fifth_return_value_render["children"][0]["contents"] == '{"sixth value"}'
default = match_child["default"].render()
assert default["name"] == "RadixThemesText"
assert default["children"][0]["contents"] == '{"default value"}'
@pytest.mark.parametrize(
"cases, expected",
[
(
(
(1, "first"),
(2, 3, "second value"),
([1, 2], "third-value"),
("random", "fourth_value"),
({"foo": "bar"}, "fifth value"),
(MatchState.num + 1, "sixth value"),
(f"{MatchState.value} - string", MatchState.string),
(MatchState.string, f"{MatchState.value} - string"),
"default value",
),
f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return '
'("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): '
'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); '
f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): '
f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: '
'return ("default value"); break;};})()',
),
(
(
(1, "first"),
(2, 3, "second value"),
([1, 2], "third-value"),
("random", "fourth_value"),
({"foo": "bar"}, "fifth value"),
(MatchState.num + 1, "sixth value"),
(f"{MatchState.value} - string", MatchState.string),
(MatchState.string, f"{MatchState.value} - string"),
MatchState.string,
),
f'(() => {{ switch (JSON.stringify({MatchState.get_name()}.value)) {{case JSON.stringify(1): return ("first"); break;case JSON.stringify(2): case JSON.stringify(3): return '
'("second value"); break;case JSON.stringify([1, 2]): return ("third-value"); break;case JSON.stringify("random"): '
'return ("fourth_value"); break;case JSON.stringify(({ ["foo"] : "bar" })): return ("fifth value"); '
f'break;case JSON.stringify(({MatchState.get_name()}.num + 1)): return ("sixth value"); break;case JSON.stringify(({MatchState.get_name()}.value+" - string")): '
f'return ({MatchState.get_name()}.string); break;case JSON.stringify({MatchState.get_name()}.string): return (({MatchState.get_name()}.value+" - string")); break;default: '
f"return ({MatchState.get_name()}.string); break;}};}})()",
),
],
)
def test_match_vars(cases, expected):
"""Test matching cases with return values as Vars.
Args:
cases: The match cases.
expected: The expected var full name.
"""
match_comp = Match.create(MatchState.value, *cases)
assert isinstance(match_comp, ImmutableVar)
assert str(match_comp) == expected
def test_match_on_component_without_default():
"""Test that matching cases with return values as components returns a Fragment
as the default case if not provided.
"""
from reflex.components.base.fragment import Fragment
match_case_tuples = (
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
)
match_comp = Match.create(MatchState.value, *match_case_tuples)
default = match_comp.render()["children"][0]["default"] # type: ignore
assert isinstance(default, Fragment)
def test_match_on_var_no_default():
"""Test that an error is thrown when cases with return Values as Var do not have a default case."""
match_case_tuples = (
(1, "red"),
(2, 3, "blue"),
([1, 2], "green"),
)
with pytest.raises(
ValueError,
match="For cases with return types as Vars, a default case must be provided",
):
Match.create(MatchState.value, *match_case_tuples)
@pytest.mark.parametrize(
"match_case",
[
(
(1, "red"),
(2, 3, "blue"),
"black",
([1, 2], "green"),
),
(
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2], rx.text("third value")),
rx.text("default value"),
("random", rx.text("fourth value")),
({"foo": "bar"}, rx.text("fifth value")),
(MatchState.num + 1, rx.text("sixth value")),
),
],
)
def test_match_default_not_last_arg(match_case):
"""Test that an error is thrown when the default case is not the last arg.
Args:
match_case: The cases to match.
"""
with pytest.raises(
ValueError,
match="rx.match should have tuples of cases and a default case as the last argument.",
):
Match.create(MatchState.value, *match_case)
@pytest.mark.parametrize(
"match_case",
[
(
(1, "red"),
(2, 3, "blue"),
("green",),
"black",
),
(
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2],),
rx.text("default value"),
),
],
)
def test_match_case_tuple_elements(match_case):
"""Test that a match has at least 2 elements(a condition and a return value).
Args:
match_case: The cases to match.
"""
with pytest.raises(
ValueError,
match="A case tuple should have at least a match case element and a return value.",
):
Match.create(MatchState.value, *match_case)
@pytest.mark.parametrize(
"cases, error_msg",
[
(
(
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2], rx.text("third value")),
("random", "red"),
({"foo": "bar"}, "green"),
(MatchState.num + 1, "black"),
rx.text("default value"),
),
'Match cases should have the same return types. Case 3 with return value `"red"` of type '
"<class 'reflex.ivars.sequence.LiteralStringVar'> is not <class 'reflex.components.component.BaseComponent'>",
),
(
(
("random", "red"),
({"foo": "bar"}, "green"),
(MatchState.num + 1, "black"),
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2], rx.text("third value")),
rx.text("default value"),
),
'Match cases should have the same return types. Case 3 with return value `<RadixThemesText as={"p"}> {"first value"} </RadixThemesText>` '
"of type <class 'reflex.components.radix.themes.typography.text.Text'> is not <class 'reflex.ivars.base.ImmutableVar'>",
),
],
)
def test_match_different_return_types(cases: Tuple, error_msg: str):
"""Test that an error is thrown when the return values are of different types.
Args:
cases: The match cases.
error_msg: Expected error message.
"""
with pytest.raises(MatchTypeError, match=error_msg):
Match.create(MatchState.value, *cases)
@pytest.mark.parametrize(
"match_case",
[
(
(1, "red"),
(2, 3, "blue"),
([1, 2], "green"),
"black",
"white",
),
(
(1, rx.text("first value")),
(2, 3, rx.text("second value")),
([1, 2], rx.text("third value")),
("random", rx.text("fourth value")),
({"foo": "bar"}, rx.text("fifth value")),
(MatchState.num + 1, rx.text("sixth value")),
rx.text("default value"),
rx.text("another default value"),
),
],
)
def test_match_multiple_default_cases(match_case):
"""Test that there is only one default case.
Args:
match_case: the cases to match.
"""
with pytest.raises(ValueError, match="rx.match can only have one default case."):
Match.create(MatchState.value, *match_case)
def test_match_no_cond():
with pytest.raises(ValueError):
_ = Match.create(None)