reflex/integration/test_form_submit.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

294 lines
9.5 KiB
Python

"""Integration tests for forms."""
import functools
import time
from typing import Generator
import pytest
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from reflex.testing import AppHarness
from reflex.utils import format
def FormSubmit(form_component):
"""App with a form using on_submit.
Args:
form_component: The str name of the form component to use.
"""
from typing import Dict, List
import reflex_chakra as rc
import reflex as rx
class FormState(rx.State):
form_data: Dict = {}
var_options: List[str] = ["option3", "option4"]
def form_submit(self, form_data: Dict):
self.form_data = form_data
app = rx.App(state=rx.State)
@app.add_page
def index():
return rx.vstack(
rc.input(
value=FormState.router.session.client_token,
is_read_only=True,
id="token",
),
eval(form_component)(
rx.vstack(
rc.input(id="name_input"),
rx.hstack(rc.pin_input(length=4, id="pin_input")),
rc.number_input(id="number_input"),
rx.checkbox(id="bool_input"),
rx.switch(id="bool_input2"),
rx.checkbox(id="bool_input3"),
rx.switch(id="bool_input4"),
rx.slider(id="slider_input", default_value=[50], width="100%"),
rc.range_slider(id="range_input"),
rx.radio(["option1", "option2"], id="radio_input"),
rx.radio(FormState.var_options, id="radio_input_var"),
rc.select(["option1", "option2"], id="select_input"),
rc.select(FormState.var_options, id="select_input_var"),
rx.text_area(id="text_area_input"),
rc.input(
id="debounce_input",
debounce_timeout=0,
on_change=rx.console_log,
),
rx.button("Submit", type_="submit"),
),
on_submit=FormState.form_submit,
custom_attrs={"action": "/invalid"},
),
rx.spacer(),
height="100vh",
)
def FormSubmitName(form_component):
"""App with a form using on_submit.
Args:
form_component: The str name of the form component to use.
"""
from typing import Dict, List
import reflex_chakra as rc
import reflex as rx
class FormState(rx.State):
form_data: Dict = {}
val: str = "foo"
options: List[str] = ["option1", "option2"]
def form_submit(self, form_data: Dict):
self.form_data = form_data
app = rx.App(state=rx.State)
@app.add_page
def index():
return rx.vstack(
rc.input(
value=FormState.router.session.client_token,
is_read_only=True,
id="token",
),
eval(form_component)(
rx.vstack(
rc.input(name="name_input"),
rx.hstack(rc.pin_input(length=4, name="pin_input")),
rc.number_input(name="number_input"),
rx.checkbox(name="bool_input"),
rx.switch(name="bool_input2"),
rx.checkbox(name="bool_input3"),
rx.switch(name="bool_input4"),
rx.slider(name="slider_input", default_value=[50], width="100%"),
rc.range_slider(name="range_input"),
rx.radio(FormState.options, name="radio_input"),
rx.select(
FormState.options,
name="select_input",
default_value=FormState.options[0],
),
rx.text_area(name="text_area_input"),
rc.input_group(
rc.input_left_element(rx.icon(tag="chevron_right")),
rc.input(
name="debounce_input",
debounce_timeout=0,
on_change=rx.console_log,
),
rc.input_right_element(rx.icon(tag="chevron_left")),
),
rc.button_group(
rx.button("Submit", type_="submit"),
rx.icon_button(FormState.val, icon=rx.icon(tag="plus")),
variant="outline",
is_attached=True,
),
),
on_submit=FormState.form_submit,
custom_attrs={"action": "/invalid"},
),
rx.spacer(),
height="100vh",
)
@pytest.fixture(
scope="module",
params=[
functools.partial(FormSubmit, form_component="rx.form.root"),
functools.partial(FormSubmitName, form_component="rx.form.root"),
functools.partial(FormSubmit, form_component="rx.el.form"),
functools.partial(FormSubmitName, form_component="rx.el.form"),
functools.partial(FormSubmit, form_component="rc.form"),
functools.partial(FormSubmitName, form_component="rc.form"),
],
ids=[
"id-radix",
"name-radix",
"id-html",
"name-html",
"id-chakra",
"name-chakra",
],
)
def form_submit(request, tmp_path_factory) -> Generator[AppHarness, None, None]:
"""Start FormSubmit app at tmp_path via AppHarness.
Args:
request: pytest request fixture
tmp_path_factory: pytest tmp_path_factory fixture
Yields:
running AppHarness instance
"""
param_id = request._pyfuncitem.callspec.id.replace("-", "_")
with AppHarness.create(
root=tmp_path_factory.mktemp("form_submit"),
app_source=request.param, # type: ignore
app_name=request.param.func.__name__ + f"_{param_id}",
) as harness:
assert harness.app_instance is not None, "app is not running"
yield harness
@pytest.fixture
def driver(form_submit: AppHarness):
"""GEt an instance of the browser open to the form_submit app.
Args:
form_submit: harness for ServerSideEvent app
Yields:
WebDriver instance.
"""
driver = form_submit.frontend()
try:
yield driver
finally:
driver.quit()
@pytest.mark.asyncio
async def test_submit(driver, form_submit: AppHarness):
"""Fill a form with various different output, submit it to backend and verify
the output.
Args:
driver: selenium WebDriver open to the app
form_submit: harness for FormSubmit app
"""
assert form_submit.app_instance is not None, "app is not running"
by = By.ID if form_submit.app_source is FormSubmit else By.NAME
# get a reference to the connected client
token_input = driver.find_element(By.ID, "token")
assert token_input
# wait for the backend connection to send the token
token = form_submit.poll_for_value(token_input)
assert token
name_input = driver.find_element(by, "name_input")
name_input.send_keys("foo")
pin_inputs = driver.find_elements(By.CLASS_NAME, "chakra-pin-input")
pin_values = ["8", "1", "6", "4"]
for i, pin_input in enumerate(pin_inputs):
pin_input.send_keys(pin_values[i])
number_input = driver.find_element(By.CLASS_NAME, "chakra-numberinput")
buttons = number_input.find_elements(By.XPATH, "//div[@role='button']")
for _ in range(3):
buttons[1].click()
checkbox_input = driver.find_element(By.XPATH, "//button[@role='checkbox']")
checkbox_input.click()
switch_input = driver.find_element(By.XPATH, "//button[@role='switch']")
switch_input.click()
radio_buttons = driver.find_elements(By.XPATH, "//button[@role='radio']")
radio_buttons[1].click()
textarea_input = driver.find_element(By.TAG_NAME, "textarea")
textarea_input.send_keys("Some", Keys.ENTER, "Text")
debounce_input = driver.find_element(by, "debounce_input")
debounce_input.send_keys("bar baz")
time.sleep(1)
prev_url = driver.current_url
submit_input = driver.find_element(By.CLASS_NAME, "rt-Button")
submit_input.click()
state_name = form_submit.get_state_name("_form_state")
full_state_name = form_submit.get_full_state_name(["_form_state"])
async def get_form_data():
return (
(await form_submit.get_state(f"{token}_{full_state_name}"))
.substates[state_name]
.form_data
)
# wait for the form data to arrive at the backend
form_data = await AppHarness._poll_for_async(get_form_data)
assert isinstance(form_data, dict)
form_data = format.collect_form_dict_names(form_data)
print(form_data)
assert form_data["name_input"] == "foo"
assert form_data["pin_input"] == pin_values
assert form_data["number_input"] == "-3"
assert form_data["bool_input"]
assert form_data["bool_input2"]
assert not form_data.get("bool_input3", False)
assert not form_data.get("bool_input4", False)
assert form_data["slider_input"] == "50"
assert form_data["range_input"] == ["25", "75"]
assert form_data["radio_input"] == "option2"
assert form_data["select_input"] == "option1"
assert form_data["text_area_input"] == "Some\nText"
assert form_data["debounce_input"] == "bar baz"
# submitting the form should NOT change the url (preventDefault on_submit event)
assert driver.current_url == prev_url