Skip to content

Commit 7bee302

Browse files
Rearrange agenda customization UI and add customization UI to upcoming meetings
- Legacy-Id: 18513
1 parent 4c709bb commit 7bee302

9 files changed

Lines changed: 971 additions & 330 deletions

File tree

ietf/meeting/tests_js.py

Lines changed: 224 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,28 @@
44

55
import time
66
import datetime
7+
import shutil
8+
import os
79
from pyquery import PyQuery
810
from unittest import skipIf
911

1012
import django
1113
from django.urls import reverse as urlreverse
14+
from django.utils.text import slugify
15+
from django.db.models import F
1216
#from django.test.utils import override_settings
1317

1418
import debug # pyflakes:ignore
1519

1620
from ietf.doc.factories import DocumentFactory
1721
from ietf.group import colors
22+
from ietf.group.models import Group
1823
from ietf.meeting.factories import SessionFactory
1924
from ietf.meeting.test_data import make_meeting_test_data
20-
from ietf.meeting.models import Schedule, SchedTimeSessAssignment, Session, Room, TimeSlot, Constraint, ConstraintName
25+
from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
26+
Room, TimeSlot, Constraint, ConstraintName,
27+
Meeting)
28+
from ietf.meeting.utils import add_event_info_to_session_qs
2129
from ietf.utils.test_runner import IetfLiveServerTestCase
2230
from ietf.utils.pipe import pipe
2331
from ietf import settings
@@ -314,25 +322,33 @@ def test_agenda_view_displays_all_items(self):
314322
def test_agenda_view_js_func_parse_query_params(self):
315323
"""Test parse_query_params() function"""
316324
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
317-
325+
326+
parse_query_params = 'return agenda_filter_for_testing.parse_query_params'
327+
318328
# Only 'show' param
319329
result = self.driver.execute_script(
320-
'return parse_query_params("?show=group1,group2,group3");'
330+
parse_query_params + '("?show=group1,group2,group3");'
321331
)
322332
self.assertEqual(result, dict(show='group1,group2,group3'))
323333

324334
# Only 'hide' param
325335
result = self.driver.execute_script(
326-
'return parse_query_params("?hide=group4,group5,group6");'
336+
parse_query_params + '("?hide=group4,group5,group6");'
327337
)
328338
self.assertEqual(result, dict(hide='group4,group5,group6'))
329-
339+
330340
# Both 'show' and 'hide'
331341
result = self.driver.execute_script(
332-
'return parse_query_params("?show=group1,group2,group3&hide=group4,group5,group6");'
342+
parse_query_params + '("?show=group1,group2,group3&hide=group4,group5,group6");'
333343
)
334344
self.assertEqual(result, dict(show='group1,group2,group3', hide='group4,group5,group6'))
335345

