make responsetypes with multiple inputtypes accessible
This commit is contained in:
committed by
muzaffaryousaf
parent
19cc68c8c3
commit
f91583c24c
@@ -72,8 +72,6 @@ log = logging.getLogger(__name__)
|
||||
#-----------------------------------------------------------------------------
|
||||
# main class for this module
|
||||
|
||||
DEFAULT_QUESTION_TEXT = "Formatting error: You must explicitly specify the question text."
|
||||
|
||||
|
||||
class LoncapaSystem(object):
|
||||
"""
|
||||
@@ -765,8 +763,7 @@ class LoncapaProblem(object):
|
||||
|
||||
if problemtree.tag in inputtypes.registry.registered_tags():
|
||||
# If this is an inputtype subtree, let it render itself.
|
||||
response_id = self.problem_id + '_' + problemtree.get('response_id')
|
||||
response_data = self.problem_data[response_id]
|
||||
response_data = self.problem_data[problemid]
|
||||
|
||||
status = 'unsubmitted'
|
||||
msg = ''
|
||||
@@ -856,16 +853,16 @@ class LoncapaProblem(object):
|
||||
problem_data = {}
|
||||
self.responders = {}
|
||||
for response in tree.xpath('//' + "|//".join(responsetypes.registry.registered_tags())):
|
||||
response_id_str = self.problem_id + "_" + str(response_id)
|
||||
responsetype_id = self.problem_id + "_" + str(response_id)
|
||||
# create and save ID for this response
|
||||
response.set('id', response_id_str)
|
||||
response.set('id', responsetype_id)
|
||||
response_id += 1
|
||||
|
||||
answer_id = 1
|
||||
input_tags = inputtypes.registry.registered_tags()
|
||||
inputfields = tree.xpath(
|
||||
"|".join(['//' + response.tag + '[@id=$id]//' + x for x in input_tags]),
|
||||
id=response_id_str
|
||||
id=responsetype_id
|
||||
)
|
||||
|
||||
# assign one answer_id for each input type
|
||||
@@ -875,10 +872,65 @@ class LoncapaProblem(object):
|
||||
entry.attrib['id'] = "%s_%i_%i" % (self.problem_id, response_id, answer_id)
|
||||
answer_id = answer_id + 1
|
||||
|
||||
question_id = u'{}_{}'.format(self.problem_id, response_id)
|
||||
label = ''
|
||||
element_to_be_deleted = None
|
||||
self.response_a11y_data(response, inputfields, responsetype_id, problem_data)
|
||||
|
||||
# instantiate capa Response
|
||||
responsetype_cls = responsetypes.registry.get_class_for_tag(response.tag)
|
||||
responder = responsetype_cls(response, inputfields, self.context, self.capa_system, self.capa_module)
|
||||
# save in list in self
|
||||
self.responders[response] = responder
|
||||
|
||||
# get responder answers (do this only once, since there may be a performance cost,
|
||||
# eg with externalresponse)
|
||||
self.responder_answers = {}
|
||||
for response in self.responders.keys():
|
||||
try:
|
||||
self.responder_answers[response] = self.responders[response].get_answers()
|
||||
except:
|
||||
log.debug('responder %s failed to properly return get_answers()',
|
||||
self.responders[response]) # FIXME
|
||||
raise
|
||||
|
||||
# <solution>...</solution> may not be associated with any specific response; give
|
||||
# IDs for those separately
|
||||
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
|
||||
solution_id = 1
|
||||
for solution in tree.findall('.//solution'):
|
||||
solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id)
|
||||
solution_id += 1
|
||||
|
||||
return problem_data
|
||||
|
||||
def response_a11y_data(self, response, inputfields, responsetype_id, problem_data):
|
||||
"""
|
||||
Construct data to be used for a11y.
|
||||
|
||||
Arguments:
|
||||
response (object): xml response object
|
||||
inputfields (list): list of inputfields in a responsetype
|
||||
responsetype_id (str): responsetype id
|
||||
problem_data (dict): dict to be filled with response data
|
||||
"""
|
||||
element_to_be_deleted = None
|
||||
label = ''
|
||||
|
||||
if len(inputfields) > 1:
|
||||
response.set('multiple_inputtypes', 'true')
|
||||
group_label_tag = response.find('label')
|
||||
group_label_tag_text = ''
|
||||
if group_label_tag is not None:
|
||||
group_label_tag.tag = 'p'
|
||||
group_label_tag.set('id', responsetype_id)
|
||||
group_label_tag.set('class', 'multi-inputs-group-label')
|
||||
group_label_tag_text = group_label_tag.text
|
||||
|
||||
for inputfield in inputfields:
|
||||
problem_data[inputfield.get('id')] = {
|
||||
'group_label': group_label_tag_text,
|
||||
'label': inputfield.attrib.get('label', ''),
|
||||
'descriptions': {}
|
||||
}
|
||||
else:
|
||||
# Extract label value from <label> tag or label attribute from inside the responsetype
|
||||
responsetype_label_tag = response.find('label')
|
||||
if responsetype_label_tag is not None:
|
||||
@@ -913,56 +965,22 @@ class LoncapaProblem(object):
|
||||
label = label_tag[0].text
|
||||
element_to_be_deleted = label_tag[0]
|
||||
|
||||
label = label.strip() or DEFAULT_QUESTION_TEXT
|
||||
|
||||
# delete label or p element only if responsetype is fully accessible
|
||||
if response.tag in ACCESSIBLE_CAPA_RESPONSE_TYPES and element_to_be_deleted is not None:
|
||||
element_to_be_deleted.getparent().remove(element_to_be_deleted)
|
||||
|
||||
# for non-accessible responsetypes it may be possible that label attribute is not present
|
||||
# in this case pass an empty label. remember label attribute is only used as value for aria-label
|
||||
if response.tag not in ACCESSIBLE_CAPA_RESPONSE_TYPES and label == DEFAULT_QUESTION_TEXT:
|
||||
label = ''
|
||||
|
||||
# Extract descriptions and set unique id on each description tag
|
||||
description_tags = response.findall('description')
|
||||
description_id = 1
|
||||
descriptions = OrderedDict()
|
||||
for description in description_tags:
|
||||
descriptions[
|
||||
"description_%s_%i_%i" % (self.problem_id, response_id, description_id)
|
||||
"description_%s_%i" % (responsetype_id, description_id)
|
||||
] = description.text
|
||||
response.remove(description)
|
||||
description_id += 1
|
||||
|
||||
problem_data[question_id] = {
|
||||
problem_data[inputfields[0].get('id')] = {
|
||||
'label': label,
|
||||
'descriptions': descriptions
|
||||
}
|
||||
|
||||
# instantiate capa Response
|
||||
responsetype_cls = responsetypes.registry.get_class_for_tag(response.tag)
|
||||
responder = responsetype_cls(response, inputfields, self.context, self.capa_system, self.capa_module)
|
||||
# save in list in self
|
||||
self.responders[response] = responder
|
||||
|
||||
# get responder answers (do this only once, since there may be a performance cost,
|
||||
# eg with externalresponse)
|
||||
self.responder_answers = {}
|
||||
for response in self.responders.keys():
|
||||
try:
|
||||
self.responder_answers[response] = self.responders[response].get_answers()
|
||||
except:
|
||||
log.debug('responder %s failed to properly return get_answers()',
|
||||
self.responders[response]) # FIXME
|
||||
raise
|
||||
|
||||
# <solution>...</solution> may not be associated with any specific response; give
|
||||
# IDs for those separately
|
||||
# TODO: We should make the namespaces consistent and unique (e.g. %s_problem_%i).
|
||||
solution_id = 1
|
||||
for solution in tree.findall('.//solution'):
|
||||
solution.attrib['id'] = "%s_solution_%i" % (self.problem_id, solution_id)
|
||||
solution_id += 1
|
||||
|
||||
return problem_data
|
||||
|
||||
@@ -263,6 +263,15 @@ class LoncapaResponse(object):
|
||||
tree.set('tabindex', '-1')
|
||||
tree.set('aria-label', response_label)
|
||||
|
||||
if self.xml.get('multiple_inputtypes'):
|
||||
# add <div> 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 <div> 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
|
||||
|
||||
|
||||
@@ -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
|
||||
<section id="choicetextinput_${id}" class="choicetextinput">
|
||||
<form class="choicetextgroup capa_inputtype" id="inputtype_${id}">
|
||||
<div class="script_placeholder" data-src="${STATIC_URL}js/capa/choicetextinput.js"/>
|
||||
|
||||
<fieldset aria-label="${response_data['label']}">
|
||||
% for choice_id, choice_description in choices:
|
||||
<%choice_id= choice_id %>
|
||||
<% choice_id = choice_id %>
|
||||
<section id="forinput${choice_id}"
|
||||
% if input_type == 'radio' and choice_id in value :
|
||||
<%
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if msg:
|
||||
<span class="message">${HTML(msg)}</span>
|
||||
% endif
|
||||
% if msg:
|
||||
<span class="message">${HTML(msg)}</span>
|
||||
% endif
|
||||
|
||||
</div>
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -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 = """
|
||||
<problem>
|
||||
<p>Be sure to check your spelling.</p>
|
||||
<stringresponse answer="War" type="ci">
|
||||
<description>Everybody needs somebody to talk to.</description>
|
||||
<textline size="40"/>
|
||||
</stringresponse>
|
||||
</problem>
|
||||
"""
|
||||
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 = """
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>{}</label>
|
||||
<optioninput options="('yellow','blue','green')" correct="blue" label="{}"/>
|
||||
<optioninput options="('yellow','blue','green')" correct="green" label="{}"/>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
""".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 = """
|
||||
<problem>
|
||||
<choiceresponse>
|
||||
<label>Select the correct synonym of paranoid?</label>
|
||||
<description>Only the paranoid survive.</description>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">over-suspicious</choice>
|
||||
<choice correct="false">funny</choice>
|
||||
</checkboxgroup>
|
||||
</choiceresponse>
|
||||
</problem>
|
||||
"""
|
||||
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 <p> 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 = """
|
||||
<problem>
|
||||
<optionresponse>
|
||||
<label>{}</label>
|
||||
<optioninput options="('yellow','blue','green')" correct="blue" label="{}"/>
|
||||
<optioninput options="('yellow','blue','green')" correct="green" label="{}"/>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
""".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("""
|
||||
<problem>
|
||||
<customresponse cfn="test_add_to_ten">
|
||||
<script type="loncapa/python">
|
||||
def test_add_to_ten(expect, ans):
|
||||
return test_add(10, ans)
|
||||
</script>
|
||||
<label>{}</label>
|
||||
<{inputtype} size="40" correct_answer="3" label="{}" /><br/>
|
||||
<{inputtype} size="40" correct_answer="7" label="{}" />
|
||||
</customresponse>
|
||||
</problem>
|
||||
""".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)
|
||||
|
||||
@@ -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': ''
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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("""\
|
||||
<problem display_name="Multiple Inputs">
|
||||
<p>Choose the correct color</p>
|
||||
<optionresponse>
|
||||
<p>What color is the sky?</p>
|
||||
<optioninput options="('yellow','blue','green')" correct="blue"/>
|
||||
<p>What color are pine needles?</p>
|
||||
<optioninput options="('yellow','blue','green')" correct="green"/>
|
||||
<label>{}</label>
|
||||
<optioninput options="('yellow','blue','green')" correct="blue" label="{}"/>
|
||||
<optioninput options="('yellow','blue','green')" correct="green" label="{}"/>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
""")
|
||||
""".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 <option> tag instead of options= attribute."""
|
||||
group_label = 'Are you the Gatekeeper?'
|
||||
input1_label = 'input 1 label'
|
||||
input2_label = 'input 2 label'
|
||||
factory = self.capa_factory_for_problem_xml("""\
|
||||
<problem display_name="Woo Hoo">
|
||||
<p>Are you the Gatekeeper?</p>
|
||||
<optionresponse>
|
||||
<optioninput>
|
||||
<label>{}</label>
|
||||
<optioninput label="{}">
|
||||
<option correct="True" label="Good Job">
|
||||
apple
|
||||
<optionhint>
|
||||
@@ -2721,7 +2725,7 @@ class TestProblemCheckTracking(unittest.TestCase):
|
||||
</option>
|
||||
</optioninput>
|
||||
|
||||
<optioninput>
|
||||
<optioninput label="{}">
|
||||
<option correct="True">
|
||||
apple
|
||||
<optionhint>
|
||||
@@ -2737,7 +2741,7 @@ class TestProblemCheckTracking(unittest.TestCase):
|
||||
</optioninput>
|
||||
</optionresponse>
|
||||
</problem>
|
||||
""")
|
||||
""".format(group_label, input1_label, input2_label))
|
||||
module = factory.create()
|
||||
|
||||
answer_input_dict = {
|
||||
@@ -2748,7 +2752,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': 'apple',
|
||||
'response_type': 'optionresponse',
|
||||
'input_type': 'optioninput',
|
||||
@@ -2756,7 +2761,8 @@ class TestProblemCheckTracking(unittest.TestCase):
|
||||
'variant': '',
|
||||
},
|
||||
factory.answer_key(2, 2): {
|
||||
'question': DEFAULT_QUESTION_TEXT,
|
||||
'group_label': group_label,
|
||||
'question': input2_label,
|
||||
'answer': 'cucumber',
|
||||
'response_type': 'optionresponse',
|
||||
'input_type': 'optioninput',
|
||||
@@ -2776,7 +2782,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',
|
||||
|
||||
@@ -592,10 +592,10 @@ class ScriptProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
|
||||
factory = CustomResponseXMLFactory()
|
||||
|
||||
factory_kwargs = {
|
||||
'question_text': 'Enter two integers that sum to 10.',
|
||||
'cfn': 'test_add_to_ten',
|
||||
'expect': '10',
|
||||
'num_inputs': 2,
|
||||
'group_label': 'Enter two integers that sum to 10.',
|
||||
'script': textwrap.dedent("""
|
||||
def test_add_to_ten(expect,ans):
|
||||
try:
|
||||
@@ -618,12 +618,6 @@ class ScriptProblemTypeTest(ProblemTypeTestBase, ProblemTypeTestMixin):
|
||||
Additional setup for ScriptProblemTypeTest
|
||||
"""
|
||||
super(ScriptProblemTypeTest, self).setUp(*args, **kwargs)
|
||||
self.problem_page.a11y_audit.config.set_rules({
|
||||
'ignore': [
|
||||
'section', # TODO: AC-491
|
||||
'label', # TODO: AC-287
|
||||
]
|
||||
})
|
||||
|
||||
def answer_problem(self, correct):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user