test_nav_sidebar.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. from django.contrib import admin
  2. from django.contrib.admin.tests import AdminSeleniumTestCase
  3. from django.contrib.auth.models import User
  4. from django.test import TestCase, override_settings
  5. from django.urls import path, reverse
  6. from .models import Héllo
  7. class AdminSiteWithSidebar(admin.AdminSite):
  8. pass
  9. class AdminSiteWithoutSidebar(admin.AdminSite):
  10. enable_nav_sidebar = False
  11. site_with_sidebar = AdminSiteWithSidebar(name="test_with_sidebar")
  12. site_without_sidebar = AdminSiteWithoutSidebar(name="test_without_sidebar")
  13. site_with_sidebar.register(User)
  14. site_with_sidebar.register(Héllo)
  15. urlpatterns = [
  16. path("test_sidebar/admin/", site_with_sidebar.urls),
  17. path("test_wihout_sidebar/admin/", site_without_sidebar.urls),
  18. ]
  19. @override_settings(ROOT_URLCONF="admin_views.test_nav_sidebar")
  20. class AdminSidebarTests(TestCase):
  21. @classmethod
  22. def setUpTestData(cls):
  23. cls.superuser = User.objects.create_superuser(
  24. username="super",
  25. password="secret",
  26. email="super@example.com",
  27. )
  28. def setUp(self):
  29. self.client.force_login(self.superuser)
  30. def test_sidebar_not_on_index(self):
  31. response = self.client.get(reverse("test_with_sidebar:index"))
  32. self.assertContains(response, '<div class="main" id="main">')
  33. self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
  34. def test_sidebar_disabled(self):
  35. response = self.client.get(reverse("test_without_sidebar:index"))
  36. self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
  37. def test_sidebar_unauthenticated(self):
  38. self.client.logout()
  39. response = self.client.get(reverse("test_with_sidebar:login"))
  40. self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
  41. def test_sidebar_aria_current_page(self):
  42. url = reverse("test_with_sidebar:auth_user_changelist")
  43. response = self.client.get(url)
  44. self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
  45. self.assertContains(
  46. response, '<a href="%s" aria-current="page">Users</a>' % url
  47. )
  48. @override_settings(
  49. TEMPLATES=[
  50. {
  51. "BACKEND": "django.template.backends.django.DjangoTemplates",
  52. "DIRS": [],
  53. "APP_DIRS": True,
  54. "OPTIONS": {
  55. "context_processors": [
  56. "django.contrib.auth.context_processors.auth",
  57. "django.contrib.messages.context_processors.messages",
  58. ],
  59. },
  60. }
  61. ]
  62. )
  63. def test_sidebar_aria_current_page_missing_without_request_context_processor(self):
  64. url = reverse("test_with_sidebar:auth_user_changelist")
  65. response = self.client.get(url)
  66. self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
  67. # Does not include aria-current attribute.
  68. self.assertContains(response, '<a href="%s">Users</a>' % url)
  69. self.assertNotContains(response, "aria-current")
  70. @override_settings(DEBUG=True)
  71. def test_included_app_list_template_context_fully_set(self):
  72. # All context variables should be set when rendering the sidebar.
  73. url = reverse("test_with_sidebar:auth_user_changelist")
  74. with self.assertNoLogs("django.template", "DEBUG"):
  75. self.client.get(url)
  76. def test_sidebar_model_name_non_ascii(self):
  77. url = reverse("test_with_sidebar:admin_views_héllo_changelist")
  78. response = self.client.get(url)
  79. self.assertContains(
  80. response, '<div class="app-admin_views module current-app">'
  81. )
  82. self.assertContains(response, '<tr class="model-héllo current-model">')
  83. self.assertContains(
  84. response,
  85. '<th scope="row">'
  86. '<a href="/test_sidebar/admin/admin_views/h%C3%A9llo/" aria-current="page">'
  87. "Héllos</a></th>",
  88. )
  89. @override_settings(ROOT_URLCONF="admin_views.test_nav_sidebar")
  90. class SeleniumTests(AdminSeleniumTestCase):
  91. available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps
  92. def setUp(self):
  93. self.superuser = User.objects.create_superuser(
  94. username="super",
  95. password="secret",
  96. email="super@example.com",
  97. )
  98. self.admin_login(
  99. username="super",
  100. password="secret",
  101. login_url=reverse("test_with_sidebar:index"),
  102. )
  103. self.selenium.execute_script(
  104. "localStorage.removeItem('django.admin.navSidebarIsOpen')"
  105. )
  106. def test_sidebar_starts_open(self):
  107. from selenium.webdriver.common.by import By
  108. self.selenium.get(
  109. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  110. )
  111. main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
  112. self.assertIn("shifted", main_element.get_attribute("class").split())
  113. def test_sidebar_can_be_closed(self):
  114. from selenium.webdriver.common.by import By
  115. self.selenium.get(
  116. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  117. )
  118. toggle_button = self.selenium.find_element(
  119. By.CSS_SELECTOR, "#toggle-nav-sidebar"
  120. )
  121. self.assertEqual(toggle_button.tag_name, "button")
  122. self.assertEqual(toggle_button.get_attribute("aria-label"), "Toggle navigation")
  123. for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
  124. self.assertEqual(link.get_attribute("tabIndex"), "0")
  125. filter_input = self.selenium.find_element_by_css_selector("#nav-filter")
  126. self.assertEqual(filter_input.get_attribute("tabIndex"), "0")
  127. toggle_button.click()
  128. # Hidden sidebar is not reachable via keyboard navigation.
  129. for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
  130. self.assertEqual(link.get_attribute("tabIndex"), "-1")
  131. filter_input = self.selenium.find_element_by_css_selector("#nav-filter")
  132. self.assertEqual(filter_input.get_attribute("tabIndex"), "-1")
  133. main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
  134. self.assertNotIn("shifted", main_element.get_attribute("class").split())
  135. def test_sidebar_state_persists(self):
  136. from selenium.webdriver.common.by import By
  137. self.selenium.get(
  138. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  139. )
  140. self.assertIsNone(
  141. self.selenium.execute_script(
  142. "return localStorage.getItem('django.admin.navSidebarIsOpen')"
  143. )
  144. )
  145. toggle_button = self.selenium.find_element(
  146. By.CSS_SELECTOR, "#toggle-nav-sidebar"
  147. )
  148. toggle_button.click()
  149. self.assertEqual(
  150. self.selenium.execute_script(
  151. "return localStorage.getItem('django.admin.navSidebarIsOpen')"
  152. ),
  153. "false",
  154. )
  155. self.selenium.get(
  156. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  157. )
  158. main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
  159. self.assertNotIn("shifted", main_element.get_attribute("class").split())
  160. toggle_button = self.selenium.find_element(
  161. By.CSS_SELECTOR, "#toggle-nav-sidebar"
  162. )
  163. # Hidden sidebar is not reachable via keyboard navigation.
  164. for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
  165. self.assertEqual(link.get_attribute("tabIndex"), "-1")
  166. filter_input = self.selenium.find_element_by_css_selector("#nav-filter")
  167. self.assertEqual(filter_input.get_attribute("tabIndex"), "-1")
  168. toggle_button.click()
  169. for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
  170. self.assertEqual(link.get_attribute("tabIndex"), "0")
  171. filter_input = self.selenium.find_element_by_css_selector("#nav-filter")
  172. self.assertEqual(filter_input.get_attribute("tabIndex"), "0")
  173. self.assertEqual(
  174. self.selenium.execute_script(
  175. "return localStorage.getItem('django.admin.navSidebarIsOpen')"
  176. ),
  177. "true",
  178. )
  179. self.selenium.get(
  180. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  181. )
  182. main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
  183. self.assertIn("shifted", main_element.get_attribute("class").split())
  184. def test_sidebar_filter_persists(self):
  185. from selenium.webdriver.common.by import By
  186. self.selenium.get(
  187. self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
  188. )
  189. filter_value_script = (
  190. "return sessionStorage.getItem('django.admin.navSidebarFilterValue')"
  191. )
  192. self.assertIsNone(self.selenium.execute_script(filter_value_script))
  193. filter_input = self.selenium.find_element(By.CSS_SELECTOR, "#nav-filter")
  194. filter_input.send_keys("users")
  195. self.assertEqual(self.selenium.execute_script(filter_value_script), "users")