reflex/tests/components/core/test_match.py
Khaleel Al-Adhami ea15b184c0
fully migrate vars into new system (#3743)
* fully migrate vars into new system

* i hate rufffff (no i don't)

* fix silly pright issues (except colormode and state)

* remove all instances of Var.create

* create immutable callable var and get rid of more base vars

* implement hash for all functions

* get reflex-web to compile

* get it to compile reflex-web successfully

* fix tests

* fix pyi

* use override from typing_extension

* put plotly inside of a catch

* dicts are unusable sadly

* fix silly mistake

* overload equals to special case immutable var

* improve test_cond

* solve more CI issues, down to 94 failures

* down to 20 errors

* down to 13 errors

* pass all testcases

* fix pyright issues

* reorder things

* use get origin more

* use fixed_type logic

* various optimizations

* go back to passing test cases

* use less boilerplate

* remove unnecessary print message

* remove weird comment

* add test for html issue

* add type ignore

* fix another silly issue

* override get all var data for var operations call

* make integration tests pass

* fix immutable call var

* better logic for finding parent class

* use even better logic for finding state wrt computedvar

* only choose the ones that are defined in the same module

* small dict to large dict

* [REF-3591] Remove chakra-related files from immutable vars PR (#3821)

* Add comments to html metadata component (#3731)

* fix: add verification for path /404 (#3723)

Co-authored-by: coolstorm <manas.gupta@fampay.in>

* Use the new state name when setting `is_hydrated` to false (#3738)

* Use `._is_mutable()` to account for parent state proxy (#3739)

When a parent state proxy is set, also allow child StateProxy._self_mutable to
override the parent's `_is_mutable()`.

* bump to 0.5.9 (#3746)

* add message when installing requirements.txt is needed for chosen template during init (#3750)

* #3752 bugfix add domain for XAxis (#3764)

* fix appharness app_source typing (#3777)

* fix import clash between connectionToaster and hooks.useState (#3749)

* use different registry when in china, fixes #3700 (#3702)

* do not reload compilation if using local app in AppHarness (#3790)

* do not reload if using local app

* Update reflex/testing.py

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

---------

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

* Bump memory on relevant actions (#3781)

Co-authored-by: Alek Petuskey <alekpetuskey@Aleks-MacBook-Pro.local>

* [REF-3334] Validate Toast Props (#3793)

* [REF-3536][REF-3537][REF-3541] Move chakra components into its repo(reflex-chakra) (#3798)

* fix get_uuid_string_var (#3795)

* minor State cleanup (#3768)

* Fix code wrap in markdown (#3755)

---------

Co-authored-by: Alek Petuskey <alek@pynecone.io>
Co-authored-by: Manas Gupta <53006261+Manas1820@users.noreply.github.com>
Co-authored-by: coolstorm <manas.gupta@fampay.in>
Co-authored-by: Thomas Brandého <thomas.brandeho@gmail.com>
Co-authored-by: Shubhankar Dimri <dimrishubhi@gmail.com>
Co-authored-by: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com>
Co-authored-by: Khaleel Al-Adhami <khaleel.aladhami@gmail.com>
Co-authored-by: Alek Petuskey <alekpetuskey@Aleks-MacBook-Pro.local>
Co-authored-by: Elijah Ahianyo <elijahahianyo@gmail.com>

* pyproject.toml: bump to 0.6.0a1

* pyproject.toml: depend on reflex-chakra>=0.6.0a

New Var system support in reflex-chakra 0.6.0a1

* poetry.lock: relock dependencies

* integration: bump listening timeout to 1200 seconds

* integration: bump listening timeout to 1800 seconds

* Use cached_var_no_lock to avoid ImmutableVar deadlocks (#3835)

* Use cached_var_no_lock to avoid ImmutableVar deadlocks

ImmutableVar subclasses will always return the same value for a _var_name or
_get_all_var_data so there is no need to use a per-class lock to protect a
cached attribute on an instance, and doing so actually is observed to cause
deadlocks when a particular _cached_var_name creates new LiteralVar instances
and attempts to serialize them.

* remove unused module global

---------

Co-authored-by: Masen Furer <m_github@0x26.net>
Co-authored-by: Alek Petuskey <alek@pynecone.io>
Co-authored-by: Manas Gupta <53006261+Manas1820@users.noreply.github.com>
Co-authored-by: coolstorm <manas.gupta@fampay.in>
Co-authored-by: Thomas Brandého <thomas.brandeho@gmail.com>
Co-authored-by: Shubhankar Dimri <dimrishubhi@gmail.com>
Co-authored-by: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com>
Co-authored-by: Alek Petuskey <alekpetuskey@Aleks-MacBook-Pro.local>
Co-authored-by: Elijah Ahianyo <elijahahianyo@gmail.com>
2024-08-26 13:28:18 -07:00

314 lines
11 KiB
Python

from typing import Dict, List, Tuple
import pytest
import reflex as rx
from reflex.components.core.match import Match
from reflex.state import BaseState
from reflex.utils.exceptions import MatchTypeError
from reflex.vars import Var
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, Var)
assert match_comp._var_full_name == 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.vars.Var'>",
),
],
)
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)