Skip to content

Commit ea38820

Browse files
Use querystring instead of URL hash for agenda filters
- Legacy-Id: 18353
1 parent c5729d5 commit ea38820

9 files changed

Lines changed: 330 additions & 157 deletions

File tree

ietf/meeting/test_data.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ def make_meeting_test_data(meeting=None, create_interims=False):
115115
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
116116
duration=datetime.timedelta(minutes=90),
117117
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
118+
plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
119+
duration=datetime.timedelta(minutes=60),
120+
time=datetime.datetime.combine(session_date, datetime.time(11,0)))
118121
# mars WG
119122
mars = Group.objects.get(acronym='mars')
120123
mars_session = Session.objects.create(meeting=meeting, group=mars,
@@ -158,6 +161,14 @@ def make_meeting_test_data(meeting=None, create_interims=False):
158161
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
159162
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=schedule)
160163

164+
# IETF Plenary
165+
plenary_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ietf"),
166+
name="IETF Plenary", attendees=250,
167+
requested_duration=datetime.timedelta(minutes=60),
168+
type_id="plenary")
169+
SchedulingEvent.objects.create(session=plenary_session, status_id='schedw', by=system_person)
170+
SchedTimeSessAssignment.objects.create(timeslot=plenary_slot, session=plenary_session, schedule=schedule)
171+
161172
meeting.schedule = schedule
162173
meeting.save()
163174

ietf/meeting/tests_api.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -477,8 +477,7 @@ class TimeSlotEditingApiTests(TestCase):
477477

478478
def test_manipulate_timeslot(self):
479479
meeting = make_meeting_test_data()
480-
slot = meeting.timeslot_set.all()[0]
481-
self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular')
480+
slot = meeting.timeslot_set.filter(type_id='regular')[0]
482481

483482
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
484483
kwargs=dict(num=meeting.number, slotid=slot.pk))

ietf/meeting/tests_js.py

Lines changed: 240 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from selenium.webdriver.common.by import By
3131
from selenium.webdriver.support.ui import WebDriverWait
3232
from selenium.webdriver.support import expected_conditions
33+
from selenium.common.exceptions import NoSuchElementException
3334
except ImportError as e:
3435
skip_selenium = True
3536
skip_message = "Skipping selenium tests: %s" % e
@@ -50,29 +51,37 @@ def start_web_driver():
5051
options.add_argument("no-sandbox") # docker needs this
5152
return webdriver.Chrome(options=options, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
5253

53-
@skipIf(skip_selenium, skip_message)
54-
class EditMeetingScheduleTests(IetfLiveServerTestCase):
54+
class MeetingTestCase(IetfLiveServerTestCase):
55+
def __init__(self, *args, **kwargs):
56+
super(MeetingTestCase, self).__init__(*args, **kwargs)
57+
self.login_view = 'ietf.ietfauth.views.login'
58+
5559
def setUp(self):
60+
super(MeetingTestCase, self).setUp()
5661
self.driver = start_web_driver()
5762
self.driver.set_window_size(1024,768)
5863

5964
def tearDown(self):
6065
self.driver.close()
6166

62-
def debug_snapshot(self,filename='debug_this.png'):
63-
self.driver.execute_script("document.body.bgColor = 'white';")
64-
self.driver.save_screenshot(filename)
65-
6667
def absreverse(self,*args,**kwargs):
6768
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
6869

69-
def login(self):
70-
url = self.absreverse('ietf.ietfauth.views.login')
70+
def login(self, username='plain'):
71+
url = self.absreverse(self.login_view)
72+
password = '%s+password' % username
7173
self.driver.get(url)
72-
self.driver.find_element_by_name('username').send_keys('plain')
73-
self.driver.find_element_by_name('password').send_keys('plain+password')
74+
self.driver.find_element_by_name('username').send_keys(username)
75+
self.driver.find_element_by_name('password').send_keys(password)
7476
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
7577

78+
def debug_snapshot(self,filename='debug_this.png'):
79+
self.driver.execute_script("document.body.bgColor = 'white';")
80+
self.driver.save_screenshot(filename)
81+
82+
83+
@skipIf(skip_selenium, skip_message)
84+
class EditMeetingScheduleTests(MeetingTestCase):
7685
def test_edit_meeting_schedule(self):
7786
meeting = make_meeting_test_data()
7887

@@ -206,28 +215,7 @@ def test_edit_meeting_schedule(self):
206215

207216
@skipIf(skip_selenium, skip_message)
208217
@skipIf(django.VERSION[0]==2, "Skipping test with race conditions under Django 2")
209-
class ScheduleEditTests(IetfLiveServerTestCase):
210-
def setUp(self):
211-
self.driver = start_web_driver()
212-
self.driver.set_window_size(1024,768)
213-
214-
def tearDown(self):
215-
self.driver.close()
216-
217-
def debug_snapshot(self,filename='debug_this.png'):
218-
self.driver.execute_script("document.body.bgColor = 'white';")
219-
self.driver.save_screenshot(filename)
220-
221-
def absreverse(self,*args,**kwargs):
222-
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
223-
224-
def login(self):
225-
url = self.absreverse('ietf.ietfauth.views.login')
226-
self.driver.get(url)
227-
self.driver.find_element_by_name('username').send_keys('plain')
228-
self.driver.find_element_by_name('password').send_keys('plain+password')
229-
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
230-
218+
class ScheduleEditTests(MeetingTestCase):
231219
def testUnschedule(self):
232220

233221
meeting = make_meeting_test_data()
@@ -265,27 +253,16 @@ def testUnschedule(self):
265253
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),0)
266254

