Skip to content

Commit db8766d

Browse files
authored
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.
1 parent 087ab73 commit db8766d

2 files changed

Lines changed: 107 additions & 1 deletion

File tree

xmodule/seq_block.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,19 @@ def _get_render_metadata(self, context, children, prereq_met, prereq_meta_info,
557557
'This section is a prerequisite. You must complete this section in order to unlock additional content.'
558558
)
559559

560-
blocks = self._render_student_view_for_blocks(context, children, fragment, view) if prereq_met else []
560+
if prereq_met:
561+
blocks = self._render_student_view_for_blocks(context, children, fragment, view)
562+
else:
563+
blocks = []
564+
for child in children:
565+
usage_id = child.scope_ids.usage_id
566+
blocks.append({
567+
'id': str(usage_id),
568+
'type': child.scope_ids.block_type,
569+
'display_name': child.display_name_with_default,
570+
'is_gated': True, # Mark as blocked
571+
'content': '', # Real content not included
572+
})
561573

562574
params = {
563575
'items': blocks,

xmodule/tests/test_sequence.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,3 +478,97 @@ def get_context_dict_from_string(self, data):
478478
# Replace tuple and un-necessary info from inside string and get the dictionary.
479479
cleaned_data = data.replace("(('seq_block.html',\n", '').replace("),\n {})", '').strip()
480480
return ast.literal_eval(cleaned_data)
481+
482+
def test_not_gated_blocks_rendered_normally(self):
483+
"""
484+
Test that non-gated blocks are rendered with full content when prerequisites are met.
485+
"""
486+
# Mock child block
487+
child = Mock()
488+
child.scope_ids.usage_id = "block1"
489+
child.scope_ids.block_type = "vertical"
490+
child.display_name_with_default = "Test Block"
491+
children = [child]
492+
493+
# Mock context
494+
context = {"next_url": "next_url", "prev_url": "prev_url"}
495+
fragment = Mock()
496+
497+
# Mock `_render_student_view_for_blocks`
498+
self.sequence_3_1._render_student_view_for_blocks = Mock(return_value="rendered_blocks") # pylint: disable=protected-access
499+
500+
# Call `_get_render_metadata` with prerequisites met
501+
metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access
502+
context, children, prereq_met=True, prereq_meta_info={}, fragment=fragment
503+
)
504+
505+
# Assert that blocks are rendered normally
506+
assert metadata["items"] == "rendered_blocks"
507+
assert metadata["next_url"] == "next_url"
508+
assert metadata["prev_url"] == "prev_url"
509+
510+
def test_gated_blocks_rendered_with_basic_info(self):
511+
"""
512+
Test that gated blocks are rendered with minimal metadata when prerequisites are not met.
513+
"""
514+
# Mock child block
515+
child = Mock()
516+
child.scope_ids.usage_id = "block1"
517+
child.scope_ids.block_type = "vertical"
518+
child.display_name_with_default = "Test Block"
519+
children = [child]
520+
521+
# Mock context
522+
context = {"next_url": "next_url", "prev_url": "prev_url"}
523+
524+
# Mock prereq_meta_info with required keys
525+
prereq_meta_info = {
526+
"url": "http://example.com/prereq",
527+
"display_name": "Prerequisite Section",
528+
"id": "prereq_block_id",
529+
}
530+
531+
# Call `_get_render_metadata` with prerequisites not met
532+
metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access
533+
context, children, prereq_met=False, prereq_meta_info=prereq_meta_info
534+
)
535+
536+
# Assert that gated blocks are rendered with basic info
537+
assert len(metadata["items"]) == 1
538+
assert metadata["items"][0]["id"] == "block1"
539+
assert metadata["items"][0]["type"] == "vertical"
540+
assert metadata["items"][0]["display_name"] == "Test Block"
541+
assert metadata["items"][0]["is_gated"] is True
542+
assert metadata["items"][0]["content"] == ""
543+
544+
# Assert that next and previous URLs are present
545+
assert metadata["next_url"] == "next_url"
546+
assert metadata["prev_url"] == "prev_url"
547+
548+
def test_prereqs_met_content_rendered_normally(self):
549+
"""
550+
Test that content is rendered normally when prerequisites are met.
551+
"""
552+
# Mock child block
553+
child = Mock()
554+
child.scope_ids.usage_id = "block1"
555+
child.scope_ids.block_type = "vertical"
556+
child.display_name_with_default = "Test Block"
557+
children = [child]
558+
559+
# Mock context
560+
context = {"next_url": "next_url", "prev_url": "prev_url"}
561+
fragment = Mock()
562+
563+
# Mock `_render_student_view_for_blocks`
564+
self.sequence_3_1._render_student_view_for_blocks = Mock(return_value="rendered_blocks") # pylint: disable=protected-access
565+
566+
# Call `_get_render_metadata` with prerequisites met
567+
metadata = self.sequence_3_1._get_render_metadata( # pylint: disable=protected-access
568+
context, children, prereq_met=True, prereq_meta_info={}, fragment=fragment
569+
)
570+
571+
# Assert that content is rendered normally
572+
assert metadata["items"] == "rendered_blocks"
573+
assert metadata["next_url"] == "next_url"
574+
assert metadata["prev_url"] == "prev_url"

0 commit comments

Comments
 (0)