Commit 1b960e3e by Ehtesham Committed by muzaffaryousaf

requiring codemirror compressed with all addons included

parent f91583c2
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
'mustache': 'js/vendor/mustache', 'mustache': 'js/vendor/mustache',
'codemirror': 'js/vendor/codemirror-compressed', 'codemirror': 'js/vendor/codemirror-compressed',
'codemirror/stex': 'js/vendor/CodeMirror/stex', 'codemirror/stex': 'js/vendor/CodeMirror/stex',
'pretty-print': 'js/lib/pretty-print',
'jquery': 'common/js/vendor/jquery', 'jquery': 'common/js/vendor/jquery',
'jquery-migrate': 'common/js/vendor/jquery-migrate', 'jquery-migrate': 'common/js/vendor/jquery-migrate',
'jquery.ui': 'js/vendor/jquery-ui.min', 'jquery.ui': 'js/vendor/jquery-ui.min',
......
...@@ -6,9 +6,8 @@ ...@@ -6,9 +6,8 @@
## and attach them to the global context manually. ## and attach them to the global context manually.
define(["jquery", "underscore", "codemirror", "tinymce", define(["jquery", "underscore", "codemirror", "tinymce",
"jquery.tinymce", "jquery.qtip", "jquery.scrollTo", "jquery.flot", "jquery.tinymce", "jquery.qtip", "jquery.scrollTo", "jquery.flot",
"jquery.cookie", "jquery.cookie", "pretty-print", "utility"],
"utility"], function($, _, CodeMirror) {
function($, _, CodeMirror, tinymce) {
window.$ = $; window.$ = $;
window._ = _; window._ = _;
require(['mathjax']); require(['mathjax']);
......
...@@ -36,14 +36,14 @@ from capa.safe_exec import safe_exec ...@@ -36,14 +36,14 @@ from capa.safe_exec import safe_exec
# extra things displayed after "show answers" is pressed # extra things displayed after "show answers" is pressed
solution_tags = ['solution'] solution_tags = ['solution']
# fully accessible capa response types # fully accessible capa input types
ACCESSIBLE_CAPA_RESPONSE_TYPES = [ ACCESSIBLE_CAPA_INPUT_TYPES = [
'choiceresponse', 'checkboxgroup',
'multiplechoiceresponse', 'radiogroup',
'optionresponse', 'choicegroup',
'numericalresponse', 'optioninput',
'stringresponse', 'textline',
'formularesponse', 'formulaequationinput',
] ]
# these get captured as student responses # these get captured as student responses
...@@ -911,6 +911,10 @@ class LoncapaProblem(object): ...@@ -911,6 +911,10 @@ class LoncapaProblem(object):
responsetype_id (str): responsetype id responsetype_id (str): responsetype id
problem_data (dict): dict to be filled with response data problem_data (dict): dict to be filled with response data
""" """
# if there are no inputtypes then don't do anything
if not inputfields:
return
element_to_be_deleted = None element_to_be_deleted = None
label = '' label = ''
...@@ -938,35 +942,17 @@ class LoncapaProblem(object): ...@@ -938,35 +942,17 @@ class LoncapaProblem(object):
# store <label> tag containing question text to delete # store <label> tag containing question text to delete
# it later otherwise question will be rendered twice # it later otherwise question will be rendered twice
element_to_be_deleted = responsetype_label_tag element_to_be_deleted = responsetype_label_tag
elif 'label' in inputfields[0].attrib: elif 'label' not in inputfields[0].attrib:
# Extract label value from label attribute
# This is the case when we have a problem
# * with multiple questions without separation
# * single question with old XML format only
label = inputfields[0].attrib['label']
# Get first <p> tag before responsetype, this <p> contains the question text.
p_tag = response.xpath('preceding-sibling::p[1]')
if p_tag:
# It may be possible that label attribute value doesn't match with <p> tag
# This happens when author updated the question <p> tag directly in XML but
# didn't changed the label attribute value. In this case we will consider the
# first <p> tag before responsetype as question.
if label != p_tag[0].text:
label = p_tag[0].text
element_to_be_deleted = p_tag[0]
else:
# In this case the problems don't have tag or label attribute inside the responsetype # In this case the problems don't have tag or label attribute inside the responsetype
# so we will get the first preceding label tag w.r.t to this responsetype. # so we will get the first preceding label tag w.r.t to this responsetype.
# This will take care of those multi-question problems that are not using --- in their markdown. # This will take care of those multi-question problems that are not using --- in their markdown.
label_tag = response.xpath("preceding-sibling::label[1]") label_tag = response.xpath('preceding-sibling::label[1]')
if label_tag: if label_tag:
label = label_tag[0].text label = label_tag[0].text
element_to_be_deleted = label_tag[0] element_to_be_deleted = label_tag[0]
# delete label or p element only if responsetype is fully accessible # delete label or p element only if inputtype is fully accessible
if response.tag in ACCESSIBLE_CAPA_RESPONSE_TYPES and element_to_be_deleted is not None: if inputfields[0].tag in ACCESSIBLE_CAPA_INPUT_TYPES and element_to_be_deleted is not None:
element_to_be_deleted.getparent().remove(element_to_be_deleted) element_to_be_deleted.getparent().remove(element_to_be_deleted)
# Extract descriptions and set unique id on each description tag # Extract descriptions and set unique id on each description tag
...@@ -981,6 +967,6 @@ class LoncapaProblem(object): ...@@ -981,6 +967,6 @@ class LoncapaProblem(object):
description_id += 1 description_id += 1
problem_data[inputfields[0].get('id')] = { problem_data[inputfields[0].get('id')] = {
'label': label, 'label': label.strip() if label else '',
'descriptions': descriptions 'descriptions': descriptions
} }
...@@ -96,10 +96,13 @@ class Status(object): ...@@ -96,10 +96,13 @@ class Status(object):
'correct': _('This answer is correct.'), 'correct': _('This answer is correct.'),
'incorrect': _('This answer is incorrect.'), 'incorrect': _('This answer is incorrect.'),
'partially-correct': _('This answer is partially correct.'), 'partially-correct': _('This answer is partially correct.'),
'unanswered': _('This answer is unanswered.'),
'unsubmitted': _('This answer is unanswered.'),
'queued': _('This answer is being processed.'), 'queued': _('This answer is being processed.'),
} }
tooltips.update(
dict.fromkeys(
['incomplete', 'unanswered', 'unsubmitted'], _('Not yet answered.')
)
)
self.display_name = names.get(status, unicode(status)) self.display_name = names.get(status, unicode(status))
self.display_tooltip = tooltips.get(status, u'') self.display_tooltip = tooltips.get(status, u'')
self._status = status or '' self._status = status or ''
...@@ -355,7 +358,7 @@ class InputTypeBase(object): ...@@ -355,7 +358,7 @@ class InputTypeBase(object):
context = self._get_render_context() context = self._get_render_context()
html = self.capa_system.render_template(self.template, context) html = self.capa_system.render_template(self.template, context).strip()
try: try:
output = etree.XML(html) output = etree.XML(html)
...@@ -426,6 +429,13 @@ class OptionInput(InputTypeBase): ...@@ -426,6 +429,13 @@ class OptionInput(InputTypeBase):
return [Attribute('options', transform=cls.parse_options), return [Attribute('options', transform=cls.parse_options),
Attribute('inline', False)] Attribute('inline', False)]
def _extra_context(self):
"""
Return extra context.
"""
_ = self.capa_system.i18n.ugettext
return {'default_option_text': _('Select an option')}
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
......
...@@ -7,7 +7,9 @@ ...@@ -7,7 +7,9 @@
%> %>
<form class="choicegroup capa_inputtype" id="inputtype_${id}"> <form class="choicegroup capa_inputtype" id="inputtype_${id}">
<fieldset ${describedby}> <fieldset ${describedby}>
<legend id="${id}-legend" class="response-fieldset-legend field-group-hd">${response_data['label']}</legend> % if response_data['label']:
<legend id="${id}-legend" class="response-fieldset-legend field-group-hd">${response_data['label']}</legend>
% endif
% for description_id, description_text in response_data['descriptions'].items(): % for description_id, description_text in response_data['descriptions'].items():
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % endfor
...@@ -57,16 +59,8 @@ ...@@ -57,16 +59,8 @@
</fieldset> </fieldset>
<div class="indicator-container"> <div class="indicator-container">
% if input_type == 'checkbox' or not value: % if input_type == 'checkbox' or not value:
<span class="status ${status.classname if show_correctness != 'never' else 'unanswered'}" id="status_${id}" aria-describedby="inputtype_${id}" data-tooltip="${status.display_tooltip}"> <span class="status ${status.classname if show_correctness != 'never' else 'unanswered'}" id="status_${id}" data-tooltip="${status.display_tooltip}">
<span class="sr"> <span class="sr">${status.display_tooltip}</span>
%for choice_id, choice_label in choices:
% if choice_id in value:
${choice_label},
%endif
%endfor
-
${status.display_tooltip}
</span>
</span> </span>
% endif % endif
</div> </div>
......
<%page expression_filter="h"/> <%page expression_filter="h"/>
<%! from openedx.core.djangolib.markup import HTML %> <%! from openedx.core.djangolib.markup import HTML %>
<% doinline = 'style="display:inline-block;vertical-align:top"' if inline else "" %> <% doinline = 'style="display:inline-block;vertical-align:top"' if inline else "" %>
<div id="formulaequationinput_${id}" class="inputtype formulaequationinput" ${doinline | n}> <div id="formulaequationinput_${id}" class="inputtype formulaequationinput" ${doinline | n, decode.utf8}>
<div class="${status.classname}" id="status_${id}"> <div class="${status.classname}" id="status_${id}">
<label class="problem-group-label" for="input_${id}">${response_data['label']}</label> % if response_data['label']:
<label class="problem-group-label" for="input_${id}">${response_data['label']}</label>
% endif
% for description_id, description_text in response_data['descriptions'].items(): % for description_id, description_text in response_data['descriptions'].items():
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % endfor
<input type="text" name="input_${id}" id="input_${id}" <input type="text" name="input_${id}" id="input_${id}"
data-input-id="${id}" value="${value}" data-input-id="${id}" value="${value}"
${describedby| n} ${describedby | n, decode.utf8}
% if size: % if size:
size="${size}" size="${size}"
% endif % endif
...@@ -17,9 +19,7 @@ ...@@ -17,9 +19,7 @@
<span class="trailing_text">${trailing_text}</span> <span class="trailing_text">${trailing_text}</span>
<span class="status" id="${id}_status" data-tooltip="${status.display_tooltip}"> <span class="status" id="${id}_status" data-tooltip="${status.display_tooltip}">
<span class="sr"> <span class="sr">${status.display_tooltip}</span>
${status.display_name}
</span>
</span> </span>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
......
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
<% doinline = "inline" if inline else "" %> <% doinline = "inline" if inline else "" %>
<form class="inputtype option-input ${doinline}"> <form class="inputtype option-input ${doinline}">
<label class="problem-group-label" for="input_${id}"> % if response_data['label']:
${response_data['label']} <label class="problem-group-label" for="input_${id}">${response_data['label']}</label>
</label> % endif
% for description_id, description_text in response_data['descriptions'].items(): % for description_id, description_text in response_data['descriptions'].items():
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % endfor
<select name="input_${id}" id="input_${id}" ${describedby}> <select name="input_${id}" id="input_${id}" ${describedby}>
<option value="option_${id}_dummy_default"> </option> <option value="option_${id}_dummy_default">${default_option_text}</option>
% for option_id, option_description in options: % for option_id, option_description in options:
<option value="${option_id}" <option value="${option_id}"
% if (option_id == value or option_id == answervariable): % if (option_id == value or option_id == answervariable):
...@@ -22,10 +22,8 @@ ...@@ -22,10 +22,8 @@
</select> </select>
<div class="indicator-container"> <div class="indicator-container">
<span class="status ${status.classname}" <span class="status ${status.classname}" id="status_${id}" data-tooltip="${status.display_tooltip}">
id="status_${id}" <span class="sr">${status.display_tooltip}</span>
aria-describedby="input_${id}" data-tooltip="${status.display_tooltip}">
<span class="sr">${value|h} - ${status.display_tooltip}</span>
</span> </span>
</div> </div>
<p class="answer" id="answer_${id}"></p> <p class="answer" id="answer_${id}"></p>
......
<section class="solution-span"> <div class="solution-span">
<span id="solution_${id}"></span> <span id="solution_${id}"></span>
</section> </div>
...@@ -16,11 +16,14 @@ ...@@ -16,11 +16,14 @@
<div style="display:none;" name="${hidden}" inputid="input_${id}" /> <div style="display:none;" name="${hidden}" inputid="input_${id}" />
% endif % endif
<label class="problem-group-label" for="input_${id}">${response_data['label']}</label> % if response_data['label']:
<label class="problem-group-label" for="input_${id}">${response_data['label']}</label>
% endif
% for description_id, description_text in response_data['descriptions'].items(): % for description_id, description_text in response_data['descriptions'].items():
<p class="question-description" id="${description_id}">${description_text}</p> <p class="question-description" id="${description_id}">${description_text}</p>
% endfor % endfor
<input type="text" name="input_${id}" id="input_${id}" ${describedby| n} value="${value}" <input type="text" name="input_${id}" id="input_${id}" ${describedby | n, decode.utf8} value="${value}"
% if do_math: % if do_math:
class="math" class="math"
% endif % endif
...@@ -33,16 +36,8 @@ ...@@ -33,16 +36,8 @@
/> />
<span class="trailing_text">${trailing_text}</span> <span class="trailing_text">${trailing_text}</span>
<span class="status" aria-describedby="input_${id}" data-tooltip="${status.display_tooltip}"> <span class="status" data-tooltip="${status.display_tooltip}">
<span class="sr"> <span class="sr">${status.display_tooltip}</span>
%if value:
${value}
% else:
${response_data['label']}
%endif
-
${status.display_name}
</span>
</span> </span>
<p id="answer_${id}" class="answer"></p> <p id="answer_${id}" class="answer"></p>
......
...@@ -46,10 +46,9 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -46,10 +46,9 @@ class CAPAProblemTest(unittest.TestCase):
) )
self.assertEqual(len(problem.tree.xpath('//label')), 0) self.assertEqual(len(problem.tree.xpath('//label')), 0)
def test_label_attribute_only(self): def test_legacy_problem(self):
""" """
Verify that label is extracted and <p> tag with question Verify that legacy problem is handled correctly.
text is removed when label attribute is set on inputtype.
""" """
question = "Once we become predictable, we become ______?" question = "Once we become predictable, we become ______?"
xml = """ xml = """
...@@ -67,15 +66,14 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -67,15 +66,14 @@ class CAPAProblemTest(unittest.TestCase):
{ {
'1_2_1': '1_2_1':
{ {
'label': question, 'label': '',
'descriptions': {} 'descriptions': {}
} }
} }
) )
self.assertEqual( self.assertEqual(
len(problem.tree.xpath('//p[text()="{}"]'.format(question))), len(problem.tree.xpath("//*[normalize-space(text())='{}']".format(question))),
0 1
) )
def test_neither_label_tag_nor_attribute(self): def test_neither_label_tag_nor_attribute(self):
...@@ -158,9 +156,9 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -158,9 +156,9 @@ class CAPAProblemTest(unittest.TestCase):
} }
) )
def test_question_is_not_removed(self): def test_non_accessible_inputtype(self):
""" """
Verify that tag with question text is not removed when responsetype is not fully accessible. Verify that tag with question text is not removed when inputtype is not fully accessible.
""" """
question = "Click the country which is home to the Pyramids." question = "Click the country which is home to the Pyramids."
xml = """ xml = """
...@@ -178,7 +176,7 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -178,7 +176,7 @@ class CAPAProblemTest(unittest.TestCase):
{ {
'1_2_1': '1_2_1':
{ {
'label': 'Click the country which is home to the Pyramids.', 'label': '',
'descriptions': {} 'descriptions': {}
} }
} }
...@@ -261,22 +259,22 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -261,22 +259,22 @@ class CAPAProblemTest(unittest.TestCase):
) )
self.assertEqual(len(problem.tree.xpath('//label')), 0) self.assertEqual(len(problem.tree.xpath('//label')), 0)
def test_label_attribute_mismatches_question_tag(self): def test_question_title_not_removed_got_children(self):
""" """
Verify that question text is extracted correctly when label attribtue value Verify that <p> question text before responsetype not deleted when
mismatched with question tag value. it contains other children and label is picked from label attribute of inputtype
This is the case when author updated the question <p> tag directly in XML but This is the case when author updated the <p> immediately before
didn't change the label attribute value. In this case we will consider the responsetype to contain other elements. We do not want to delete information in that case.
first <p> tag before responsetype as question.
""" """
question = 'Select the correct synonym of paranoid?' question = 'Is egg plant a fruit?'
xml = """ xml = """
<problem> <problem>
<p>Choose wisely.</p> <p>Choose wisely.</p>
<p>{}</p> <p>Select the correct synonym of paranoid?</p>
<p><img src="" /></p>
<choiceresponse> <choiceresponse>
<checkboxgroup label="Is egg plant a fruit?"> <checkboxgroup label="{}">
<choice correct="true">over-suspicious</choice> <choice correct="true">over-suspicious</choice>
<choice correct="false">funny</choice> <choice correct="false">funny</choice>
</checkboxgroup> </checkboxgroup>
...@@ -289,14 +287,14 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -289,14 +287,14 @@ class CAPAProblemTest(unittest.TestCase):
{ {
'1_2_1': '1_2_1':
{ {
'label': question, 'label': '',
'descriptions': {} 'descriptions': {}
} }
} }
) )
self.assertEqual( self.assertEqual(
len(problem.tree.xpath('//p[text()="{}"]'.format(question))), len(problem.tree.xpath('//p/img')),
0 1
) )
def test_multiple_inputtypes(self): def test_multiple_inputtypes(self):
...@@ -339,18 +337,19 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -339,18 +337,19 @@ class CAPAProblemTest(unittest.TestCase):
""" """
Verify that HTML is correctly rendered when there is single inputtype. Verify that HTML is correctly rendered when there is single inputtype.
""" """
xml = """ question = 'Enter sum of 1+2'
xml = textwrap.dedent("""
<problem> <problem>
<choiceresponse> <customresponse cfn="test_sum" expect="3">
<label>Select the correct synonym of paranoid?</label> <script type="loncapa/python">
<description>Only the paranoid survive.</description> def test_sum(expect, ans):
<checkboxgroup> return int(expect) == int(ans)
<choice correct="true">over-suspicious</choice> </script>
<choice correct="false">funny</choice> <label>{}</label>
</checkboxgroup> <textline size="20" correct_answer="3" />
</choiceresponse> </customresponse>
</problem> </problem>
""" """.format(question))
problem = new_loncapa_problem(xml, use_capa_render_template=True) problem = new_loncapa_problem(xml, use_capa_render_template=True)
problem_html = etree.XML(problem.get_html()) problem_html = etree.XML(problem.get_html())
...@@ -358,6 +357,10 @@ class CAPAProblemTest(unittest.TestCase): ...@@ -358,6 +357,10 @@ class CAPAProblemTest(unittest.TestCase):
multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]') multi_inputs_group = problem_html.xpath('//div[@class="multi-inputs-group"]')
self.assertEqual(len(multi_inputs_group), 0) self.assertEqual(len(multi_inputs_group), 0)
# verify that question is rendered only once
question = problem_html.xpath("//*[normalize-space(text())='{}']".format(question))
self.assertEqual(len(question), 1)
@ddt.ddt @ddt.ddt
class CAPAMultiInputProblemTest(unittest.TestCase): class CAPAMultiInputProblemTest(unittest.TestCase):
......
...@@ -165,6 +165,40 @@ class TemplateTestCase(unittest.TestCase): ...@@ -165,6 +165,40 @@ class TemplateTestCase(unittest.TestCase):
describedbys = xml.xpath(describedby_xpath) describedbys = xml.xpath(describedby_xpath)
self.assertFalse(describedbys) self.assertFalse(describedbys)
def assert_status(self, status_div=False, status_class=False):
"""
Verify status information.
Arguments:
status_div (bool): check presence of status div
status_class (bool): check presence of status class
"""
cases = [
('correct', 'correct'),
('unsubmitted', 'unanswered'),
('incorrect', 'incorrect'),
('incomplete', 'incorrect')
]
for context_status, div_class in cases:
self.context['status'] = Status(context_status)
xml = self.render_to_xml(self.context)
# Expect that we get a <div> with correct class
if status_div:
xpath = "//div[normalize-space(@class)='%s']" % div_class
self.assert_has_xpath(xml, xpath, self.context)
# Expect that we get a <span> with class="status"
# (used to by CSS to draw the green check / red x)
self.assert_has_text(
xml,
"//span[@class=normalize-space('status {}')]/span[@class='sr']".format(
div_class if status_class else ''
),
self.context['status'].display_tooltip
)
class ChoiceGroupTemplateTest(TemplateTestCase): class ChoiceGroupTemplateTest(TemplateTestCase):
""" """
...@@ -407,6 +441,12 @@ class ChoiceGroupTemplateTest(TemplateTestCase): ...@@ -407,6 +441,12 @@ class ChoiceGroupTemplateTest(TemplateTestCase):
self.assert_description(xpaths) self.assert_description(xpaths)
self.assert_describedby_attribute(xpaths) self.assert_describedby_attribute(xpaths)
def test_status(self):
"""
Verify status information.
"""
self.assert_status(status_class=True)
class TextlineTemplateTest(TemplateTestCase): class TextlineTemplateTest(TemplateTestCase):
""" """
...@@ -441,23 +481,10 @@ class TextlineTemplateTest(TemplateTestCase): ...@@ -441,23 +481,10 @@ class TextlineTemplateTest(TemplateTestCase):
self.assert_has_xpath(xml, xpath, self.context) self.assert_has_xpath(xml, xpath, self.context)
def test_status(self): def test_status(self):
cases = [('correct', 'correct', 'correct'), """
('unsubmitted', 'unanswered', 'unanswered'), Verify status information.
('incorrect', 'incorrect', 'incorrect'), """
('incomplete', 'incorrect', 'incomplete')] self.assert_status(status_div=True)
for (context_status, div_class, status_mark) in cases:
self.context['status'] = Status(context_status)
xml = self.render_to_xml(self.context)
# Expect that we get a <div> with correct class
xpath = "//div[@class='%s ']" % div_class
self.assert_has_xpath(xml, xpath, self.context)
# Expect that we get a <span> with class="status"
# (used to by CSS to draw the green check / red x)
self.assert_has_text(xml, "//span[@class='status']/span[@class='sr']",
status_mark, exact=False)
def test_label(self): def test_label(self):
xml = self.render_to_xml(self.context) xml = self.render_to_xml(self.context)
...@@ -582,6 +609,12 @@ class FormulaEquationInputTemplateTest(TemplateTestCase): ...@@ -582,6 +609,12 @@ class FormulaEquationInputTemplateTest(TemplateTestCase):
self.assert_description(xpaths) self.assert_description(xpaths)
self.assert_describedby_attribute(xpaths) self.assert_describedby_attribute(xpaths)
def test_status(self):
"""
Verify status information.
"""
self.assert_status(status_div=True)
class AnnotationInputTemplateTest(TemplateTestCase): class AnnotationInputTemplateTest(TemplateTestCase):
""" """
...@@ -767,6 +800,7 @@ class OptionInputTemplateTest(TemplateTestCase): ...@@ -767,6 +800,7 @@ class OptionInputTemplateTest(TemplateTestCase):
'options': [], 'options': [],
'status': Status('unsubmitted'), 'status': Status('unsubmitted'),
'value': 0, 'value': 0,
'default_option_text': 'Select an option',
'response_data': self.RESPONSE_DATA, 'response_data': self.RESPONSE_DATA,
'describedby': self.DESCRIBEDBY, 'describedby': self.DESCRIBEDBY,
} }
...@@ -796,20 +830,10 @@ class OptionInputTemplateTest(TemplateTestCase): ...@@ -796,20 +830,10 @@ class OptionInputTemplateTest(TemplateTestCase):
self.assert_has_text(xml, xpath, 'Option 2') self.assert_has_text(xml, xpath, 'Option 2')
def test_status(self): def test_status(self):
"""
# Test cases, where each tuple represents Verify status information.
# `(input_status, expected_css_class)` """
test_cases = [('unsubmitted', 'status unanswered'), self.assert_status(status_class=True)
('correct', 'status correct'),
('incorrect', 'status incorrect'),
('incomplete', 'status incorrect')]
for (input_status, expected_css_class) in test_cases:
self.context['status'] = Status(input_status)
xml = self.render_to_xml(self.context)
xpath = "//span[@class='{0}']".format(expected_css_class)
self.assert_has_xpath(xml, xpath, self.context)
def test_label(self): def test_label(self):
xml = self.render_to_xml(self.context) xml = self.render_to_xml(self.context)
......
...@@ -61,6 +61,7 @@ class OptionInputTest(unittest.TestCase): ...@@ -61,6 +61,7 @@ class OptionInputTest(unittest.TestCase):
'value': 'Down', 'value': 'Down',
'id': 'sky_input', 'id': 'sky_input',
'status': 'answered', 'status': 'answered',
'default_option_text': 'Select an option',
'response_data': RESPONSE_DATA 'response_data': RESPONSE_DATA
} }
option_input = lookup_tag('optioninput')(test_capa_system(), element, state) option_input = lookup_tag('optioninput')(test_capa_system(), element, state)
...@@ -75,6 +76,7 @@ class OptionInputTest(unittest.TestCase): ...@@ -75,6 +76,7 @@ class OptionInputTest(unittest.TestCase):
'msg': '', 'msg': '',
'inline': False, 'inline': False,
'id': 'sky_input', 'id': 'sky_input',
'default_option_text': 'Select an option',
'response_data': RESPONSE_DATA, 'response_data': RESPONSE_DATA,
'describedby': DESCRIBEDBY 'describedby': DESCRIBEDBY
} }
......
...@@ -23,7 +23,8 @@ var options = { ...@@ -23,7 +23,8 @@ var options = {
{pattern: 'common_static/coffee/src/ajax_prefix.js', included: true}, {pattern: 'common_static/coffee/src/ajax_prefix.js', included: true},
{pattern: 'common_static/common/js/vendor/underscore.js', included: true}, {pattern: 'common_static/common/js/vendor/underscore.js', included: true},
{pattern: 'common_static/common/js/vendor/backbone.js', included: true}, {pattern: 'common_static/common/js/vendor/backbone.js', included: true},
{pattern: 'common_static/js/vendor/CodeMirror/codemirror.js', included: true}, {pattern: 'common_static/js/vendor/codemirror-compressed.js', included: true},
{pattern: 'common_static/js/lib/pretty-print.js', included: true},
{pattern: 'common_static/js/vendor/draggabilly.js'}, {pattern: 'common_static/js/vendor/draggabilly.js'},
{pattern: 'common_static/common/js/vendor/jquery.js', included: true}, {pattern: 'common_static/common/js/vendor/jquery.js', included: true},
{pattern: 'common_static/common/js/vendor/jquery-migrate.js', included: true}, {pattern: 'common_static/common/js/vendor/jquery-migrate.js', included: true},
......
...@@ -314,7 +314,7 @@ describe 'Problem', -> ...@@ -314,7 +314,7 @@ describe 'Problem', ->
html = ''' html = '''
<div id="problem_sel"> <div id="problem_sel">
<select> <select>
<option value="val0"></option> <option value="val0">Select an option</option>
<option value="val1">1</option> <option value="val1">1</option>
<option value="val2">2</option> <option value="val2">2</option>
</select> </select>
......
...@@ -27,26 +27,38 @@ describe 'Markdown to xml extended hint dropdown', -> ...@@ -27,26 +27,38 @@ describe 'Markdown to xml extended hint dropdown', ->
""") """)
expect(data).toEqual(""" expect(data).toEqual("""
<problem> <problem>
<p>Translation between Dropdown and ________ is straightforward.</p> <p>Translation between Dropdown and ________ is straightforward.</p>
<optionresponse> <optionresponse>
<optioninput> <optioninput>
<option correct="True">Multiple Choice <optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint></option> <option correct="True">Multiple Choice
<option correct="False">Text Input <optionhint>No, text input problems don't present options.</optionhint></option> <optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint>
<option correct="False">Numerical Input <optionhint>No, numerical input problems don't present options.</optionhint></option> </option>
</optioninput> <option correct="False">Text Input
</optionresponse> <optionhint>No, text input problems don't present options.</optionhint>
</option>
<p>Clowns have funny _________ to make people laugh.</p> <option correct="False">Numerical Input
<optionresponse> <optionhint>No, numerical input problems don't present options.</optionhint>
<optioninput> </option>
<option correct="False">dogs <optionhint label="NOPE">Not dogs, not cats, not toads</optionhint></option> </optioninput>
<option correct="True">FACES <optionhint>With lots of makeup, doncha know?</optionhint></option> </optionresponse>
<option correct="False">money <optionhint>Clowns don't have any money, of course</optionhint></option> <p>Clowns have funny _________ to make people laugh.</p>
<option correct="False">donkeys <optionhint>don't be an ass.</optionhint></option> <optionresponse>
<option correct="False">-no hint-</option> <optioninput>
</optioninput> <option correct="False">dogs
</optionresponse> <optionhint label="NOPE">Not dogs, not cats, not toads</optionhint>
</option>
<option correct="True">FACES
<optionhint>With lots of makeup, doncha know?</optionhint>
</option>
<option correct="False">money
<optionhint>Clowns don't have any money, of course</optionhint>
</option>
<option correct="False">donkeys
<optionhint>don't be an ass.</optionhint>
</option>
<option correct="False">-no hint-</option>
</optioninput>
</optionresponse>
</problem> </problem>
""") """)
......
...@@ -498,7 +498,7 @@ class @Problem ...@@ -498,7 +498,7 @@ class @Problem
@el.find("select").each (i, select_field) => @el.find("select").each (i, select_field) =>
selected_option = $(select_field).find("option:selected").text().trim() selected_option = $(select_field).find("option:selected").text().trim()
if selected_option is '' if selected_option is 'Select an option'
answered = false answered = false
if bind if bind
$(select_field).on 'change', (e) => $(select_field).on 'change', (e) =>
......
...@@ -531,7 +531,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor ...@@ -531,7 +531,7 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
// replace code blocks // replace code blocks
xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) { xml = xml.replace(/\[code\]\n?([^\]]*)\[\/?code\]/gmi, function(match, p1) {
var selectString = '<pre><code>\n' + p1 + '</code></pre>'; var selectString = '<pre><code>' + p1 + '</code></pre>';
return selectString; return selectString;
}); });
...@@ -626,4 +626,6 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor ...@@ -626,4 +626,6 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
# make all responsetypes descendants of a single problem element # make all responsetypes descendants of a single problem element
## safe-lint: disable=javascript-concat-html ## safe-lint: disable=javascript-concat-html
return '<problem>\n' + responseTypesXML.join('\n\n') + demandHints + '\n</problem>' # format and return xml
finalXml = '<problem>' + responseTypesXML.join('\n\n') + demandHints + '</problem>'
return PrettyPrint.xml(finalXml);
...@@ -40,9 +40,9 @@ data: | ...@@ -40,9 +40,9 @@ data: |
return test_add(10, ans) return test_add(10, ans)
</script> </script>
<p>Enter two integers that sum to 10.</p> <label>Enter two integers that sum to 10.</label>
<textline size="40" correct_answer="3"/><br/> <textline size="40" correct_answer="3" label="Enter first number" /><br/>
<textline size="40" correct_answer="7"/> <textline size="40" correct_answer="7" label="Enter second number" />
<solution> <solution>
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
...@@ -63,9 +63,9 @@ data: | ...@@ -63,9 +63,9 @@ data: |
return False return False
</script> </script>
<p>Enter two integers that sum to 20.</p> <label>Enter two integers that sum to 20.</label>
<textline size="40" correct_answer="11"/><br/> <textline size="40" correct_answer="11" label="Enter first number" /><br/>
<textline size="40" correct_answer="9"/> <textline size="40" correct_answer="9" label="Enter second number" />
<solution> <solution>
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
......
...@@ -3,8 +3,7 @@ metadata: ...@@ -3,8 +3,7 @@ metadata:
display_name: Dropdown with Hints and Feedback display_name: Dropdown with Hints and Feedback
markdown: | markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment. You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt >>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
like this. <<
[[ [[
an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}} an incorrect answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
(the correct answer) (the correct answer)
...@@ -20,8 +19,7 @@ data: | ...@@ -20,8 +19,7 @@ data: |
<optionresponse> <optionresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p> <p>You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label> <label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt <description>You can add an optional tip or note related to the prompt like this. </description>
like this. </description>
<optioninput> <optioninput>
<option correct="False">an incorrect answer <optionhint>You can specify optional feedback like this, which appears after this answer is submitted.</optionhint></option> <option correct="False">an incorrect answer <optionhint>You can specify optional feedback like this, which appears after this answer is submitted.</optionhint></option>
<option correct="True">the correct answer</option> <option correct="True">the correct answer</option>
......
...@@ -35,7 +35,7 @@ data: | ...@@ -35,7 +35,7 @@ data: |
hint = "&lt;font color='blue'&gt;Hint: {0}&lt;/font&gt;".format(hint) hint = "&lt;font color='blue'&gt;Hint: {0}&lt;/font&gt;".format(hint)
new_cmap.set_hint_and_mode(aid,hint,'always') new_cmap.set_hint_and_mode(aid,hint,'always')
</script> </script>
<p>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</p> <label>What is the best programming language that exists today? You may enter your answer in upper or lower case, with or without quotes.</label>
<textline correct_answer="python"/> <textline correct_answer="python"/>
<hintgroup hintfn="hint_fn"/> <hintgroup hintfn="hint_fn"/>
</customresponse> </customresponse>
......
...@@ -3,12 +3,9 @@ metadata: ...@@ -3,12 +3,9 @@ metadata:
display_name: Text Input with Hints and Feedback display_name: Text Input with Hints and Feedback
markdown: | markdown: |
You can use this template as a guide to the simple editor markdown and OLX markup to use for text You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.
input with hints and feedback problems. Edit this component to replace this template with your
own assessment.
>>Add the question text, or prompt, here. This text is required.||You can add an optional tip or >>Add the question text, or prompt, here. This text is required.||You can add an optional tip or note related to the prompt like this. <<
note related to the prompt like this. <<
= the correct answer {{You can specify optional feedback like this, which appears after this answer is submitted.}} = the correct answer {{You can specify optional feedback like this, which appears after this answer is submitted.}}
or= optional acceptable variant of the correct answer or= optional acceptable variant of the correct answer
...@@ -21,8 +18,7 @@ hinted: true ...@@ -21,8 +18,7 @@ hinted: true
data: | data: |
<problem> <problem>
<stringresponse answer="the correct answer" type="ci"> <stringresponse answer="the correct answer" type="ci">
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with</p> <p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<p>hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
<label>Add the question text, or prompt, here. This text is required.</label> <label>Add the question text, or prompt, here. This text is required.</label>
<description>You can add an optional tip or note related to the prompt like this.</description> <description>You can add an optional tip or note related to the prompt like this.</description>
<correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint> <correcthint>You can specify optional feedback like this, which appears after this answer is submitted.</correcthint>
......
/**
* pretty-data - nodejs plugin to pretty-print or minify data in XML, JSON and CSS formats.
*
* Version - 0.40.0
* Copyright (c) 2012 Vadim Kiryukhin
* vkiryukhin @ gmail.com
* http://www.eslinstructor.net/pretty-data/
*
*
* Code extracted for xml formatting only
*/
/* eslint-disable */
(function (root, factory){
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function (){
return (root.PrettyPrint = factory());
});
} else {
// Browser globals
root.PrettyPrint = factory();
}
}(this, function () {
function PrettyPrint(){
var maxdeep = 100, // nesting level
ix = 0;
this.shift = ['\n']; // array of shifts
this.step = ' '; // 2 spaces
// initialize array with shifts //
for (ix = 0; ix < maxdeep; ix++) {
this.shift.push(this.shift[ix] + this.step);
}
}
PrettyPrint.prototype.xml = function (text) {
var ar = text.replace(/>\s{0,}</g, "><")
.replace(/</g, "~::~<")
.replace(/xmlns\:/g, "~::~xmlns:")
.replace(/xmlns\=/g, "~::~xmlns=")
.split('~::~'),
len = ar.length,
inComment = false,
deep = 0,
str = '',
ix = 0;
for (ix = 0; ix < len; ix++) {
// start comment or <![CDATA[...]]> or <!DOCTYPE //
if (ar[ix].search(/<!/) > -1) {
str += this.shift[deep] + ar[ix];
inComment = true;
// end comment or <![CDATA[...]]> //
if (ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1 || ar[ix].search(/!DOCTYPE/) > -1) {
inComment = false;
}
} else
// end comment or <![CDATA[...]]> //
if (ar[ix].search(/-->/) > -1 || ar[ix].search(/\]>/) > -1) {
str += ar[ix];
inComment = false;
} else
// <elm></elm> //
if (/^<\w/.exec(ar[ix - 1]) && /^<\/\w/.exec(ar[ix]) &&
/^<[\w:\-\.\,]+/.exec(ar[ix - 1]) == /^<\/[\w:\-\.\,]+/.exec(ar[ix])[0].replace('/', '')) {
str += ar[ix];
if (!inComment) deep--;
} else
// <elm> //
if (ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) == -1 && ar[ix].search(/\/>/) == -1) {
str = !inComment ? str += this.shift[deep++] + ar[ix] : str += ar[ix];
} else
// <elm>...</elm> //
if (ar[ix].search(/<\w/) > -1 && ar[ix].search(/<\//) > -1) {
str = !inComment ? str += this.shift[deep] + ar[ix] : str += ar[ix];
} else
// </elm> //
if (ar[ix].search(/<\//) > -1) {
str = !inComment ? str += this.shift[--deep] + ar[ix] : str += ar[ix];
} else
// <elm/> //
if (ar[ix].search(/\/>/) > -1) {
str = !inComment ? str += this.shift[deep] + ar[ix] : str += ar[ix];
} else
// <? xml ... ?> //
if (ar[ix].search(/<\?/) > -1) {
str += this.shift[deep] + ar[ix];
} else
// xmlns //
if (ar[ix].search(/xmlns\:/) > -1 || ar[ix].search(/xmlns\=/) > -1) {
str += this.shift[deep] + ar[ix];
}
else {
str += ar[ix];
}
}
return (str[0] == '\n') ? str.slice(1) : str;
};
return new PrettyPrint();
}));
describe('XML Formatting Lib', function() {
'use strict';
it('correctly format the xml', function() {
var rawXml = '<breakfast><food><name>Belgian Waffles</name><price>$5.95</price></food></breakfast>',
expectedXml = '<breakfast>\n <food>\n <name>Belgian Waffles</name>' +
'\n <price>$5.95</price>\n </food>\n</breakfast>';
expect(window.PrettyPrint.xml(rawXml)).toEqual(expectedXml);
});
it('correctly handles the whitespaces and newlines', function() {
var rawXml = '<breakfast> <food> <name>Belgian Waffles</name>' +
'\n\n\n<price>$5.95</price></food> </breakfast>',
expectedXml = '<breakfast>\n <food>\n <name>Belgian Waffles</name>' +
'\n <price>$5.95</price>\n </food>\n</breakfast>';
expect(window.PrettyPrint.xml(rawXml)).toEqual(expectedXml);
});
});
...@@ -28,6 +28,7 @@ var options = { ...@@ -28,6 +28,7 @@ var options = {
{pattern: 'js/vendor/URI.min.js', included: true}, {pattern: 'js/vendor/URI.min.js', included: true},
{pattern: 'js/test/add_ajax_prefix.js', included: true}, {pattern: 'js/test/add_ajax_prefix.js', included: true},
{pattern: 'js/test/i18n.js', included: true}, {pattern: 'js/test/i18n.js', included: true},
{pattern: 'js/lib/pretty-print.js', included: true},
{pattern: 'common/js/vendor/underscore.js', included: true}, {pattern: 'common/js/vendor/underscore.js', included: true},
{pattern: 'common/js/vendor/underscore.string.js', included: true}, {pattern: 'common/js/vendor/underscore.string.js', included: true},
......
...@@ -218,11 +218,11 @@ class CertificateProgressPageTest(UniqueCourseTest): ...@@ -218,11 +218,11 @@ class CertificateProgressPageTest(UniqueCourseTest):
self.course_nav.q(css='select option[value="{}"]'.format('blue')).first.click() self.course_nav.q(css='select option[value="{}"]'.format('blue')).first.click()
# Select correct radio button for the answer # Select correct radio button for the answer
self.course_nav.q(css='fieldset div.field:nth-child(4) input').nth(0).click() self.course_nav.q(css='fieldset div.field:nth-child(3) input').nth(0).click()
# Select correct radio buttons for the answer # Select correct radio buttons for the answer
self.course_nav.q(css='fieldset div.field:nth-child(2) input').nth(1).click() self.course_nav.q(css='fieldset div.field:nth-child(1) input').nth(1).click()
self.course_nav.q(css='fieldset div.field:nth-child(4) input').nth(1).click() self.course_nav.q(css='fieldset div.field:nth-child(3) input').nth(1).click()
# Submit the answer # Submit the answer
self.course_nav.q(css='button.check.Check').click() self.course_nav.q(css='button.check.Check').click()
......
...@@ -31,7 +31,7 @@ var options = { ...@@ -31,7 +31,7 @@ var options = {
{pattern: 'common/js/xblock/*.js', included: true}, {pattern: 'common/js/xblock/*.js', included: true},
{pattern: 'xmodule_js/common_static/js/src/logger.js', included: true}, {pattern: 'xmodule_js/common_static/js/src/logger.js', included: true},
{pattern: 'xmodule_js/common_static/js/test/i18n.js', included: true}, {pattern: 'xmodule_js/common_static/js/test/i18n.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/CodeMirror/codemirror.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/codemirror-compressed.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/jquery.cookie.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/jquery.cookie.js', included: true},
{pattern: 'xmodule_js/common_static/js/vendor/flot/jquery.flot.js', included: true}, {pattern: 'xmodule_js/common_static/js/vendor/flot/jquery.flot.js', included: true},
{pattern: 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js', included: true}, {pattern: 'xmodule_js/common_static/coffee/src/jquery.immediateDescendents.js', included: true},
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment