Commit 3b60ff69 by muhammad-ammar

separate multiple questions in a single problem

FEDX-173
parent fa7ed070
...@@ -36,7 +36,7 @@ class TemplateTests(ModuleStoreTestCase): ...@@ -36,7 +36,7 @@ class TemplateTests(ModuleStoreTestCase):
self.assertIn('markdown', dropdown['metadata']) self.assertIn('markdown', dropdown['metadata'])
self.assertIn('data', dropdown) self.assertIn('data', dropdown)
self.assertRegexpMatches(dropdown['metadata']['markdown'], r'^Dropdown.*') self.assertRegexpMatches(dropdown['metadata']['markdown'], r'^Dropdown.*')
self.assertRegexpMatches(dropdown['data'], r'<problem>\s*<p>Dropdown.*') self.assertRegexpMatches(dropdown['data'], r'<problem>\s*<question>\s*<p>Dropdown.*')
def test_get_some_templates(self): def test_get_some_templates(self):
self.assertEqual(len(SequenceDescriptor.templates()), 0) self.assertEqual(len(SequenceDescriptor.templates()), 0)
......
...@@ -41,6 +41,7 @@ response_properties = ["codeparam", "responseparam", "answer", "openendedparam"] ...@@ -41,6 +41,7 @@ response_properties = ["codeparam", "responseparam", "answer", "openendedparam"]
# special problem tags which should be turned into innocuous HTML # special problem tags which should be turned into innocuous HTML
html_transforms = { html_transforms = {
'problem': {'tag': 'div'}, 'problem': {'tag': 'div'},
'question': {'tag': 'div'},
'text': {'tag': 'span'}, 'text': {'tag': 'span'},
'math': {'tag': 'span'}, 'math': {'tag': 'span'},
} }
...@@ -164,7 +165,7 @@ class LoncapaProblem(object): ...@@ -164,7 +165,7 @@ class LoncapaProblem(object):
# parse problem XML file into an element tree # parse problem XML file into an element tree
self.tree = etree.XML(problem_text) self.tree = etree.XML(problem_text)
self.make_xml_compatible(self.tree) self.tree = self.make_xml_compatible(self.tree)
# handle any <include file="foo"> tags # handle any <include file="foo"> tags
self._process_includes() self._process_includes()
...@@ -215,6 +216,15 @@ class LoncapaProblem(object): ...@@ -215,6 +216,15 @@ class LoncapaProblem(object):
This translation takes in the new format and synthesizes the old option= attribute This translation takes in the new format and synthesizes the old option= attribute
so all downstream logic works unchanged with the new <option> tag format. so all downstream logic works unchanged with the new <option> tag format.
""" """
# Convert the existing problem's XML to new format
# <problem>...</problem> to <problem><question>...</question></problem>
questions = tree.xpath('//problem/question')
if not questions:
tree.tag = 'question'
problem = etree.Element('problem')
problem.insert(0, tree)
tree = problem
additionals = tree.xpath('//stringresponse/additional_answer') additionals = tree.xpath('//stringresponse/additional_answer')
for additional in additionals: for additional in additionals:
answer = additional.get('answer') answer = additional.get('answer')
...@@ -238,6 +248,8 @@ class LoncapaProblem(object): ...@@ -238,6 +248,8 @@ class LoncapaProblem(object):
if correct_option: if correct_option:
optioninput.attrib.update({'correct': correct_option}) optioninput.attrib.update({'correct': correct_option})
return tree
def do_reset(self): def do_reset(self):
""" """
Reset internal state to unfinished, with no answers Reset internal state to unfinished, with no answers
...@@ -479,7 +491,7 @@ class LoncapaProblem(object): ...@@ -479,7 +491,7 @@ class LoncapaProblem(object):
def do_targeted_feedback(self, tree): def do_targeted_feedback(self, tree):
""" """
Implements targeted-feedback in-place on <multiplechoiceresponse> -- For each question, Implement targeted-feedback in-place on <multiplechoiceresponse> --
choice-level explanations shown to a student after submission. choice-level explanations shown to a student after submission.
Does nothing if there is no targeted-feedback attribute. Does nothing if there is no targeted-feedback attribute.
""" """
...@@ -488,67 +500,69 @@ class LoncapaProblem(object): ...@@ -488,67 +500,69 @@ class LoncapaProblem(object):
return return
self.has_targeted = True # pylint: disable=attribute-defined-outside-init self.has_targeted = True # pylint: disable=attribute-defined-outside-init
for mult_choice_response in tree.xpath('//multiplechoiceresponse[@targeted-feedback]'): questions = tree.xpath('//problem/question')
show_explanation = mult_choice_response.get('targeted-feedback') == 'alwaysShowCorrectChoiceExplanation' for question in questions:
for mult_choice_response in question.xpath('//multiplechoiceresponse[@targeted-feedback]'):
# Grab the first choicegroup (there should only be one within each <multiplechoiceresponse> tag) show_explanation = mult_choice_response.get('targeted-feedback') == 'alwaysShowCorrectChoiceExplanation'
choicegroup = mult_choice_response.xpath('./choicegroup[@type="MultipleChoice"]')[0]
choices_list = list(choicegroup.iter('choice')) # Grab the first choicegroup (there should only be one within each <multiplechoiceresponse> tag)
choicegroup = mult_choice_response.xpath('./choicegroup[@type="MultipleChoice"]')[0]
# Find the student answer key that matches our <choicegroup> id choices_list = list(choicegroup.iter('choice'))
student_answer = self.student_answers.get(choicegroup.get('id'))
expl_id_for_student_answer = None # Find the student answer key that matches our <choicegroup> id
student_answer = self.student_answers.get(choicegroup.get('id'))
# Keep track of the explanation-id that corresponds to the student's answer expl_id_for_student_answer = None
# Also, keep track of the solution-id
solution_id = None # Keep track of the explanation-id that corresponds to the student's answer
for choice in choices_list: # Also, keep track of the solution-id
if choice.get('name') == student_answer: solution_id = None
expl_id_for_student_answer = choice.get('explanation-id') for choice in choices_list:
if choice.get('correct') == 'true': if choice.get('name') == student_answer:
solution_id = choice.get('explanation-id') expl_id_for_student_answer = choice.get('explanation-id')
if choice.get('correct') == 'true':
# Filter out targetedfeedback that doesn't correspond to the answer the student selected solution_id = choice.get('explanation-id')
# Note: following-sibling will grab all following siblings, so we just want the first in the list
targetedfeedbackset = mult_choice_response.xpath('./following-sibling::targetedfeedbackset') # Filter out targetedfeedback that doesn't correspond to the answer the student selected
if len(targetedfeedbackset) != 0: # Note: following-sibling will grab all following siblings, so we just want the first in the list
targetedfeedbackset = targetedfeedbackset[0] targetedfeedbackset = mult_choice_response.xpath('./following-sibling::targetedfeedbackset')
targetedfeedbacks = targetedfeedbackset.xpath('./targetedfeedback') if len(targetedfeedbackset) != 0:
for targetedfeedback in targetedfeedbacks: targetedfeedbackset = targetedfeedbackset[0]
# Don't show targeted feedback if the student hasn't answer the problem targetedfeedbacks = targetedfeedbackset.xpath('./targetedfeedback')
# or if the target feedback doesn't match the student's (incorrect) answer for targetedfeedback in targetedfeedbacks:
if not self.done or targetedfeedback.get('explanation-id') != expl_id_for_student_answer: # Don't show targeted feedback if the student hasn't answer the problem
targetedfeedbackset.remove(targetedfeedback) # or if the target feedback doesn't match the student's (incorrect) answer
if not self.done or targetedfeedback.get('explanation-id') != expl_id_for_student_answer:
# Do not displace the solution under these circumstances targetedfeedbackset.remove(targetedfeedback)
if not show_explanation or not self.done:
continue # Do not displace the solution under these circumstances
if not show_explanation or not self.done:
# The next element should either be <solution> or <solutionset> continue
next_element = targetedfeedbackset.getnext()
parent_element = tree # The next element should either be <solution> or <solutionset>
solution_element = None next_element = targetedfeedbackset.getnext()
if next_element is not None and next_element.tag == 'solution': parent_element = question
solution_element = next_element solution_element = None
elif next_element is not None and next_element.tag == 'solutionset': if next_element is not None and next_element.tag == 'solution':
solutions = next_element.xpath('./solution') solution_element = next_element
for solution in solutions: elif next_element is not None and next_element.tag == 'solutionset':
if solution.get('explanation-id') == solution_id: solutions = next_element.xpath('./solution')
parent_element = next_element for solution in solutions:
solution_element = solution if solution.get('explanation-id') == solution_id:
parent_element = next_element
# If could not find the solution element, then skip the remaining steps below solution_element = solution
if solution_element is None:
continue # If could not find the solution element, then skip the remaining steps below
if solution_element is None:
# Change our correct-choice explanation from a "solution explanation" to within continue
# the set of targeted feedback, which means the explanation will render on the page
# without the student clicking "Show Answer" or seeing a checkmark next to the correct choice # Change our correct-choice explanation from a "solution explanation" to within
parent_element.remove(solution_element) # the set of targeted feedback, which means the explanation will render on the page
# without the student clicking "Show Answer" or seeing a checkmark next to the correct choice
# Add our solution instead to the targetedfeedbackset and change its tag name parent_element.remove(solution_element)
solution_element.tag = 'targetedfeedback'
targetedfeedbackset.append(solution_element) # Add our solution instead to the targetedfeedbackset and change its tag name
solution_element.tag = 'targetedfeedback'
targetedfeedbackset.append(solution_element)
def get_html(self): def get_html(self):
""" """
...@@ -723,7 +737,7 @@ class LoncapaProblem(object): ...@@ -723,7 +737,7 @@ class LoncapaProblem(object):
context['extra_files'] = extra_files or None context['extra_files'] = extra_files or None
return context return context
def _extract_html(self, problemtree): # private def _extract_html(self, problemtree, question_id=0): # private
""" """
Main (private) function which converts Problem XML tree to HTML. Main (private) function which converts Problem XML tree to HTML.
Calls itself recursively. Calls itself recursively.
...@@ -808,7 +822,13 @@ class LoncapaProblem(object): ...@@ -808,7 +822,13 @@ class LoncapaProblem(object):
# otherwise, render children recursively, and copy over attributes # otherwise, render children recursively, and copy over attributes
tree = etree.Element(problemtree.tag) tree = etree.Element(problemtree.tag)
for item in problemtree: for item in problemtree:
item_xhtml = self._extract_html(item) item_xhtml = self._extract_html(item, question_id)
if item.tag == 'question':
item_xhtml.set('class', 'question')
item_xhtml.set('id', 'question-{}'.format(question_id))
question_id += 1
if item_xhtml is not None: if item_xhtml is not None:
tree.append(item_xhtml) tree.append(item_xhtml)
......
...@@ -100,6 +100,28 @@ registry.register(SolutionRenderer) ...@@ -100,6 +100,28 @@ registry.register(SolutionRenderer)
#----------------------------------------------------------------------------- #-----------------------------------------------------------------------------
class DemandhintRenderer(object):
"""
Render demand demandhint HTML.
"""
tags = ['demandhint']
def __init__(self, system, xml): # pylint: disable=unused-variable
self.system = system
def get_html(self):
"""
Return HTML for demandhint tag.
"""
html = self.system.render_template("demandhint.html", {})
return etree.XML(html)
registry.register(DemandhintRenderer)
#-----------------------------------------------------------------------------
class TargetedFeedbackRenderer(object): class TargetedFeedbackRenderer(object):
""" """
A targeted feedback is just a <span>...</span> that is used for displaying an A targeted feedback is just a <span>...</span> that is used for displaying an
......
<%! from django.utils.translation import ugettext as _ %>
<div class="action demandhint">
<div class="problem-hint" aria-live="polite"></div>
<button class="hint-button" data-value="${_('Hint')}">${_('Hint')}</button>
</div>
...@@ -273,7 +273,7 @@ class CapaHtmlRenderTest(unittest.TestCase): ...@@ -273,7 +273,7 @@ class CapaHtmlRenderTest(unittest.TestCase):
# Render the HTML # Render the HTML
the_html = problem.get_html() the_html = problem.get_html()
self.assertRegexpMatches(the_html, r"<div>\s+</div>") self.assertRegexpMatches(the_html, r"<div class=\"question\" id=\"question-0\">\s+</div>")
def _create_test_file(self, path, content_str): def _create_test_file(self, path, content_str):
test_fp = self.capa_system.filestore.open(path, "w") test_fp = self.capa_system.filestore.open(path, "w")
...@@ -281,3 +281,31 @@ class CapaHtmlRenderTest(unittest.TestCase): ...@@ -281,3 +281,31 @@ class CapaHtmlRenderTest(unittest.TestCase):
test_fp.close() test_fp.close()
self.addCleanup(lambda: os.remove(test_fp.name)) self.addCleanup(lambda: os.remove(test_fp.name))
def test_existing_xml_compatibility(self):
"""
Verifies that existing problem's XML is converted to new format.
In new format single are multiple questions should be come inside <question></question>
"""
xml_str = textwrap.dedent("""\
<problem>
<p>That is the question</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Alpha <choicehint>A hint</choicehint>
</choice>
<choice correct="true">Beta</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>question 1 hint 1</hint>
<hint>question 1 hint 2</hint>
</demandhint>
</problem>
""")
# Create the problem
problem = new_loncapa_problem(xml_str)
childs = [child.tag for child in problem.tree.getchildren()] # pylint: disable=no-member
self.assertEqual(set(childs), set(['question']))
...@@ -209,6 +209,12 @@ class CapaMixin(CapaFields): ...@@ -209,6 +209,12 @@ class CapaMixin(CapaFields):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(CapaMixin, self).__init__(*args, **kwargs) super(CapaMixin, self).__init__(*args, **kwargs)
# For Blank Advanced Problem, there is no template so `<problem></problem>` is generated,
# It should be converted to <problem><question></question></problem> to make the things consistent
# TODO! Find a better way to do this
if self.data == '<problem></problem>':
self.data = '<problem><question></question></problem>'
due_date = self.due due_date = self.due
if self.graceperiod is not None and due_date: if self.graceperiod is not None and due_date:
...@@ -591,17 +597,19 @@ class CapaMixin(CapaFields): ...@@ -591,17 +597,19 @@ class CapaMixin(CapaFields):
return html return html
def get_demand_hint(self, hint_index): def get_demand_hint(self, question_id, hint_index):
""" """
Return html for the problem. Return html for the problem.
Adds check, reset, save, and hint buttons as necessary based on the problem config Adds check, reset, save, and hint buttons as necessary based on the problem config
and state. and state.
encapsulate: if True (the default) embed the html in a problem <div> encapsulate: if True (the default) embed the html in a problem <div>
question_id: question id for which hint is requested
hint_index: (None is the default) if not None, this is the index of the next demand hint_index: (None is the default) if not None, this is the index of the next demand
hint to show. hint to show.
""" """
demand_hints = self.lcp.tree.xpath("//problem/demandhint/hint") # indexing in XPath starts with 1
demand_hints = self.lcp.tree.xpath("//problem/question[{}]/demandhint/hint".format(question_id + 1))
hint_index = hint_index % len(demand_hints) hint_index = hint_index % len(demand_hints)
_ = self.runtime.service(self, "i18n").ugettext _ = self.runtime.service(self, "i18n").ugettext
...@@ -664,10 +672,6 @@ class CapaMixin(CapaFields): ...@@ -664,10 +672,6 @@ class CapaMixin(CapaFields):
'weight': self.weight, 'weight': self.weight,
} }
# If demand hints are available, emit hint button and div.
demand_hints = self.lcp.tree.xpath("//problem/demandhint/hint")
demand_hint_possible = len(demand_hints) > 0
context = { context = {
'problem': content, 'problem': content,
'id': self.location.to_deprecated_string(), 'id': self.location.to_deprecated_string(),
...@@ -678,7 +682,6 @@ class CapaMixin(CapaFields): ...@@ -678,7 +682,6 @@ class CapaMixin(CapaFields):
'answer_available': self.answer_available(), 'answer_available': self.answer_available(),
'attempts_used': self.attempts, 'attempts_used': self.attempts,
'attempts_allowed': self.max_attempts, 'attempts_allowed': self.max_attempts,
'demand_hint_possible': demand_hint_possible
} }
html = self.runtime.render_template('problem.html', context) html = self.runtime.render_template('problem.html', context)
...@@ -718,8 +721,10 @@ class CapaMixin(CapaFields): ...@@ -718,8 +721,10 @@ class CapaMixin(CapaFields):
""" """
Hint button handler, returns new html using hint_index from the client. Hint button handler, returns new html using hint_index from the client.
""" """
question_id = int(data['question_id'])
hint_index = int(data['hint_index']) hint_index = int(data['hint_index'])
return self.get_demand_hint(hint_index)
return self.get_demand_hint(question_id, hint_index)
def is_past_due(self): def is_past_due(self):
""" """
......
...@@ -57,6 +57,7 @@ h2 { ...@@ -57,6 +57,7 @@ h2 {
&.problem-header { &.problem-header {
display: inline-block; display: inline-block;
margin-bottom: 0;
section.staff { section.staff {
margin-top: ($baseline*1.5); margin-top: ($baseline*1.5);
font-size: 80%; font-size: 80%;
...@@ -123,6 +124,7 @@ div.problem-progress { ...@@ -123,6 +124,7 @@ div.problem-progress {
@include padding-left($baseline/4); @include padding-left($baseline/4);
@extend %t-ultralight; @extend %t-ultralight;
display: inline-block; display: inline-block;
margin-bottom: $baseline;
color: $gray-d1; color: $gray-d1;
font-weight: 100; font-weight: 100;
font-size: em(16); font-size: em(16);
...@@ -148,6 +150,10 @@ div.problem { ...@@ -148,6 +150,10 @@ div.problem {
margin-top: $baseline; margin-top: $baseline;
} }
} }
div.question:not(:last-child) {
margin-bottom: $baseline;
}
} }
// +Problem - Choice Group // +Problem - Choice Group
......
...@@ -65,7 +65,7 @@ var options = { ...@@ -65,7 +65,7 @@ var options = {
specFiles: [ specFiles: [
{pattern: 'spec/helper.js', included: true, ignoreCoverage: true}, // Helper which depends on source files. {pattern: 'spec/helper.js', included: true, ignoreCoverage: true}, // Helper which depends on source files.
{pattern: 'spec/**/*.js', included: true} {pattern: 'spec/problem/*.js', included: true}
], ],
fixtureFiles: [ fixtureFiles: [
......
describe 'MarkdownEditingDescriptor', -> describe 'MarkdownEditingDescriptor', ->
beforeEach ->
jasmine.addMatchers
toXMLEqual: ->
return {
compare: (actual, expected) ->
{
pass: actual.replace(/\s+/g, '') == expected.replace(/\s+/g, '')
}
}
describe 'save stores the correct data', -> describe 'save stores the correct data', ->
it 'saves markdown from markdown editor', -> it 'saves markdown from markdown editor', ->
loadFixtures 'problem-with-markdown.html' loadFixtures 'problem-with-markdown.html'
@descriptor = new MarkdownEditingDescriptor($('.problem-editor')) @descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
saveResult = @descriptor.save() saveResult = @descriptor.save()
expect(saveResult.metadata.markdown).toEqual('markdown') expect(saveResult.metadata.markdown).toEqual('markdown')
expect(saveResult.data).toEqual('<problem>\n<p>markdown</p>\n</problem>') expect(saveResult.data).toEqual('<problem>\n<question>\n<p>markdown</p>\n</question>\n</problem>')
it 'clears markdown when xml editor is selected', -> it 'clears markdown when xml editor is selected', ->
loadFixtures 'problem-with-markdown.html' loadFixtures 'problem-with-markdown.html'
@descriptor = new MarkdownEditingDescriptor($('.problem-editor')) @descriptor = new MarkdownEditingDescriptor($('.problem-editor'))
...@@ -101,7 +110,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -101,7 +110,7 @@ describe 'MarkdownEditingDescriptor', ->
describe 'markdownToXml', -> describe 'markdownToXml', ->
it 'converts raw text to paragraph', -> it 'converts raw text to paragraph', ->
data = MarkdownEditingDescriptor.markdownToXml('foo') data = MarkdownEditingDescriptor.markdownToXml('foo')
expect(data).toEqual('<problem>\n<p>foo</p>\n</problem>') expect(data).toEqual('<problem>\n<question>\n<p>foo</p>\n</question>\n</problem>')
# test default templates # test default templates
it 'converts numerical response to xml', -> it 'converts numerical response to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value. data = MarkdownEditingDescriptor.markdownToXml("""A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.
...@@ -110,30 +119,38 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -110,30 +119,38 @@ describe 'MarkdownEditingDescriptor', ->
Enter the numerical value of Pi: Enter the numerical value of Pi:
= 3.14159 +- .02 = 3.14159 +- .02
[Explanation]
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
[Explanation]
---
Enter the approximate value of 502*9: Enter the approximate value of 502*9:
= 502*9 +- 15% = 502*9 +- 15%
[Explanation]
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
[Explanation]
---
Enter the number of fingers on a human hand: Enter the number of fingers on a human hand:
= 5 = 5
[Explanation]
If you look at your hand, you can count that you have five fingers.
[Explanation]
---
Range tolerance case Range tolerance case
= [6, 7] = [6, 7]
= (1, 2) = (1, 2)
---
If first and last symbols are not brackets, or they are not closed, stringresponse will appear. If first and last symbols are not brackets, or they are not closed, stringresponse will appear.
= (7), 7 = (7), 7
= (1+2 = (1+2
[Explanation]
Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.
Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.
If you look at your hand, you can count that you have five fingers.
[Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p> <p>A numerical response problem accepts a line of text input from the student, and evaluates the input for correctness based on its numerical value.</p>
<p>The answer is correct if it is within a specified numerical tolerance of the expected answer.</p> <p>The answer is correct if it is within a specified numerical tolerance of the expected answer.</p>
...@@ -143,6 +160,19 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -143,6 +160,19 @@ describe 'MarkdownEditingDescriptor', ->
<responseparam type="tolerance" default=".02" /> <responseparam type="tolerance" default=".02" />
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
</div>
</solution>
</question>
<question>
<p>Enter the approximate value of 502*9:</p> <p>Enter the approximate value of 502*9:</p>
<numericalresponse answer="502*9"> <numericalresponse answer="502*9">
...@@ -150,11 +180,39 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -150,11 +180,39 @@ describe 'MarkdownEditingDescriptor', ->
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
</div>
</solution>
</question>
<question>
<p>Enter the number of fingers on a human hand:</p> <p>Enter the number of fingers on a human hand:</p>
<numericalresponse answer="5"> <numericalresponse answer="5">
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>If you look at your hand, you can count that you have five fingers.</p>
</div>
</solution>
</question>
<question>
<p>Range tolerance case</p> <p>Range tolerance case</p>
<numericalresponse answer="[6, 7]"> <numericalresponse answer="[6, 7]">
<formulaequationinput /> <formulaequationinput />
...@@ -163,6 +221,12 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -163,6 +221,12 @@ describe 'MarkdownEditingDescriptor', ->
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
</question>
<question>
<p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p> <p>If first and last symbols are not brackets, or they are not closed, stringresponse will appear.</p>
<stringresponse answer="(7), 7" type="ci" > <stringresponse answer="(7), 7" type="ci" >
<textline size="20"/> <textline size="20"/>
...@@ -170,33 +234,21 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -170,33 +234,21 @@ describe 'MarkdownEditingDescriptor', ->
<stringresponse answer="(1+2" type="ci" > <stringresponse answer="(1+2" type="ci" >
<textline size="20"/> <textline size="20"/>
</stringresponse> </stringresponse>
</question>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Pi, or the the ratio between a circle's circumference to its diameter, is an irrational number known to extreme precision. It is value is approximately equal to 3.14.</p>
<p>Although you can get an exact value by typing 502*9 into a calculator, the result will be close to 500*10, or 5,000. The grader accepts any response within 15% of the true value, 4518, so that you can use any estimation technique that you like.</p>
<p>If you look at your hand, you can count that you have five fingers.</p>
</div>
</solution>
</problem>""") </problem>""")
it 'will convert 0 as a numerical response (instead of string response)', -> it 'will convert 0 as a numerical response (instead of string response)', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
Enter 0 with a tolerance: Enter 0 with a tolerance:
= 0 +- .02 = 0 +- .02
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Enter 0 with a tolerance:</p> <p>Enter 0 with a tolerance:</p>
<numericalresponse answer="0"> <numericalresponse answer="0">
<responseparam type="tolerance" default=".02" /> <responseparam type="tolerance" default=".02" />
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
</question>
</problem>""") </problem>""")
it 'markup with multiple answers doesn\'t break numerical response', -> it 'markup with multiple answers doesn\'t break numerical response', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
...@@ -204,14 +256,14 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -204,14 +256,14 @@ describe 'MarkdownEditingDescriptor', ->
= 1 +- .02 = 1 +- .02
or= 2 +- 5% or= 2 +- 5%
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Enter 1 with a tolerance:</p> <p>Enter 1 with a tolerance:</p>
<numericalresponse answer="1"> <numericalresponse answer="1">
<responseparam type="tolerance" default=".02" /> <responseparam type="tolerance" default=".02" />
<formulaequationinput /> <formulaequationinput />
</numericalresponse> </numericalresponse>
</question>
</problem>""") </problem>""")
it 'converts multiple choice to xml', -> it 'converts multiple choice to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets. data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
...@@ -230,7 +282,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -230,7 +282,8 @@ describe 'MarkdownEditingDescriptor', ->
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks. The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p> <p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p> <p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
...@@ -255,6 +308,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -255,6 +308,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts multiple choice shuffle to xml', -> it 'converts multiple choice shuffle to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets. data = MarkdownEditingDescriptor.markdownToXml("""A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.
...@@ -273,7 +327,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -273,7 +327,8 @@ describe 'MarkdownEditingDescriptor', ->
The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks. The release of the iPod allowed consumers to carry their entire music library with them in a format that did not rely on fragile and energy-intensive spinning disks.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p> <p>A multiple choice problem presents radio buttons for student input. Students can only select a single option presented. Multiple Choice questions have been the subject of many areas of research due to the early invention and adoption of bubble sheets.</p>
<p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p> <p>One of the main elements that goes into a good multiple choice question is the existence of good distractors. That is, each of the alternate responses presented to the student should be the result of a plausible mistake that a student might make.</p>
...@@ -298,6 +353,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -298,6 +353,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts a series of multiplechoice to xml', -> it 'converts a series of multiplechoice to xml', ->
...@@ -305,10 +361,12 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -305,10 +361,12 @@ describe 'MarkdownEditingDescriptor', ->
(!x) a (!x) a
() b () b
() c () c
---
yatta yatta
( ) x ( ) x
( ) y ( ) y
(x) z (x) z
---
testa testa
(!) i (!) i
( ) ii ( ) ii
...@@ -317,7 +375,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -317,7 +375,8 @@ describe 'MarkdownEditingDescriptor', ->
When the student is ready, the explanation appears. When the student is ready, the explanation appears.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>bleh</p> <p>bleh</p>
<multiplechoiceresponse> <multiplechoiceresponse>
<choicegroup type="MultipleChoice" shuffle="true"> <choicegroup type="MultipleChoice" shuffle="true">
...@@ -326,7 +385,9 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -326,7 +385,9 @@ describe 'MarkdownEditingDescriptor', ->
<choice correct="false">c</choice> <choice correct="false">c</choice>
</choicegroup> </choicegroup>
</multiplechoiceresponse> </multiplechoiceresponse>
</question>
<question>
<p>yatta</p> <p>yatta</p>
<multiplechoiceresponse> <multiplechoiceresponse>
<choicegroup type="MultipleChoice"> <choicegroup type="MultipleChoice">
...@@ -335,7 +396,9 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -335,7 +396,9 @@ describe 'MarkdownEditingDescriptor', ->
<choice correct="true">z</choice> <choice correct="true">z</choice>
</choicegroup> </choicegroup>
</multiplechoiceresponse> </multiplechoiceresponse>
</question>
<question>
<p>testa</p> <p>testa</p>
<multiplechoiceresponse> <multiplechoiceresponse>
<choicegroup type="MultipleChoice" shuffle="true"> <choicegroup type="MultipleChoice" shuffle="true">
...@@ -353,6 +416,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -353,6 +416,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts OptionResponse to xml', -> it 'converts OptionResponse to xml', ->
...@@ -367,7 +431,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -367,7 +431,8 @@ describe 'MarkdownEditingDescriptor', ->
Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question. Multiple Choice also allows students to select from a variety of pre-written responses, although the format makes it easier for students to read very long response options. Optionresponse also differs slightly because students are more likely to think of an answer and then search for it rather than relying purely on recognition to answer the question.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p> <p>OptionResponse gives a limited set of options for students to respond with, and presents those options in a format that encourages them to search for a specific answer rather than being immediately presented with options from which to recognize the correct answer.</p>
<p>The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.</p> <p>The answer options and the identification of the correct answer is defined in the <b>optioninput</b> tag.</p>
...@@ -386,6 +451,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -386,6 +451,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts StringResponse to xml', -> it 'converts StringResponse to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box. data = MarkdownEditingDescriptor.markdownToXml("""A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.
...@@ -399,7 +465,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -399,7 +465,8 @@ describe 'MarkdownEditingDescriptor', ->
Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides. Lansing is the capital of Michigan, although it is not Michgan's largest city, or even the seat of the county in which it resides.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p> <p>A string response problem accepts a line of text input from the student, and evaluates the input for correctness based on an expected answer within each input box.</p>
<p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p> <p>The answer is correct if it matches every character of the expected answer. This can be a problem with international spelling, dates, or anything where the format of the answer is not clear.</p>
...@@ -417,6 +484,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -417,6 +484,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts StringResponse with regular expression to xml', -> it 'converts StringResponse with regular expression to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America? data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America?
...@@ -426,7 +494,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -426,7 +494,8 @@ describe 'MarkdownEditingDescriptor', ->
Test Explanation. Test Explanation.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Who lead the civil right movement in the United States of America?</p> <p>Who lead the civil right movement in the United States of America?</p>
<stringresponse answer="\w*\.?\s*Luther King\s*.*" type="ci regexp" > <stringresponse answer="\w*\.?\s*Luther King\s*.*" type="ci regexp" >
<textline size="20"/> <textline size="20"/>
...@@ -440,6 +509,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -440,6 +509,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts StringResponse with multiple answers to xml', -> it 'converts StringResponse with multiple answers to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America? data = MarkdownEditingDescriptor.markdownToXml("""Who lead the civil right movement in the United States of America?
...@@ -452,7 +522,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -452,7 +522,8 @@ describe 'MarkdownEditingDescriptor', ->
Test Explanation. Test Explanation.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Who lead the civil right movement in the United States of America?</p> <p>Who lead the civil right movement in the United States of America?</p>
<stringresponse answer="Dr. Martin Luther King Jr." type="ci" > <stringresponse answer="Dr. Martin Luther King Jr." type="ci" >
<additional_answer answer="Doctor Martin Luther King Junior"></additional_answer> <additional_answer answer="Doctor Martin Luther King Junior"></additional_answer>
...@@ -469,6 +540,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -469,6 +540,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'converts StringResponse with multiple answers and regular expressions to xml', -> it 'converts StringResponse with multiple answers and regular expressions to xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""Write a number from 1 to 4. data = MarkdownEditingDescriptor.markdownToXml("""Write a number from 1 to 4.
...@@ -481,7 +553,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -481,7 +553,8 @@ describe 'MarkdownEditingDescriptor', ->
Test Explanation. Test Explanation.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Write a number from 1 to 4.</p> <p>Write a number from 1 to 4.</p>
<stringresponse answer="^One$" type="ci regexp" > <stringresponse answer="^One$" type="ci regexp" >
<additional_answer answer="two"></additional_answer> <additional_answer answer="two"></additional_answer>
...@@ -498,6 +571,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -498,6 +571,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
# test labels # test labels
it 'converts markdown labels to label attributes', -> it 'converts markdown labels to label attributes', ->
...@@ -508,7 +582,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -508,7 +582,8 @@ describe 'MarkdownEditingDescriptor', ->
Test Explanation. Test Explanation.
[Explanation] [Explanation]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Who lead the civil right movement in the United States of America?</p> <p>Who lead the civil right movement in the United States of America?</p>
<stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp" > <stringresponse answer="w*.?s*Luther Kings*.*" type="ci regexp" >
<textline label="Who lead the civil right movement in the United States of America?" size="20"/> <textline label="Who lead the civil right movement in the United States of America?" size="20"/>
...@@ -522,6 +597,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -522,6 +597,7 @@ describe 'MarkdownEditingDescriptor', ->
</div> </div>
</solution> </solution>
</question>
</problem>""") </problem>""")
it 'handles multiple questions with labels', -> it 'handles multiple questions with labels', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
...@@ -529,7 +605,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -529,7 +605,7 @@ describe 'MarkdownEditingDescriptor', ->
>>What is the capital of France?<< >>What is the capital of France?<<
= Paris = Paris
---
Germany is a country in Europe, too. Germany is a country in Europe, too.
>>What is the capital of Germany?<< >>What is the capital of Germany?<<
...@@ -538,14 +614,17 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -538,14 +614,17 @@ describe 'MarkdownEditingDescriptor', ->
(x) Berlin (x) Berlin
( ) Donut ( ) Donut
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>France is a country in Europe.</p> <p>France is a country in Europe.</p>
<p>What is the capital of France?</p> <p>What is the capital of France?</p>
<stringresponse answer="Paris" type="ci" > <stringresponse answer="Paris" type="ci" >
<textline label="What is the capital of France?" size="20"/> <textline label="What is the capital of France?" size="20"/>
</stringresponse> </stringresponse>
</question>
<question>
<p>Germany is a country in Europe, too.</p> <p>Germany is a country in Europe, too.</p>
<p>What is the capital of Germany?</p> <p>What is the capital of Germany?</p>
...@@ -558,7 +637,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -558,7 +637,7 @@ describe 'MarkdownEditingDescriptor', ->
</choicegroup> </choicegroup>
</multiplechoiceresponse> </multiplechoiceresponse>
</question>
</problem>""") </problem>""")
it 'tests multiple questions with only one label', -> it 'tests multiple questions with only one label', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
...@@ -566,7 +645,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -566,7 +645,7 @@ describe 'MarkdownEditingDescriptor', ->
>>What is the capital of France?<< >>What is the capital of France?<<
= Paris = Paris
---
Germany is a country in Europe, too. Germany is a country in Europe, too.
What is the capital of Germany? What is the capital of Germany?
...@@ -575,14 +654,17 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -575,14 +654,17 @@ describe 'MarkdownEditingDescriptor', ->
(x) Berlin (x) Berlin
( ) Donut ( ) Donut
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>France is a country in Europe.</p> <p>France is a country in Europe.</p>
<p>What is the capital of France?</p> <p>What is the capital of France?</p>
<stringresponse answer="Paris" type="ci" > <stringresponse answer="Paris" type="ci" >
<textline label="What is the capital of France?" size="20"/> <textline label="What is the capital of France?" size="20"/>
</stringresponse> </stringresponse>
</question>
<question>
<p>Germany is a country in Europe, too.</p> <p>Germany is a country in Europe, too.</p>
<p>What is the capital of Germany?</p> <p>What is the capital of Germany?</p>
...@@ -595,7 +677,7 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -595,7 +677,7 @@ describe 'MarkdownEditingDescriptor', ->
</choicegroup> </choicegroup>
</multiplechoiceresponse> </multiplechoiceresponse>
</question>
</problem>""") </problem>""")
it 'tests malformed labels', -> it 'tests malformed labels', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
...@@ -603,21 +685,24 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -603,21 +685,24 @@ describe 'MarkdownEditingDescriptor', ->
>>What is the capital of France?< >>What is the capital of France?<
= Paris = Paris
---
blah>>What is the capital of <<Germany?<< blah>>What is the capital of <<Germany?<<
( ) Bonn ( ) Bonn
( ) Hamburg ( ) Hamburg
(x) Berlin (x) Berlin
( ) Donut ( ) Donut
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>France is a country in Europe.</p> <p>France is a country in Europe.</p>
<p>>>What is the capital of France?<</p> <p>>>What is the capital of France?<</p>
<stringresponse answer="Paris" type="ci" > <stringresponse answer="Paris" type="ci" >
<textline size="20"/> <textline size="20"/>
</stringresponse> </stringresponse>
</question>
<question>
<p>blahWhat is the capital of Germany?</p> <p>blahWhat is the capital of Germany?</p>
<multiplechoiceresponse> <multiplechoiceresponse>
<choicegroup label="What is the capital of &lt;&lt;Germany?" type="MultipleChoice"> <choicegroup label="What is the capital of &lt;&lt;Germany?" type="MultipleChoice">
...@@ -628,34 +713,36 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -628,34 +713,36 @@ describe 'MarkdownEditingDescriptor', ->
</choicegroup> </choicegroup>
</multiplechoiceresponse> </multiplechoiceresponse>
</question>
</problem>""") </problem>""")
it 'adds labels to formulae', -> it 'adds labels to formulae', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
>>Enter the numerical value of Pi:<< >>Enter the numerical value of Pi:<<
= 3.14159 +- .02 = 3.14159 +- .02
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Enter the numerical value of Pi:</p> <p>Enter the numerical value of Pi:</p>
<numericalresponse answer="3.14159"> <numericalresponse answer="3.14159">
<responseparam type="tolerance" default=".02" /> <responseparam type="tolerance" default=".02" />
<formulaequationinput label="Enter the numerical value of Pi:" /> <formulaequationinput label="Enter the numerical value of Pi:" />
</numericalresponse> </numericalresponse>
</question>
</problem>""") </problem>""")
it 'escapes entities in labels', -> it 'escapes entities in labels', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
>>What is the "capital" of France & the 'best' > place < to live"?<< >>What is the "capital" of France & the 'best' > place < to live"?<<
= Paris = Paris
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>What is the "capital" of France & the 'best' > place < to live"?</p> <p>What is the "capital" of France & the 'best' > place < to live"?</p>
<stringresponse answer="Paris" type="ci" > <stringresponse answer="Paris" type="ci" >
<textline label="What is the &quot;capital&quot; of France &amp; the &apos;best&apos; &gt; place &lt; to live&quot;?" size="20"/> <textline label="What is the &quot;capital&quot; of France &amp; the &apos;best&apos; &gt; place &lt; to live&quot;?" size="20"/>
</stringresponse> </stringresponse>
</question>
</problem>""") </problem>""")
# test oddities # test oddities
it 'converts headers and oddities to xml', -> it 'converts headers and oddities to xml', ->
...@@ -708,7 +795,8 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -708,7 +795,8 @@ describe 'MarkdownEditingDescriptor', ->
Code should be nicely monospaced. Code should be nicely monospaced.
[/code] [/code]
""") """)
expect(data).toEqual("""<problem> expect(data).toXMLEqual("""<problem>
<question>
<p>Not a header</p> <p>Not a header</p>
<h3 class="hd hd-2 problem-header">A header</h3> <h3 class="hd hd-2 problem-header">A header</h3>
...@@ -779,5 +867,6 @@ describe 'MarkdownEditingDescriptor', -> ...@@ -779,5 +867,6 @@ describe 'MarkdownEditingDescriptor', ->
<pre><code> <pre><code>
Code should be nicely monospaced. Code should be nicely monospaced.
</code></pre> </code></pre>
</question>
</problem>""") </problem>""")
# failure tests # failure tests
# This file tests the parsing of extended-hints, double bracket sections {{ .. }} # This file tests the parsing of extended-hints, double bracket sections {{ .. }}
# for all sorts of markdown. # for all sorts of markdown.
describe 'Markdown to xml extended hint dropdown', -> describe 'Hint Problems', ->
it 'produces xml', -> beforeEach ->
data = MarkdownEditingDescriptor.markdownToXml(""" jasmine.addMatchers
Translation between Dropdown and ________ is straightforward. toXMLEqual: ->
return {
[[ compare: (actual, expected) ->
(Multiple Choice) {{ Good Job::Yes, multiple choice is the right answer. }} {
Text Input {{ No, text input problems don't present options. }} pass: actual.replace(/\s+/g, '') == expected.replace(/\s+/g, '')
Numerical Input {{ No, numerical input problems don't present options. }} }
]] }
describe 'Markdown to xml extended hint dropdown', ->
it 'produces xml', ->
Clowns have funny _________ to make people laugh.
[[
dogs {{ NOPE::Not dogs, not cats, not toads }}
(FACES) {{ With lots of makeup, doncha know?}}
money {{ Clowns don't have any money, of course }}
donkeys {{don't be an ass.}}
-no hint-
]]
""")
expect(data).toEqual("""
<problem>
<p>Translation between Dropdown and ________ is straightforward.</p>
<optionresponse>
<optioninput>
<option correct="True">Multiple Choice <optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint></option>
<option correct="False">Text Input <optionhint>No, text input problems don't present options.</optionhint></option>
<option correct="False">Numerical Input <optionhint>No, numerical input problems don't present options.</optionhint></option>
</optioninput>
</optionresponse>
<p>Clowns have funny _________ to make people laugh.</p>
<optionresponse>
<optioninput>
<option correct="False">dogs <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>
""")
it 'produces xml with demand hint', ->
data = MarkdownEditingDescriptor.markdownToXml("""
Translation between Dropdown and ________ is straightforward.
[[
(Right) {{ Good Job::yes }}
Wrong 1 {{no}}
Wrong 2 {{ Label::no }}
]]
|| 0) zero ||
|| 1) one ||
|| 2) two ||
""")
expect(data).toEqual("""
<problem>
<p>Translation between Dropdown and ________ is straightforward.</p>
<optionresponse>
<optioninput>
<option correct="True">Right <optionhint label="Good Job">yes</optionhint></option>
<option correct="False">Wrong 1 <optionhint>no</optionhint></option>
<option correct="False">Wrong 2 <optionhint label="Label">no</optionhint></option>
</optioninput>
</optionresponse>
<demandhint>
<hint>0) zero</hint>
<hint>1) one</hint>
<hint>2) two</hint>
</demandhint>
</problem>
""")
it 'produces xml with single-line markdown syntax', ->
data = MarkdownEditingDescriptor.markdownToXml("""
A Question ________ is answered.
[[(Right), Wrong 1, Wrong 2]]
|| 0) zero ||
|| 1) one ||
""")
expect(data).toEqual("""
<problem>
<p>A Question ________ is answered.</p>
<optionresponse>
<optioninput options="('Right','Wrong 1','Wrong 2')" correct="Right"></optioninput>
</optionresponse>
<demandhint>
<hint>0) zero</hint>
<hint>1) one</hint>
</demandhint>
</problem>
""")
it 'produces xml with fewer newlines', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<<
[[ (aa) {{ hint1 }}
bb
cc {{ hint2 }} ]]
""")
expect(data).toEqual("""
<problem>
<p>q1</p>
<optionresponse>
<optioninput label="q1">
<option correct="True">aa <optionhint>hint1</optionhint></option>
<option correct="False">bb</option>
<option correct="False">cc <optionhint>hint2</optionhint></option>
</optioninput>
</optionresponse>
</problem>
""")
it 'produces xml even with lots of whitespace', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<<
[[
aa {{ hint1 }}
bb {{ hint2 }}
(cc)
]]
""")
expect(data).toEqual("""
<problem>
<p>q1</p>
<optionresponse>
<optioninput label="q1">
<option correct="False">aa <optionhint>hint1</optionhint></option>
<option correct="False">bb <optionhint>hint2</optionhint></option>
<option correct="True">cc</option>
</optioninput>
</optionresponse>
</problem>
""")
describe 'Markdown to xml extended hint checkbox', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select all the fruits from the list<<
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
[ ] Mustang
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit. }}
>>Select all the vegetables from the list<<
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegetables.}, { selected: Mushroom is a fungus, not a vegetable.}}
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
""")
expect(data).toEqual("""
<problem>
<p>Select all the fruits from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the fruits from the list">
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint></choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint></choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint></choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint></choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
<p>Select all the vegetables from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the vegetables from the list">
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint></choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegetables.</choicehint></choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint></choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
</problem>
""")
it 'produces xml also with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select all the fruits from the list<<
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
[ ] Mustang
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
>>Select all the vegetables from the list<<
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegatbles}, { selected: Mushroom is a fungus, not a vegetable.}}
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|| Hint one.||
|| Hint two. ||
|| Hint three. ||
""")
expect(data).toEqual("""
<problem>
<p>Select all the fruits from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the fruits from the list">
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint></choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint></choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint></choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint></choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
<p>Select all the vegetables from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the vegetables from the list">
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint></choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegatbles</choicehint></choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint></choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
<demandhint>
<hint>Hint one.</hint>
<hint>Hint two.</hint>
<hint>Hint three.</hint>
</demandhint>
</problem>
""")
describe 'Markdown to xml extended hint multiple choice', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select the fruit from the list<<
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
() Potato
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}
""")
expect(data).toEqual("""
<problem>
<p>Select the fruit from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the fruit from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a fruit.</choicehint></choice>
<choice correct="false">Potato</choice>
<choice correct="true">Apple <choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
<p>Select the vegetables from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the vegetables from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a vegetable.</choicehint></choice>
<choice correct="true">Potato <choicehint>Potato is a root vegetable.</choicehint></choice>
<choice correct="false">Apple <choicehint label="OOPS">Apple is a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</problem>
""")
it 'produces xml with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml(""" data = MarkdownEditingDescriptor.markdownToXml("""
>>Select the fruit from the list<< Translation between Dropdown and ________ is straightforward.
() Mushroom {{ Mushroom is a fungus, not a fruit.}} [[
() Potato (Multiple Choice) {{ Good Job::Yes, multiple choice is the right answer. }}
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}} Text Input {{ No, text input problems don't present options. }}
Numerical Input {{ No, numerical input problems don't present options. }}
]]
|| 0) spaces on previous line. ||
|| 1) roses are red. || ---
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}} Clowns have funny _________ to make people laugh.
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}
|| 2) where are the lions? ||
[[
dogs {{ NOPE::Not dogs, not cats, not toads }}
(FACES) {{ With lots of makeup, doncha know?}}
money {{ Clowns don't have any money, of course }}
donkeys {{don't be an ass.}}
-no hint-
]]
""") """)
expect(data).toEqual(""" expect(data).toXMLEqual("""
<problem> <problem>
<p>Select the fruit from the list</p> <question>
<multiplechoiceresponse> <p>Translation between Dropdown and ________ is straightforward.</p>
<choicegroup label="Select the fruit from the list" type="MultipleChoice"> <optionresponse>
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a fruit.</choicehint></choice> <optioninput>
<choice correct="false">Potato</choice> <option correct="True">Multiple Choice <optionhint label="Good Job">Yes, multiple choice is the right answer.</optionhint></option>
<choice correct="true">Apple <choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint></choice> <option correct="False">Text Input <optionhint>No, text input problems don't present options.</optionhint></option>
</choicegroup> <option correct="False">Numerical Input <optionhint>No, numerical input problems don't present options.</optionhint></option>
</multiplechoiceresponse> </optioninput>
</optionresponse>
<p>Select the vegetables from the list</p> </question>
<multiplechoiceresponse> <question>
<choicegroup label="Select the vegetables from the list" type="MultipleChoice"> <p>Clowns have funny _________ to make people laugh.</p>
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a vegetable.</choicehint></choice> <optionresponse>
<choice correct="true">Potato <choicehint>Potato is a root vegetable.</choicehint></choice> <optioninput>
<choice correct="false">Apple <choicehint label="OOPS">Apple is a fruit.</choicehint></choice> <option correct="False">dogs <optionhint label="NOPE">Not dogs, not cats, not toads</optionhint></option>
</choicegroup> <option correct="True">FACES <optionhint>With lots of makeup, doncha know?</optionhint></option>
</multiplechoiceresponse> <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>
<demandhint> </optioninput>
<hint>0) spaces on previous line.</hint> </optionresponse>
<hint>1) roses are red.</hint> </question>
<hint>2) where are the lions?</hint> </problem>
</demandhint> """)
</problem>
""") it 'produces xml with demand hint', ->
data = MarkdownEditingDescriptor.markdownToXml("""
Translation between Dropdown and ________ is straightforward.
describe 'Markdown to xml extended hint text input', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>In which country would you find the city of Paris?<<
= France {{ BRAVO::Viva la France! }}
""")
expect(data).toEqual("""
<problem>
<p>In which country would you find the city of Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">Viva la France!</correcthint>
<textline label="In which country would you find the city of Paris?" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with or=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
= France {{ BRAVO::hint1}}
or= USA {{ meh::hint2 }}
""")
expect(data).toEqual("""
<problem>
<p>Where Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">hint1</correcthint>
<additional_answer answer="USA"><correcthint label="meh">hint2</correcthint></additional_answer>
<textline label="Where Paris?" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with not=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Revenge is a dish best served<<
= cold {{khaaaaaan!}}
not= warm {{feedback2}}
""")
expect(data).toEqual("""
<problem>
<p>Revenge is a dish best served</p>
<stringresponse answer="cold" type="ci" >
<correcthint>khaaaaaan!</correcthint>
<stringequalhint answer="warm">feedback2</stringequalhint>
<textline label="Revenge is a dish best served" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with s=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= 2 {{feedback1}}
""")
expect(data).toEqual("""
<problem>
<p>q</p>
<stringresponse answer="2" type="ci" >
<correcthint>feedback1</correcthint>
<textline label="q" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with = and or= and not=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
= aaa
or= bbb {{feedback1}}
not= no {{feedback2}}
or= ccc
""")
expect(data).toEqual("""
<problem>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"><correcthint>feedback1</correcthint></additional_answer>
<stringequalhint answer="no">feedback2</stringequalhint>
<additional_answer answer="ccc"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with s= and or=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= 2 {{feedback1}}
or= bbb {{feedback2}}
or= ccc
""")
expect(data).toEqual("""
<problem>
<p>q</p>
<stringresponse answer="2" type="ci" >
<correcthint>feedback1</correcthint>
<additional_answer answer="bbb"><correcthint>feedback2</correcthint></additional_answer>
<additional_answer answer="ccc"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with each = making a new question', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
= aaa
or= bbb
s= ccc
""")
expect(data).toEqual("""
<problem>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<textline size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with each = making a new question amid blank lines and paragraphs', ->
data = MarkdownEditingDescriptor.markdownToXml("""
paragraph
>>q<<
= aaa
or= bbb
s= ccc
paragraph 2
""")
expect(data).toEqual("""
<problem>
<p>paragraph</p>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<textline size="20"/>
</stringresponse>
<p>paragraph 2</p>
</problem>
""")
it 'produces xml without a question when or= is just hung out there by itself', ->
data = MarkdownEditingDescriptor.markdownToXml("""
paragraph
>>q<<
or= aaa
paragraph 2
""")
expect(data).toEqual("""
<problem>
<p>paragraph</p>
<p>q</p>
<p>or= aaa</p>
<p>paragraph 2</p>
</problem>
""")
it 'produces xml with each = with feedback making a new question', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= aaa
or= bbb {{feedback1}}
= ccc {{feedback2}}
""")
expect(data).toEqual("""
<problem>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"><correcthint>feedback1</correcthint></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<correcthint>feedback2</correcthint>
<textline size="20"/>
</stringresponse>
</problem>
""")
it 'produces xml with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
= France {{ BRAVO::hint1 }}
|| There are actually two countries with cities named Paris. ||
|| Paris is the capital of one of those countries. ||
""")
expect(data).toEqual("""
<problem>
<p>Where Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">hint1</correcthint>
<textline label="Where Paris?" size="20"/>
</stringresponse>
<demandhint>
<hint>There are actually two countries with cities named Paris.</hint>
<hint>Paris is the capital of one of those countries.</hint>
</demandhint>
</problem>""")
describe 'Markdown to xml extended hint numeric input', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Enter the numerical value of Pi:<<
= 3.14159 +- .02 {{ Pie for everyone! }}
>>Enter the approximate value of 502*9:<<
= 4518 +- 15% {{PIE:: No pie for you!}}
>>Enter the number of fingers on a human hand<<
= 5
""")
expect(data).toEqual("""
<problem>
<p>Enter the numerical value of Pi:</p>
<numericalresponse answer="3.14159">
<responseparam type="tolerance" default=".02" />
<formulaequationinput label="Enter the numerical value of Pi:" />
<correcthint>Pie for everyone!</correcthint>
</numericalresponse>
<p>Enter the approximate value of 502*9:</p>
<numericalresponse answer="4518">
<responseparam type="tolerance" default="15%" />
<formulaequationinput label="Enter the approximate value of 502*9:" />
<correcthint label="PIE">No pie for you!</correcthint>
</numericalresponse>
<p>Enter the number of fingers on a human hand</p>
<numericalresponse answer="5">
<formulaequationinput label="Enter the number of fingers on a human hand" />
</numericalresponse>
</problem>
""")
# The output xml here shows some of the quirks of how historical markdown parsing does or does not put
# in blank lines.
it 'numeric input with hints and demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>text1<<
= 1 {{ hint1 }}
|| hintA ||
>>text2<<
= 2 {{ hint2 }}
|| hintB ||
""")
expect(data).toEqual("""
<problem>
<p>text1</p>
<numericalresponse answer="1">
<formulaequationinput label="text1" />
<correcthint>hint1</correcthint>
</numericalresponse>
<p>text2</p>
<numericalresponse answer="2">
<formulaequationinput label="text2" />
<correcthint>hint2</correcthint>
</numericalresponse>
<demandhint>
<hint>hintA</hint>
<hint>hintB</hint>
</demandhint>
</problem>
""")
describe 'Markdown to xml extended hint with multiline hints', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Checkboxes<<
[x] A {{
selected: aaa },
{unselected:bbb}}
[ ] B {{U: c}, {
selected: d.}}
{{ ((A*B)) A*B hint}}
>>What is 1 + 1?<<
= 2 {{ part one, and
part two
}}
>>hello?<<
= hello {{
hello
hint
}}
>>multiple choice<<
(x) AA{{hint1}}
() BB {{
hint2
}}
( ) CC {{ hint3
}}
>>dropdown<<
[[ [[
W1 {{ (Right) {{ Good Job::yes }}
no }} Wrong 1 {{no}}
W2 {{ Wrong 2 {{ Label::no }}
nope}}
(C1) {{ yes
}}
]] ]]
|| aaa || || 0) zero ||
||bbb|| || 1) one ||
|| ccc || || 2) two ||
""")
""") expect(data).toXMLEqual("""
expect(data).toEqual(""" <problem>
<problem> <question>
<p>Checkboxes</p> <p>Translation between Dropdown and ________ is straightforward.</p>
<choiceresponse> <optionresponse>
<checkboxgroup label="Checkboxes"> <optioninput>
<choice correct="true">A <option correct="True">Right <optionhint label="Good Job">yes</optionhint></option>
<choicehint selected="true">aaa</choicehint> <option correct="False">Wrong 1 <optionhint>no</optionhint></option>
<choicehint selected="false">bbb</choicehint></choice> <option correct="False">Wrong 2 <optionhint label="Label">no</optionhint></option>
<choice correct="false">B </optioninput>
<choicehint selected="true">d.</choicehint> </optionresponse>
<choicehint selected="false">c</choicehint></choice>
<compoundhint value="A*B">A*B hint</compoundhint> <demandhint>
</checkboxgroup> <hint>0) zero</hint>
</choiceresponse> <hint>1) one</hint>
<hint>2) two</hint>
<p>What is 1 + 1?</p> </demandhint>
<numericalresponse answer="2"> </question>
<formulaequationinput label="What is 1 + 1?" /> </problem>
<correcthint>part one, and part two</correcthint> """)
</numericalresponse>
it 'produces xml with single-line markdown syntax', ->
<p>hello?</p> data = MarkdownEditingDescriptor.markdownToXml("""
<stringresponse answer="hello" type="ci" > A Question ________ is answered.
<correcthint>hello hint</correcthint>
<textline label="hello?" size="20"/> [[(Right), Wrong 1, Wrong 2]]
</stringresponse> || 0) zero ||
|| 1) one ||
<p>multiple choice</p> """)
<multiplechoiceresponse> expect(data).toXMLEqual("""
<choicegroup label="multiple choice" type="MultipleChoice"> <problem>
<choice correct="true">AA <choicehint>hint1</choicehint></choice> <question>
<choice correct="false">BB <choicehint>hint2</choicehint></choice> <p>A Question ________ is answered.</p>
<choice correct="false">CC <choicehint>hint3</choicehint></choice> <optionresponse>
</choicegroup> <optioninput options="('Right','Wrong 1','Wrong 2')" correct="Right"></optioninput>
</multiplechoiceresponse> </optionresponse>
<p>dropdown</p> <demandhint>
<hint>0) zero</hint>
<optionresponse> <hint>1) one</hint>
<optioninput label="dropdown"> </demandhint>
<option correct="False">W1 <optionhint>no</optionhint></option> </question>
<option correct="False">W2 <optionhint>nope</optionhint></option> </problem>
<option correct="True">C1 <optionhint>yes</optionhint></option> """)
</optioninput>
</optionresponse> it 'produces xml with fewer newlines', ->
data = MarkdownEditingDescriptor.markdownToXml("""
<demandhint>
<hint>aaa</hint>
<hint>bbb</hint>
<hint>ccc</hint>
</demandhint>
</problem>
""")
describe 'Markdown to xml extended hint with tricky syntax cases', ->
# I'm entering this as utf-8 in this file.
# I cannot find a way to set the encoding for .coffee files but it seems to work.
it 'produces xml with unicode', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>á and Ø<<
(x) Ø{{Ø}}
() BB
|| Ø ||
""")
expect(data).toEqual("""
<problem>
<p>á and Ø</p>
<multiplechoiceresponse>
<choicegroup label="á and Ø" type="MultipleChoice">
<choice correct="true">Ø <choicehint>Ø</choicehint></choice>
<choice correct="false">BB</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>Ø</hint>
</demandhint>
</problem>
""")
it 'produces xml with quote-type characters', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>"quotes" aren't `fun`<<
() "hello" {{ isn't }}
(x) "isn't" {{ "hello" }}
""")
expect(data).toEqual("""
<problem>
<p>"quotes" aren't `fun`</p>
<multiplechoiceresponse>
<choicegroup label="&quot;quotes&quot; aren&apos;t `fun`" type="MultipleChoice">
<choice correct="false">"hello" <choicehint>isn't</choicehint></choice>
<choice correct="true">"isn't" <choicehint>"hello"</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</problem>
""")
it 'produces xml with almost but not quite multiple choice syntax', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<< >>q1<<
this (x) [[ (aa) {{ hint1 }}
() a {{ (hint) }} bb
(x) b cc {{ hint2 }} ]]
that (y) """)
""") expect(data).toEqual("""
expect(data).toEqual(""" <problem>
<problem> <question>
<p>q1</p> <p>q1</p>
<p>this (x)</p>
<multiplechoiceresponse> <optionresponse>
<choicegroup label="q1" type="MultipleChoice"> <optioninput label="q1">
<choice correct="false">a <choicehint>(hint)</choicehint></choice> <option correct="True">aa <optionhint>hint1</optionhint></option>
<choice correct="true">b</choice> <option correct="False">bb</option>
</choicegroup> <option correct="False">cc <optionhint>hint2</optionhint></option>
</multiplechoiceresponse> </optioninput>
</optionresponse>
<p>that (y)</p>
</problem>
""") </question>
</problem>
# An incomplete checkbox hint passes through to cue the author """)
it 'produce xml with almost but not quite checkboxgroup syntax', ->
data = MarkdownEditingDescriptor.markdownToXml(""" it 'produces xml even with lots of whitespace', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<< >>q1<<
this [x] [[
[ ] a [square]
[x] b {{ this hint passes through }}
that [] aa {{ hint1 }}
""")
expect(data).toEqual(""" bb {{ hint2 }}
<problem> (cc)
<p>q1</p>
<p>this [x]</p> ]]
<choiceresponse> """)
<checkboxgroup label="q1"> expect(data).toEqual("""
<choice correct="false">a [square]</choice> <problem>
<choice correct="true">b {{ this hint passes through }}</choice> <question>
</checkboxgroup> <p>q1</p>
</choiceresponse>
<optionresponse>
<p>that []</p> <optioninput label="q1">
</problem> <option correct="False">aa <optionhint>hint1</optionhint></option>
""") <option correct="False">bb <optionhint>hint2</optionhint></option>
<option correct="True">cc</option>
# It's sort of a pain to edit DOS line endings without some editor or other "fixing" them </optioninput>
# for you. Therefore, we construct DOS line endings on the fly just for the test. </optionresponse>
it 'produces xml with DOS \r\n line endings', ->
markdown = """
>>q22<< </question>
</problem>
[[ """)
(x) {{ hintx
these describe 'Markdown to xml extended hint checkbox', ->
span it 'produces xml', ->
}} data = MarkdownEditingDescriptor.markdownToXml("""
>>Select all the fruits from the list<<
yy {{ meh::hinty }}
zzz {{ hintz }} [x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
]] [ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
""" [x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
markdown = markdown.replace(/\n/g, '\r\n') # make DOS line endings [ ] Mustang
data = MarkdownEditingDescriptor.markdownToXml(markdown) [ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
expect(data).toEqual("""
<problem>
<p>q22</p> {{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
<optionresponse> {{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit. }}
<optioninput label="q22">
<option correct="True">x <optionhint>hintx these span</optionhint></option> ---
<option correct="False">yy <optionhint label="meh">hinty</optionhint></option>
<option correct="False">zzz <optionhint>hintz</optionhint></option> >>Select all the vegetables from the list<<
</optioninput>
</optionresponse> [ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegetables.}, { selected: Mushroom is a fungus, not a vegetable.}}
</problem> [x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
""")
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Select all the fruits from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the fruits from the list">
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint></choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint></choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint></choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint></choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
</question>
<question>
<p>Select all the vegetables from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the vegetables from the list">
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint></choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegetables.</choicehint></choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint></choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
</question>
</problem>
""")
it 'produces xml also with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select all the fruits from the list<<
[x] Apple {{ selected: You're right that apple is a fruit. }, {unselected: Remember that apple is also a fruit.}}
[ ] Mushroom {{U: You're right that mushrooms aren't fruit}, { selected: Mushroom is a fungus, not a fruit.}}
[x] Grape {{ selected: You're right that grape is a fruit }, {unselected: Remember that grape is also a fruit.}}
[ ] Mustang
[ ] Camero {{S:I don't know what a Camero is but it isn't a fruit.},{U:What is a camero anyway?}}
{{ ((A*B)) You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
{{ ((B*C)) You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.}}
---
>>Select all the vegetables from the list<<
[ ] Banana {{ selected: No, sorry, a banana is a fruit. }, {unselected: poor banana.}}
[ ] Ice Cream
[ ] Mushroom {{U: You're right that mushrooms aren't vegatbles}, { selected: Mushroom is a fungus, not a vegetable.}}
[x] Brussel Sprout {{S: Brussel sprouts are vegetables.}, {u: Brussel sprout is the only vegetable in this list.}}
{{ ((A*B)) Making a banana split? }}
{{ ((B*D)) That will make a horrible dessert: a brussel sprout split? }}
|| Hint one.||
|| Hint two. ||
|| Hint three. ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Select all the fruits from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the fruits from the list">
<choice correct="true">Apple
<choicehint selected="true">You're right that apple is a fruit.</choicehint>
<choicehint selected="false">Remember that apple is also a fruit.</choicehint></choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a fruit.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't fruit</choicehint></choice>
<choice correct="true">Grape
<choicehint selected="true">You're right that grape is a fruit</choicehint>
<choicehint selected="false">Remember that grape is also a fruit.</choicehint></choice>
<choice correct="false">Mustang</choice>
<choice correct="false">Camero
<choicehint selected="true">I don't know what a Camero is but it isn't a fruit.</choicehint>
<choicehint selected="false">What is a camero anyway?</choicehint></choice>
<compoundhint value="A*B">You're right that apple is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
<compoundhint value="B*C">You're right that grape is a fruit, but there's one you're missing. Also, mushroom is not a fruit.</compoundhint>
</checkboxgroup>
</choiceresponse>
</question>
<question>
<p>Select all the vegetables from the list</p>
<choiceresponse>
<checkboxgroup label="Select all the vegetables from the list">
<choice correct="false">Banana
<choicehint selected="true">No, sorry, a banana is a fruit.</choicehint>
<choicehint selected="false">poor banana.</choicehint></choice>
<choice correct="false">Ice Cream</choice>
<choice correct="false">Mushroom
<choicehint selected="true">Mushroom is a fungus, not a vegetable.</choicehint>
<choicehint selected="false">You're right that mushrooms aren't vegatbles</choicehint></choice>
<choice correct="true">Brussel Sprout
<choicehint selected="true">Brussel sprouts are vegetables.</choicehint>
<choicehint selected="false">Brussel sprout is the only vegetable in this list.</choicehint></choice>
<compoundhint value="A*B">Making a banana split?</compoundhint>
<compoundhint value="B*D">That will make a horrible dessert: a brussel sprout split?</compoundhint>
</checkboxgroup>
</choiceresponse>
<demandhint>
<hint>Hint one.</hint>
<hint>Hint two.</hint>
<hint>Hint three.</hint>
</demandhint>
</question>
</problem>
""")
describe 'Markdown to xml extended hint multiple choice', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select the fruit from the list<<
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
() Potato
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
---
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Select the fruit from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the fruit from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a fruit.</choicehint></choice>
<choice correct="false">Potato</choice>
<choice correct="true">Apple <choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</question>
<question>
<p>Select the vegetables from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the vegetables from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a vegetable.</choicehint></choice>
<choice correct="true">Potato <choicehint>Potato is a root vegetable.</choicehint></choice>
<choice correct="false">Apple <choicehint label="OOPS">Apple is a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</question>
</problem>
""")
it 'produces xml with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Select the fruit from the list<<
() Mushroom {{ Mushroom is a fungus, not a fruit.}}
() Potato
(x) Apple {{ OUTSTANDING::Apple is indeed a fruit.}}
|| 0) spaces on previous line. ||
|| 1) roses are red. ||
---
>>Select the vegetables from the list<<
() Mushroom {{ Mushroom is a fungus, not a vegetable.}}
(x) Potato {{ Potato is a root vegetable. }}
() Apple {{ OOPS::Apple is a fruit.}}
|| 2) where are the lions? ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Select the fruit from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the fruit from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a fruit.</choicehint></choice>
<choice correct="false">Potato</choice>
<choice correct="true">Apple <choicehint label="OUTSTANDING">Apple is indeed a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>0) spaces on previous line.</hint>
<hint>1) roses are red.</hint>
</demandhint>
</question>
<question>
<p>Select the vegetables from the list</p>
<multiplechoiceresponse>
<choicegroup label="Select the vegetables from the list" type="MultipleChoice">
<choice correct="false">Mushroom <choicehint>Mushroom is a fungus, not a vegetable.</choicehint></choice>
<choice correct="true">Potato <choicehint>Potato is a root vegetable.</choicehint></choice>
<choice correct="false">Apple <choicehint label="OOPS">Apple is a fruit.</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>2) where are the lions?</hint>
</demandhint>
</question>
</problem>
""")
describe 'Markdown to xml extended hint text input', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>In which country would you find the city of Paris?<<
= France {{ BRAVO::Viva la France! }}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>In which country would you find the city of Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">Viva la France!</correcthint>
<textline label="In which country would you find the city of Paris?" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with or=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
= France {{ BRAVO::hint1}}
or= USA {{ meh::hint2 }}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Where Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">hint1</correcthint>
<additional_answer answer="USA"><correcthint label="meh">hint2</correcthint></additional_answer>
<textline label="Where Paris?" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with not=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Revenge is a dish best served<<
= cold {{khaaaaaan!}}
not= warm {{feedback2}}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Revenge is a dish best served</p>
<stringresponse answer="cold" type="ci" >
<correcthint>khaaaaaan!</correcthint>
<stringequalhint answer="warm">feedback2</stringequalhint>
<textline label="Revenge is a dish best served" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with s=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= 2 {{feedback1}}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q</p>
<stringresponse answer="2" type="ci" >
<correcthint>feedback1</correcthint>
<textline label="q" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with = and or= and not=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
= aaa
or= bbb {{feedback1}}
not= no {{feedback2}}
or= ccc
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"><correcthint>feedback1</correcthint></additional_answer>
<stringequalhint answer="no">feedback2</stringequalhint>
<additional_answer answer="ccc"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with s= and or=', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= 2 {{feedback1}}
or= bbb {{feedback2}}
or= ccc
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q</p>
<stringresponse answer="2" type="ci" >
<correcthint>feedback1</correcthint>
<additional_answer answer="bbb"><correcthint>feedback2</correcthint></additional_answer>
<additional_answer answer="ccc"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with each = making a new question', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
= aaa
or= bbb
s= ccc
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<textline size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with each = making a new question amid blank lines and paragraphs', ->
data = MarkdownEditingDescriptor.markdownToXml("""
paragraph
>>q<<
= aaa
or= bbb
s= ccc
paragraph 2
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>paragraph</p>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<textline size="20"/>
</stringresponse>
<p>paragraph 2</p>
</question>
</problem>
""")
it 'produces xml without a question when or= is just hung out there by itself', ->
data = MarkdownEditingDescriptor.markdownToXml("""
paragraph
>>q<<
or= aaa
paragraph 2
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>paragraph</p>
<p>q</p>
<p>or= aaa</p>
<p>paragraph 2</p>
</question>
</problem>
""")
it 'produces xml with each = with feedback making a new question', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>q<<
s= aaa
or= bbb {{feedback1}}
= ccc {{feedback2}}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q</p>
<stringresponse answer="aaa" type="ci" >
<additional_answer answer="bbb"><correcthint>feedback1</correcthint></additional_answer>
<textline label="q" size="20"/>
</stringresponse>
<stringresponse answer="ccc" type="ci" >
<correcthint>feedback2</correcthint>
<textline size="20"/>
</stringresponse>
</question>
</problem>
""")
it 'produces xml with demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml(""">>Where Paris?<<
= France {{ BRAVO::hint1 }}
|| There are actually two countries with cities named Paris. ||
|| Paris is the capital of one of those countries. ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Where Paris?</p>
<stringresponse answer="France" type="ci" >
<correcthint label="BRAVO">hint1</correcthint>
<textline label="Where Paris?" size="20"/>
</stringresponse>
<demandhint>
<hint>There are actually two countries with cities named Paris.</hint>
<hint>Paris is the capital of one of those countries.</hint>
</demandhint>
</question>
</problem>""")
describe 'Markdown to xml extended hint numeric input', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Enter the numerical value of Pi:<<
= 3.14159 +- .02 {{ Pie for everyone! }}
---
>>Enter the approximate value of 502*9:<<
= 4518 +- 15% {{PIE:: No pie for you!}}
---
>>Enter the number of fingers on a human hand<<
= 5
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Enter the numerical value of Pi:</p>
<numericalresponse answer="3.14159">
<responseparam type="tolerance" default=".02" />
<formulaequationinput label="Enter the numerical value of Pi:" />
<correcthint>Pie for everyone!</correcthint>
</numericalresponse>
</question>
<question>
<p>Enter the approximate value of 502*9:</p>
<numericalresponse answer="4518">
<responseparam type="tolerance" default="15%" />
<formulaequationinput label="Enter the approximate value of 502*9:" />
<correcthint label="PIE">No pie for you!</correcthint>
</numericalresponse>
</question>
<question>
<p>Enter the number of fingers on a human hand</p>
<numericalresponse answer="5">
<formulaequationinput label="Enter the number of fingers on a human hand" />
</numericalresponse>
</question>
</problem>
""")
# The output xml here shows some of the quirks of how historical markdown parsing does or does not put
# in blank lines.
it 'numeric input with hints and demand hints', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>text1<<
= 1 {{ hint1 }}
|| hintA ||
---
>>text2<<
= 2 {{ hint2 }}
|| hintB ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>text1</p>
<numericalresponse answer="1">
<formulaequationinput label="text1" />
<correcthint>hint1</correcthint>
</numericalresponse>
<demandhint>
<hint>hintA</hint>
</demandhint>
</question>
<question>
<p>text2</p>
<numericalresponse answer="2">
<formulaequationinput label="text2" />
<correcthint>hint2</correcthint>
</numericalresponse>
<demandhint>
<hint>hintB</hint>
</demandhint>
</question>
</problem>
""")
describe 'Markdown to xml extended hint with multiline hints', ->
it 'produces xml', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>Checkboxes<<
[x] A {{
selected: aaa },
{unselected:bbb}}
[ ] B {{U: c}, {
selected: d.}}
{{ ((A*B)) A*B hint}}
---
>>What is 1 + 1?<<
= 2 {{ part one, and
part two
}}
---
>>hello?<<
= hello {{
hello
hint
}}
---
>>multiple choice<<
(x) AA{{hint1}}
() BB {{
hint2
}}
( ) CC {{ hint3
}}
---
>>dropdown<<
[[
W1 {{
no }}
W2 {{
nope}}
(C1) {{ yes
}}
]]
|| aaa ||
||bbb||
|| ccc ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>Checkboxes</p>
<choiceresponse>
<checkboxgroup label="Checkboxes">
<choice correct="true">A
<choicehint selected="true">aaa</choicehint>
<choicehint selected="false">bbb</choicehint></choice>
<choice correct="false">B
<choicehint selected="true">d.</choicehint>
<choicehint selected="false">c</choicehint></choice>
<compoundhint value="A*B">A*B hint</compoundhint>
</checkboxgroup>
</choiceresponse>
</question>
<question>
<p>What is 1 + 1?</p>
<numericalresponse answer="2">
<formulaequationinput label="What is 1 + 1?" />
<correcthint>part one, and part two</correcthint>
</numericalresponse>
</question>
<question>
<p>hello?</p>
<stringresponse answer="hello" type="ci" >
<correcthint>hello hint</correcthint>
<textline label="hello?" size="20"/>
</stringresponse>
</question>
<question>
<p>multiple choice</p>
<multiplechoiceresponse>
<choicegroup label="multiple choice" type="MultipleChoice">
<choice correct="true">AA <choicehint>hint1</choicehint></choice>
<choice correct="false">BB <choicehint>hint2</choicehint></choice>
<choice correct="false">CC <choicehint>hint3</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</question>
<question>
<p>dropdown</p>
<optionresponse>
<optioninput label="dropdown">
<option correct="False">W1 <optionhint>no</optionhint></option>
<option correct="False">W2 <optionhint>nope</optionhint></option>
<option correct="True">C1 <optionhint>yes</optionhint></option>
</optioninput>
</optionresponse>
<demandhint>
<hint>aaa</hint>
<hint>bbb</hint>
<hint>ccc</hint>
</demandhint>
</question>
</problem>
""")
describe 'Markdown to xml extended hint with tricky syntax cases', ->
# I'm entering this as utf-8 in this file.
# I cannot find a way to set the encoding for .coffee files but it seems to work.
it 'produces xml with unicode', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>á and Ø<<
(x) Ø{{Ø}}
() BB
|| Ø ||
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>á and Ø</p>
<multiplechoiceresponse>
<choicegroup label="á and Ø" type="MultipleChoice">
<choice correct="true">Ø <choicehint>Ø</choicehint></choice>
<choice correct="false">BB</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>Ø</hint>
</demandhint>
</question>
</problem>
""")
it 'produces xml with quote-type characters', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>"quotes" aren't `fun`<<
() "hello" {{ isn't }}
(x) "isn't" {{ "hello" }}
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>"quotes" aren't `fun`</p>
<multiplechoiceresponse>
<choicegroup label="&quot;quotes&quot; aren&apos;t `fun`" type="MultipleChoice">
<choice correct="false">"hello" <choicehint>isn't</choicehint></choice>
<choice correct="true">"isn't" <choicehint>"hello"</choicehint></choice>
</choicegroup>
</multiplechoiceresponse>
</question>
</problem>
""")
it 'produces xml with almost but not quite multiple choice syntax', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<<
this (x)
() a {{ (hint) }}
(x) b
that (y)
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q1</p>
<p>this (x)</p>
<multiplechoiceresponse>
<choicegroup label="q1" type="MultipleChoice">
<choice correct="false">a <choicehint>(hint)</choicehint></choice>
<choice correct="true">b</choice>
</choicegroup>
</multiplechoiceresponse>
<p>that (y)</p>
</question>
</problem>
""")
# An incomplete checkbox hint passes through to cue the author
it 'produce xml with almost but not quite checkboxgroup syntax', ->
data = MarkdownEditingDescriptor.markdownToXml("""
>>q1<<
this [x]
[ ] a [square]
[x] b {{ this hint passes through }}
that []
""")
expect(data).toXMLEqual("""
<problem>
<question>
<p>q1</p>
<p>this [x]</p>
<choiceresponse>
<checkboxgroup label="q1">
<choice correct="false">a [square]</choice>
<choice correct="true">b {{ this hint passes through }}</choice>
</checkboxgroup>
</choiceresponse>
<p>that []</p>
</question>
</problem>
""")
# It's sort of a pain to edit DOS line endings without some editor or other "fixing" them
# for you. Therefore, we construct DOS line endings on the fly just for the test.
it 'produces xml with DOS \r\n line endings', ->
markdown = """
>>q22<<
[[
(x) {{ hintx
these
span
}}
yy {{ meh::hinty }}
zzz {{ hintz }}
]]
"""
markdown = markdown.replace(/\n/g, '\r\n') # make DOS line endings
data = MarkdownEditingDescriptor.markdownToXml(markdown)
expect(data).toEqual("""
<problem>
<question>
<p>q22</p>
<optionresponse>
<optioninput label="q22">
<option correct="True">x <optionhint>hintx these span</optionhint></option>
<option correct="False">yy <optionhint label="meh">hinty</optionhint></option>
<option correct="False">zzz <optionhint>hintz</optionhint></option>
</optioninput>
</optionresponse>
</question>
</problem>
""")
...@@ -25,6 +25,8 @@ class @Problem ...@@ -25,6 +25,8 @@ class @Problem
window.update_schematics() window.update_schematics()
debugger
problem_prefix = @element_id.replace(/problem_/,'') problem_prefix = @element_id.replace(/problem_/,'')
@inputs = @$("[id^='input_#{problem_prefix}_']") @inputs = @$("[id^='input_#{problem_prefix}_']")
@$('div.action button').click @refreshAnswers @$('div.action button').click @refreshAnswers
...@@ -226,6 +228,7 @@ class @Problem ...@@ -226,6 +228,7 @@ class @Problem
### ###
check_fd: => check_fd: =>
# If there are no file inputs in the problem, we can fall back on @check # If there are no file inputs in the problem, we can fall back on @check
debugger
if @el.find('input:file').length == 0 if @el.find('input:file').length == 0
@check() @check()
return return
...@@ -803,16 +806,24 @@ class @Problem ...@@ -803,16 +806,24 @@ class @Problem
@enableCheckButton true @enableCheckButton true
window.setTimeout(enableCheckButton, 750) window.setTimeout(enableCheckButton, 750)
hint_button: => hint_button: (event)=>
# Store the index of the currently shown hint as an attribute. # Store the index of the currently shown hint as an attribute.
# Use that to compute the next hint number when the button is clicked. # Use that to compute the next hint number when the button is clicked.
hint_index = @$('.problem-hint').attr('hint_index') debugger
question = $(event.target).closest('div.question')
hint_container = question.find('.problem-hint')
hint_index = hint_container.attr('hint_index')
if hint_index == undefined if hint_index == undefined
next_index = 0 next_index = 0
else else
next_index = parseInt(hint_index) + 1 next_index = parseInt(hint_index) + 1
$.postWithPrefix "#{@url}/hint_button", hint_index: next_index, input_id: @id, (response) =>
hint_container = @.$('.problem-hint') data =
question_id: question.attr('id').split('-')[1]
hint_index: next_index
input_id: @id
$.postWithPrefix "#{@url}/hint_button", data, (response) =>
hint_container.html(response.contents) hint_container.html(response.contents)
MathJax.Hub.Queue [ MathJax.Hub.Queue [
'Typeset' 'Typeset'
...@@ -820,5 +831,4 @@ class @Problem ...@@ -820,5 +831,4 @@ class @Problem
hint_container[0] hint_container[0]
] ]
hint_container.attr('hint_index', response.hint_index) hint_container.attr('hint_index', response.hint_index)
@$('.hint-button').focus() # a11y focus on click, like the Check button event.target.focus() # a11y focus on click, like the Check button
...@@ -573,9 +573,18 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor ...@@ -573,9 +573,18 @@ class @MarkdownEditingDescriptor extends XModule.Descriptor
demandhints = '\n<demandhint>\n' + demandhints + '</demandhint>'; demandhints = '\n<demandhint>\n' + demandhints + '</demandhint>';
} }
// make all elements descendants of a single problem element // treat this as a single question
xml = '<problem>\n' + xml + demandhints + '\n</problem>'; xml = '<question>\n' + xml + demandhints + '\n</question>';
return xml; return xml;
}` }`
return toXml markdown
questionsXML = []
# TODO! Should we change it to markdown.split('\n---\n'). What is the right choice?
questionsMarkdown = markdown.split('---')
_.each questionsMarkdown, (questionMarkdown, index) ->
if questionMarkdown.trim().length > 0
questionsXML.push toXml(questionMarkdown)
# make all questions descendants of a single problem element
return '<problem>\n' + questionsXML.join('\n\n') + '\n</problem>'
...@@ -2,4 +2,4 @@ ...@@ -2,4 +2,4 @@
metadata: metadata:
display_name: Blank Common Problem display_name: Blank Common Problem
markdown: "" markdown: ""
data: "<problem></problem>" data: "<problem><question></question></problem>"
...@@ -23,6 +23,7 @@ metadata: ...@@ -23,6 +23,7 @@ metadata:
data: | data: |
<problem> <problem>
<question>
<p>Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.</p> <p>Checkbox problems allow learners to select multiple options. Learners can see all the options along with the problem text.</p>
<p>When you add the component, be sure to select <strong>Settings</strong> <p>When you add the component, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.</p> to specify a <strong>Display Name</strong> and other values that apply.</p>
...@@ -44,4 +45,5 @@ data: | ...@@ -44,4 +45,5 @@ data: |
<p>Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.</p> <p>Urdu, Marathi, and French are all Indo-European languages, while Finnish and Hungarian are in the Uralic family.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
\ No newline at end of file
...@@ -30,7 +30,7 @@ metadata: ...@@ -30,7 +30,7 @@ metadata:
hinted: true hinted: true
data: | data: |
<problem> <problem>
<question>
<p>You can provide feedback for each option in a checkbox problem, with distinct feedback depending on whether or not the learner selects that option.</p> <p>You can provide feedback for each option in a checkbox problem, with distinct feedback depending on whether or not the learner selects that option.</p>
<p>You can also provide compound feedback for a specific combination of answers. For example, if you have three possible answers in the problem, you can configure specific feedback for when a learner selects each combination of possible answers.</p> <p>You can also provide compound feedback for a specific combination of answers. For example, if you have three possible answers in the problem, you can configure specific feedback for when a learner selects each combination of possible answers.</p>
...@@ -66,4 +66,5 @@ data: | ...@@ -66,4 +66,5 @@ data: |
<hint>A fruit is the fertilized ovary from a flower.</hint> <hint>A fruit is the fertilized ovary from a flower.</hint>
<hint>A fruit contains seeds of the plant.</hint> <hint>A fruit contains seeds of the plant.</hint>
</demandhint> </demandhint>
</question>
</problem> </problem>
...@@ -4,6 +4,7 @@ metadata: ...@@ -4,6 +4,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<p> <p>
Circuit schematic problems allow students to create virtual circuits by Circuit schematic problems allow students to create virtual circuits by
arranging elements such as voltage sources, capacitors, resistors, and arranging elements such as voltage sources, capacitors, resistors, and
...@@ -39,6 +40,20 @@ data: | ...@@ -39,6 +40,20 @@ data: |
correct = ['incorrect'] correct = ['incorrect']
</answer> </answer>
</schematicresponse> </schematicresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>
You can form a voltage divider that evenly divides the input
voltage with two identically valued resistors, with the sampled
voltage taken in between the two.
</p>
<p><img src="/static/images/voltage_divider.png" alt=""/></p>
</div>
</solution>
</question>
<question>
<p>Make a high-pass filter.</p> <p>Make a high-pass filter.</p>
<schematicresponse> <schematicresponse>
<center> <center>
...@@ -66,12 +81,6 @@ data: | ...@@ -66,12 +81,6 @@ data: |
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
<p> <p>
You can form a voltage divider that evenly divides the input
voltage with two identically valued resistors, with the sampled
voltage taken in between the two.
</p>
<p><img src="/static/images/voltage_divider.png" alt=""/></p>
<p>
You can form a simple high-pass filter without any further You can form a simple high-pass filter without any further
constraints by simply putting a resistor in series with a constraints by simply putting a resistor in series with a
capacitor. The actual values of the components do not really capacitor. The actual values of the components do not really
...@@ -80,4 +89,5 @@ data: | ...@@ -80,4 +89,5 @@ data: |
<p><img src="/static/images/high_pass_filter.png" alt=""/></p> <p><img src="/static/images/high_pass_filter.png" alt=""/></p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
...@@ -4,6 +4,7 @@ metadata: ...@@ -4,6 +4,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<p> <p>
In custom Python-evaluated input (also called "write-your-own-grader" In custom Python-evaluated input (also called "write-your-own-grader"
problems), the grader uses a Python script that you create and embed in problems), the grader uses a Python script that you create and embed in
...@@ -50,6 +51,15 @@ data: | ...@@ -50,6 +51,15 @@ data: |
<textline size="40" correct_answer="3" label="Integer #1"/><br/> <textline size="40" correct_answer="3" label="Integer #1"/><br/>
<textline size="40" correct_answer="7" label="Integer #2"/> <textline size="40" correct_answer="7" label="Integer #2"/>
</customresponse> </customresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>Any set of integers on the line \(y = 10 - x\) satisfy these constraints.</p>
</div>
</solution>
</question>
<question>
<p>Enter two integers that sum to 20.</p> <p>Enter two integers that sum to 20.</p>
<customresponse cfn="test_add" expect="20"> <customresponse cfn="test_add" expect="20">
<textline size="40" correct_answer="11" label="Integer #1"/><br/> <textline size="40" correct_answer="11" label="Integer #1"/><br/>
...@@ -58,7 +68,7 @@ data: | ...@@ -58,7 +68,7 @@ data: |
<solution> <solution>
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
<p>Any set of integers on the line \(y = 10 - x\) and \(y = 20 - x\) satisfy these constraints.</p> <p>Any set of integers on the line \(y = 20 - x\) satisfy these constraints.</p>
<p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p> <p>To add an image to the solution, use an HTML "img" tag. Make sure to include alt text.</p>
<img src="/static/images/placeholder-image.png" width="400" <img src="/static/images/placeholder-image.png" width="400"
alt="Description of image, with a primary goal of explaining its alt="Description of image, with a primary goal of explaining its
...@@ -66,4 +76,6 @@ data: | ...@@ -66,4 +76,6 @@ data: |
who is unable to see the image."/> who is unable to see the image."/>
</div> </div>
</solution> </solution>
<question>
</problem> </problem>
...@@ -5,6 +5,7 @@ metadata: ...@@ -5,6 +5,7 @@ metadata:
showanswer: never showanswer: never
data: | data: |
<problem> <problem>
<question>
<p> <p>
In drag and drop problems, students respond to a question by dragging text or objects to a specific location on an image. In drag and drop problems, students respond to a question by dragging text or objects to a specific location on an image.
</p> </p>
...@@ -51,6 +52,10 @@ data: | ...@@ -51,6 +52,10 @@ data: |
correct = ['incorrect'] correct = ['incorrect']
</answer> </answer>
</customresponse> </customresponse>
</question>
<question>
<customresponse> <customresponse>
<h3>Drag and Drop with Outline</h3> <h3>Drag and Drop with Outline</h3>
<p>Label the hydrogen atoms connected with the left carbon atom.</p> <p>Label the hydrogen atoms connected with the left carbon atom.</p>
...@@ -81,4 +86,5 @@ data: | ...@@ -81,4 +86,5 @@ data: |
correct = ['incorrect'] correct = ['incorrect']
</answer> </answer>
</customresponse> </customresponse>
</question>
</problem> </problem>
...@@ -4,6 +4,7 @@ metadata: ...@@ -4,6 +4,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<p> <p>
In math expression input problems, learners enter text that represents a In math expression input problems, learners enter text that represents a
mathematical expression into a field, and text is converted to a symbolic mathematical expression into a field, and text is converted to a symbolic
...@@ -40,7 +41,9 @@ data: | ...@@ -40,7 +41,9 @@ data: |
<script type="loncapa/python"> <script type="loncapa/python">
VoVi = "(R_1*R_2)/R_3" VoVi = "(R_1*R_2)/R_3"
</script> </script>
</question>
<question>
<p>Let \( x\) be a variable, and let \( n\) be an arbitrary constant. <p>Let \( x\) be a variable, and let \( n\) be an arbitrary constant.
What is the derivative of \( x^n\)?</p> What is the derivative of \( x^n\)?</p>
...@@ -52,5 +55,6 @@ data: | ...@@ -52,5 +55,6 @@ data: |
<responseparam type="tolerance" default="0.00001"/> <responseparam type="tolerance" default="0.00001"/>
<formulaequationinput size="40" label="Enter the equation"/> <formulaequationinput size="40" label="Enter the equation"/>
</formularesponse> </formularesponse>
</question>
</problem> </problem>
...@@ -4,6 +4,7 @@ metadata: ...@@ -4,6 +4,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<p> <p>
In an image mapped input problem, also known as a "pointing on a picture" In an image mapped input problem, also known as a "pointing on a picture"
problem, students click inside a defined region in an image. You define this problem, students click inside a defined region in an image. You define this
...@@ -31,5 +32,6 @@ data: | ...@@ -31,5 +32,6 @@ data: |
the Sphinx and the ancient Royal Library of Alexandria.</p> the Sphinx and the ancient Royal Library of Alexandria.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
...@@ -5,6 +5,7 @@ metadata: ...@@ -5,6 +5,7 @@ metadata:
showanswer: never showanswer: never
data: | data: |
<problem> <problem>
<question>
<p> <p>
In these problems (also called custom JavaScript problems or JS Input In these problems (also called custom JavaScript problems or JS Input
problems), you add a problem or tool that uses JavaScript in Studio. problems), you add a problem or tool that uses JavaScript in Studio.
...@@ -65,4 +66,5 @@ data: | ...@@ -65,4 +66,5 @@ data: |
html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html" html_file="https://studio.edx.org/c4x/edX/DemoX/asset/webGLDemo.html"
sop="false"/> sop="false"/>
</customresponse> </customresponse>
</question>
</problem> </problem>
...@@ -89,8 +89,9 @@ metadata: ...@@ -89,8 +89,9 @@ metadata:
data: | data: |
<?xml version="1.0"?> <?xml version="1.0"?>
<problem showanswer="closed" rerandomize="never" weight="10" display_name="lec1_Q2"> <problem showanswer="closed" rerandomize="never" weight="10" display_name="lec1_Q2">
<question>
<p>If you have a problem that is already written in LaTeX, you can use this problem type to <p>If you have a problem that is already written in LaTeX, you can use this problem type to
easily convert your code into XML. After you paste your code into the LaTeX editor, easily convert your code into XML. After you paste your code into the LaTeX editor,
you only need to make a few minor adjustments.</p> you only need to make a few minor adjustments.</p>
<p>For more information, see <p>For more information, see
<a href="http://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/problem_in_latex.html" target="_blank"> <a href="http://edx.readthedocs.io/projects/edx-partner-course-staff/en/latest/exercises_tools/problem_in_latex.html" target="_blank">
...@@ -108,7 +109,10 @@ data: | ...@@ -108,7 +109,10 @@ data: |
<p>India became an independent nation on August 15, 1947.</p> <p>India became an independent nation on August 15, 1947.</p>
</div> </div>
</solution> </solution>
</question>
<br/> <br/>
<question>
<p><strong>Example Multiple Choice Problem</strong></p> <p><strong>Example Multiple Choice Problem</strong></p>
<p>Which of the following countries has the largest population?</p> <p>Which of the following countries has the largest population?</p>
<multiplechoiceresponse> <multiplechoiceresponse>
...@@ -129,13 +133,18 @@ data: | ...@@ -129,13 +133,18 @@ data: |
<p>The population of Germany is approximately 81 million.</p> <p>The population of Germany is approximately 81 million.</p>
</div> </div>
</solution> </solution>
</question>
<br/> <br/>
<question>
<p><strong>Example Math Expression Problem</strong></p> <p><strong>Example Math Expression Problem</strong></p>
<p>What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]?</p> <p>What is Einstein's equation for the energy equivalent of a mass [mathjaxinline]m[/mathjaxinline]?</p>
<symbolicresponse expect="m*c^2"> <symbolicresponse expect="m*c^2">
<textline size="90" correct_answer="m*c^2" math="1"/> <textline size="90" correct_answer="m*c^2" math="1"/>
</symbolicresponse> </symbolicresponse>
</question>
<br/> <br/>
<question>
<p><strong>Example Numerical Problem</strong></p> <p><strong>Example Numerical Problem</strong></p>
<p>Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U.&#xA0;S. switched from U.&#xA0;S. code to low-flow shower heads.</p> <p>Estimate the energy savings (in J/y) if all the people ([mathjaxinline]3\times 10^8[/mathjaxinline]) in the U.&#xA0;S. switched from U.&#xA0;S. code to low-flow shower heads.</p>
<p style="display:inline">Energy saved = </p> <p style="display:inline">Energy saved = </p>
...@@ -145,7 +154,9 @@ data: | ...@@ -145,7 +154,9 @@ data: |
</textline> </textline>
<p style="display:inline">&#xA0;EJ/year</p> <p style="display:inline">&#xA0;EJ/year</p>
</numericalresponse> </numericalresponse>
</question>
<br/> <br/>
<question>
<p><strong>Example Fill-in-the-Blank Problem</strong></p> <p><strong>Example Fill-in-the-Blank Problem</strong></p>
<p>What was the first post-secondary school in China to allow both male and female students?</p> <p>What was the first post-secondary school in China to allow both male and female students?</p>
<stringresponse answer="Nanjing Higher Normal Institute" type="ci" > <stringresponse answer="Nanjing Higher Normal Institute" type="ci" >
...@@ -159,7 +170,9 @@ data: | ...@@ -159,7 +170,9 @@ data: |
<p>Nanjing Higher Normal Institute first admitted female students in 1920.</p> <p>Nanjing Higher Normal Institute first admitted female students in 1920.</p>
</div> </div>
</solution> </solution>
</question>
<br/> <br/>
<question>
<p><strong>Example Custom Python-Evaluated Input Problem</strong></p> <p><strong>Example Custom Python-Evaluated Input Problem</strong></p>
<script type="loncapa/python"> <script type="loncapa/python">
def test_add(expect, ans): def test_add(expect, ans):
...@@ -191,7 +204,9 @@ data: | ...@@ -191,7 +204,9 @@ data: |
<img src="/static/images/placeholder-image.png" width="400" alt="Description of image"/> <img src="/static/images/placeholder-image.png" width="400" alt="Description of image"/>
</div> </div>
</solution> </solution>
</question>
<br/> <br/>
<question>
<p><strong>Example Image Mapped Input Problem</strong></p> <p><strong>Example Image Mapped Input Problem</strong></p>
<p>What country is home to the Great Pyramid of Giza as well as the cities <p>What country is home to the Great Pyramid of Giza as well as the cities
of Cairo and Memphis? Click the country on the map below.</p> of Cairo and Memphis? Click the country on the map below.</p>
...@@ -207,7 +222,9 @@ data: | ...@@ -207,7 +222,9 @@ data: |
the Sphinx and the ancient Royal Library of Alexandria.</p> the Sphinx and the ancient Royal Library of Alexandria.</p>
</div> </div>
</solution> </solution>
</question>
<br/> <br/>
<question>
<p><strong>Example Hidden Explanation</strong></p> <p><strong>Example Hidden Explanation</strong></p>
<p>You can provide additional information that only appears at certain times by including a "showhide" flag. </p> <p>You can provide additional information that only appears at certain times by including a "showhide" flag. </p>
<p> <p>
...@@ -225,4 +242,5 @@ data: | ...@@ -225,4 +242,5 @@ data: |
</tbody> </tbody>
</table> </table>
</p> </p>
</question>
</problem> </problem>
...@@ -24,6 +24,7 @@ metadata: ...@@ -24,6 +24,7 @@ metadata:
data: | data: |
<problem> <problem>
<question>
<p>Multiple choice problems allow learners to select only one option. <p>Multiple choice problems allow learners to select only one option.
Learners can see all the options along with the problem text.</p> Learners can see all the options along with the problem text.</p>
<p>When you add the problem, be sure to select <strong>Settings</strong> <p>When you add the problem, be sure to select <strong>Settings</strong>
...@@ -50,4 +51,5 @@ data: | ...@@ -50,4 +51,5 @@ data: |
<p>The population of Germany is approximately 81 million.</p> <p>The population of Germany is approximately 81 million.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
\ No newline at end of file
...@@ -23,7 +23,7 @@ metadata: ...@@ -23,7 +23,7 @@ metadata:
hinted: true hinted: true
data: | data: |
<problem> <problem>
<question>
<p>You can provide feedback for each option in a multiple choice problem.</p> <p>You can provide feedback for each option in a multiple choice problem.</p>
<p>You can also add hints for learners.</p> <p>You can also add hints for learners.</p>
...@@ -43,4 +43,5 @@ data: | ...@@ -43,4 +43,5 @@ data: |
<hint>A fruit is the fertilized ovary from a flower.</hint> <hint>A fruit is the fertilized ovary from a flower.</hint>
<hint>A fruit contains seeds of the plant.</hint> <hint>A fruit contains seeds of the plant.</hint>
</demandhint> </demandhint>
</question>
</problem> </problem>
...@@ -25,7 +25,7 @@ metadata: ...@@ -25,7 +25,7 @@ metadata:
[explanation] [explanation]
data: | data: |
<problem> <problem>
<question>
<p>In a numerical input problem, learners enter numbers or a specific and <p>In a numerical input problem, learners enter numbers or a specific and
relatively simple mathematical expression. Learners enter the response in relatively simple mathematical expression. Learners enter the response in
plain text, and the system then converts the text to a symbolic expression plain text, and the system then converts the text to a symbolic expression
...@@ -44,6 +44,15 @@ data: | ...@@ -44,6 +44,15 @@ data: |
<formulaequationinput label="How many million miles are between Earth and the sun? Use scientific notation to answer." /> <formulaequationinput label="How many million miles are between Earth and the sun? Use scientific notation to answer." />
</numericalresponse> </numericalresponse>
<solution>
<div class="detailed-solution">
<p>Explanation</p>
<p>The sun is 93,000,000, or 9.3*10^7, miles away from Earth.</p>
</div>
</solution>
</question>
<question>
<p>The square of what number is -100?</p> <p>The square of what number is -100?</p>
<numericalresponse answer="10*i"> <numericalresponse answer="10*i">
<formulaequationinput label="The square of what number is -100?" /> <formulaequationinput label="The square of what number is -100?" />
...@@ -51,8 +60,8 @@ data: | ...@@ -51,8 +60,8 @@ data: |
<solution> <solution>
<div class="detailed-solution"> <div class="detailed-solution">
<p>Explanation</p> <p>Explanation</p>
<p>The sun is 93,000,000, or 9.3*10^7, miles away from Earth.</p>
<p>-100 is the square of 10 times the imaginary number, i.</p> <p>-100 is the square of 10 times the imaginary number, i.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
...@@ -27,7 +27,7 @@ metadata: ...@@ -27,7 +27,7 @@ metadata:
hinted: true hinted: true
data: | data: |
<problem> <problem>
<question>
<p>You can provide feedback for correct answers in numerical input problems. You cannot provide feedback for incorrect answers.</p> <p>You can provide feedback for correct answers in numerical input problems. You cannot provide feedback for incorrect answers.</p>
<p>Use feedback for the correct answer to reinforce the process for arriving at the numerical value.</p> <p>Use feedback for the correct answer to reinforce the process for arriving at the numerical value.</p>
...@@ -51,4 +51,5 @@ data: | ...@@ -51,4 +51,5 @@ data: |
<hint>The mean is calculated by summing the set of numbers and dividing by n.</hint> <hint>The mean is calculated by summing the set of numbers and dividing by n.</hint>
<hint>n is the count of items in the set.</hint> <hint>n is the count of items in the set.</hint>
</demandhint> </demandhint>
<question>
</problem> </problem>
\ No newline at end of file
...@@ -17,6 +17,7 @@ metadata: ...@@ -17,6 +17,7 @@ metadata:
[explanation] [explanation]
data: | data: |
<problem> <problem>
<question>
<p>Dropdown problems allow learners to select only one option from a list of options.</p> <p>Dropdown problems allow learners to select only one option from a list of options.</p>
<p>When you add the problem, be sure to select <strong>Settings</strong> <p>When you add the problem, be sure to select <strong>Settings</strong>
to specify a <strong>Display Name</strong> and other values that apply.</p> to specify a <strong>Display Name</strong> and other values that apply.</p>
...@@ -32,5 +33,6 @@ data: | ...@@ -32,5 +33,6 @@ data: |
<p>India became an independent nation on August 15, 1947.</p> <p>India became an independent nation on August 15, 1947.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
...@@ -26,7 +26,7 @@ metadata: ...@@ -26,7 +26,7 @@ metadata:
hinted: true hinted: true
data: | data: |
<problem> <problem>
<question>
<p>You can provide feedback for each available option in a dropdown problem.</p> <p>You can provide feedback for each available option in a dropdown problem.</p>
<p>You can also add hints for learners.</p> <p>You can also add hints for learners.</p>
...@@ -48,4 +48,5 @@ data: | ...@@ -48,4 +48,5 @@ data: |
<hint>A fruit is the fertilized ovary from a flower.</hint> <hint>A fruit is the fertilized ovary from a flower.</hint>
<hint>A fruit contains seeds of the plant.</hint> <hint>A fruit contains seeds of the plant.</hint>
</demandhint> </demandhint>
</question>
</problem> </problem>
...@@ -4,6 +4,7 @@ metadata: ...@@ -4,6 +4,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<text> <text>
<p> <p>
<h4>Problem With Adaptive Hint</h4> <h4>Problem With Adaptive Hint</h4>
...@@ -44,4 +45,5 @@ data: | ...@@ -44,4 +45,5 @@ data: |
</customresponse> </customresponse>
</label> </label>
</text> </text>
</question>
</problem> </problem>
...@@ -49,6 +49,7 @@ metadata: ...@@ -49,6 +49,7 @@ metadata:
markdown: !!null markdown: !!null
data: | data: |
<problem> <problem>
<question>
<text> <text>
<p> <p>
<h4>Problem With Adaptive Hint</h4> <h4>Problem With Adaptive Hint</h4>
...@@ -89,4 +90,5 @@ data: | ...@@ -89,4 +90,5 @@ data: |
</customresponse> </customresponse>
</label> </label>
</text> </text>
</question>
</problem> </problem>
...@@ -20,6 +20,7 @@ metadata: ...@@ -20,6 +20,7 @@ metadata:
data: | data: |
<problem> <problem>
<question>
<p>In text input problems, also known as "fill-in-the-blank" problems, <p>In text input problems, also known as "fill-in-the-blank" problems,
learners enter text into a response field. The text that the learner enters learners enter text into a response field. The text that the learner enters
must match your specified answer text exactly. You can specify more than must match your specified answer text exactly. You can specify more than
...@@ -40,4 +41,5 @@ data: | ...@@ -40,4 +41,5 @@ data: |
<p>Nanjing Higher Normal Institute first admitted female students in 1920.</p> <p>Nanjing Higher Normal Institute first admitted female students in 1920.</p>
</div> </div>
</solution> </solution>
</question>
</problem> </problem>
...@@ -26,7 +26,7 @@ metadata: ...@@ -26,7 +26,7 @@ metadata:
hinted: true hinted: true
data: | data: |
<problem> <problem>
<question>
<p>You can provide feedback for the correct answer in text input problems, as well as for specific incorrect answers.</p> <p>You can provide feedback for the correct answer in text input problems, as well as for specific incorrect answers.</p>
<p>Use feedback on expected incorrect answers to address common misconceptions and to provide guidance on how to arrive at the correct answer.</p> <p>Use feedback on expected incorrect answers to address common misconceptions and to provide guidance on how to arrive at the correct answer.</p>
...@@ -50,5 +50,5 @@ data: | ...@@ -50,5 +50,5 @@ data: |
<hint>Consider the square miles, not population.</hint> <hint>Consider the square miles, not population.</hint>
<hint>Consider all 50 states, not just the continental United States.</hint> <hint>Consider all 50 states, not just the continental United States.</hint>
</demandhint> </demandhint>
</question>
</problem> </problem>
...@@ -183,6 +183,58 @@ if submission[0] == '': ...@@ -183,6 +183,58 @@ if submission[0] == '':
""") """)
SINGLE_QUESTION_XML = """
<problem>
<question>
<p>That is the question</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Alpha <choicehint>A hint</choicehint>
</choice>
<choice correct="true">Beta</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>question 1 hint 1</hint>
<hint>question 1 hint 2</hint>
</demandhint>
</question>
</problem>"""
MULTIPLE_QUESTIONS_XML = """
<problem>
<question>
<p>That is first question</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Alpha <choicehint>A hint</choicehint>
</choice>
<choice correct="true">Beta</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>question 1 hint 1</hint>
<hint>question 1 hint 2</hint>
</demandhint>
</question>
<question>
<p>That is second question</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Alpha <choicehint>A hint</choicehint>
</choice>
<choice correct="true">Beta</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>question 2 hint 1</hint>
<hint>question 2 hint 2</hint>
</demandhint>
</question>
</problem>"""
@ddt.ddt @ddt.ddt
class CapaModuleTest(unittest.TestCase): class CapaModuleTest(unittest.TestCase):
...@@ -1278,50 +1330,39 @@ class CapaModuleTest(unittest.TestCase): ...@@ -1278,50 +1330,39 @@ class CapaModuleTest(unittest.TestCase):
# Assert that the encapsulated html contains the original html # Assert that the encapsulated html contains the original html
self.assertIn(html, html_encapsulated) self.assertIn(html, html_encapsulated)
demand_xml = """ @ddt.unpack
<problem> @ddt.data(
<p>That is the question</p> {'xml': SINGLE_QUESTION_XML, 'num_questions': 1},
<multiplechoiceresponse> {'xml': MULTIPLE_QUESTIONS_XML, 'num_questions': 2}
<choicegroup type="MultipleChoice"> )
<choice correct="false">Alpha <choicehint>A hint</choicehint> def test_demand_hint(self, xml, num_questions):
</choice> """
<choice correct="true">Beta</choice> Verifies that demandhint works as expected for problem with single and multiple questions.
</choicegroup> """
</multiplechoiceresponse> module = CapaFactory.create(xml=xml)
<demandhint>
<hint>Demand 1</hint>
<hint>Demand 2</hint>
</demandhint>
</problem>"""
def test_demand_hint(self):
# HTML generation is mocked out to be meaningless here, so instead we check
# the context dict passed into HTML generation.
module = CapaFactory.create(xml=self.demand_xml)
module.get_problem_html() # ignoring html result module.get_problem_html() # ignoring html result
context = module.system.render_template.call_args[0][1]
self.assertEqual(context['demand_hint_possible'], True) # Check the AJAX call that gets the hint by question id and hint index
for question_id in range(num_questions):
# Check the AJAX call that gets the hint by index for hint_index in (0, 1, 2):
result = module.get_demand_hint(0) result = module.get_demand_hint(question_id, hint_index)
self.assertEqual(result['contents'], u'Hint (1 of 2): Demand 1') hint_num = hint_index % 2
self.assertEqual(result['hint_index'], 0) self.assertEqual(
result = module.get_demand_hint(1) result['contents'], u'Hint ({} of 2): question {} hint {}'.format(
self.assertEqual(result['contents'], u'Hint (2 of 2): Demand 2') hint_num + 1, question_id + 1, hint_num + 1
self.assertEqual(result['hint_index'], 1) )
result = module.get_demand_hint(2) # here the server wraps around to index 0 )
self.assertEqual(result['contents'], u'Hint (1 of 2): Demand 1') self.assertEqual(result['hint_index'], hint_num)
self.assertEqual(result['hint_index'], 0)
def test_demand_hint_logging(self): def test_demand_hint_logging(self):
module = CapaFactory.create(xml=self.demand_xml) module = CapaFactory.create(xml=SINGLE_QUESTION_XML)
# Re-mock the module_id to a fixed string, so we can check the logging # Re-mock the module_id to a fixed string, so we can check the logging
module.location = Mock(module.location) module.location = Mock(module.location)
module.location.to_deprecated_string.return_value = 'i4x://edX/capa_test/problem/meh' module.location.to_deprecated_string.return_value = 'i4x://edX/capa_test/problem/meh'
with patch.object(module.runtime, 'publish') as mock_track_function: with patch.object(module.runtime, 'publish') as mock_track_function:
module.get_problem_html() module.get_problem_html()
module.get_demand_hint(0) module.get_demand_hint(0, 0)
mock_track_function.assert_called_with( mock_track_function.assert_called_with(
module, 'edx.problem.hint.demandhint_displayed', module, 'edx.problem.hint.demandhint_displayed',
{'hint_index': 0, 'module_id': u'i4x://edX/capa_test/problem/meh', {'hint_index': 0, 'module_id': u'i4x://edX/capa_test/problem/meh',
......
...@@ -12,9 +12,24 @@ class ProblemPage(PageObject): ...@@ -12,9 +12,24 @@ class ProblemPage(PageObject):
url = None url = None
CSS_PROBLEM_HEADER = '.problem-header' CSS_PROBLEM_HEADER = '.problem-header'
# There can be multiple questions in a problem, so we need to make query selector specific to question
question_id = 0
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='.xblock-student_view').present return self.q(css='.xblock-student_view').present
def construct_query_selector(self, selector):
"""
Construct query selector specific to a question.
Arguments:
selector (str): css selector.
Returns:
str: Element selector specific to a question.
"""
return 'div.problem #question-{}.question {}'.format(self.question_id, selector)
@property @property
def problem_name(self): def problem_name(self):
""" """
...@@ -27,7 +42,7 @@ class ProblemPage(PageObject): ...@@ -27,7 +42,7 @@ class ProblemPage(PageObject):
""" """
Return the text of the question of the problem. Return the text of the question of the problem.
""" """
return self.q(css="div.problem p").text return self.q(css=self.construct_query_selector("p")).text
@property @property
def problem_content(self): def problem_content(self):
...@@ -41,21 +56,21 @@ class ProblemPage(PageObject): ...@@ -41,21 +56,21 @@ class ProblemPage(PageObject):
""" """
Return the "message" text of the question of the problem. Return the "message" text of the question of the problem.
""" """
return self.q(css="div.problem span.message").text[0] return self.q(css=self.construct_query_selector("span.message")).text[0]
@property @property
def extract_hint_text_from_html(self): def extract_hint_text_from_html(self):
""" """
Return the "hint" text of the problem from html Return the "hint" text of the problem from html
""" """
return self.q(css="div.problem div.problem-hint").html[0].split(' <', 1)[0] return self.q(css=self.construct_query_selector("div.problem-hint")).html[0].split(' <', 1)[0]
@property @property
def hint_text(self): def hint_text(self):
""" """
Return the "hint" text of the problem from its div. Return the "hint" text of the problem from its div.
""" """
return self.q(css="div.problem div.problem-hint").text[0] return self.q(css=self.construct_query_selector("div.problem-hint")).text[0]
def verify_mathjax_rendered_in_problem(self): def verify_mathjax_rendered_in_problem(self):
""" """
...@@ -63,7 +78,7 @@ class ProblemPage(PageObject): ...@@ -63,7 +78,7 @@ class ProblemPage(PageObject):
""" """
def mathjax_present(): def mathjax_present():
""" Returns True if MathJax css is present in the problem body """ """ Returns True if MathJax css is present in the problem body """
mathjax_container = self.q(css="div.problem p .MathJax_SVG") mathjax_container = self.q(css=self.construct_query_selector("p .MathJax_SVG"))
return mathjax_container.visible and mathjax_container.present return mathjax_container.visible and mathjax_container.present
self.wait_for( self.wait_for(
...@@ -77,7 +92,7 @@ class ProblemPage(PageObject): ...@@ -77,7 +92,7 @@ class ProblemPage(PageObject):
""" """
def mathjax_present(): def mathjax_present():
""" Returns True if MathJax css is present in the problem body """ """ Returns True if MathJax css is present in the problem body """
mathjax_container = self.q(css="div.problem div.problem-hint .MathJax_SVG") mathjax_container = self.q(css=self.construct_query_selector("div.problem-hint .MathJax_SVG"))
return mathjax_container.visible and mathjax_container.present return mathjax_container.visible and mathjax_container.present
self.wait_for( self.wait_for(
...@@ -96,7 +111,7 @@ class ProblemPage(PageObject): ...@@ -96,7 +111,7 @@ class ProblemPage(PageObject):
input_num: If provided, fills only the input_numth field. Else, all input_num: If provided, fills only the input_numth field. Else, all
input fields will be filled. input fields will be filled.
""" """
fields = self.q(css='div.problem div.capa_inputtype.textline input') fields = self.q(css=self.construct_query_selector('div.capa_inputtype.textline input'))
fields = fields.nth(input_num) if input_num is not None else fields fields = fields.nth(input_num) if input_num is not None else fields
fields.fill(text) fields.fill(text)
...@@ -104,7 +119,7 @@ class ProblemPage(PageObject): ...@@ -104,7 +119,7 @@ class ProblemPage(PageObject):
""" """
Fill in the answer to a numerical problem. Fill in the answer to a numerical problem.
""" """
self.q(css='div.problem section.inputtype input').fill(text) self.q(css=self.construct_query_selector('section.inputtype input')).fill(text)
self.wait_for_element_invisibility('.loading', 'wait for loading icon to disappear') self.wait_for_element_invisibility('.loading', 'wait for loading icon to disappear')
self.wait_for_ajax() self.wait_for_ajax()
...@@ -150,39 +165,47 @@ class ProblemPage(PageObject): ...@@ -150,39 +165,47 @@ class ProblemPage(PageObject):
""" """
Click the Hint button. Click the Hint button.
""" """
self.q(css='div.problem button.hint-button').click() self.q(css=self.construct_query_selector('button.hint-button')).click()
self.wait_for_ajax() self.wait_for_ajax()
def click_choice(self, choice_value): def click_choice(self, choice_value):
""" """
Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group. Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group.
""" """
self.q(css='div.problem .choicegroup input[value="' + choice_value + '"]').click() self.q(css=self.construct_query_selector('.choicegroup input[value="' + choice_value + '"]')).click()
self.wait_for_ajax() self.wait_for_ajax()
def is_correct(self): def is_correct(self):
""" """
Is there a "correct" status showing? Is there a "correct" status showing?
""" """
return self.q(css="div.problem div.capa_inputtype.textline div.correct span.status").is_present() return self.q(
css=self.construct_query_selector("div.capa_inputtype.textline div.correct span.status")
).is_present()
def simpleprob_is_correct(self): def simpleprob_is_correct(self):
""" """
Is there a "correct" status showing? Works with simple problem types. Is there a "correct" status showing? Works with simple problem types.
""" """
return self.q(css="div.problem section.inputtype div.correct span.status").is_present() return self.q(
css=self.construct_query_selector("section.inputtype div.correct span.status")
).is_present()
def simpleprob_is_partially_correct(self): def simpleprob_is_partially_correct(self):
""" """
Is there a "partially correct" status showing? Works with simple problem types. Is there a "partially correct" status showing? Works with simple problem types.
""" """
return self.q(css="div.problem section.inputtype div.partially-correct span.status").is_present() return self.q(
css=self.construct_query_selector("section.inputtype div.partially-correct span.status")
).is_present()
def simpleprob_is_incorrect(self): def simpleprob_is_incorrect(self):
""" """
Is there an "incorrect" status showing? Works with simple problem types. Is there an "incorrect" status showing? Works with simple problem types.
""" """
return self.q(css="div.problem section.inputtype div.incorrect span.status").is_present() return self.q(
css=self.construct_query_selector("section.inputtype div.incorrect span.status")
).is_present()
def click_clarification(self, index=0): def click_clarification(self, index=0):
""" """
...@@ -190,7 +213,9 @@ class ProblemPage(PageObject): ...@@ -190,7 +213,9 @@ class ProblemPage(PageObject):
Problem <clarification>clarification text hidden by an icon in rendering</clarification> Text Problem <clarification>clarification text hidden by an icon in rendering</clarification> Text
""" """
self.q(css='div.problem .clarification:nth-child({index}) i[data-tooltip]'.format(index=index + 1)).click() self.q(css=self.construct_query_selector(
'.clarification:nth-child({index}) i[data-tooltip]'.format(index=index + 1)
)).click()
@property @property
def visible_tooltip_text(self): def visible_tooltip_text(self):
......
...@@ -186,68 +186,126 @@ class ProblemHintWithHtmlTest(ProblemsTest, EventsTestMixin): ...@@ -186,68 +186,126 @@ class ProblemHintWithHtmlTest(ProblemsTest, EventsTestMixin):
""" """
xml = dedent(""" xml = dedent("""
<problem> <problem>
<p>question text</p> <question>
<stringresponse answer="A"> <p>question text</p>
<stringequalhint answer="C"><a href="#">aa bb</a> cc</stringequalhint> <stringresponse answer="A">
<textline size="20"/> <stringequalhint answer="C"><a href="#">aa bb</a> cc</stringequalhint>
</stringresponse> <textline size="20"/>
<demandhint> </stringresponse>
<hint>aa <a href="#">bb</a> cc</hint> <demandhint>
<hint><a href="#">dd ee</a> ff</hint> <hint>question 1 hint 1</hint>
</demandhint> <hint>question 1 hint 2</hint>
</demandhint>
</question>
<question>
<p>That is the question</p>
<multiplechoiceresponse>
<choicegroup type="MultipleChoice">
<choice correct="false">Alpha <choicehint>A hint</choicehint></choice>
<choice correct="true">Beta</choice>
</choicegroup>
</multiplechoiceresponse>
<demandhint>
<hint>question 2 hint 1</hint>
<hint>question 2 hint 2</hint>
</demandhint>
</question>
</problem> </problem>
""") """)
return XBlockFixtureDesc('problem', 'PROBLEM HTML HINT TEST', data=xml) return XBlockFixtureDesc('problem', 'PROBLEM HTML HINT TEST', data=xml)
def test_check_hint(self): def test_check_hint(self):
""" """
Test clicking Check shows the extended hint in the problem message. Scenario: Test clicking Check shows the extended hint in the problem message.
Given I am enrolled in a course.
And I visit a unit page with two CAPA question
Then I gave incorrect answers for both questions
When I click the check button
Then I should see 2 hint messages
And expected events are emitted
""" """
self.courseware_page.visit() self.courseware_page.visit()
problem_page = ProblemPage(self.browser) problem_page = ProblemPage(self.browser)
# first question
self.assertEqual(problem_page.problem_text[0], u'question text') self.assertEqual(problem_page.problem_text[0], u'question text')
problem_page.fill_answer('C') problem_page.fill_answer('C')
# second question
problem_page.question_id = 1
self.assertEqual(problem_page.problem_text[0], u'That is the question')
problem_page.click_choice('choice_0')
problem_page.click_check() problem_page.click_check()
self.assertEqual(problem_page.message_text, u'Incorrect: A hint')
problem_page.question_id = 0
self.assertEqual(problem_page.message_text, u'Incorrect: aa bb cc') self.assertEqual(problem_page.message_text, u'Incorrect: aa bb cc')
# Check for corresponding tracking event # Check for corresponding tracking event
actual_events = self.wait_for_events( actual_events = self.wait_for_events(
event_filter={'event_type': 'edx.problem.hint.feedback_displayed'}, event_filter={'event_type': 'edx.problem.hint.feedback_displayed'},
number_of_matches=1 number_of_matches=2
) )
self.assert_events_match( self.assert_events_match(
[{'event': {'hint_label': u'Incorrect', [
{
'event':
{
'hint_label': u'Incorrect',
'trigger_type': u'single',
'student_answer': [u'choice_0'],
'correctness': False,
'question_type': u'multiplechoiceresponse',
'hints': [{u'text': u'A hint'}]}
},
{
'event':
{
'hint_label': u'Incorrect',
'trigger_type': 'single', 'trigger_type': 'single',
'student_answer': [u'C'], 'student_answer': [u'C'],
'correctness': False, 'correctness': False,
'question_type': 'stringresponse', 'question_type': 'stringresponse',
'hints': [{'text': '<a href="#">aa bb</a> cc'}]}}], 'hints': [{'text': '<a href="#">aa bb</a> cc'}]
}
}
],
actual_events) actual_events)
def test_demand_hint(self): def test_demand_hint(self):
""" """
Test clicking hint button shows the demand hint in its div. Scenario: Verify that demandhint works as expected.
Given I am enrolled in a course.
And I visit a unit page with two CAPA question
When I click on Hint button for each question
Then I should see correct hint message for each question
And expected events are emitted
""" """
self.courseware_page.visit() self.courseware_page.visit()
problem_page = ProblemPage(self.browser) problem_page = ProblemPage(self.browser)
# The hint button rotates through multiple hints
problem_page.click_hint() for question in (0, 1):
self.assertEqual(problem_page.hint_text, u'Hint (1 of 2): aa bb cc') problem_page.question_id = question
problem_page.click_hint() for hint in (0, 1, 2):
self.assertEqual(problem_page.hint_text, u'Hint (2 of 2): dd ee ff') problem_page.click_hint()
problem_page.click_hint() hint_num = hint % 2
self.assertEqual(problem_page.hint_text, u'Hint (1 of 2): aa bb cc') prefix = u'Hint ({} of 2): '.format(hint_num + 1)
# Check corresponding tracking events hint_text = 'question {} hint {}'.format(question + 1, hint_num + 1)
actual_events = self.wait_for_events( self.assertEqual(problem_page.hint_text, prefix + hint_text)
event_filter={'event_type': 'edx.problem.hint.demandhint_displayed'},
number_of_matches=3 # Check corresponding tracking events
) actual_events = self.wait_for_events(
self.assert_events_match( event_filter={'event_type': 'edx.problem.hint.demandhint_displayed'},
[ number_of_matches=1
{'event': {u'hint_index': 0, u'hint_len': 2, u'hint_text': u'aa <a href="#">bb</a> cc'}}, )
{'event': {u'hint_index': 1, u'hint_len': 2, u'hint_text': u'<a href="#">dd ee</a> ff'}}, self.assert_events_match(
{'event': {u'hint_index': 0, u'hint_len': 2, u'hint_text': u'aa <a href="#">bb</a> cc'}} [
], {'event': {u'hint_index': hint_num, u'hint_len': 2, u'hint_text': hint_text}}
actual_events) ],
actual_events
)
self.reset_event_tracking()
class ProblemWithMathjax(ProblemsTest): class ProblemWithMathjax(ProblemsTest):
......
...@@ -8,20 +8,12 @@ ...@@ -8,20 +8,12 @@
<div class="problem-progress"></div> <div class="problem-progress"></div>
<div class="problem"> <div class="problem">
<div aria-live="polite"> ${ problem['html'] }
${ problem['html'] }
</div>
<div class="action"> <div class="action">
<input type="hidden" name="problem_id" value="${ problem['name'] }" /> <input type="hidden" name="problem_id" value="${ problem['name'] }" />
% if demand_hint_possible:
<div class="problem-hint" aria-live="polite"></div>
% endif
% if check_button: % if check_button:
<button class="check ${ check_button }" data-checking="${ check_button_checking }" data-value="${ check_button }"><span class="check-label">${ check_button }</span><span class="sr"> ${_("your answer")}</span></button> <button class="check ${ check_button }" data-checking="${ check_button_checking }" data-value="${ check_button }"><span class="check-label">${ check_button }</span><span class="sr"> ${_("your answer")}</span></button>
% endif % endif
% if demand_hint_possible:
<button class="hint-button" data-value="${_('Hint')}">${_('Hint')}</button>
% endif
% if reset_button: % if reset_button:
<button class="reset" data-value="${_('Reset')}">${_('Reset')}<span class="sr"> ${_("your answer")}</span></button> <button class="reset" data-value="${_('Reset')}">${_('Reset')}<span class="sr"> ${_("your answer")}</span></button>
% endif % endif
......
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