Skip to content

Commit 71d27e9

Browse files
committed
test: add a JS unit test to verify filterSubVerticals
1 parent 2884cc5 commit 71d27e9

File tree

2 files changed

+116
-2
lines changed

2 files changed

+116
-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: 114 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
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.test import LiveServerTestCase, TestCase
55
from django.urls import reverse
6+
from selenium import webdriver
7+
from selenium.webdriver.common.by import By
8+
from selenium.webdriver.firefox.options import Options
9+
from selenium.webdriver.support.ui import Select, WebDriverWait
610

711
from course_discovery.apps.core.tests.factories import UserFactory
812
from course_discovery.apps.course_metadata.tests.factories import CourseFactory
@@ -68,6 +72,115 @@ def test_post_invalid_sub_vertical(self):
6872
self.assertContains(response, 'Sub-vertical does not belong to the selected vertical.')
6973

7074

75+
class CourseTaggingDetailViewJSTests(LiveServerTestCase):
76+
"""
77+
Functional tests using Selenium to verify the JS script filterSubVerticals behavior in the CourseTaggingDetailView.
78+
"""
79+
80+
@classmethod
81+
def setUpClass(cls):
82+
super().setUpClass()
83+
firefox_options = Options()
84+
firefox_options.headless = True
85+
cls.driver = webdriver.Firefox(options=firefox_options)
86+
cls.driver.implicitly_wait(10)
87+
88+
@classmethod
89+
def tearDownClass(cls):
90+
cls.driver.quit()
91+
super().tearDownClass()
92+
93+
def setUp(self):
94+
"""
95+
Create a superuser, a course, verticals and sub-verticals used for testing.
96+
"""
97+
self.user = UserFactory(username='superuser_func', is_superuser=True, is_staff=True)
98+
self.user.set_password('password')
99+
self.user.save()
100+
101+
self.course = CourseFactory(title='Advanced Python')
102+
103+
self.vertical = VerticalFactory(name='AI')
104+
self.sub_vertical = SubVerticalFactory(name='Machine Learning', vertical=self.vertical)
105+
106+
self.other_vertical = VerticalFactory(name='Business')
107+
self.other_sub_vertical = SubVerticalFactory(name='Innovation', vertical=self.other_vertical)
108+
109+
self.multi_vertical = VerticalFactory(name='Data')
110+
self.multi_sub_vertical1 = SubVerticalFactory(name='Analytics', vertical=self.multi_vertical)
111+
self.multi_sub_vertical2 = SubVerticalFactory(name='Visualization', vertical=self.multi_vertical)
112+
113+
self.url = self.live_server_url + reverse('tagging:course_tagging_detail', kwargs={'uuid': self.course.uuid})
114+
self.login_url = self.live_server_url + reverse('admin:login')
115+
116+
# Login via the admin login page as it does not share cookies with Django's test client.
117+
self.driver.get(self.login_url)
118+
self.driver.find_element(By.NAME, 'username').send_keys(self.user.username)
119+
self.driver.find_element(By.NAME, 'password').send_keys('password')
120+
self.driver.find_element(By.XPATH, "//input[@type='submit']").click()
121+
122+
self.driver.get(self.url)
123+
124+
def get_visible_options(self, select_id):
125+
"""
126+
Returns a list of visible <option> elements for the given select element.
127+
Visible options are defined as those with a 'data-vertical' attribute and no inline 'display: none' style.
128+
"""
129+
select_element = self.driver.find_element(By.ID, select_id)
130+
options = select_element.find_elements(By.TAG_NAME, 'option')
131+
visible_options = [
132+
option for option in options
133+
if option.get_attribute('data-vertical') is not None and
134+
'display: none' not in (option.get_attribute('style') or '')
135+
]
136+
return visible_options
137+
138+
def filter_sub_verticals(self, vertical_slug, expected_slugs):
139+
"""
140+
Selects the vertical with the given slug, triggers the JavaScript change event, and waits
141+
until the expected sub-vertical options (by their slug values) are visible in the sub_vertical select.
142+
Returns the list of visible option elements.
143+
"""
144+
vertical_select = Select(self.driver.find_element(By.ID, 'vertical'))
145+
vertical_select.select_by_value(vertical_slug)
146+
self.driver.execute_script(
147+
"document.getElementById('vertical').dispatchEvent(new Event('change'));"
148+
)
149+
# Wait until the visible sub-vertical options match the expected set.
150+
WebDriverWait(self.driver, 5).until(
151+
lambda d: set(
152+
option.get_attribute('value')
153+
for option in self.get_visible_options('sub_vertical')
154+
) == set(expected_slugs)
155+
)
156+
return self.get_visible_options('sub_vertical')
157+
158+
def test_filter_sub_verticals_javascript(self):
159+
"""
160+
Verify that selecting a vertical with one sub-vertical (AI and Business) shows the expected single sub-vertical
161+
and a vertical with two sub-verticals ('Data') shows both sub-verticals.
162+
"""
163+
# Test for vertical 'AI' with one sub-vertical 'Machine Learning'
164+
visible_options = self.filter_sub_verticals(self.vertical.slug, [self.sub_vertical.slug])
165+
self.assertEqual(len(visible_options), 1)
166+
self.assertEqual(visible_options[0].get_attribute('value'), self.sub_vertical.slug)
167+
168+
# Test for vertical 'Business' with one sub-vertical 'Innovation'
169+
visible_options = self.filter_sub_verticals(self.other_vertical.slug, [self.other_sub_vertical.slug])
170+
self.assertEqual(len(visible_options), 1)
171+
self.assertEqual(visible_options[0].get_attribute('value'), self.other_sub_vertical.slug)
172+
173+
# Test for vertical 'Data' with two sub-verticals: 'Analytics' and 'Visualization'
174+
visible_options = self.filter_sub_verticals(
175+
self.multi_vertical.slug,
176+
[self.multi_sub_vertical1.slug, self.multi_sub_vertical2.slug]
177+
)
178+
option_values = sorted([option.get_attribute('value') for option in visible_options])
179+
expected_values = sorted([self.multi_sub_vertical1.slug, self.multi_sub_vertical2.slug])
180+
self.assertEqual(len(visible_options), 2)
181+
self.assertEqual(option_values, expected_values)
182+
183+
71184
@ddt
72185
class CourseListViewTests(BaseViewsTestCase):
73186
"""Tests for the CourseListView."""

0 commit comments

Comments
 (0)