From 044293b4a983dc13c43b56bd60c0b45afcb471b6 Mon Sep 17 00:00:00 2001 From: Jennifer Richards Date: Mon, 28 Jun 2021 18:57:20 +0000 Subject: [PATCH] Fix broken meeting materials button on upcoming meetings page. Fixes #3278. Commit ready for merge. - Legacy-Id: 19164 --- ietf/doc/tests_js.py | 16 +--- ietf/meeting/tests_js.py | 68 ++++++++++++--- .../static/ietf/js/agenda/agenda_materials.js | 84 +++++++++++++++++++ ietf/templates/meeting/agenda.html | 71 +--------------- .../meeting/interim_session_buttons.html | 3 +- .../meeting/session_agenda_include.html | 2 +- .../meeting/session_buttons_include.html | 1 + .../meeting/session_details_panel.html | 3 +- ietf/templates/meeting/upcoming.html | 32 +------ ietf/utils/jstest.py | 13 +++ 10 files changed, 164 insertions(+), 129 deletions(-) create mode 100644 ietf/static/ietf/js/agenda/agenda_materials.js diff --git a/ietf/doc/tests_js.py b/ietf/doc/tests_js.py index 1709e846c..7f08b51c8 100644 --- a/ietf/doc/tests_js.py +++ b/ietf/doc/tests_js.py @@ -6,7 +6,8 @@ import debug # pyflakes:ignore from ietf.doc.factories import WgDraftFactory, DocumentAuthorFactory from ietf.person.factories import PersonFactory from ietf.person.models import Person -from ietf.utils.jstest import IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled +from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled, + presence_of_element_child_by_css_selector ) if selenium_enabled(): from selenium.webdriver.common.by import By @@ -14,19 +15,6 @@ if selenium_enabled(): from selenium.webdriver.support import expected_conditions -class presence_of_element_child_by_css_selector: - """Wait for presence of a child of a WebElement matching a CSS selector - - This is a condition class for use with WebDriverWait. - """ - def __init__(self, element, child_selector): - self.element = element - self.child_selector = child_selector - - def __call__(self, driver): - child = self.element.find_element_by_css_selector(self.child_selector) - return child if child is not None else False - @ifSeleniumEnabled class EditAuthorsTests(IetfSeleniumTestCase): def setUp(self): diff --git a/ietf/meeting/tests_js.py b/ietf/meeting/tests_js.py index 38b83e9b7..f0c616fdb 100644 --- a/ietf/meeting/tests_js.py +++ b/ietf/meeting/tests_js.py @@ -31,7 +31,8 @@ from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session, Meeting, SchedulingEvent, SessionStatusName) from ietf.meeting.utils import add_event_info_to_session_qs from ietf.utils.test_utils import assert_ical_response_is_valid -from ietf.utils.jstest import IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled +from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled, + presence_of_element_child_by_css_selector ) from ietf import settings if selenium_enabled(): @@ -1572,6 +1573,7 @@ class InterimTests(IetfSeleniumTestCase): sg_sess.save() sg_slot.save() + self.wait = WebDriverWait(self.driver, 2) def tearDown(self): settings.AGENDA_PATH = self.saved_agenda_path @@ -1647,7 +1649,7 @@ class InterimTests(IetfSeleniumTestCase): def assert_upcoming_meeting_calendar(self, visible_meetings=None): """Assert that correct items are sent to the calendar""" def advance_month(): - button = WebDriverWait(self.driver, 2).until( + button = self.wait.until( expected_conditions.element_to_be_clickable( (By.CSS_SELECTOR, 'div#calendar button.fc-next-button'))) self.scroll_to_element(button) @@ -1850,8 +1852,6 @@ class InterimTests(IetfSeleniumTestCase): self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings) def test_upcoming_view_time_zone_selection(self): - wait = WebDriverWait(self.driver, 2) - def _assert_interim_tz_correct(sessions, tz): zone = pytz.timezone(tz) for session in sessions: @@ -1897,7 +1897,7 @@ class InterimTests(IetfSeleniumTestCase): # wait for the select box to be updated - look for an arbitrary time zone to be in # its options list to detect this arbitrary_tz = 'America/Halifax' - arbitrary_tz_opt = wait.until( + arbitrary_tz_opt = self.wait.until( expected_conditions.presence_of_element_located( (By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz) ) @@ -1923,7 +1923,7 @@ class InterimTests(IetfSeleniumTestCase): # click 'utc' button utc_tz_link.click() - wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(arbitrary_tz_opt.is_selected()) @@ -1935,7 +1935,7 @@ class InterimTests(IetfSeleniumTestCase): # click back to 'local' local_tz_link.click() - wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) self.assertTrue(local_tz_opt.is_selected()) self.assertTrue(local_tz_bottom_opt.is_selected()) self.assertFalse(arbitrary_tz_opt.is_selected()) @@ -1947,7 +1947,7 @@ class InterimTests(IetfSeleniumTestCase): # Now select a different item from the select input arbitrary_tz_opt.click() - wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertTrue(arbitrary_tz_opt.is_selected()) @@ -1960,7 +1960,7 @@ class InterimTests(IetfSeleniumTestCase): # Now repeat those tests using the widgets at the bottom of the page # click 'utc' button utc_tz_bottom_link.click() - wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt)) self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertFalse(arbitrary_tz_opt.is_selected()) @@ -1972,7 +1972,7 @@ class InterimTests(IetfSeleniumTestCase): # click back to 'local' local_tz_bottom_link.click() - wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt)) self.assertTrue(local_tz_opt.is_selected()) self.assertTrue(local_tz_bottom_opt.is_selected()) self.assertFalse(arbitrary_tz_opt.is_selected()) @@ -1984,7 +1984,7 @@ class InterimTests(IetfSeleniumTestCase): # Now select a different item from the select input arbitrary_tz_bottom_opt.click() - wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) + self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt)) self.assertFalse(local_tz_opt.is_selected()) self.assertFalse(local_tz_bottom_opt.is_selected()) self.assertTrue(arbitrary_tz_opt.is_selected()) @@ -1994,6 +1994,52 @@ class InterimTests(IetfSeleniumTestCase): _assert_interim_tz_correct(sessions, arbitrary_tz) _assert_ietf_tz_correct(ietf_meetings, arbitrary_tz) + def test_upcoming_materials_modal(self): + """Test opening and closing a materals modal + + This does not test dynamic reloading of the meeting materials - it relies on the main + agenda page testing that. If the materials modal handling diverges between here and + there, this should be updated to include that test. + """ + url = self.absreverse('ietf.meeting.views.upcoming') + self.driver.get(url) + + interim = self.displayed_interims(['mars'])[0] + session = interim.session_set.first() + assignment = session.official_timeslotassignment() + slug = assignment.slug() + + # modal should start hidden + modal_div = self.driver.find_element_by_css_selector('div#modal-%s' % slug) + self.assertFalse(modal_div.is_displayed()) + + # Click the 'materials' button + open_modal_button = self.wait.until( + expected_conditions.element_to_be_clickable( + (By.CSS_SELECTOR, '[data-target="#modal-%s"]' % slug) + ), + 'Modal open button not found or not clickable', + ) + open_modal_button.click() + self.wait.until( + expected_conditions.visibility_of(modal_div), + 'Modal did not become visible after clicking open button', + ) + + # Now close the modal + close_modal_button = self.wait.until( + presence_of_element_child_by_css_selector( + modal_div, + '.modal-footer button[data-dismiss="modal"]', + ), + 'Modal close button not found or not clickable', + ) + close_modal_button.click() + self.wait.until( + expected_conditions.invisibility_of_element(modal_div), + 'Modal was not hidden after clicking close button', + ) + # The following are useful debugging tools diff --git a/ietf/static/ietf/js/agenda/agenda_materials.js b/ietf/static/ietf/js/agenda/agenda_materials.js new file mode 100644 index 000000000..97d3d1396 --- /dev/null +++ b/ietf/static/ietf/js/agenda/agenda_materials.js @@ -0,0 +1,84 @@ +// Copyright The IETF Trust 2021, All Rights Reserved + +/* + Javascript support for the materials modal rendered by session_agenda_include.html + + Requires jquery be loaded + */ + +var agenda_materials; // public interface + +(function() { + 'use strict'; + /** + * Retrieve and display materials for a session + * + * If output_elt exists and has a "data-src" attribute, retrieves the document + * from that URL and displays under output_elt. Handles text/plain, text/markdown, + * and text/html. + * + * @param output_elt Element, probably a div, to hold the output + */ + function retrieve_session_materials(output_elt) { + if (!output_elt) {return;} + output_elt = $(output_elt); + var data_src = output_elt.attr("data-src"); + if (!data_src) { + output_elt.html("

