diff --git a/openedx/features/course_experience/tests/views/test_course_outline.py b/openedx/features/course_experience/tests/views/test_course_outline.py index f473f248df..dc4263c178 100644 --- a/openedx/features/course_experience/tests/views/test_course_outline.py +++ b/openedx/features/course_experience/tests/views/test_course_outline.py @@ -3,6 +3,7 @@ Tests for the Course Outline view and supporting views. """ import datetime import json +import re from completion import waffle from completion.models import BlockCompletion @@ -60,8 +61,16 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): chapter = ItemFactory.create(category='chapter', parent_location=course.location) sequential = ItemFactory.create(category='sequential', parent_location=chapter.location) sequential2 = ItemFactory.create(category='sequential', parent_location=chapter.location) - vertical = ItemFactory.create(category='vertical', parent_location=sequential.location) - vertical2 = ItemFactory.create(category='vertical', parent_location=sequential2.location) + vertical = ItemFactory.create( + category='vertical', + parent_location=sequential.location, + display_name="Vertical 1" + ) + vertical2 = ItemFactory.create( + category='vertical', + parent_location=sequential2.location, + display_name="Vertical 2" + ) course.children = [chapter] chapter.children = [sequential, sequential2] sequential.children = [vertical] @@ -518,6 +527,40 @@ class TestCourseOutlineResumeCourse(SharedModuleStoreTestCase, CompletionWaffleT content = pq(response.content) self.assertTrue(content('.action-resume-course').attr('href').endswith('/course/' + course.url_name)) + @override_switch( + '{}.{}'.format( + waffle.WAFFLE_NAMESPACE, waffle.ENABLE_COMPLETION_TRACKING + ), + active=True + ) + def test_course_outline_auto_open(self): + """ + Tests that the course outline auto-opens to the first unit + in a course if a user has no completion data, and to the + last-accessed unit if a user does have completion data. + """ + def get_sequential_button(url, is_hidden): + is_hidden_string = "is-hidden" if is_hidden else "" + + return "" + + with patch('openedx.features.course_experience.waffle.new_course_outline_enabled', Mock(return_value=True)): + # Course tree + course = self.course + chapter = course.children[0] + sequential1 = chapter.children[0] + sequential2 = chapter.children[1] + + response_content = self.client.get(course_home_url(course)).content + stripped_response = text_type(re.sub("\\s+", "", response_content), "utf-8") + + self.assertTrue(get_sequential_button(text_type(sequential1.location), False) in stripped_response) + self.assertTrue(get_sequential_button(text_type(sequential2.location), True) in stripped_response) + class TestCourseOutlinePreview(SharedModuleStoreTestCase): """ diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py index 2e938d5299..13362196c5 100644 --- a/openedx/features/course_experience/utils.py +++ b/openedx/features/course_experience/utils.py @@ -162,3 +162,20 @@ def get_course_outline_block_tree(request, course_id): else: mark_last_accessed(request.user, course_key, course_outline_root_block) return course_outline_root_block + + +def get_resume_block(block): + """ + Gets the deepest block marked as 'resume_block'. + + """ + if not block['resume_block']: + return None + if not block.get('children'): + return block + + for child in block['children']: + resume_block = get_resume_block(child) + if resume_block: + return resume_block + return block diff --git a/openedx/features/course_experience/views/course_home.py b/openedx/features/course_experience/views/course_home.py index 3ce3f3595d..48abb63e68 100644 --- a/openedx/features/course_experience/views/course_home.py +++ b/openedx/features/course_experience/views/course_home.py @@ -29,7 +29,7 @@ from student.models import CourseEnrollment from util.views import ensure_valid_course_key from .. import LATEST_UPDATE_FLAG, SHOW_UPGRADE_MSG_ON_COURSE_HOME, USE_BOOTSTRAP_FLAG -from ..utils import get_course_outline_block_tree +from ..utils import get_course_outline_block_tree, get_resume_block from .course_dates import CourseDatesFragmentView from .course_home_messages import CourseHomeMessageFragmentView from .course_outline import CourseOutlineFragmentView @@ -81,23 +81,6 @@ class CourseHomeFragmentView(EdxFragmentView): otherwise the URL of the course root. """ - - def get_resume_block(block): - """ - Gets the deepest block marked as 'resume_block'. - - """ - if not block['resume_block']: - return None - if not block.get('children'): - return block - - for child in block['children']: - resume_block = get_resume_block(child) - if resume_block: - return resume_block - return block - course_outline_root_block = get_course_outline_block_tree(request, course_id) resume_block = get_resume_block(course_outline_root_block) if course_outline_root_block else None has_visited_course = bool(resume_block) diff --git a/openedx/features/course_experience/views/course_outline.py b/openedx/features/course_experience/views/course_outline.py index eeeee8aac5..841979c902 100644 --- a/openedx/features/course_experience/views/course_outline.py +++ b/openedx/features/course_experience/views/course_outline.py @@ -17,7 +17,7 @@ from openedx.features.course_experience import waffle as course_experience_waffl from completion import waffle as completion_waffle from student.models import CourseEnrollment -from ..utils import get_course_outline_block_tree +from ..utils import get_course_outline_block_tree, get_resume_block from util.milestones_helpers import get_course_content_milestones @@ -52,8 +52,11 @@ class CourseOutlineFragmentView(EdxFragmentView): # TODO: EDUCATOR-2283 Remove this check when the waffle flag is turned on in production if course_experience_waffle.new_course_outline_enabled(course_key=course_key): - xblock_display_names = self.create_xblock_id_and_name_dict(course_block_tree) + resume_block = get_resume_block(course_block_tree) + if not resume_block: + self.mark_first_unit_to_resume(course_block_tree) + xblock_display_names = self.create_xblock_id_and_name_dict(course_block_tree) gated_content = self.get_content_milestones(request, course_key) context['gated_content'] = gated_content @@ -151,3 +154,9 @@ class CourseOutlineFragmentView(EdxFragmentView): return user_enrollment.created > begin_collection_date except CourseEnrollment.DoesNotExist: return False + + def mark_first_unit_to_resume(self, block_node): + children = block_node.get('children') + if children: + children[0]['resume_block'] = True + self.mark_first_unit_to_resume(children[0])