reflex/integration/test_form_submit.py
benedikt-bartscher 3039b54a75
add module prefix to generated state names (#3214)
* add module prefix to state names

* fix state names in test_app

* update state names in test_state

* fix state names in test_var

* fix state name in test_component

* fix state names in test_format

* fix state names in test_foreach

* fix state names in test_cond

* fix state names in test_datatable

* fix state names in test_colors

* fix state names in test_script

* fix state names in test_match

* fix state name in event1 fixture

* fix pyright and darglint

* fix state names in state_tree

* fix state names in redis only test

* fix state names in test_client_storage

* fix state name in js template

* add `get_state_name` and `get_full_state_name` helpers for `AppHarness`

* fix state names in test_dynamic_routes

* use new state name helpers in test_client_storage

* fix state names in test_event_actions

* fix state names in test_event_chain

* fix state names in test_upload

* fix state name in test_login_flow

* fix state names in test_input

* fix state names in test_form_submit

* ruff

* validate state module names

* wtf is going on here?

* remove comments leftover from refactoring

* adjust new test_add_style_embedded_vars

* fix state name in state.js

* fix integration/test_client_state.py

new SessionStorage feature was added with more full state names that need to be formatted in

* fix pre-commit issues in test_client_storage.py

* adjust test_computed_vars

* adjust safe-guards

* fix redis tests with new exception state

---------

Co-authored-by: Masen Furer <m_github@0x26.net>
2024-07-11 11:13:57 -07:00

288 lines
9.6 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 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(
rx.chakra.input(
value=FormState.router.session.client_token,
is_read_only=True,
id="token",
),
eval(form_component)(
rx.vstack(
rx.chakra.input(id="name_input"),
rx.hstack(rx.chakra.pin_input(length=4, id="pin_input")),
rx.chakra.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%"),
rx.chakra.range_slider(id="range_input"),
rx.radio(["option1", "option2"], id="radio_input"),
rx.radio(FormState.var_options, id="radio_input_var"),
rx.chakra.select(["option1", "option2"], id="select_input"),
rx.chakra.select(FormState.var_options, id="select_input_var"),
rx.text_area(id="text_area_input"),
rx.chakra.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 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(
rx.chakra.input(
value=FormState.router.session.client_token,
is_read_only=True,
id="token",
),
eval(form_component)(
rx.vstack(
rx.chakra.input(name="name_input"),
rx.hstack(rx.chakra.pin_input(length=4, name="pin_input")),
rx.chakra.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%"),
rx.chakra.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"),
rx.chakra.input_group(
rx.chakra.input_left_element(rx.icon(tag="chevron_right")),
rx.chakra.input(
name="debounce_input",
debounce_timeout=0,
on_change=rx.console_log,
),
rx.chakra.input_right_element(rx.icon(tag="chevron_left")),
),
rx.chakra.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="rx.chakra.form"),
functools.partial(FormSubmitName, form_component="rx.chakra.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)
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