44
55import time
66import datetime
7+ import shutil
8+ import os
79from pyquery import PyQuery
810from unittest import skipIf
911
1012import django
1113from 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
1418import debug # pyflakes:ignore
1519
1620from ietf .doc .factories import DocumentFactory
1721from ietf .group import colors
22+ from ietf .group .models import Group
1823from ietf .meeting .factories import SessionFactory
1924from 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
2129from ietf .utils .test_runner import IetfLiveServerTestCase
2230from ietf .utils .pipe import pipe
2331from 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