346+
# Encoded
347+
result = self.driver.execute_script(
348+
parse_query_params + '("?show=%20group1,%20group2,%20group3&hide=group4,group5,group6");'
349+
)
350+
self.assertEqual(result, dict(show=' group1, group2, group3', hide='group4,group5,group6'))
351+
336352
def test_agenda_view_js_func_toggle_list_item(self):
337353
"""Test toggle_list_item() function"""
338354
self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
@@ -341,30 +357,30 @@ def test_agenda_view_js_func_toggle_list_item(self):
341357
"""
342358
// start empty, add item
343359
var list0=[];
344-
toggle_list_item(list0, 'item');
360+
%(toggle_list_item)s(list0, 'item');
345361
346362
// one item, remove it
347363
var list1=['item'];
348-
toggle_list_item(list1, 'item');
364+
%(toggle_list_item)s(list1, 'item');
349365
350366
// one item, add another
351367
var list2=['item1'];
352-
toggle_list_item(list2, 'item2');
368+
%(toggle_list_item)s(list2, 'item2');
353369
354370
// multiple items, remove first
355371
var list3=['item1', 'item2', 'item3'];
356-
toggle_list_item(list3, 'item1');
372+
%(toggle_list_item)s(list3, 'item1');
357373
358374
// multiple items, remove middle
359375
var list4=['item1', 'item2', 'item3'];
360-
toggle_list_item(list4, 'item2');
376+
%(toggle_list_item)s(list4, 'item2');
361377
362378
// multiple items, remove last
363379
var list5=['item1', 'item2', 'item3'];
364-
toggle_list_item(list5, 'item3');
380+
%(toggle_list_item)s(list5, 'item3');
365381
366382
return [list0, list1, list2, list3, list4, list5];
367-
"""
383+
""" % {'toggle_list_item': 'agenda_filter_for_testing.toggle_list_item'}
368384
)
369385
self.assertEqual(result[0], ['item'], 'Adding item to empty list failed')
370386
self.assertEqual(result[1], [], 'Removing only item in a list failed')
@@ -385,11 +401,20 @@ def do_agenda_view_filter_test(self, querystring, visible_groups=()):
385401
self.driver.switch_to.frame(weekview_iframe)
386402
self.assert_weekview_item_visibility(visible_groups)
387403
self.driver.switch_to.default_content()
388-
404+
405+
def test_agenda_view_filter_show_none(self):
406+
"""Filtered agenda view should display only matching rows (no group selected)"""
407+
self.do_agenda_view_filter_test('?show=', [])
408+
389409
def test_agenda_view_filter_show_one(self):
390410
"""Filtered agenda view should display only matching rows (one group selected)"""
391411
self.do_agenda_view_filter_test('?show=mars', ['mars'])
392412

413+
def test_agenda_view_filter_show_area(self):
414+
mars = Group.objects.get(acronym='mars')
415+
area = mars.parent
416+
self.do_agenda_view_filter_test('?show=%s' % area.acronym, ['ames', 'mars'])
417+
393418
def test_agenda_view_filter_show_two(self):
394419
"""Filtered agenda view should display only matching rows (two groups selected)"""
395420
self.do_agenda_view_filter_test('?show=mars,ames', ['mars', 'ames'])
@@ -401,6 +426,11 @@ def test_agenda_view_filter_all(self):
401426
def test_agenda_view_filter_hide(self):
402427
self.do_agenda_view_filter_test('?hide=ietf', [])
403428

429+
def test_agenda_view_filter_hide_area(self):
430+
mars = Group.objects.get(acronym='mars')
431+
area = mars.parent
432+
self.do_agenda_view_filter_test('?show=mars&hide=%s' % area.acronym, [])
433+
404434
def test_agenda_view_filter_show_and_hide(self):
405435
self.do_agenda_view_filter_test('?show=mars&hide=ietf', ['mars'])
406436

@@ -551,7 +581,186 @@ def test_agenda_view_group_filter_toggle_without_replace_state(self):
551581
WebDriverWait(self.driver, 2).until(expected_conditions.url_to_be(expected_url))
552582
# no assertion here - if WebDriverWait raises an exception, the test will fail.
553583
# We separately test whether this URL will filter correctly.
554-
584+
585+
586+
@skipIf(skip_selenium, skip_message)
587+
class InterimTests(MeetingTestCase):
588+
def setUp(self):
589+
super(InterimTests, self).setUp()
590+
self.materials_dir = self.tempdir('materials')
591+
self.saved_agenda_path = settings.AGENDA_PATH
592+
settings.AGENDA_PATH = self.materials_dir
593+
self.meeting = make_meeting_test_data(create_interims=True)
594+
595+
def tearDown(self):
596+
settings.AGENDA_PATH = self.saved_agenda_path
597+
shutil.rmtree(self.materials_dir)
598+
super(InterimTests, self).tearDown()
599+
600+
def tempdir(self, label):
601+
# Borrowed from test_utils.TestCase
602+
slug = slugify(self.__class__.__name__.replace('.','-'))
603+
dirname = "tmp-{label}-{slug}-dir".format(**locals())
604+
if 'VIRTUAL_ENV' in os.environ:
605+
dirname = os.path.join(os.environ['VIRTUAL_ENV'], dirname)
606+
path = os.path.abspath(dirname)
607+
if not os.path.exists(path):
608+
os.mkdir(path)
609+
return path
610+
611+
def displayed_interims(self, groups=None):
612+
sessions = add_event_info_to_session_qs(
613+
Session.objects.filter(
614+
meeting__type_id='interim',
615+
timeslotassignments__schedule=F('meeting__schedule'),
616+
timeslotassignments__timeslot__time__gte=datetime.datetime.today()
617+
)
618+
).filter(current_status__in=('sched','canceled'))
619+
meetings = []
620+
for s in sessions:
621+
if groups is None or s.group.acronym in groups:
622+
s.meeting.calendar_label = s.group.acronym # annotate with group
623+
meetings.append(s.meeting)
624+
return meetings
625+
626+
def all_ietf_meetings(self):
627+
meetings = Meeting.objects.filter(
628+
type_id='ietf',
629+
date__gte=datetime.datetime.today()-datetime.timedelta(days=7)
630+
)
631+
for m in meetings:
632+
m.calendar_label = 'IETF %s' % m.number
633+
return meetings
634+
635+
def assert_upcoming_meeting_visibility(self, visible_meetings=None):
636+
"""Assert that correct items are visible in current browser window
637+
638+
If visible_meetings is None (the default), expects all items to be visible.
639+
"""
640+
expected = {mtg.number for mtg in visible_meetings}
641+
not_visible = set()
642+
unexpected = set()
643+
entries = self.driver.find_elements_by_css_selector(
644+
'table#upcoming-meeting-table > tbody > tr.entry'
645+
)
646+
for entry in entries:
647+
nums = [n for n in expected if n in entry.text]
648+
self.assertLessEqual(len(nums), 1, 'Multiple matching meeting numbers')
649+
if len(nums) > 0: # asserted that it's at most 1, so if it's not 0, it's 1.
650+
expected.remove(nums[0])
651+
if not entry.is_displayed():
652+
not_visible.add(nums[0])
653+
continue
654+
# Found an unexpected row - this is ok as long as it's hidden
655+
if entry.is_displayed():
656+
unexpected.add(entry.text)
657+
658+
self.assertEqual(expected, set(), "Missing entries for expected iterim meetings.")
659+
self.assertEqual(not_visible, set(), "Hidden rows for expected interim meetings.")
660+
self.assertEqual(unexpected, set(), "Unexpected row visible")
661+
662+
def assert_upcoming_meeting_calendar(self, visible_meetings=None):
663+
"""Assert that correct items are sent to the calendar"""
664+
def advance_month():
665+
button = WebDriverWait(self.driver, 2).until(
666+
expected_conditions.element_to_be_clickable(
667+
(By.CSS_SELECTOR, 'div#calendar button.fc-next-button')))
668+
button.click()
669+
670+
seen = set()
671+
not_visible = set()
672+
unexpected = set()
673+
674+
# Test that we see all the expected meetings when we scroll through the
675+
# entire year. We only check the group names / IETF numbers. This should
676+
# be good enough to catch filtering errors but does not validate the
677+
# details of what's shown on the calendar. Need 13 iterations instead of
678+
# 12 in order to check the starting month of the following year, which
679+
# will usually contain the day 1 year from the start date.
680+
for _ in range(13):
681+
entries = self.driver.find_elements_by_css_selector(
682+
'div#calendar div.fc-content'
683+
)
684+
for entry in entries:
685+
meetings = [m for m in visible_meetings if m.calendar_label in entry.text]
686+
if len(meetings) > 0:
687+
seen.add(meetings[0])
688+
if not entry.is_displayed():
689+
not_visible.add(meetings[0])
690+
continue
691+
# Found an unexpected row - this is ok as long as it's hidden
692+
if entry.is_displayed():
693+
unexpected.add(entry.text)
694+
advance_month()
695+
696+
self.assertEqual(seen, visible_meetings, "Expected calendar entries not shown.")
697+
self.assertEqual(not_visible, set(), "Hidden calendar entries for expected interim meetings.")
698+
self.assertEqual(unexpected, set(), "Unexpected calendar entries visible")
699+
700+
def do_upcoming_view_filter_test(self, querystring, visible_meetings=()):
701+
self.login()
702+
self.driver.get(self.absreverse('ietf.meeting.views.upcoming') + querystring)
703+
self.assert_upcoming_meeting_visibility(visible_meetings)
704+
self.assert_upcoming_meeting_calendar(visible_meetings)
705+
706+
# Check the ical links
707+
simplified_querystring = querystring.replace(' ', '%20') # encode spaces'
708+
ics_link = self.driver.find_element_by_link_text('Download as .ics')
709+
self.assertIn(simplified_querystring, ics_link.get_attribute('href'))
710+
webcal_link = self.driver.find_element_by_link_text('Subscribe with webcal')
711+
self.assertIn(simplified_querystring, webcal_link.get_attribute('href'))
712+
713+
def test_upcoming_view_displays_all_interims(self):
714+
"""By default, all upcoming interims and IETF meetings should be displayed"""
715+
meetings = set(self.all_ietf_meetings())
716+
meetings.update(self.displayed_interims())
717+
self.do_upcoming_view_filter_test('', meetings)
718+
719+
def test_upcoming_view_filter_show_none(self):
720+
meetings = set(self.all_ietf_meetings())
721+
self.do_upcoming_view_filter_test('?show=', meetings)
722+
723+
def test_upcoming_view_filter_show_one(self):
724+
meetings = set(self.all_ietf_meetings())
725+
meetings.update(self.displayed_interims(groups=['mars']))
726+
self.do_upcoming_view_filter_test('?show=mars', meetings)
727+
728+
def test_upcoming_view_filter_show_area(self):
729+
mars = Group.objects.get(acronym='mars')
730+
area = mars.parent
731+
meetings = set(self.all_ietf_meetings())
732+
meetings.update(self.displayed_interims(groups=['mars', 'ames']))
733+
self.do_upcoming_view_filter_test('?show=%s' % area.acronym, meetings)
734+
735+
def test_upcoming_view_filter_show_two(self):
736+
meetings = set(self.all_ietf_meetings())
737+
meetings.update(self.displayed_interims(groups=['mars', 'ames']))
738+
self.do_upcoming_view_filter_test('?show=mars,ames', meetings)
739+
740+
def test_upcoming_view_filter_whitespace(self):
741+
"""Whitespace in filter lists should be ignored"""
742+
meetings = set(self.all_ietf_meetings())
743+
meetings.update(self.displayed_interims(groups=['mars']))
744+
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings)
745+
746+
def test_upcoming_view_filter_hide(self):
747+
# Not a useful application, but test for completeness...
748+
meetings = set(self.all_ietf_meetings())
749+
self.do_upcoming_view_filter_test('?hide=mars', meetings)
750+
751+
def test_upcoming_view_filter_hide_area(self):
752+
mars = Group.objects.get(acronym='mars')
753+
area = mars.parent
754+
meetings = set(self.all_ietf_meetings())
755+
self.do_upcoming_view_filter_test('?show=mars&hide=%s' % area.acronym, meetings)
756+
757+
def test_upcoming_view_filter_show_and_hide_same_group(self):
758+
meetings = set(self.all_ietf_meetings())
759+
meetings.update(self.displayed_interims(groups=['mars']))
760+
self.do_upcoming_view_filter_test('?show=mars,ames&hide=ames', meetings)
761+
762+
# The upcoming meetings view does not handle showtypes / hidetypes
763+
555764
# The following are useful debugging tools
556765

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

0 commit comments

Comments
 (0)