Error: missing data-src attribute

"); + } else { + output_elt.html("

Loading " + data_src + "...

"); + var outer_xhr = $.get(data_src) + outer_xhr.done(function(data, status, xhr) { + var t = xhr.getResponseHeader("content-type"); + if (!t) { + data = "

Error retrieving " + data_src + + ": Missing content-type in response header

"; + } else if (t.indexOf("text/plain") > -1) { + data = "
" + data + "
"; + } else if (t.indexOf("text/markdown") > -1) { + data = "
" + data + "
"; + } else if(t.indexOf("text/html") > -1) { + // nothing to do here + } else { + data = "

Unknown type: " + xhr.getResponseHeader("content-type") + "

"; + } + output_elt.html(data); + }).fail(function() { + output_elt.html("

Error retrieving " + data_src + + ": (" + outer_xhr.status.toString() + ") " + + outer_xhr.statusText + "

"); + }) + } + } + + /** + * Retrieve contents of a session materials modal + * + * Expects output_elt to exist and have a "data-src" attribute. Retrieves the + * contents of that URL, then attempts to populate the .agenda-frame and + * .minutes-frame elements. + * + * @param output_elt Element, probably a div, to hold the output + */ + function retrieve_session_modal(output_elt) { + if (!output_elt) {return;} + output_elt = $(output_elt); + var data_src = output_elt.attr("data-src"); + if (!data_src) { + output_elt.html("

