From db8766d4aa5287ec66d6cbb80a1342a04159fdbc Mon Sep 17 00:00:00 2001 From: KEVYN SUAREZ <91025555+efortish@users.noreply.github.com> Date: Tue, 10 Jun 2025 13:06:46 -0500 Subject: [PATCH] fix: Include metadata for blocked sequence units to enable proper navigation (#36485) The original issue was that when a sequence was locked due to prerequisites, the API returned an empty items array ([]). This prevented the frontend from knowing what units were inside the locked sequence, meaning it couldn't construct the URLs correctly for navigation so the next/previous buttons stop working. --- xmodule/seq_block.py | 14 ++++- xmodule/tests/test_sequence.py | 94 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/xmodule/seq_block.py b/xmodule/seq_block.py index 1b94f47d89..f06d3030f5 100644 --- a/xmodule/seq_block.py +++ b/xmodule/seq_block.py @@ -557,7 +557,19 @@ class SequenceBlock( 'This section is a prerequisite. You must complete this section in order to unlock additional content.' ) - blocks = self._render_student_view_for_blocks(context, children, fragment, view) if prereq_met else [] + if prereq_met: + blocks = self._render_student_view_for_blocks(context, children, fragment, view) + else: + blocks = [] + for child in children: + usage_id = child.scope_ids.usage_id + blocks.append({ + 'id': str(usage_id), + 'type': child.scope_ids.block_type, + 'display_name': child.display_name_with_default, + 'is_gated': True, # Mark as blocked + 'content': '', # Real content not included + }) params = { 'items': blocks, diff --git a/xmodule/tests/test_sequence.py b/xmodule/tests/test_sequence.py index be773865a7..c299f7bed0 100644 --- a/xmodule/tests/test_sequence.py +++ b/xmodule/tests/test_sequence.py @@ -478,3 +478,97 @@ class SequenceBlockTestCase(XModuleXmlImportTest): # Replace tuple and un-necessary info from inside string and get the dictionary. cleaned_data = data.replace("(('seq_block.html',\n", '').replace("),\n {})", '').strip() return ast.literal_eval(cleaned_data) + + def test_not_gated_blocks_rendered_normally(self): + """ + Test that non-gated blocks are rendered with full content when prerequisites are met. + """ + # Mock child block + child = Mock() + child.scope_ids.usage_id = "block1" + child.scope_ids.block_type = "vertical" + child.display_name_with_default = "Test Block" + children = [child] + + # Mock context + context = {"next_url": "next_url", "prev_url": "prev_url"} + fragment = Mock() + + # Mock `_render_student_view_for_blocks` + self.sequence_3_1._render_student_view_for_blocks = Mock(return_value="rendered_blocks") # pylint: disable=protected-access + + # Call `_get_render_metadata` with prerequisites met + metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access + context, children, prereq_met=True, prereq_meta_info={}, fragment=fragment + ) + + # Assert that blocks are rendered normally + assert metadata["items"] == "rendered_blocks" + assert metadata["next_url"] == "next_url" + assert metadata["prev_url"] == "prev_url" + + def test_gated_blocks_rendered_with_basic_info(self): + """ + Test that gated blocks are rendered with minimal metadata when prerequisites are not met. + """ + # Mock child block + child = Mock() + child.scope_ids.usage_id = "block1" + child.scope_ids.block_type = "vertical" + child.display_name_with_default = "Test Block" + children = [child] + + # Mock context + context = {"next_url": "next_url", "prev_url": "prev_url"} + + # Mock prereq_meta_info with required keys + prereq_meta_info = { + "url": "http://example.com/prereq", + "display_name": "Prerequisite Section", + "id": "prereq_block_id", + } + + # Call `_get_render_metadata` with prerequisites not met + metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access + context, children, prereq_met=False, prereq_meta_info=prereq_meta_info + ) + + # Assert that gated blocks are rendered with basic info + assert len(metadata["items"]) == 1 + assert metadata["items"][0]["id"] == "block1" + assert metadata["items"][0]["type"] == "vertical" + assert metadata["items"][0]["display_name"] == "Test Block" + assert metadata["items"][0]["is_gated"] is True + assert metadata["items"][0]["content"] == "" + + # Assert that next and previous URLs are present + assert metadata["next_url"] == "next_url" + assert metadata["prev_url"] == "prev_url" + + def test_prereqs_met_content_rendered_normally(self): + """ + Test that content is rendered normally when prerequisites are met. + """ + # Mock child block + child = Mock() + child.scope_ids.usage_id = "block1" + child.scope_ids.block_type = "vertical" + child.display_name_with_default = "Test Block" + children = [child] + + # Mock context + context = {"next_url": "next_url", "prev_url": "prev_url"} + fragment = Mock() + + # Mock `_render_student_view_for_blocks` + self.sequence_3_1._render_student_view_for_blocks = Mock(return_value="rendered_blocks") # pylint: disable=protected-access + + # Call `_get_render_metadata` with prerequisites met + metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access + context, children, prereq_met=True, prereq_meta_info={}, fragment=fragment + ) + + # Assert that content is rendered normally + assert metadata["items"] == "rendered_blocks" + assert metadata["next_url"] == "next_url" + assert metadata["prev_url"] == "prev_url"