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']")