267255
@skipIf(skip_selenium, skip_message)
268-
class SlideReorderTests(IetfLiveServerTestCase):
256+
class SlideReorderTests(MeetingTestCase):
269257
def setUp(self):
270-
self.driver = start_web_driver()
271-
self.driver.set_window_size(1024,768)
258+
super(SlideReorderTests, self).setUp()
272259
self.session = SessionFactory(meeting__type_id='ietf', status_id='sched')
273260
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1)
274261
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2)
275262
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3)
276263

277-
def tearDown(self):
278-
self.driver.close()
279-
280-
def absreverse(self,*args,**kwargs):
281-
return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
282-
283264
def secr_login(self):
284-
url = '%s%s'%(self.live_server_url, urlreverse('ietf.ietfauth.views.login'))
285-
self.driver.get(url)
286-
self.driver.find_element_by_name('username').send_keys('secretary')
287-
self.driver.find_element_by_name('password').send_keys('secretary+password')
288-
self.driver.find_element_by_xpath('//button[@type="submit"]').click()
265+
self.login('secretary')
289266

290267
#@override_settings(DEBUG=True)
291268
def testReorderSlides(self):
@@ -305,6 +282,223 @@ def testReorderSlides(self):
305282
names=self.session.sessionpresentation_set.values_list('document__name',flat=True)
306283
self.assertEqual(list(names),['one','three','two'])
307284

