diff options
| author | antoliny0919 <antoliny0919@gmail.com> | 2025-08-07 22:17:50 +0900 |
|---|---|---|
| committer | Jacob Walls <jacobtylerwalls@gmail.com> | 2026-02-27 07:43:45 -0500 |
| commit | 187a789f99ecbc708de517c6b54d480b68ba59fe (patch) | |
| tree | a9ad3ba592bf6c65cb14b39110adbfbaa2825a51 /tests | |
| parent | d4ab33af061c13e290b6996756b2c72578891285 (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.py | 24 | ||||
| -rw-r--r-- | tests/admin_views/admin.py | 1 | ||||
| -rw-r--r-- | tests/admin_views/models.py | 4 | ||||
| -rw-r--r-- | tests/admin_views/tests.py | 38 | ||||
| -rw-r--r-- | tests/admin_widgets/tests.py | 35 | ||||
| -rw-r--r-- | tests/auth_tests/test_templates.py | 46 | ||||
| -rw-r--r-- | tests/auth_tests/test_views.py | 3 |
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"') |
