diff --git a/tests/test_app/forms/components.py b/tests/test_app/forms/components.py
index e78cf36b..26ab8704 100644
--- a/tests/test_app/forms/components.py
+++ b/tests/test_app/forms/components.py
@@ -1,8 +1,8 @@
-from reactpy import component, html
+from reactpy import component, hooks, html
from reactpy_django.components import django_form
-from .forms import BasicForm, DatabaseBackedForm
+from .forms import BasicForm, DatabaseBackedForm, EventForm
@component
@@ -22,3 +22,73 @@ def bootstrap_form():
@component
def database_backed_form():
return django_form(DatabaseBackedForm, bottom_children=(html.input({"type": "submit"}),))
+
+
+@component
+def sync_event_form():
+ success, set_success = hooks.use_state(False)
+ error, set_error = hooks.use_state(False)
+ receive_data, set_receive_data = hooks.use_state(False)
+ change, set_change = hooks.use_state(False)
+
+ def on_success(event):
+ set_success(True)
+
+ def on_error(event):
+ set_error(True)
+
+ def on_receive_data(event):
+ set_receive_data(True)
+
+ def on_change(event):
+ set_change(True)
+
+ return django_form(
+ EventForm,
+ on_success=on_success,
+ on_error=on_error,
+ on_receive_data=on_receive_data,
+ on_change=on_change,
+ top_children=[
+ html.div({"id": "success", "data-value": success}, f"Success: {success}"),
+ html.div({"id": "error", "data-value": error}, f"Error: {error}"),
+ html.div({"id": "receive_data", "data-value": receive_data}, f"Receive Data: {receive_data}"),
+ html.div({"id": "change", "data-value": change}, f"Change: {change}"),
+ ],
+ bottom_children=[html.input({"type": "submit"})],
+ )
+
+
+@component
+def async_event_form():
+ success, set_success = hooks.use_state(False)
+ error, set_error = hooks.use_state(False)
+ receive_data, set_receive_data = hooks.use_state(False)
+ change, set_change = hooks.use_state(False)
+
+ async def on_success(event):
+ set_success(True)
+
+ async def on_error(event):
+ set_error(True)
+
+ async def on_receive_data(event):
+ set_receive_data(True)
+
+ async def on_change(event):
+ set_change(True)
+
+ return django_form(
+ EventForm,
+ on_success=on_success,
+ on_error=on_error,
+ on_receive_data=on_receive_data,
+ on_change=on_change,
+ top_children=[
+ html.div({"id": "success", "data-value": success}, f"Success: {success}"),
+ html.div({"id": "error", "data-value": error}, f"Error: {error}"),
+ html.div({"id": "receive_data", "data-value": receive_data}, f"Receive Data: {receive_data}"),
+ html.div({"id": "change", "data-value": change}, f"Change: {change}"),
+ ],
+ bottom_children=[html.input({"type": "submit"})],
+ )
diff --git a/tests/test_app/forms/forms.py b/tests/test_app/forms/forms.py
index fab8263b..6b8dedd6 100644
--- a/tests/test_app/forms/forms.py
+++ b/tests/test_app/forms/forms.py
@@ -7,7 +7,7 @@ class BasicForm(forms.Form):
# Render one of every Django field type
# https://docs.djangoproject.com/en/stable/ref/forms/fields/#field-types
boolean_field = forms.BooleanField(label="boolean")
- char_field = forms.CharField(label="chars", max_length=7)
+ char_field = forms.CharField(label="chars")
choice_field = forms.ChoiceField(label="choice", choices=[("1", "One"), ("2", "Two")])
date_field = forms.DateField(label="date")
date_time_field = forms.DateTimeField(label="date time")
@@ -44,3 +44,7 @@ class DatabaseBackedForm(forms.ModelForm):
class Meta:
model = models.TodoItem
fields = "__all__"
+
+
+class EventForm(forms.Form):
+ char_field = forms.CharField(label="chars")
diff --git a/tests/test_app/forms/urls.py b/tests/test_app/forms/urls.py
index 34cad0e9..ed93c587 100644
--- a/tests/test_app/forms/urls.py
+++ b/tests/test_app/forms/urls.py
@@ -6,4 +6,6 @@
path("form/", views.form),
path("form/bootstrap/", views.bootstrap_form),
path("form/model/", views.model_form),
+ path("form/sync_event/", views.sync_event_form),
+ path("form/async_event/", views.async_event_form),
]
diff --git a/tests/test_app/forms/views.py b/tests/test_app/forms/views.py
index c49f1120..8ee7862f 100644
--- a/tests/test_app/forms/views.py
+++ b/tests/test_app/forms/views.py
@@ -11,3 +11,11 @@ def bootstrap_form(request):
def model_form(request):
return render(request, "model_form.html", {})
+
+
+def sync_event_form(request):
+ return render(request, "sync_event_form.html", {})
+
+
+def async_event_form(request):
+ return render(request, "async_event_form.html", {})
diff --git a/tests/test_app/templates/async_event_form.html b/tests/test_app/templates/async_event_form.html
new file mode 100644
index 00000000..ac98ade6
--- /dev/null
+++ b/tests/test_app/templates/async_event_form.html
@@ -0,0 +1,26 @@
+{% load static %} {% load reactpy %}
+
+
+
+
+
+
+
+
+ ReactPy
+
+
+
+
+ ReactPy Async Event Form Test Page
+
+ {% component "test_app.forms.components.async_event_form" %}
+
+
+
+
diff --git a/tests/test_app/templates/form.html b/tests/test_app/templates/form.html
index 67ae0074..749564b3 100644
--- a/tests/test_app/templates/form.html
+++ b/tests/test_app/templates/form.html
@@ -17,7 +17,7 @@
- ReactPy Forms Test Page
+ ReactPy Form Test Page
{% component "test_app.forms.components.basic_form" %}
diff --git a/tests/test_app/templates/sync_event_form.html b/tests/test_app/templates/sync_event_form.html
new file mode 100644
index 00000000..49badba2
--- /dev/null
+++ b/tests/test_app/templates/sync_event_form.html
@@ -0,0 +1,26 @@
+{% load static %} {% load reactpy %}
+
+
+
+
+
+
+
+
+ ReactPy
+
+
+
+
+ ReactPy Sync Event Form Test Page
+
+ {% component "test_app.forms.components.sync_event_form" %}
+
+
+
+
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index d744ddfa..4c00b522 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -2,6 +2,7 @@
import os
import socket
from time import sleep
+from uuid import uuid4
import pytest
from playwright.sync_api import TimeoutError, expect
@@ -782,6 +783,7 @@ def test_bootstrap_form(self):
def test_model_form(self):
navigate_to_page(self, "/form/model/")
+ uuid = uuid4().hex
self.page.wait_for_selector("form")
sleep(1)
@@ -792,8 +794,9 @@ def test_model_form(self):
assert len(self.page.query_selector_all(".errorlist")) == 1
# Fill out the form
- self.page.locator("#id_text").type("test", delay=CLICK_DELAY)
+ self.page.locator("#id_text").type(uuid, delay=CLICK_DELAY)
+ # Submit the form
self.page.wait_for_selector("input[type=submit]").click(delay=CLICK_DELAY)
# Wait for the error message to disappear (indicating that the form has been re-rendered)
@@ -803,4 +806,75 @@ def test_model_form(self):
assert len(self.page.query_selector_all(".errorlist")) == 0
# Make sure text field is empty
+ expect(self.page.locator("#id_text")).to_be_empty()
assert self.page.locator("#id_text").get_attribute("value") == ""
+
+ # Check if `auto_save` created the TodoItem's database entry
+ try:
+ from test_app.models import TodoItem
+
+ os.environ["DJANGO_ALLOW_ASYNC_UNSAFE"] = "true"
+
+ assert TodoItem.objects.filter(text=uuid).exists()
+ finally:
+ os.environ.pop("DJANGO_ALLOW_ASYNC_UNSAFE")
+
+ def test_sync_form_events(self):
+ navigate_to_page(self, "/form/sync_event/")
+ self.page.wait_for_selector("form")
+
+ # Check initial state
+ self.page.wait_for_selector("#success[data-value='false']")
+ self.page.wait_for_selector("#error[data-value='false']")
+ self.page.wait_for_selector("#receive_data[data-value='false']")
+ self.page.wait_for_selector("#change[data-value='false']")
+
+ # Submit empty the form
+ sleep(1)
+ self.page.wait_for_selector("input[type=submit]").click(delay=CLICK_DELAY)
+
+ # The empty form was submitted, should result in an error
+ self.page.wait_for_selector("#success[data-value='false']")
+ self.page.wait_for_selector("#error[data-value='true']")
+ self.page.wait_for_selector("#receive_data[data-value='true']")
+ self.page.wait_for_selector("#change[data-value='false']")
+
+ # Fill out the form and re-submit
+ self.page.wait_for_selector("#id_char_field").type("test", delay=CLICK_DELAY)
+ self.page.wait_for_selector("input[type=submit]").click(delay=CLICK_DELAY)
+
+ # Form should have been successfully submitted
+ self.page.wait_for_selector("#success[data-value='true']")
+ self.page.wait_for_selector("#error[data-value='true']")
+ self.page.wait_for_selector("#receive_data[data-value='true']")
+ self.page.wait_for_selector("#change[data-value='true']")
+
+ def test_async_form_events(self):
+ navigate_to_page(self, "/form/async_event/")
+ self.page.wait_for_selector("form")
+
+ # Check initial state
+ self.page.wait_for_selector("#success[data-value='false']")
+ self.page.wait_for_selector("#error[data-value='false']")
+ self.page.wait_for_selector("#receive_data[data-value='false']")
+ self.page.wait_for_selector("#change[data-value='false']")
+
+ # Submit empty the form
+ sleep(1)
+ self.page.wait_for_selector("input[type=submit]").click(delay=CLICK_DELAY)
+
+ # The empty form was submitted, should result in an error
+ self.page.wait_for_selector("#success[data-value='false']")
+ self.page.wait_for_selector("#error[data-value='true']")
+ self.page.wait_for_selector("#receive_data[data-value='true']")
+ self.page.wait_for_selector("#change[data-value='false']")
+
+ # Fill out the form and re-submit
+ self.page.wait_for_selector("#id_char_field").type("test", delay=CLICK_DELAY)
+ self.page.wait_for_selector("input[type=submit]").click(delay=CLICK_DELAY)
+
+ # Form should have been successfully submitted
+ self.page.wait_for_selector("#success[data-value='true']")
+ self.page.wait_for_selector("#error[data-value='true']")
+ self.page.wait_for_selector("#receive_data[data-value='true']")
+ self.page.wait_for_selector("#change[data-value='true']")