285+
286+
@skipIf(skip_selenium, skip_message)
287+
class AgendaTests(MeetingTestCase):
288+
# Groups whose display logic is inverted in agenda.html. These have
289+
# toggles with class 'pickviewneg' in the template.
290+
PICKVIEWNEG = ['iepg', 'tools', 'edu', 'ietf', 'iesg', 'iab']
291+
292+
def setUp(self):
293+
super(AgendaTests, self).setUp()
294+
self.meeting = make_meeting_test_data()
295+
296+
def row_id_for_item(self, item):
297+
return 'row-%s' % item.slug()
298+
299+
def get_expected_items(self):
300+
expected_items = self.meeting.schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
301+
self.assertGreater(len(expected_items), 0, 'Test setup generated an empty schedule')
302+
return expected_items
303+
304+
def test_agenda_view_displays_all_items(self):
305+
"""By default, all agenda items should be displayed"""
306+
self.login()
307+
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
308+
309+
for item in self.get_expected_items():
310+
row_id = 'row-%s' % item.slug()
311+
try:
312+
item_row = self.driver.find_element_by_id(row_id)
313+
except NoSuchElementException:
314+
item_row = None
315+
self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
316+
self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed' % row_id)
317+
318+
def test_agenda_view_js_func_parse_query_params(self):
319+
"""Test parse_query_params() function"""
320+
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
321+
322+
# Only 'show' param
323+
result = self.driver.execute_script(
324+
'return parse_query_params("?show=group1,group2,group3");'
325+
)
326+
self.assertEqual(result, dict(show='group1,group2,group3'))
327+
328+
# Only 'hide' param
329+
result = self.driver.execute_script(
330+
'return parse_query_params("?hide=group4,group5,group6");'
331+
)
332+
self.assertEqual(result, dict(hide='group4,group5,group6'))
333+
334+
# Both 'show' and 'hide'
335+
result = self.driver.execute_script(
336+
'return parse_query_params("?show=group1,group2,group3&hide=group4,group5,group6");'
337+
)
338+
self.assertEqual(result, dict(show='group1,group2,group3', hide='group4,group5,group6'))
339+
340+
def test_agenda_view_js_func_toggle_list_item(self):
341+
"""Test toggle_list_item() function"""
342+
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
343+
344+
result = self.driver.execute_script(
345+
"""
346+
// start empty, add item
347+
var list0=[];
348+
toggle_list_item(list0, 'item');
349+
350+
// one item, remove it
351+
var list1=['item'];
352+
toggle_list_item(list1, 'item');
353+
354+
// one item, add another
355+
var list2=['item1'];
356+
toggle_list_item(list2, 'item2');
357+
358+
// multiple items, remove first
359+
var list3=['item1', 'item2', 'item3'];
360+
toggle_list_item(list3, 'item1');
361+
362+
// multiple items, remove middle
363+
var list4=['item1', 'item2', 'item3'];
364+
toggle_list_item(list4, 'item2');
365+
366+
// multiple items, remove last
367+
var list5=['item1', 'item2', 'item3'];
368+
toggle_list_item(list5, 'item3');
369+
370+
return [list0, list1, list2, list3, list4, list5];
371+
"""
372+
)
373+
self.assertEqual(result[0], ['item'], 'Adding item to empty list failed')
374+
self.assertEqual(result[1], [], 'Removing only item in a list failed')
375+
self.assertEqual(result[2], ['item1', 'item2'], 'Adding second item to list failed')
376+
self.assertEqual(result[3], ['item2', 'item3'], 'Removing first item from list failed')
377+
self.assertEqual(result[4], ['item1', 'item3'], 'Removing middle item from list failed')
378+
self.assertEqual(result[5], ['item1', 'item2'], 'Removing last item from list failed')
379+
380+
def test_agenda_view_filter_show_one(self):
381+
"""Filtered agenda view should display only matching rows (one group selected)"""
382+
self.login()
383+
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars')
384+
self.assert_agenda_item_visibility(['mars'] + self.PICKVIEWNEG) # ames and secretariat not selected
385+
386+
def test_agenda_view_filter_show_two(self):
387+
"""Filtered agenda view should display only matching rows (two groups selected)"""
388+
self.login()
389+
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars,ames')
390+
self.assert_agenda_item_visibility(['mars', 'ames'] + self.PICKVIEWNEG) # secretariat not selected
391+
392+
def test_agenda_view_filter_all(self):
393+
"""Filtered agenda view should display only matching rows (all groups selected)"""
394+
self.login()
395+
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
396+
self.assert_agenda_item_visibility()
397+
398+
def test_agenda_view_filter_hide(self):
399+
self.login()
400+
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?hide=ietf')
401+
self.assert_agenda_item_visibility([g for g in self.PICKVIEWNEG if g != 'ietf'])
402+
403+
def test_agenda_view_filter_show_and_hide(self):
404+
self.login()
405+
self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars&hide=ietf')
406+
self.assert_agenda_item_visibility(
407+
['mars'] + [g for g in self.PICKVIEWNEG if g != 'ietf']
408+
)
409+
410+
def assert_agenda_item_visibility(self, visible_groups=()):
411+
"""Assert that correct items are visible in current browser window
412+
413+
If visible_groups is empty (the default), expects all items to be visible.
414+
"""
415+
for item in self.get_expected_items():
416+
row_id = self.row_id_for_item(item)
417+
try:
418+
item_row = self.driver.find_element_by_id(row_id)
419+
except NoSuchElementException:
420+
item_row = None
421+
self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
422+
if len(visible_groups) == 0 or item.session.group.acronym in visible_groups:
423+
self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed but should be' % row_id)
424+
else:
425+
self.assertFalse(item_row.is_displayed(), 'Row for schedule item "%s" is displayed but should not be' % row_id)
426+
427+
def test_agenda_view_group_filter_toggle(self):
428+
"""Clicking a group toggle enables/disables agenda filtering"""
429+
group_acronym = 'mars'
430+
431+
self.login()
432+
url = self.absreverse('ietf.meeting.views.agenda')
433+
self.driver.get(url)
434+
435+
# Click the 'customize' anchor to reveal the group buttons
436+
customize_anchor = WebDriverWait(self.driver, 2).until(
437+
expected_conditions.element_to_be_clickable(
438+
(By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
439+
)
440+
)
441+
customize_anchor.click()
442+
443+
# Click the group button
444+
group_button = WebDriverWait(self.driver, 2).until(
445+
expected_conditions.element_to_be_clickable(
446+
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
447+
)
448+
)
449+
group_button.click()
450+
451+
# Check visibility
452+
self.assert_agenda_item_visibility([group_acronym] + self.PICKVIEWNEG)
453+
454+
# Click the group button again
455+
group_button = WebDriverWait(self.driver, 2).until(
456+
expected_conditions.element_to_be_clickable(
457+
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
458+
)
459+
)
460+
group_button.click()
461+
462+
# Check visibility
463+
self.assert_agenda_item_visibility()
464+
465+
def test_agenda_view_group_filter_toggle_without_replace_state(self):
466+
"""Toggle should function for browsers without window.history.replaceState"""
467+
group_acronym = 'mars'
468+
469+
self.login()
470+
url = self.absreverse('ietf.meeting.views.agenda')
471+
self.driver.get(url)
472+
473+
# Rather than digging up an ancient browser, simulate absence of history.replaceState
474+
self.driver.execute_script('window.history.replaceState = undefined;')
475+
476+
477+
# Click the 'customize' anchor to reveal the group buttons
478+
customize_anchor = WebDriverWait(self.driver, 2).until(
479+
expected_conditions.element_to_be_clickable(
480+
(By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
481+
)
482+
)
483+
customize_anchor.click()
484+
485+
486+
# Get ready to click the group button
487+
group_button = WebDriverWait(self.driver, 2).until(
488+
expected_conditions.element_to_be_clickable(
489+
(By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
490+
)
491+
)
492+
493+
# Be sure we're at the URL we think we're at before we click
494+
self.assertEqual(self.driver.current_url, url)
495+
group_button.click() # click!
496+
497+
expected_url = '%s?show=%s' % (url, group_acronym)
498+
WebDriverWait(self.driver, 2).until(expected_conditions.url_to_be(expected_url))
499+
# no assertion here - if WebDriverWait raises an exception, the test will fail.
500+
# We separately test whether this URL will filter correctly.
501+
308502
# The following are useful debugging tools
309503

310504
# If you add this to a LiveServerTestCase and run just this test, you can browse

ietf/meeting/tests_views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1596,7 +1596,7 @@ def test_upcoming_ical(self):
15961596
r = self.client.get(url)
15971597
self.assertEqual(r.status_code, 200)
15981598
self.assertEqual(r.get('Content-Type'), "text/calendar")
1599-
self.assertEqual(r.content.count(b'UID'), 7)
1599+
self.assertEqual(r.content.count(b'UID'), 8)
16001600
# check filtered output
16011601
url = url + '?filters=mars'
16021602
r = self.client.get(url)

0 commit comments

Comments
 (0)