Skip to content

Commit 44d5402

Browse files
authored
test: add a JS unit test to verify filterSubVerticals (#4561)
1 parent 2eba8ef commit 44d5402

File tree

2 files changed

+156
-2
lines changed

2 files changed

+156
-2
lines changed

conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
# List of test classes that are backed by TransactionTestCase
2121
TTC = ['course_discovery/apps/course_metadata/management/commands/tests/test_refresh_course_metadata.py::'
2222
'RefreshCourseMetadataCommandTests',
23-
'course_discovery/apps/course_metadata/tests/test_admin.py::ProgramAdminFunctionalTests']
23+
'course_discovery/apps/course_metadata/tests/test_admin.py::ProgramAdminFunctionalTests',
24+
'course_discovery/apps/tagging/tests/test_views.py::CourseTaggingDetailViewJSTests']
2425

2526

2627
class LoadScopeSchedulingDjangoOrdered(LoadScopeScheduling):

course_discovery/apps/tagging/tests/test_views.py

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
from ddt import data, ddt
22
from django.conf import settings
33
from django.contrib.auth.models import Group
4-
from django.test import TestCase
4+
from django.contrib.contenttypes.models import ContentType
5+
from django.test import LiveServerTestCase, TestCase
56
from django.urls import reverse
7+
from selenium import webdriver
8+
from selenium.webdriver.common.by import By
9+
from selenium.webdriver.firefox.options import Options
10+
from selenium.webdriver.support.ui import Select, WebDriverWait
611

12+
from course_discovery.apps.api.tests.mixins import SiteMixin
713
from course_discovery.apps.core.tests.factories import UserFactory
814
from course_discovery.apps.course_metadata.tests.factories import CourseFactory
915
from course_discovery.apps.tagging.models import CourseVertical
@@ -68,6 +74,153 @@ def test_post_invalid_sub_vertical(self):
6874
self.assertContains(response, 'Sub-vertical does not belong to the selected vertical.')
6975

7076

