to wrap all inputtypes
+ content = etree.SubElement(tree, 'div')
+ content.set('class', 'multi-inputs-group')
+ content.set('role', 'group')
+ content.set('aria-labelledby', self.xml.get('id'))
+ else:
+ content = tree
+
# problem author can make this span display:inline
if self.xml.get('inline', ''):
tree.set('class', 'inline')
@@ -271,12 +280,12 @@ class LoncapaResponse(object):
# call provided procedure to do the rendering
item_xhtml = renderer(item)
if item_xhtml is not None:
- tree.append(item_xhtml)
+ content.append(item_xhtml)
tree.tail = self.xml.tail
# Add a
for the message at the end of the response
if response_msg:
- tree.append(self._render_response_msg_html(response_msg))
+ content.append(self._render_response_msg_html(response_msg))
return tree
diff --git a/common/lib/capa/capa/templates/choicetext.html b/common/lib/capa/capa/templates/choicetext.html
index c6f812dc91..ca2d3ff5a9 100644
--- a/common/lib/capa/capa/templates/choicetext.html
+++ b/common/lib/capa/capa/templates/choicetext.html
@@ -1,18 +1,18 @@
<%! from django.utils.translation import ugettext as _ %>
<% element_checked = False %>
% for choice_id, _ in choices:
- <%choice_id = choice_id %>
+ <% choice_id = choice_id %>
%if choice_id in value:
<% element_checked = True %>
%endif
-%endfor
+% endfor
diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py
index 658833b614..7bd12bb65f 100644
--- a/common/lib/capa/capa/tests/__init__.py
+++ b/common/lib/capa/capa/tests/__init__.py
@@ -1,6 +1,7 @@
"""Tools for helping with testing capa."""
import gettext
+from path import path # pylint: disable=no-name-in-module
import os
import os.path
@@ -9,12 +10,29 @@ import fs.osfs
from capa.capa_problem import LoncapaProblem, LoncapaSystem
from capa.inputtypes import Status
from mock import Mock, MagicMock
+from mako.lookup import TemplateLookup
import xml.sax.saxutils as saxutils
TEST_DIR = os.path.dirname(os.path.realpath(__file__))
+def get_template(template_name):
+ """
+ Return template for a capa inputtype.
+ """
+ return TemplateLookup(
+ directories=[path(__file__).dirname().dirname() / 'templates']
+ ).get_template(template_name)
+
+
+def capa_render_template(template, context):
+ """
+ Render template for a capa inputtype.
+ """
+ return get_template(template).render_unicode(**context)
+
+
def tst_render_template(template, context):
"""
A test version of render to template. Renders to the repr of the context, completely ignoring
@@ -30,7 +48,7 @@ xqueue_interface = MagicMock()
xqueue_interface.send_to_queue.return_value = (0, 'Success!')
-def test_capa_system():
+def test_capa_system(render_template=None):
"""
Construct a mock LoncapaSystem instance.
@@ -46,7 +64,7 @@ def test_capa_system():
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
i18n=gettext.NullTranslations(),
node_path=os.environ.get("NODE_PATH", "/usr/local/lib/node_modules"),
- render_template=tst_render_template,
+ render_template=render_template or tst_render_template,
seed=0,
STATIC_URL='/dummy-static/',
STATUS_CLASS=Status,
@@ -66,9 +84,10 @@ def mock_capa_module():
return capa_module
-def new_loncapa_problem(xml, capa_system=None, seed=723):
+def new_loncapa_problem(xml, capa_system=None, seed=723, use_capa_render_template=False):
"""Construct a `LoncapaProblem` suitable for unit tests."""
- return LoncapaProblem(xml, id='1', seed=seed, capa_system=capa_system or test_capa_system(),
+ render_template = capa_render_template if use_capa_render_template else None
+ return LoncapaProblem(xml, id='1', seed=seed, capa_system=capa_system or test_capa_system(render_template),
capa_module=mock_capa_module())
diff --git a/common/lib/capa/capa/tests/response_xml_factory.py b/common/lib/capa/capa/tests/response_xml_factory.py
index 20081b9536..b01dbf4fc8 100644
--- a/common/lib/capa/capa/tests/response_xml_factory.py
+++ b/common/lib/capa/capa/tests/response_xml_factory.py
@@ -267,6 +267,9 @@ class CustomResponseXMLFactory(ResponseXMLFactory):
*answer_attr*: The "answer" attribute on the tag itself (treated as an
alias to "expect", though "expect" takes priority if both are given)
+
+ *group_label*: Text to represent group of inputs when there are
+ multiple inputs.
"""
# Retrieve **kwargs
@@ -276,6 +279,7 @@ class CustomResponseXMLFactory(ResponseXMLFactory):
answer = kwargs.get('answer', None)
options = kwargs.get('options', None)
cfn_extra_args = kwargs.get('cfn_extra_args', None)
+ group_label = kwargs.get('group_label', None)
# Create the response element
response_element = etree.Element("customresponse")
@@ -293,6 +297,10 @@ class CustomResponseXMLFactory(ResponseXMLFactory):
answer_element = etree.SubElement(response_element, "answer")
answer_element.text = str(answer)
+ if group_label:
+ group_label_element = etree.SubElement(response_element, "label")
+ group_label_element.text = group_label
+
if options:
response_element.set('options', str(options))
diff --git a/common/lib/capa/capa/tests/test_capa_problem.py b/common/lib/capa/capa/tests/test_capa_problem.py
index 66f3c62c82..3f02e44f96 100644
--- a/common/lib/capa/capa/tests/test_capa_problem.py
+++ b/common/lib/capa/capa/tests/test_capa_problem.py
@@ -1,10 +1,12 @@
"""
Test capa problem.
"""
+import ddt
+import textwrap
+from lxml import etree
import unittest
from . import new_loncapa_problem
-from capa.capa_problem import DEFAULT_QUESTION_TEXT
class CAPAProblemTest(unittest.TestCase):
@@ -35,10 +37,10 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': 'Select the correct synonym of paranoid?',
- 'descriptions': {'description_1_2_1': 'Only the paranoid survive.'}
+ 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
}
}
)
@@ -62,7 +64,13 @@ class CAPAProblemTest(unittest.TestCase):
problem = new_loncapa_problem(xml)
self.assertEqual(
problem.problem_data,
- {'1_2': {'label': question, 'descriptions': {}}}
+ {
+ '1_2_1':
+ {
+ 'label': question,
+ 'descriptions': {}
+ }
+ }
)
self.assertEqual(
@@ -101,12 +109,12 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': question1,
'descriptions': {}
},
- '1_3':
+ '1_3_1':
{
'label': question2,
'descriptions': {}
@@ -139,39 +147,12 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': '___ requires sacrifices.',
'descriptions': {
- 'description_1_2_1': "The problem with trying to be the bad guy, there's always someone worse.",
- 'description_1_2_2': "Anyone who looks the world as if it was a game of chess deserves to lose."
- }
- }
- }
- )
-
- def test_default_question_text(self):
- """
- Verify that default question text is shown when question is missing.
- """
- xml = """
-
- Be sure to check your spelling.
-
- Everybody needs somebody to talk to.
-
-
-
- """
- problem = new_loncapa_problem(xml)
- self.assertEqual(
- problem.problem_data,
- {
- '1_2':
- {
- 'label': DEFAULT_QUESTION_TEXT,
- 'descriptions': {
- 'description_1_2_1': "Everybody needs somebody to talk to."
+ 'description_1_1_1': "The problem with trying to be the bad guy, there's always someone worse.",
+ 'description_1_1_2': "Anyone who looks the world as if it was a game of chess deserves to lose."
}
}
}
@@ -195,7 +176,7 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': 'Click the country which is home to the Pyramids.',
'descriptions': {}
@@ -224,7 +205,7 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': '',
'descriptions': {}
@@ -266,15 +247,15 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': 'Select the correct synonym of paranoid?',
- 'descriptions': {'description_1_2_1': 'Only the paranoid survive.'}
+ 'descriptions': {'description_1_1_1': 'Only the paranoid survive.'}
},
- '1_3':
+ '1_3_1':
{
'label': 'What Apple device competed with the portable CD player?',
- 'descriptions': {'description_1_3_1': 'Device looks like an egg plant.'}
+ 'descriptions': {'description_1_2_1': 'Device looks like an egg plant.'}
}
}
)
@@ -306,7 +287,7 @@ class CAPAProblemTest(unittest.TestCase):
self.assertEqual(
problem.problem_data,
{
- '1_2':
+ '1_2_1':
{
'label': question,
'descriptions': {}
@@ -317,3 +298,146 @@ class CAPAProblemTest(unittest.TestCase):
len(problem.tree.xpath('//p[text()="{}"]'.format(question))),
0
)
+
+ def test_multiple_inputtypes(self):
+ """
+ Verify that group label and labels for individual inputtypes are extracted correctly.
+ """
+ group_label = 'Choose the correct color'
+ input1_label = 'What color is the sky?'
+ input2_label = 'What color are pine needles?'
+ xml = """
+
+
+
+
+
+
+
+ """.format(group_label, input1_label, input2_label)
+
+ problem = new_loncapa_problem(xml)
+ self.assertEqual(
+ problem.problem_data,
+ {
+ '1_2_1':
+ {
+ 'group_label': group_label,
+ 'label': input1_label,
+ 'descriptions': {}
+ },
+ '1_2_2':
+ {
+ 'group_label': group_label,
+ 'label': input2_label,
+ 'descriptions': {}
+ }
+ }
+ )
+
+ def test_single_inputtypes(self):
+ """
+ Verify that HTML is correctly rendered when there is single inputtype.
+ """
+ xml = """
+
+
+
+ Only the paranoid survive.
+
+ over-suspicious
+ funny
+
+
+
+ """
+ problem = new_loncapa_problem(xml, use_capa_render_template=True)
+ problem_html = etree.XML(problem.get_html())
+
+ # verify that only no multi input group div is present
+ multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')
+ self.assertEqual(len(multi_inputs_group), 0)
+
+
+@ddt.ddt
+class CAPAMultiInputProblemTest(unittest.TestCase):
+ """ TestCase for CAPA problems with multiple inputtypes """
+
+ def capa_problem(self, xml):
+ """
+ Create capa problem.
+ """
+ return new_loncapa_problem(xml, use_capa_render_template=True)
+
+ def assert_problem_html(self, problme_html, group_label, *input_labels):
+ """
+ Verify that correct html is rendered for multiple inputtypes.
+ """
+ html = etree.XML(problme_html)
+
+ # verify that only one multi input group div is present at correct path
+ multi_inputs_group = html.xpath(
+ '//section[@class="wrapper-problem-response"]/div[@class="multi-inputs-group"]'
+ )
+ self.assertEqual(len(multi_inputs_group), 1)
+
+ # verify that multi input group label
tag exists and its
+ # id matches with correct multi input group aria-labelledby
+ multi_inputs_group_label_id = multi_inputs_group[0].attrib.get('aria-labelledby')
+ multi_inputs_group_label = html.xpath('//p[@id="{}"]'.format(multi_inputs_group_label_id))
+ self.assertEqual(len(multi_inputs_group_label), 1)
+ self.assertEqual(multi_inputs_group_label[0].text, group_label)
+
+ # verify that label for each input comes only once
+ for input_label in input_labels:
+ # normalize-space is used to remove whitespace around the text
+ input_label_element = multi_inputs_group[0].xpath('//*[normalize-space(text())="{}"]'.format(input_label))
+ self.assertEqual(len(input_label_element), 1)
+
+ def test_optionresponse(self):
+ """
+ Verify that optionresponse problem with multiple inputtypes is rendered correctly.
+ """
+ group_label = 'Choose the correct color'
+ input1_label = 'What color is the sky?'
+ input2_label = 'What color are pine needles?'
+ xml = """
+
+
+
+
+
+
+
+ """.format(group_label, input1_label, input2_label)
+ problem = self.capa_problem(xml)
+ self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)
+
+ @ddt.unpack
+ @ddt.data(
+ {'inputtype': 'textline'},
+ {'inputtype': 'formulaequationinput'}
+ )
+ def test_customresponse(self, inputtype):
+ """
+ Verify that customresponse problem with multiple textline
+ and formulaequationinput inputtypes is rendered correctly.
+ """
+ group_label = 'Enter two integers that sum to 10.'
+ input1_label = 'Integer 1'
+ input2_label = 'Integer 2'
+ xml = textwrap.dedent("""
+
+
+
+
+ <{inputtype} size="40" correct_answer="3" label="{}" />
+ <{inputtype} size="40" correct_answer="7" label="{}" />
+
+
+ """.format(group_label, input1_label, input2_label, inputtype=inputtype))
+ problem = self.capa_problem(xml)
+ self.assert_problem_html(problem.get_html(), group_label, input1_label, input2_label)
diff --git a/common/lib/capa/capa/tests/test_html_render.py b/common/lib/capa/capa/tests/test_html_render.py
index df2d5a3906..85c88de8ac 100644
--- a/common/lib/capa/capa/tests/test_html_render.py
+++ b/common/lib/capa/capa/tests/test_html_render.py
@@ -7,7 +7,6 @@ import mock
from .response_xml_factory import StringResponseXMLFactory, CustomResponseXMLFactory
from . import test_capa_system, new_loncapa_problem
-from capa.capa_problem import DEFAULT_QUESTION_TEXT
class CapaHtmlRenderTest(unittest.TestCase):
@@ -186,7 +185,7 @@ class CapaHtmlRenderTest(unittest.TestCase):
'id': '1_2_1',
'trailing_text': '',
'size': None,
- 'response_data': {'label': DEFAULT_QUESTION_TEXT, 'descriptions': {}},
+ 'response_data': {'label': '', 'descriptions': {}},
'describedby': ''
}
diff --git a/common/lib/xmodule/xmodule/capa_base.py b/common/lib/xmodule/xmodule/capa_base.py
index c5ffbd14fb..3fbcdf0d4d 100644
--- a/common/lib/xmodule/xmodule/capa_base.py
+++ b/common/lib/xmodule/xmodule/capa_base.py
@@ -1296,6 +1296,9 @@ class CapaMixin(CapaFields):
'correct': is_correct,
'variant': variant,
}
+ # Add group_label in event data only if the responsetype contains multiple inputtypes
+ if answer_input.response_data.get('group_label'):
+ input_metadata[input_id]['group_label'] = answer_input.response_data.get('group_label')
return input_metadata
diff --git a/common/lib/xmodule/xmodule/tests/test_capa_module.py b/common/lib/xmodule/xmodule/tests/test_capa_module.py
index 9b738f57e5..84c3125e55 100644
--- a/common/lib/xmodule/xmodule/tests/test_capa_module.py
+++ b/common/lib/xmodule/xmodule/tests/test_capa_module.py
@@ -21,7 +21,6 @@ from webob.multidict import MultiDict
import xmodule
from xmodule.tests import DATA_DIR
from capa import responsetypes
-from capa.capa_problem import DEFAULT_QUESTION_TEXT
from capa.responsetypes import (StudentInputError, LoncapaProblemError,
ResponseError)
from capa.xqueue_interface import XQueueInterface
@@ -2652,7 +2651,7 @@ class TestProblemCheckTracking(unittest.TestCase):
event = self.get_event_for_answers(module, answer_input_dict)
self.assertEquals(event['submission'], {
factory.answer_key(2): {
- 'question': DEFAULT_QUESTION_TEXT,
+ 'question': '',
'answer': '3.14',
'response_type': 'numericalresponse',
'input_type': 'textline',
@@ -2662,19 +2661,19 @@ class TestProblemCheckTracking(unittest.TestCase):
})
def test_multiple_inputs(self):
+ group_label = 'Choose the correct color'
+ input1_label = 'What color is the sky?'
+ input2_label = 'What color are pine needles?'
factory = self.capa_factory_for_problem_xml("""\
- Choose the correct color
- What color is the sky?
-
- What color are pine needles?
-
+
+
+
- """)
+ """.format(group_label, input1_label, input2_label))
module = factory.create()
-
answer_input_dict = {
factory.input_key(2, 1): 'blue',
factory.input_key(2, 2): 'yellow',
@@ -2683,7 +2682,8 @@ class TestProblemCheckTracking(unittest.TestCase):
event = self.get_event_for_answers(module, answer_input_dict)
self.assertEquals(event['submission'], {
factory.answer_key(2, 1): {
- 'question': DEFAULT_QUESTION_TEXT,
+ 'group_label': group_label,
+ 'question': input1_label,
'answer': 'blue',
'response_type': 'optionresponse',
'input_type': 'optioninput',
@@ -2691,7 +2691,8 @@ class TestProblemCheckTracking(unittest.TestCase):
'variant': '',
},
factory.answer_key(2, 2): {
- 'question': DEFAULT_QUESTION_TEXT,
+ 'group_label': group_label,
+ 'question': input2_label,
'answer': 'yellow',
'response_type': 'optionresponse',
'input_type': 'optioninput',
@@ -2702,11 +2703,14 @@ class TestProblemCheckTracking(unittest.TestCase):
def test_optioninput_extended_xml(self):
"""Test the new XML form of writing with