Error: missing data-src attribute

"); + } else { + output_elt.html("

Loading...

"); + $.get(data_src).done(function(data) { + output_elt.html(data); + retrieve_session_materials(output_elt.find(".agenda-frame")); + retrieve_session_materials(output_elt.find(".minutes-frame")); + }); + } + } + + $(document).ready(function() { + $(".modal").on("show.bs.modal", function () { + retrieve_session_modal($(this).find(".session-materials")); + }); + }) +})(); \ No newline at end of file diff --git a/ietf/templates/meeting/agenda.html b/ietf/templates/meeting/agenda.html index 65217663e..95d93c3d0 100644 --- a/ietf/templates/meeting/agenda.html +++ b/ietf/templates/meeting/agenda.html @@ -469,80 +469,11 @@ update_ical_links(filter_params) } - /** - * Retrieve and display materials for a session - * - * If output_elt exists and has a "data-src" attribute, retrieves the document - * from that URL and displays under output_elt. Handles text/plain, text/markdown, - * and text/html. - * - * @param output_elt Element, probably a div, to hold the output - */ - function retrieve_session_materials(output_elt) { - if (!output_elt) {return;} - output_elt = $(output_elt); - var data_src = output_elt.attr("data-src"); - if (!data_src) { - output_elt.html("

Error: missing data-src attribute

"); - } else { - output_elt.html("

Loading " + data_src + "...

"); - outer_xhr = $.get(data_src) - outer_xhr.done(function(data, status, xhr) { - var t = xhr.getResponseHeader("content-type"); - if (!t) { - data = "

Error retrieving " + data_src - + ": Missing content-type in response header

"; - } else if (t.indexOf("text/plain") > -1) { - data = "
" + data + "
"; - } else if (t.indexOf("text/markdown") > -1) { - data = "
" + data + "
"; - } else if(t.indexOf("text/html") > -1) { - // nothing to do here - } else { - data = "

Unknown type: " + xhr.getResponseHeader("content-type") + "

"; - } - output_elt.html(data); - }).fail(function() { - output_elt.html("

Error retrieving " + data_src - + ": (" + outer_xhr.status.toString() + ") " - + outer_xhr.statusText + "

"); - }) - } - } - - /** - * Retrieve contents of a session materials modal - * - * Expects output_elt to exist and have a "data-src" attribute. Retrieves the - * contents of that URL, then attempts to populate the .agenda-frame and - * .minutes-frame elements. - * - * @param output_elt Element, probably a div, to hold the output - */ - function retrieve_session_modal(output_elt) { - if (!output_elt) {return;} - output_elt = $(output_elt); - var data_src = output_elt.attr("data-src"); - if (!data_src) { - output_elt.html("

Error: missing data-src attribute

"); - } else { - output_elt.html("

Loading...

"); - $.get(data_src).done(function(data) { - output_elt.html(data); - retrieve_session_materials(output_elt.find(".agenda-frame")); - retrieve_session_materials(output_elt.find(".minutes-frame")); - }); - } - } - - $(".modal").on("show.bs.modal", function () { - retrieve_session_modal($(this).find(".session-materials")); - }); - + +