summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorantoliny0919 <antoliny0919@gmail.com>2025-08-07 22:17:50 +0900
committerJacob Walls <jacobtylerwalls@gmail.com>2026-02-27 07:43:45 -0500
commit187a789f99ecbc708de517c6b54d480b68ba59fe (patch)
treea9ad3ba592bf6c65cb14b39110adbfbaa2825a51 /tests
parentd4ab33af061c13e290b6996756b2c72578891285 (diff)
Fixed #34643 -- Moved inputs beneath labels and errors in admin forms.
Thanks Sarah Boyce and Jacob Walls for reviews. Co-authored-by: Hrushikesh Vaidya <hrushikeshrv@gmail.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/admin_inlines/tests.py24
-rw-r--r--tests/admin_views/admin.py1
-rw-r--r--tests/admin_views/models.py4
-rw-r--r--tests/admin_views/tests.py38
-rw-r--r--tests/admin_widgets/tests.py35
-rw-r--r--tests/auth_tests/test_templates.py46
-rw-r--r--tests/auth_tests/test_views.py3
7 files changed, 131 insertions, 20 deletions
diff --git a/tests/admin_inlines/tests.py b/tests/admin_inlines/tests.py
index 6956c37740..50b3a7baba 100644
--- a/tests/admin_inlines/tests.py
+++ b/tests/admin_inlines/tests.py
@@ -384,21 +384,23 @@ class TestInline(TestDataMixin, TestCase):
response = self.client.get(url)
# The whole line containing name + position fields is not hidden.
self.assertContains(
- response, '<div class="form-row field-name field-position">'
+ response,
+ "<div class="
+ '"form-row flex-container form-multiline field-name field-position">',
)
# The div containing the position field is hidden.
self.assertInHTML(
- '<div class="flex-container fieldBox field-position hidden">'
+ '<div class="flex-container field-position fieldBox hidden">'
'<label class="inline">Position:</label>'
- '<div class="readonly">0</div></div>'
- '<div class="help hidden"><div>Position help_text.</div></div>',
+ '<div class="help hidden"><div>Position help_text.</div></div>'
+ '<div class="readonly">0</div></div>',
response.rendered_content,
)
self.assertInHTML(
- '<div class="flex-container fieldBox field-position hidden">'
+ '<div class="flex-container field-position fieldBox hidden">'
'<label class="inline">Position:</label>'
- '<div class="readonly">1</div></div>'
- '<div class="help hidden"><div>Position help_text.</div></div>',
+ '<div class="help hidden"><div>Position help_text.</div></div>'
+ '<div class="readonly">1</div></div>',
response.rendered_content,
)
@@ -419,17 +421,17 @@ class TestInline(TestDataMixin, TestCase):
# The whole line containing position field is hidden.
self.assertInHTML(
'<div class="form-row hidden field-position">'
- '<div><div class="flex-container"><label>Position:</label>'
- '<div class="readonly">0</div></div>'
+ '<div class="flex-container"><label>Position:</label>'
'<div class="help hidden"><div>Position help_text.</div></div>'
+ '<div class="readonly">0</div>'
"</div></div>",
response.rendered_content,
)
self.assertInHTML(
'<div class="form-row hidden field-position">'
- '<div><div class="flex-container"><label>Position:</label>'
- '<div class="readonly">1</div></div>'
+ '<div class="flex-container"><label>Position:</label>'
'<div class="help hidden"><div>Position help_text.</div></div>'
+ '<div class="readonly">1</div>'
"</div></div>",
response.rendered_content,
)
diff --git a/tests/admin_views/admin.py b/tests/admin_views/admin.py
index 6f7cd79e50..0f05a66746 100644
--- a/tests/admin_views/admin.py
+++ b/tests/admin_views/admin.py
@@ -498,6 +498,7 @@ class PictureAdmin(admin.ModelAdmin):
class LanguageAdmin(admin.ModelAdmin):
list_display = ["iso", "shortlist", "english_name", "name"]
list_editable = ["shortlist"]
+ fields = [("iso", "english_name"), "name"]
class RecommendationAdmin(admin.ModelAdmin):
diff --git a/tests/admin_views/models.py b/tests/admin_views/models.py
index 5191d1605e..38e26cb95a 100644
--- a/tests/admin_views/models.py
+++ b/tests/admin_views/models.py
@@ -407,8 +407,8 @@ class Picture(models.Model):
class Language(models.Model):
- iso = models.CharField(max_length=5, primary_key=True)
- name = models.CharField(max_length=50)
+ iso = models.CharField(max_length=5, primary_key=True, help_text="iso helptext")
+ name = models.CharField(max_length=50, help_text="name helptext")
english_name = models.CharField(max_length=50)
shortlist = models.BooleanField(default=False)
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 4489dc9950..ac4265c4b4 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -7311,12 +7311,10 @@ class SeleniumTests(AdminSeleniumTestCase):
By.CSS_SELECTOR, "#content-main .field-difficulty, .form-multiline"
)
# Two field boxes.
- field_boxes = multiline.find_elements(By.CSS_SELECTOR, "div > div.fieldBox")
+ field_boxes = multiline.find_elements(By.XPATH, "./*")
self.assertEqual(len(field_boxes), 2)
# One of them is under a <fieldset>.
- under_fieldset = multiline.find_elements(
- By.CSS_SELECTOR, "fieldset > div > div.fieldBox"
- )
+ under_fieldset = multiline.find_elements(By.TAG_NAME, "fieldset")
self.assertEqual(len(under_fieldset), 1)
self.take_screenshot("horizontal_fieldset")
@@ -7461,6 +7459,26 @@ class SeleniumTests(AdminSeleniumTestCase):
self.assertTrue(changelist_filter.is_displayed())
self.take_screenshot("filter_sidebar")
+ @screenshot_cases(["desktop_size", "mobile_size", "rtl", "dark", "high_contrast"])
+ def test_form_errors_render_layout(self):
+ from selenium.webdriver.common.by import By
+
+ self.admin_login(
+ username="super", password="secret", login_url=reverse("admin:index")
+ )
+ self.selenium.get(
+ self.live_server_url + reverse("admin:admin_views_language_add")
+ )
+
+ with self.wait_page_loaded():
+ self.selenium.find_element(By.NAME, "_save").click()
+
+ form_rows = self.selenium.find_elements(By.CSS_SELECTOR, "div.form-row")
+ for row in form_rows:
+ error_list = row.find_element(By.CSS_SELECTOR, "ul.errorlist")
+ self.assertTrue(error_list.is_displayed())
+ self.take_screenshot("error_list")
+
@override_settings(ROOT_URLCONF="admin_views.urls")
class ReadonlyTest(AdminFieldExtractionMixin, TestCase):
@@ -9063,10 +9081,18 @@ class TestLabelVisibility(TestCase):
)
def assert_fieldline_visible(self, response):
- self.assertContains(response, '<div class="form-row field-first field-second">')
+ self.assertContains(
+ response,
+ "<div class="
+ '"form-row flex-container form-multiline field-first field-second">',
+ )
def assert_fieldline_hidden(self, response):
- self.assertContains(response, '<div class="form-row hidden')
+ self.assertContains(
+ response,
+ "<div class="
+ '"form-row flex-container form-multiline hidden field-first field-second">',
+ )
@override_settings(ROOT_URLCONF="admin_views.urls")
diff --git a/tests/admin_widgets/tests.py b/tests/admin_widgets/tests.py
index e0ae5b7747..5a8b29b83a 100644
--- a/tests/admin_widgets/tests.py
+++ b/tests/admin_widgets/tests.py
@@ -1203,6 +1203,41 @@ class DateTimePickerSeleniumTests(AdminWidgetSeleniumTestCase):
# The right month and year are displayed.
self.wait_for_text("#calendarin0 caption", expected_caption)
+ @override_settings(TIME_ZONE="Asia/Seoul")
+ def test_timezone_warning_message(self):
+ from selenium.webdriver.common.by import By
+
+ self.admin_login(username="super", password="secret", login_url="/")
+
+ self.selenium.get(
+ self.live_server_url + reverse("admin:admin_widgets_member_add")
+ )
+
+ datetime = self.selenium.find_element(By.CSS_SELECTOR, "p.datetime")
+ warnings = self.selenium.find_elements(
+ By.CSS_SELECTOR, "div.field-birthdate div.timezonewarning"
+ )
+ self.assertEqual(len(warnings), 1)
+
+ warning = warnings[0]
+ self.assertTrue(warning.is_displayed())
+ next_element = warning.find_element(By.XPATH, "./following-sibling::*[1]")
+ # Warning messages are generally located just above the field block.
+ self.assertEqual(next_element, datetime)
+
+ date = datetime.find_element(By.TAG_NAME, "input")
+ date.send_keys("invalid")
+ with self.wait_page_loaded():
+ self.selenium.find_element(By.NAME, "_save").click()
+
+ errors = self.selenium.find_element(By.ID, "id_birthdate_error")
+ warning = self.selenium.find_element(
+ By.CSS_SELECTOR, "div.help.timezonewarning"
+ )
+ next_element = warning.find_element(By.XPATH, "./following-sibling::*[1]")
+ # warning message appears above the error message.
+ self.assertEqual(next_element, errors)
+
@requires_tz_support
@override_settings(TIME_ZONE="Asia/Singapore")
diff --git a/tests/auth_tests/test_templates.py b/tests/auth_tests/test_templates.py
index edde6ca6b4..775409af59 100644
--- a/tests/auth_tests/test_templates.py
+++ b/tests/auth_tests/test_templates.py
@@ -37,6 +37,20 @@ class AuthTemplateTests(TestCase):
)
self.assertContains(response, "<h1>Password reset</h1>")
+ def test_password_reset_view_error_form(self):
+ response = self.client.post(reverse("password_reset"), {})
+ self.assertContains(
+ response,
+ '<div class="flex-container">'
+ '<label for="id_email">Email address:</label>'
+ '<ul class="errorlist" id="id_email_error">'
+ "<li>This field is required.</li></ul>"
+ '<input type="email" name="email" autocomplete="email" maxlength="254" '
+ 'required aria-invalid="true" aria-describedby="id_email_error" '
+ 'id="id_email"></div>',
+ html=True,
+ )
+
def test_password_reset_view_error_title(self):
response = self.client.post(reverse("password_reset"), {})
self.assertContains(
@@ -96,6 +110,38 @@ class AuthTemplateTests(TestCase):
response, "<title>Error: Enter new password | Django site admin</title>"
)
+ def test_password_reset_confirm_view_error_form(self):
+ client = PasswordResetConfirmClient()
+ default_token_generator = PasswordResetTokenGenerator()
+ token = default_token_generator.make_token(self.user)
+ uidb64 = urlsafe_base64_encode(str(self.user.pk).encode())
+ url = reverse(
+ "password_reset_confirm", kwargs={"uidb64": uidb64, "token": token}
+ )
+ response = client.post(url, {})
+ self.assertContains(
+ response,
+ '<div class="flex-container errors">'
+ '<label for="id_new_password1">New password:</label>'
+ '<ul class="errorlist" id="id_new_password1_error">'
+ "<li>This field is required.</li></ul>"
+ '<input type="password" name="new_password1" autocomplete="new-password" '
+ 'required aria-invalid="true" aria-describedby="id_new_password1_error" '
+ 'id="id_new_password1"></div>',
+ html=True,
+ )
+ self.assertContains(
+ response,
+ '<div class="flex-container errors">'
+ '<label for="id_new_password2">Confirm password:</label>'
+ '<ul class="errorlist" id="id_new_password2_error">'
+ "<li>This field is required.</li></ul>"
+ '<input type="password" name="new_password2" autocomplete="new-password" '
+ 'required aria-invalid="true" aria-describedby="id_new_password2_helptext '
+ 'id_new_password2_error" id="id_new_password2"></div>',
+ html=True,
+ )
+
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUser")
def test_password_reset_confirm_view_custom_username_hint(self):
custom_user = CustomUser.custom_objects.create_user(
diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py
index d7d59d9fc0..a3863b6233 100644
--- a/tests/auth_tests/test_views.py
+++ b/tests/auth_tests/test_views.py
@@ -1555,7 +1555,8 @@ class ChangelistTests(MessagesTestMixin, AuthViewsTestCase):
# Usable password field.
self.assertContains(
response,
- "<fieldset><legend>Password-based authentication:</legend>",
+ '<fieldset class="flex-container">'
+ "<legend>Password-based authentication:</legend>",
)
# Submit buttons
self.assertContains(response, '<input type="submit" name="set-password"')