diff --git a/ietf/meeting/test_data.py b/ietf/meeting/test_data.py
index 3545b9589..37ad1b557 100644
--- a/ietf/meeting/test_data.py
+++ b/ietf/meeting/test_data.py
@@ -115,6 +115,9 @@ def make_meeting_test_data(meeting=None, create_interims=False):
break_slot = TimeSlot.objects.create(meeting=meeting, type_id="break", location=break_room,
duration=datetime.timedelta(minutes=90),
time=datetime.datetime.combine(session_date, datetime.time(7,0)))
+ plenary_slot = TimeSlot.objects.create(meeting=meeting, type_id="plenary", location=room,
+ duration=datetime.timedelta(minutes=60),
+ time=datetime.datetime.combine(session_date, datetime.time(11,0)))
# mars WG
mars = Group.objects.get(acronym='mars')
mars_session = Session.objects.create(meeting=meeting, group=mars,
@@ -158,6 +161,14 @@ def make_meeting_test_data(meeting=None, create_interims=False):
SchedulingEvent.objects.create(session=break_session, status_id='schedw', by=system_person)
SchedTimeSessAssignment.objects.create(timeslot=break_slot, session=break_session, schedule=schedule)
+ # IETF Plenary
+ plenary_session = Session.objects.create(meeting=meeting, group=Group.objects.get(acronym="ietf"),
+ name="IETF Plenary", attendees=250,
+ requested_duration=datetime.timedelta(minutes=60),
+ type_id="plenary")
+ SchedulingEvent.objects.create(session=plenary_session, status_id='schedw', by=system_person)
+ SchedTimeSessAssignment.objects.create(timeslot=plenary_slot, session=plenary_session, schedule=schedule)
+
meeting.schedule = schedule
meeting.save()
diff --git a/ietf/meeting/tests_api.py b/ietf/meeting/tests_api.py
index d176d3749..c7d010dc5 100644
--- a/ietf/meeting/tests_api.py
+++ b/ietf/meeting/tests_api.py
@@ -477,8 +477,7 @@ class TimeSlotEditingApiTests(TestCase):
def test_manipulate_timeslot(self):
meeting = make_meeting_test_data()
- slot = meeting.timeslot_set.all()[0]
- self.assertEqual(TimeSlot.objects.get(pk=slot.pk).type_id,'regular')
+ slot = meeting.timeslot_set.filter(type_id='regular')[0]
url = urlreverse("ietf.meeting.ajax.timeslot_sloturl",
kwargs=dict(num=meeting.number, slotid=slot.pk))
diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py
index 728854dd8..c66beace9 100644
--- a/ietf/meeting/tests_js.py
+++ b/ietf/meeting/tests_js.py
@@ -30,6 +30,7 @@ try:
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions
+ from selenium.common.exceptions import NoSuchElementException
except ImportError as e:
skip_selenium = True
skip_message = "Skipping selenium tests: %s" % e
@@ -50,29 +51,37 @@ def start_web_driver():
options.add_argument("no-sandbox") # docker needs this
return webdriver.Chrome(options=options, service_log_path=settings.TEST_GHOSTDRIVER_LOG_PATH)
-@skipIf(skip_selenium, skip_message)
-class EditMeetingScheduleTests(IetfLiveServerTestCase):
+class MeetingTestCase(IetfLiveServerTestCase):
+ def __init__(self, *args, **kwargs):
+ super(MeetingTestCase, self).__init__(*args, **kwargs)
+ self.login_view = 'ietf.ietfauth.views.login'
+
def setUp(self):
+ super(MeetingTestCase, self).setUp()
self.driver = start_web_driver()
self.driver.set_window_size(1024,768)
def tearDown(self):
self.driver.close()
+ def absreverse(self,*args,**kwargs):
+ return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
+
+ def login(self, username='plain'):
+ url = self.absreverse(self.login_view)
+ password = '%s+password' % username
+ self.driver.get(url)
+ self.driver.find_element_by_name('username').send_keys(username)
+ self.driver.find_element_by_name('password').send_keys(password)
+ self.driver.find_element_by_xpath('//button[@type="submit"]').click()
+
def debug_snapshot(self,filename='debug_this.png'):
self.driver.execute_script("document.body.bgColor = 'white';")
self.driver.save_screenshot(filename)
- def absreverse(self,*args,**kwargs):
- return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
-
- def login(self):
- url = self.absreverse('ietf.ietfauth.views.login')
- self.driver.get(url)
- self.driver.find_element_by_name('username').send_keys('plain')
- self.driver.find_element_by_name('password').send_keys('plain+password')
- self.driver.find_element_by_xpath('//button[@type="submit"]').click()
+@skipIf(skip_selenium, skip_message)
+class EditMeetingScheduleTests(MeetingTestCase):
def test_edit_meeting_schedule(self):
meeting = make_meeting_test_data()
@@ -206,28 +215,7 @@ class EditMeetingScheduleTests(IetfLiveServerTestCase):
@skipIf(skip_selenium, skip_message)
@skipIf(django.VERSION[0]==2, "Skipping test with race conditions under Django 2")
-class ScheduleEditTests(IetfLiveServerTestCase):
- def setUp(self):
- self.driver = start_web_driver()
- self.driver.set_window_size(1024,768)
-
- def tearDown(self):
- self.driver.close()
-
- def debug_snapshot(self,filename='debug_this.png'):
- self.driver.execute_script("document.body.bgColor = 'white';")
- self.driver.save_screenshot(filename)
-
- def absreverse(self,*args,**kwargs):
- return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
-
- def login(self):
- url = self.absreverse('ietf.ietfauth.views.login')
- self.driver.get(url)
- self.driver.find_element_by_name('username').send_keys('plain')
- self.driver.find_element_by_name('password').send_keys('plain+password')
- self.driver.find_element_by_xpath('//button[@type="submit"]').click()
-
+class ScheduleEditTests(MeetingTestCase):
def testUnschedule(self):
meeting = make_meeting_test_data()
@@ -265,27 +253,16 @@ class ScheduleEditTests(IetfLiveServerTestCase):
self.assertEqual(SchedTimeSessAssignment.objects.filter(session__meeting__number=72,session__group__acronym='mars',schedule__name='test-schedule').count(),0)
@skipIf(skip_selenium, skip_message)
-class SlideReorderTests(IetfLiveServerTestCase):
+class SlideReorderTests(MeetingTestCase):
def setUp(self):
- self.driver = start_web_driver()
- self.driver.set_window_size(1024,768)
+ super(SlideReorderTests, self).setUp()
self.session = SessionFactory(meeting__type_id='ietf', status_id='sched')
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='one'),order=1)
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='two'),order=2)
self.session.sessionpresentation_set.create(document=DocumentFactory(type_id='slides',name='three'),order=3)
- def tearDown(self):
- self.driver.close()
-
- def absreverse(self,*args,**kwargs):
- return '%s%s'%(self.live_server_url,urlreverse(*args,**kwargs))
-
def secr_login(self):
- url = '%s%s'%(self.live_server_url, urlreverse('ietf.ietfauth.views.login'))
- self.driver.get(url)
- self.driver.find_element_by_name('username').send_keys('secretary')
- self.driver.find_element_by_name('password').send_keys('secretary+password')
- self.driver.find_element_by_xpath('//button[@type="submit"]').click()
+ self.login('secretary')
#@override_settings(DEBUG=True)
def testReorderSlides(self):
@@ -305,6 +282,223 @@ class SlideReorderTests(IetfLiveServerTestCase):
names=self.session.sessionpresentation_set.values_list('document__name',flat=True)
self.assertEqual(list(names),['one','three','two'])
+
+@skipIf(skip_selenium, skip_message)
+class AgendaTests(MeetingTestCase):
+ # Groups whose display logic is inverted in agenda.html. These have
+ # toggles with class 'pickviewneg' in the template.
+ PICKVIEWNEG = ['iepg', 'tools', 'edu', 'ietf', 'iesg', 'iab']
+
+ def setUp(self):
+ super(AgendaTests, self).setUp()
+ self.meeting = make_meeting_test_data()
+
+ def row_id_for_item(self, item):
+ return 'row-%s' % item.slug()
+
+ def get_expected_items(self):
+ expected_items = self.meeting.schedule.assignments.exclude(timeslot__type__in=['lead','offagenda'])
+ self.assertGreater(len(expected_items), 0, 'Test setup generated an empty schedule')
+ return expected_items
+
+ def test_agenda_view_displays_all_items(self):
+ """By default, all agenda items should be displayed"""
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
+
+ for item in self.get_expected_items():
+ row_id = 'row-%s' % item.slug()
+ try:
+ item_row = self.driver.find_element_by_id(row_id)
+ except NoSuchElementException:
+ item_row = None
+ self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
+ self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed' % row_id)
+
+ def test_agenda_view_js_func_parse_query_params(self):
+ """Test parse_query_params() function"""
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
+
+ # Only 'show' param
+ result = self.driver.execute_script(
+ 'return parse_query_params("?show=group1,group2,group3");'
+ )
+ self.assertEqual(result, dict(show='group1,group2,group3'))
+
+ # Only 'hide' param
+ result = self.driver.execute_script(
+ 'return parse_query_params("?hide=group4,group5,group6");'
+ )
+ self.assertEqual(result, dict(hide='group4,group5,group6'))
+
+ # Both 'show' and 'hide'
+ result = self.driver.execute_script(
+ 'return parse_query_params("?show=group1,group2,group3&hide=group4,group5,group6");'
+ )
+ self.assertEqual(result, dict(show='group1,group2,group3', hide='group4,group5,group6'))
+
+ def test_agenda_view_js_func_toggle_list_item(self):
+ """Test toggle_list_item() function"""
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
+
+ result = self.driver.execute_script(
+ """
+ // start empty, add item
+ var list0=[];
+ toggle_list_item(list0, 'item');
+
+ // one item, remove it
+ var list1=['item'];
+ toggle_list_item(list1, 'item');
+
+ // one item, add another
+ var list2=['item1'];
+ toggle_list_item(list2, 'item2');
+
+ // multiple items, remove first
+ var list3=['item1', 'item2', 'item3'];
+ toggle_list_item(list3, 'item1');
+
+ // multiple items, remove middle
+ var list4=['item1', 'item2', 'item3'];
+ toggle_list_item(list4, 'item2');
+
+ // multiple items, remove last
+ var list5=['item1', 'item2', 'item3'];
+ toggle_list_item(list5, 'item3');
+
+ return [list0, list1, list2, list3, list4, list5];
+ """
+ )
+ self.assertEqual(result[0], ['item'], 'Adding item to empty list failed')
+ self.assertEqual(result[1], [], 'Removing only item in a list failed')
+ self.assertEqual(result[2], ['item1', 'item2'], 'Adding second item to list failed')
+ self.assertEqual(result[3], ['item2', 'item3'], 'Removing first item from list failed')
+ self.assertEqual(result[4], ['item1', 'item3'], 'Removing middle item from list failed')
+ self.assertEqual(result[5], ['item1', 'item2'], 'Removing last item from list failed')
+
+ def test_agenda_view_filter_show_one(self):
+ """Filtered agenda view should display only matching rows (one group selected)"""
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars')
+ self.assert_agenda_item_visibility(['mars'] + self.PICKVIEWNEG) # ames and secretariat not selected
+
+ def test_agenda_view_filter_show_two(self):
+ """Filtered agenda view should display only matching rows (two groups selected)"""
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars,ames')
+ self.assert_agenda_item_visibility(['mars', 'ames'] + self.PICKVIEWNEG) # secretariat not selected
+
+ def test_agenda_view_filter_all(self):
+ """Filtered agenda view should display only matching rows (all groups selected)"""
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda'))
+ self.assert_agenda_item_visibility()
+
+ def test_agenda_view_filter_hide(self):
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?hide=ietf')
+ self.assert_agenda_item_visibility([g for g in self.PICKVIEWNEG if g != 'ietf'])
+
+ def test_agenda_view_filter_show_and_hide(self):
+ self.login()
+ self.driver.get(self.absreverse('ietf.meeting.views.agenda') + '?show=mars&hide=ietf')
+ self.assert_agenda_item_visibility(
+ ['mars'] + [g for g in self.PICKVIEWNEG if g != 'ietf']
+ )
+
+ def assert_agenda_item_visibility(self, visible_groups=()):
+ """Assert that correct items are visible in current browser window
+
+ If visible_groups is empty (the default), expects all items to be visible.
+ """
+ for item in self.get_expected_items():
+ row_id = self.row_id_for_item(item)
+ try:
+ item_row = self.driver.find_element_by_id(row_id)
+ except NoSuchElementException:
+ item_row = None
+ self.assertIsNotNone(item_row, 'No row for schedule item "%s"' % row_id)
+ if len(visible_groups) == 0 or item.session.group.acronym in visible_groups:
+ self.assertTrue(item_row.is_displayed(), 'Row for schedule item "%s" is not displayed but should be' % row_id)
+ else:
+ self.assertFalse(item_row.is_displayed(), 'Row for schedule item "%s" is displayed but should not be' % row_id)
+
+ def test_agenda_view_group_filter_toggle(self):
+ """Clicking a group toggle enables/disables agenda filtering"""
+ group_acronym = 'mars'
+
+ self.login()
+ url = self.absreverse('ietf.meeting.views.agenda')
+ self.driver.get(url)
+
+ # Click the 'customize' anchor to reveal the group buttons
+ customize_anchor = WebDriverWait(self.driver, 2).until(
+ expected_conditions.element_to_be_clickable(
+ (By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
+ )
+ )
+ customize_anchor.click()
+
+ # Click the group button
+ group_button = WebDriverWait(self.driver, 2).until(
+ expected_conditions.element_to_be_clickable(
+ (By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
+ )
+ )
+ group_button.click()
+
+ # Check visibility
+ self.assert_agenda_item_visibility([group_acronym] + self.PICKVIEWNEG)
+
+ # Click the group button again
+ group_button = WebDriverWait(self.driver, 2).until(
+ expected_conditions.element_to_be_clickable(
+ (By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
+ )
+ )
+ group_button.click()
+
+ # Check visibility
+ self.assert_agenda_item_visibility()
+
+ def test_agenda_view_group_filter_toggle_without_replace_state(self):
+ """Toggle should function for browsers without window.history.replaceState"""
+ group_acronym = 'mars'
+
+ self.login()
+ url = self.absreverse('ietf.meeting.views.agenda')
+ self.driver.get(url)
+
+ # Rather than digging up an ancient browser, simulate absence of history.replaceState
+ self.driver.execute_script('window.history.replaceState = undefined;')
+
+
+ # Click the 'customize' anchor to reveal the group buttons
+ customize_anchor = WebDriverWait(self.driver, 2).until(
+ expected_conditions.element_to_be_clickable(
+ (By.CSS_SELECTOR, '#accordion a[data-toggle="collapse"]')
+ )
+ )
+ customize_anchor.click()
+
+
+ # Get ready to click the group button
+ group_button = WebDriverWait(self.driver, 2).until(
+ expected_conditions.element_to_be_clickable(
+ (By.CSS_SELECTOR, 'button.pickview.%s' % group_acronym)
+ )
+ )
+
+ # Be sure we're at the URL we think we're at before we click
+ self.assertEqual(self.driver.current_url, url)
+ group_button.click() # click!
+
+ expected_url = '%s?show=%s' % (url, group_acronym)
+ WebDriverWait(self.driver, 2).until(expected_conditions.url_to_be(expected_url))
+ # no assertion here - if WebDriverWait raises an exception, the test will fail.
+ # We separately test whether this URL will filter correctly.
+
# The following are useful debugging tools
# If you add this to a LiveServerTestCase and run just this test, you can browse
diff --git a/ietf/meeting/tests_views.py b/ietf/meeting/tests_views.py
index 84897c660..b887920c2 100644
--- a/ietf/meeting/tests_views.py
+++ b/ietf/meeting/tests_views.py
@@ -1596,7 +1596,7 @@ class InterimTests(TestCase):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
self.assertEqual(r.get('Content-Type'), "text/calendar")
- self.assertEqual(r.content.count(b'UID'), 7)
+ self.assertEqual(r.content.count(b'UID'), 8)
# check filtered output
url = url + '?filters=mars'
r = self.client.get(url)
diff --git a/ietf/static/ietf/js/toggle-visibility.js b/ietf/static/ietf/js/toggle-visibility.js
deleted file mode 100644
index 75f6c59a9..000000000
--- a/ietf/static/ietf/js/toggle-visibility.js
+++ /dev/null
@@ -1,74 +0,0 @@
-
-function toggle_visibility() {
- var h = window.location.hash;
- h = h.replace(/^#?,?/, '');
-
- // reset UI elements to default state
- $(".pickview").removeClass("active disabled");
- $(".pickviewneg").addClass("active");
-
- if (h) {
- // if there are items in the hash, hide all rows
- $('[id^="row-"]').hide();
-
- // show the customizer
- $("#customize").collapse("show");
-
- // loop through the has items and change the UI element and row visibilities accordingly
- var query_array = [];
- $.each(h.split(","), function (i, v) {
- if (v.indexOf("-") == 0) {
- // this is a "negative" item: when present, hide these rows
- v = v.replace(/^-/, '');
- $('[id^="row-"]').filter('[id*="-' + v + '"]').hide();
- $(".view." + v).find("button").removeClass("active disabled");
- $("button.pickviewneg." + v).removeClass("active");
- } else {
- // this is a regular item: when present, show these rows
- $('[id^="row-"]').filter('[id*="-' + v + '"]').show();
- $(".view." + v).find("button").addClass("active disabled");
- $("button.pickview." + v).addClass("active");
- query_array.push("filters=" + v)
- }
- });
-
- // adjust the custom .ics link
- var link = $('a[href*="upcoming.ics"]');
- var new_href = link.attr("href").split("?")[0]+"?"+query_array.join("&");
- link.attr("href",new_href);
-
- } else {
- // if the hash is empty, show all
- $('[id^="row-"]').show();
- // adjust the custom .ics link
- var link = $('a[href*="upcoming.ics"]');
- link.attr("href",link.attr("href").split("?")[0]);
- }
-}
-
-$(".pickview, .pickviewneg").click(function () {
- var h = window.location.hash;
- var item = $(this).text().trim().toLowerCase();
- if ($(this).hasClass("pickviewneg")) {
- item = "-" + item;
- }
-
- re = new RegExp('(^|#|,)' + item + "(,|$)");
- if (h.match(re) == null) {
- if (h.replace("#", "").length == 0) {
- h = item;
- } else {
- h += "," + item;
- }
- h = h.replace(/^#?,/, '');
- } else {
- h = h.replace(re, "$2").replace(/^#?,/, '');
- }
- window.location.hash = h.replace(/^#$/, '');
- toggle_visibility();
-});
-
-$(document).ready(function () {
- toggle_visibility();
-});
-
diff --git a/ietf/templates/meeting/agenda.html b/ietf/templates/meeting/agenda.html
index 135ba35a9..68fa60f8c 100644
--- a/ietf/templates/meeting/agenda.html
+++ b/ietf/templates/meeting/agenda.html
@@ -359,16 +359,32 @@
{% block js %}
-
{% endblock %}
diff --git a/ietf/utils/test_data.py b/ietf/utils/test_data.py
index 9aa7bbdbd..231631b5d 100644
--- a/ietf/utils/test_data.py
+++ b/ietf/utils/test_data.py
@@ -428,7 +428,6 @@ def make_test_data():
return draft
- return draft
def make_review_data(doc):
team1 = create_group(acronym="reviewteam", name="Review Team", type_id="review", list_email="reviewteam@ietf.org", parent=Group.objects.get(acronym="farfut"))
diff --git a/ietf/utils/test_runner.py b/ietf/utils/test_runner.py
index 5826a6f67..a2b2fbc82 100644
--- a/ietf/utils/test_runner.py
+++ b/ietf/utils/test_runner.py
@@ -789,6 +789,8 @@ class IetfLiveServerTestCase(StaticLiveServerTestCase):
set_coverage_checking(False)
super(IetfLiveServerTestCase, cls).setUpClass()
+ def setUp(self):
+ super(IetfLiveServerTestCase, self).setUp()
# LiveServerTestCase uses TransactionTestCase which seems to
# somehow interfere with the fixture loading process in
# IetfTestRunner when running multiple tests (the first test