77+
class CourseTaggingDetailViewJSTests(SiteMixin, LiveServerTestCase):
78+
"""
79+
Functional tests using Selenium to verify the JS script filterSubVerticals behavior in the CourseTaggingDetailView.
80+
"""
81+
82+
@classmethod
83+
def setUpClass(cls):
84+
super().setUpClass()
85+
firefox_options = Options()
86+
firefox_options.headless = True
87+
cls.driver = webdriver.Firefox(options=firefox_options)
88+
cls.driver.implicitly_wait(10)
89+
90+
@classmethod
91+
def tearDownClass(cls):
92+
cls.driver.quit()
93+
super().tearDownClass()
94+
95+
def _wait_for_page_load(self, timeout=10):
96+
"""Wait until the page's document.readyState is 'complete'."""
97+
WebDriverWait(self.driver, timeout).until(
98+
lambda d: d.execute_script("return document.readyState") == "complete"
99+
)
100+
101+
def setUp(self):
102+
"""
103+
Create a superuser, a course, verticals and sub-verticals used for testing.
104+
"""
105+
super().setUp()
106+
ContentType.objects.clear_cache()
107+
108+
self.user = UserFactory(username='superuser', is_superuser=True, is_staff=True)
109+
self.user.set_password('password')
110+
self.user.save()
111+
112+
self.course = CourseFactory(title='Advanced Python')
113+
114+
self.vertical = VerticalFactory(name='vertical')
115+
self.sub_vertical = SubVerticalFactory(name='sub_vertical', vertical=self.vertical)
116+
117+
self.other_vertical = VerticalFactory(name='other_vertical')
118+
self.other_sub_vertical = SubVerticalFactory(name='other_sub_vertical', vertical=self.other_vertical)
119+
120+
self.multi_vertical = VerticalFactory(name='multi_vertical')
121+
self.multi_sub_vertical1 = SubVerticalFactory(name='multi_sub_vertical1', vertical=self.multi_vertical)
122+
self.multi_sub_vertical2 = SubVerticalFactory(name='multi_sub_vertical2', vertical=self.multi_vertical)
123+
124+
_ = CourseVerticalFactory(course=self.course, vertical=self.vertical, sub_vertical=self.sub_vertical)
125+
126+
self.url = self.live_server_url + reverse('tagging:course_tagging_detail', kwargs={'uuid': self.course.uuid})
127+
128+
self._login()
129+
130+
self.driver.get(self.url)
131+
self._wait_for_page_load()
132+
133+
def _login(self):
134+
"""Log into Django via test client, then add the session cookie to Selenium."""
135+
self.client.force_login(self.user)
136+
session_cookie = self.client.cookies[settings.SESSION_COOKIE_NAME]
137+
self.driver.get(self.live_server_url)
138+
cookie_dict = {
139+
'name': settings.SESSION_COOKIE_NAME,
140+
'value': session_cookie.value,
141+
'path': '/',
142+
}
143+
self.driver.add_cookie(cookie_dict)
144+
self.driver.get(self.url)
145+
self._wait_for_page_load()
146+
147+
def get_visible_options(self, select_id):
148+
"""
149+
Returns a list of visible <option> elements for the given select element.
150+
Visible options are defined as those with a 'data-vertical' attribute and no inline 'display: none' style.
151+
"""
152+
select_element = self.driver.find_element(By.ID, select_id)
153+
options = select_element.find_elements(By.TAG_NAME, 'option')
154+
visible_options = [
155+
option for option in options
156+
if option.get_attribute('data-vertical') is not None and
157+
'display: none' not in (option.get_attribute('style') or '')
158+
]
159+
return visible_options
160+
161+
def filter_sub_verticals(self, vertical_slug, expected_slugs):
162+
"""
163+
Selects the vertical with the given slug, triggers the JavaScript change event, and waits
164+
until the expected sub-vertical options (by their slug values) are visible in the sub_vertical select.
165+
Returns the list of visible option elements.
166+
"""
167+
vertical_select = Select(self.driver.find_element(By.ID, 'vertical'))
168+
vertical_select.select_by_value(vertical_slug)
169+
self.driver.execute_script(
170+
"document.getElementById('vertical').dispatchEvent(new Event('change'));"
171+
)
172+
visible_options = self.get_visible_options('sub_vertical')
173+
WebDriverWait(self.driver, 10).until(
174+
lambda d: set(
175+
option.get_attribute('value')
176+
for option in visible_options
177+
) == set(expected_slugs)
178+
)
179+
return visible_options
180+
181+
def test_initial_load(self):
182+
"""
183+
Test that on initial load (before any user interaction), the course displays the pre-assigned vertical
184+
and sub-vertical. Additionally, verify that the sub-vertical dropdown only contains options associated
185+
with the selected vertical.
186+
"""
187+
vertical_select = Select(self.driver.find_element(By.ID, 'vertical'))
188+
selected_vertical = vertical_select.first_selected_option.get_attribute('value')
189+
self.assertEqual(selected_vertical, self.vertical.slug)
190+
191+
sub_vertical_select = Select(self.driver.find_element(By.ID, 'sub_vertical'))
192+
selected_sub_vertical = sub_vertical_select.first_selected_option.get_attribute('value')
193+
self.assertEqual(selected_sub_vertical, self.sub_vertical.slug)
194+
195+
visible_options = self.get_visible_options('sub_vertical')
196+
197+
expected_options = {self.sub_vertical.slug}
198+
actual_options = {option.get_attribute('value') for option in visible_options}
199+
self.assertEqual(actual_options, expected_options)
200+
201+
def test_filter_sub_verticals_javascript(self):
202+
"""
203+
Verify that selecting a vertical with one sub-vertical shows the expected single sub-vertical
204+
and a vertical with two sub-verticals shows both sub-verticals.
205+
"""
206+
visible_options = self.filter_sub_verticals(self.vertical.slug, [self.sub_vertical.slug])
207+
self.assertEqual(len(visible_options), 1)
208+
self.assertEqual(visible_options[0].get_attribute('value'), self.sub_vertical.slug)
209+
210+
visible_options = self.filter_sub_verticals(self.other_vertical.slug, [self.other_sub_vertical.slug])
211+
self.assertEqual(len(visible_options), 1)
212+
self.assertEqual(visible_options[0].get_attribute('value'), self.other_sub_vertical.slug)
213+
214+
visible_options = self.filter_sub_verticals(
215+
self.multi_vertical.slug,
216+
[self.multi_sub_vertical1.slug, self.multi_sub_vertical2.slug]
217+
)
218+
option_values = sorted([option.get_attribute('value') for option in visible_options])
219+
expected_values = sorted([self.multi_sub_vertical1.slug, self.multi_sub_vertical2.slug])
220+
self.assertEqual(len(visible_options), 2)
221+
self.assertEqual(option_values, expected_values)
222+
223+
71224
@ddt
72225
class CourseListViewTests(BaseViewsTestCase):
73226
"""Tests for the CourseListView."""

0 commit comments

Comments
 (0)