diff --git a/xblocks_contrib/problem/capa/capa_problem.py b/xblocks_contrib/problem/capa/capa_problem.py index 00a9e6f3..db09d703 100644 --- a/xblocks_contrib/problem/capa/capa_problem.py +++ b/xblocks_contrib/problem/capa/capa_problem.py @@ -1150,7 +1150,19 @@ def _preprocess_problem(self, tree, minimal_init): # private return problem_data - def response_a11y_data( # pylint: disable=too-many-locals,too-many-branches + @staticmethod + def _preceding_prompt_ids(response, responsetype_id): + """Assign ids to contiguous preceding ``

`` siblings and return them in document order.""" + ids = [] + sibling = response.getprevious() + while sibling is not None and isinstance(sibling.tag, str) and sibling.tag.lower() == "p": + pid = sibling.get("id") or f"prompt_{responsetype_id}_{len(ids) + 1}" + sibling.set("id", pid) + ids.append(pid) + sibling = sibling.getprevious() + return list(reversed(ids)) + + def response_a11y_data( # pylint: disable=too-many-locals,too-many-branches,too-many-statements self, response, inputfields, responsetype_id, problem_data ): """ @@ -1193,12 +1205,21 @@ def response_a11y_data( # pylint: disable=too-many-locals,too-many-branches if group_description_ids: response.set("multiinput-group_description_ids", " ".join(group_description_ids)) + preceding_prompt_ids = ( + self._preceding_prompt_ids(response, responsetype_id) + if inputfields[0].tag in ACCESSIBLE_CAPA_INPUT_TYPES + else [] + ) + for inputfield in inputfields: - problem_data[inputfield.get("id")] = { + entry = { "group_label": group_label_tag_text, "label": HTML(inputfield.attrib.get("label", "")), "descriptions": {}, } + if preceding_prompt_ids: + entry["additional_describedby_ids"] = list(preceding_prompt_ids) + problem_data[inputfield.get("id")] = entry else: # Extract label value from

""" problem = new_loncapa_problem(xml) - assert problem.problem_data == {"1_2_1": {"label": question, "descriptions": {}}} + assert problem.problem_data == { + "1_2_1": { + "label": question, + "descriptions": {}, + "additional_describedby_ids": ["prompt_1_1_1"], + } + } assert len(problem.tree.xpath(f"//*[normalize-space(text())='{question}']")) == 0 @ddt.unpack @@ -121,7 +127,11 @@ def test_neither_label_tag_nor_attribute(self, question1, question2): """ problem = new_loncapa_problem(xml) assert problem.problem_data == { - "1_2_1": {"label": question1, "descriptions": {}}, + "1_2_1": { + "label": question1, + "descriptions": {}, + "additional_describedby_ids": ["prompt_1_1_1"], + }, "1_3_1": {"label": question2, "descriptions": {}}, } for question in (question1, question2): @@ -149,6 +159,7 @@ def test_multiple_descriptions(self): "1_2_1": { "label": "___ requires sacrifices.", "descriptions": {"description_1_1_1": desc1, "description_1_1_2": desc2}, + "additional_describedby_ids": ["prompt_1_1_1"], } } @@ -273,7 +284,13 @@ def test_question_title_not_removed_got_children(self): """ problem = new_loncapa_problem(xml) - assert problem.problem_data == {"1_2_1": {"label": "", "descriptions": {}}} + assert problem.problem_data == { + "1_2_1": { + "label": "", + "descriptions": {}, + "additional_describedby_ids": ["prompt_1_1_3", "prompt_1_1_2", "prompt_1_1_1"], + } + } assert len(problem.tree.xpath("//p/img")) == 1 @ddt.unpack @@ -303,6 +320,42 @@ def test_multiple_inputtypes(self, group_label): "1_2_2": {"group_label": group_label, "label": input2_label, "descriptions": {}}, } + def test_multiple_inputtypes_with_preceding_prompts(self): + """ + Verify that preceding

prompts are associated with each inputfield + of a multi-input responsetype via additional_describedby_ids. + """ + group_label = "Choose the correct color" + input1_label = "What color is the sky?" + input2_label = "What color are pine needles?" + xml = f""" + +

Instructions: pick a color from the dropdown.

+

Consider the context of each item.

+ + + + + + + """ + problem = new_loncapa_problem(xml) + expected_prompt_ids = ["prompt_1_1_2", "prompt_1_1_1"] + assert problem.problem_data == { + "1_2_1": { + "group_label": group_label, + "label": input1_label, + "descriptions": {}, + "additional_describedby_ids": expected_prompt_ids, + }, + "1_2_2": { + "group_label": group_label, + "label": input2_label, + "descriptions": {}, + "additional_describedby_ids": expected_prompt_ids, + }, + } + def test_single_inputtypes(self): """ Verify that HTML is correctly rendered when there is single inputtype.