Added tests for HTMl rendering of problems
Added support for rendering response messages
This commit is contained in:
@@ -510,7 +510,9 @@ class LoncapaProblem(object):
|
||||
|
||||
# let each Response render itself
|
||||
if problemtree in self.responders:
|
||||
return self.responders[problemtree].render_html(self._extract_html)
|
||||
overall_msg = self.correct_map.get_overall_message()
|
||||
return self.responders[problemtree].render_html(self._extract_html,
|
||||
response_msg=overall_msg)
|
||||
|
||||
# let each custom renderer render itself:
|
||||
if problemtree.tag in customrender.registry.registered_tags():
|
||||
|
||||
@@ -174,13 +174,14 @@ class LoncapaResponse(object):
|
||||
'''
|
||||
return sum(self.maxpoints.values())
|
||||
|
||||
def render_html(self, renderer):
|
||||
def render_html(self, renderer, response_msg=None):
|
||||
'''
|
||||
Return XHTML Element tree representation of this Response.
|
||||
|
||||
Arguments:
|
||||
|
||||
- renderer : procedure which produces HTML given an ElementTree
|
||||
- response_msg: a message displayed at the end of the Response
|
||||
'''
|
||||
# render ourself as a <span> + our content
|
||||
tree = etree.Element('span')
|
||||
@@ -195,6 +196,13 @@ class LoncapaResponse(object):
|
||||
if item_xhtml is not None:
|
||||
tree.append(item_xhtml)
|
||||
tree.tail = self.xml.tail
|
||||
|
||||
# Add a <div> for the message at the end of the response
|
||||
if response_msg:
|
||||
response_msg_div = etree.SubElement(tree, 'div')
|
||||
response_msg_div.set("class", "response_message")
|
||||
response_msg_div.text = response_msg
|
||||
|
||||
return tree
|
||||
|
||||
def evaluate_answers(self, student_answers, old_cmap):
|
||||
@@ -1060,7 +1068,7 @@ def sympy_check2():
|
||||
# and the first input stores the message
|
||||
if 'ok' in ret:
|
||||
correct = ['correct'] * len(idset) if ret['ok'] else ['incorrect'] * len(idset)
|
||||
msg = ret['msg']
|
||||
msg = ret.get('msg', None)
|
||||
|
||||
if 1:
|
||||
# try to clean up message html
|
||||
|
||||
146
common/lib/capa/capa/tests/test_correctmap.py
Normal file
146
common/lib/capa/capa/tests/test_correctmap.py
Normal file
@@ -0,0 +1,146 @@
|
||||
import unittest
|
||||
from capa.correctmap import CorrectMap
|
||||
import datetime
|
||||
|
||||
class CorrectMapTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.cmap = CorrectMap()
|
||||
|
||||
def test_set_input_properties(self):
|
||||
|
||||
# Set the correctmap properties for two inputs
|
||||
self.cmap.set(answer_id='1_2_1',
|
||||
correctness='correct',
|
||||
npoints=5,
|
||||
msg='Test message',
|
||||
hint='Test hint',
|
||||
hintmode='always',
|
||||
queuestate={'key':'secretstring',
|
||||
'time':'20130228100026'})
|
||||
|
||||
self.cmap.set(answer_id='2_2_1',
|
||||
correctness='incorrect',
|
||||
npoints=None,
|
||||
msg=None,
|
||||
hint=None,
|
||||
hintmode=None,
|
||||
queuestate=None)
|
||||
|
||||
# Assert that each input has the expected properties
|
||||
self.assertTrue(self.cmap.is_correct('1_2_1'))
|
||||
self.assertFalse(self.cmap.is_correct('2_2_1'))
|
||||
|
||||
self.assertEqual(self.cmap.get_correctness('1_2_1'), 'correct')
|
||||
self.assertEqual(self.cmap.get_correctness('2_2_1'), 'incorrect')
|
||||
|
||||
self.assertEqual(self.cmap.get_npoints('1_2_1'), 5)
|
||||
self.assertEqual(self.cmap.get_npoints('2_2_1'), 0)
|
||||
|
||||
self.assertEqual(self.cmap.get_msg('1_2_1'), 'Test message')
|
||||
self.assertEqual(self.cmap.get_msg('2_2_1'), None)
|
||||
|
||||
self.assertEqual(self.cmap.get_hint('1_2_1'), 'Test hint')
|
||||
self.assertEqual(self.cmap.get_hint('2_2_1'), None)
|
||||
|
||||
self.assertEqual(self.cmap.get_hintmode('1_2_1'), 'always')
|
||||
self.assertEqual(self.cmap.get_hintmode('2_2_1'), None)
|
||||
|
||||
self.assertTrue(self.cmap.is_queued('1_2_1'))
|
||||
self.assertFalse(self.cmap.is_queued('2_2_1'))
|
||||
|
||||
self.assertEqual(self.cmap.get_queuetime_str('1_2_1'), '20130228100026')
|
||||
self.assertEqual(self.cmap.get_queuetime_str('2_2_1'), None)
|
||||
|
||||
self.assertTrue(self.cmap.is_right_queuekey('1_2_1', 'secretstring'))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('1_2_1', 'invalidstr'))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('1_2_1', ''))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('1_2_1', None))
|
||||
|
||||
self.assertFalse(self.cmap.is_right_queuekey('2_2_1', 'secretstring'))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('2_2_1', 'invalidstr'))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('2_2_1', ''))
|
||||
self.assertFalse(self.cmap.is_right_queuekey('2_2_1', None))
|
||||
|
||||
|
||||
def test_get_npoints(self):
|
||||
# Set the correctmap properties for 4 inputs
|
||||
# 1) correct, 5 points
|
||||
# 2) correct, None points
|
||||
# 3) incorrect, 5 points
|
||||
# 4) incorrect, None points
|
||||
self.cmap.set(answer_id='1_2_1',
|
||||
correctness='correct',
|
||||
npoints=5)
|
||||
|
||||
self.cmap.set(answer_id='2_2_1',
|
||||
correctness='correct',
|
||||
npoints=None)
|
||||
|
||||
self.cmap.set(answer_id='3_2_1',
|
||||
correctness='incorrect',
|
||||
npoints=5)
|
||||
|
||||
self.cmap.set(answer_id='4_2_1',
|
||||
correctness='incorrect',
|
||||
npoints=None)
|
||||
|
||||
# Assert that we get the expected points
|
||||
# If points assigned and correct --> npoints
|
||||
# If no points assigned and correct --> 1 point
|
||||
# Otherwise --> 0 points
|
||||
self.assertEqual(self.cmap.get_npoints('1_2_1'), 5)
|
||||
self.assertEqual(self.cmap.get_npoints('2_2_1'), 1)
|
||||
self.assertEqual(self.cmap.get_npoints('3_2_1'), 0)
|
||||
self.assertEqual(self.cmap.get_npoints('4_2_1'), 0)
|
||||
|
||||
|
||||
def test_set_overall_message(self):
|
||||
|
||||
# Default is an empty string string
|
||||
self.assertEqual(self.cmap.get_overall_message(), "")
|
||||
|
||||
# Set a message that applies to the whole question
|
||||
self.cmap.set_overall_message("Test message")
|
||||
|
||||
# Retrieve the message
|
||||
self.assertEqual(self.cmap.get_overall_message(), "Test message")
|
||||
|
||||
# Setting the message to None --> empty string
|
||||
self.cmap.set_overall_message(None)
|
||||
self.assertEqual(self.cmap.get_overall_message(), "")
|
||||
|
||||
def test_update_from_correctmap(self):
|
||||
# Initialize a CorrectMap with some properties
|
||||
self.cmap.set(answer_id='1_2_1',
|
||||
correctness='correct',
|
||||
npoints=5,
|
||||
msg='Test message',
|
||||
hint='Test hint',
|
||||
hintmode='always',
|
||||
queuestate={'key':'secretstring',
|
||||
'time':'20130228100026'})
|
||||
|
||||
self.cmap.set_overall_message("Test message")
|
||||
|
||||
# Create a second cmap, then update it to have the same properties
|
||||
# as the first cmap
|
||||
other_cmap = CorrectMap()
|
||||
other_cmap.update(self.cmap)
|
||||
|
||||
# Assert that it has all the same properties
|
||||
self.assertEqual(other_cmap.get_overall_message(),
|
||||
self.cmap.get_overall_message())
|
||||
|
||||
self.assertEqual(other_cmap.get_dict(),
|
||||
self.cmap.get_dict())
|
||||
|
||||
|
||||
def test_update_from_invalid(self):
|
||||
# Should get an exception if we try to update() a CorrectMap
|
||||
# with a non-CorrectMap value
|
||||
invalid_list = [None, "string", 5, datetime.datetime.today()]
|
||||
|
||||
for invalid in invalid_list:
|
||||
with self.assertRaises(Exception):
|
||||
self.cmap.update(invalid)
|
||||
186
common/lib/capa/capa/tests/test_html_render.py
Normal file
186
common/lib/capa/capa/tests/test_html_render.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import unittest
|
||||
from lxml import etree
|
||||
import os
|
||||
import textwrap
|
||||
import json
|
||||
import mock
|
||||
|
||||
from capa.capa_problem import LoncapaProblem
|
||||
from response_xml_factory import StringResponseXMLFactory, CustomResponseXMLFactory
|
||||
from . import test_system
|
||||
|
||||
class CapaHtmlRenderTest(unittest.TestCase):
|
||||
|
||||
def test_include_html(self):
|
||||
# Create a test file to include
|
||||
self._create_test_file('test_include.xml',
|
||||
'<test>Test include</test>')
|
||||
|
||||
# Generate some XML with an <include>
|
||||
xml_str = textwrap.dedent("""
|
||||
<problem>
|
||||
<include file="test_include.xml"/>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# Create the problem
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
|
||||
# Render the HTML
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect that the include file was embedded in the problem
|
||||
test_element = rendered_html.find("test")
|
||||
self.assertEqual(test_element.tag, "test")
|
||||
self.assertEqual(test_element.text, "Test include")
|
||||
|
||||
|
||||
def test_process_outtext(self):
|
||||
# Generate some XML with <startouttext /> and <endouttext />
|
||||
xml_str = textwrap.dedent("""
|
||||
<problem>
|
||||
<startouttext/>Test text<endouttext/>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# Create the problem
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
|
||||
# Render the HTML
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect that the <startouttext /> and <endouttext />
|
||||
# were converted to <span></span> tags
|
||||
span_element = rendered_html.find('span')
|
||||
self.assertEqual(span_element.text, 'Test text')
|
||||
|
||||
def test_render_script(self):
|
||||
# Generate some XML with a <script> tag
|
||||
xml_str = textwrap.dedent("""
|
||||
<problem>
|
||||
<script>test=True</script>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# Create the problem
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
|
||||
# Render the HTML
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect that the script element has been removed from the rendered HTML
|
||||
script_element = rendered_html.find('script')
|
||||
self.assertEqual(None, script_element)
|
||||
|
||||
def test_render_response_xml(self):
|
||||
# Generate some XML for a string response
|
||||
kwargs = {'question_text': "Test question",
|
||||
'explanation_text': "Test explanation",
|
||||
'answer': 'Test answer',
|
||||
'hints': [('test prompt', 'test_hint', 'test hint text')]}
|
||||
xml_str = StringResponseXMLFactory().build_xml(**kwargs)
|
||||
|
||||
# Mock out the template renderer
|
||||
test_system.render_template = mock.Mock()
|
||||
test_system.render_template.return_value = "<div>Input Template Render</div>"
|
||||
|
||||
# Create the problem and render the HTML
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect problem has been turned into a <div>
|
||||
self.assertEqual(rendered_html.tag, "div")
|
||||
|
||||
# Expect question text is in a <p> child
|
||||
question_element = rendered_html.find("p")
|
||||
self.assertEqual(question_element.text, "Test question")
|
||||
|
||||
# Expect that the response has been turned into a <span>
|
||||
response_element = rendered_html.find("span")
|
||||
self.assertEqual(response_element.tag, "span")
|
||||
|
||||
# Expect that the response <span>
|
||||
# that contains a <div> for the textline
|
||||
textline_element = response_element.find("div")
|
||||
self.assertEqual(textline_element.text, 'Input Template Render')
|
||||
|
||||
# Expect a child <div> for the solution
|
||||
# with the rendered template
|
||||
solution_element = rendered_html.find("div")
|
||||
self.assertEqual(solution_element.text, 'Input Template Render')
|
||||
|
||||
# Expect that the template renderer was called with the correct
|
||||
# arguments, once for the textline input and once for
|
||||
# the solution
|
||||
expected_textline_context = {'status': 'unsubmitted',
|
||||
'value': '',
|
||||
'preprocessor': None,
|
||||
'msg': '',
|
||||
'inline': False,
|
||||
'hidden': False,
|
||||
'do_math': False,
|
||||
'id': '1_2_1',
|
||||
'size': None}
|
||||
|
||||
expected_solution_context = {'id': '1_solution_1'}
|
||||
|
||||
expected_calls = [mock.call('textline.html', expected_textline_context),
|
||||
mock.call('solutionspan.html', expected_solution_context)]
|
||||
|
||||
self.assertEqual(test_system.render_template.call_args_list,
|
||||
expected_calls)
|
||||
|
||||
|
||||
def test_render_response_with_overall_msg(self):
|
||||
# CustomResponse script that sets an overall_message
|
||||
script=textwrap.dedent("""
|
||||
def check_func(*args):
|
||||
return {'overall_message': 'Test message',
|
||||
'input_list': [ {'ok': True, 'msg': '' } ] }
|
||||
""")
|
||||
|
||||
# Generate some XML for a CustomResponse
|
||||
kwargs = {'script':script, 'cfn': 'check_func'}
|
||||
xml_str = CustomResponseXMLFactory().build_xml(**kwargs)
|
||||
|
||||
# Create the problem and render the html
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
|
||||
# Grade the problem
|
||||
correctmap = problem.grade_answers({'1_2_1': 'test'})
|
||||
|
||||
# Render the html
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
|
||||
# Expect that there is a <div> within the response <div>
|
||||
# with css class response_message
|
||||
msg_div_element = rendered_html.find(".//div[@class='response_message']")
|
||||
self.assertEqual(msg_div_element.tag, "div")
|
||||
self.assertEqual(msg_div_element.get('class'), "response_message")
|
||||
|
||||
|
||||
def test_substitute_python_vars(self):
|
||||
# Generate some XML with Python variables defined in a script
|
||||
# and used later as attributes
|
||||
xml_str = textwrap.dedent("""
|
||||
<problem>
|
||||
<script>test="TEST"</script>
|
||||
<span attr="$test"></span>
|
||||
</problem>
|
||||
""")
|
||||
|
||||
# Create the problem and render the HTML
|
||||
problem = LoncapaProblem(xml_str, '1', system=test_system)
|
||||
rendered_html = etree.XML(problem.get_html())
|
||||
|
||||
# Expect that the variable $test has been replaced with its value
|
||||
span_element = rendered_html.find('span')
|
||||
self.assertEqual(span_element.get('attr'), "TEST")
|
||||
|
||||
def _create_test_file(self, path, content_str):
|
||||
test_fp = test_system.filestore.open(path, "w")
|
||||
test_fp.write(content_str)
|
||||
test_fp.close()
|
||||
|
||||
self.addCleanup(lambda: os.remove(test_fp.name))
|
||||
Reference in New Issue
Block a user