@@ -13,36 +11,28 @@
- % if state == 'unsubmitted':
-
- % elif state == 'correct':
-
- % elif state == 'incorrect':
-
- % elif state == 'incomplete':
-
- % endif
- % if hidden:
-
+ % if status == 'unsubmitted':
+
+ % elif status == 'correct':
+
+ % elif status == 'incorrect':
+
+ % elif status == 'incomplete':
+
% endif
- % if state == 'unsubmitted':
+ % if status == 'unsubmitted':
unanswered
- % elif state == 'correct':
+ % elif status == 'correct':
correct
- % elif state == 'incorrect':
+ % elif status == 'incorrect':
incorrect
- % elif state == 'incomplete':
+ % elif status == 'incomplete':
incomplete
% endif
@@ -52,7 +42,7 @@
% if msg:
${msg|n}
% endif
-% if state in ['unsubmitted', 'correct', 'incorrect', 'incomplete'] or hidden:
+% if status in ['unsubmitted', 'correct', 'incorrect', 'incomplete']:
% endif
diff --git a/common/lib/capa/capa/tests/__init__.py b/common/lib/capa/capa/tests/__init__.py
index c72d2a1538..b06975f6ce 100644
--- a/common/lib/capa/capa/tests/__init__.py
+++ b/common/lib/capa/capa/tests/__init__.py
@@ -4,13 +4,23 @@ import os
from mock import Mock
+import xml.sax.saxutils as saxutils
+
TEST_DIR = os.path.dirname(os.path.realpath(__file__))
+def tst_render_template(template, context):
+ """
+ A test version of render to template. Renders to the repr of the context, completely ignoring
+ the template name. To make the output valid xml, quotes the content, and wraps it in a
+ """
+ return '
{0}
'.format(saxutils.escape(repr(context)))
+
+
test_system = Mock(
ajax_url='courses/course_id/modx/a_location',
track_function=Mock(),
get_module=Mock(),
- render_template=Mock(),
+ render_template=tst_render_template,
replace_urls=Mock(),
user=Mock(),
filestore=fs.osfs.OSFS(os.path.join(TEST_DIR, "test_files")),
diff --git a/common/lib/capa/capa/tests/test_customrender.py b/common/lib/capa/capa/tests/test_customrender.py
new file mode 100644
index 0000000000..7208ab2941
--- /dev/null
+++ b/common/lib/capa/capa/tests/test_customrender.py
@@ -0,0 +1,76 @@
+from lxml import etree
+import unittest
+import xml.sax.saxutils as saxutils
+
+from . import test_system
+from capa import customrender
+
+# just a handy shortcut
+lookup_tag = customrender.registry.get_class_for_tag
+
+def extract_context(xml):
+ """
+ Given an xml element corresponding to the output of test_system.render_template, get back the
+ original context
+ """
+ return eval(xml.text)
+
+def quote_attr(s):
+ return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
+
+class HelperTest(unittest.TestCase):
+ '''
+ Make sure that our helper function works!
+ '''
+ def check(self, d):
+ xml = etree.XML(test_system.render_template('blah', d))
+ self.assertEqual(d, extract_context(xml))
+
+ def test_extract_context(self):
+ self.check({})
+ self.check({1, 2})
+ self.check({'id', 'an id'})
+ self.check({'with"quote', 'also"quote'})
+
+
+class SolutionRenderTest(unittest.TestCase):
+ '''
+ Make sure solutions render properly.
+ '''
+
+ def test_rendering(self):
+ solution = 'To compute unicorns, count them.'
+ xml_str = """
{s} """.format(s=solution)
+ element = etree.fromstring(xml_str)
+
+ renderer = lookup_tag('solution')(test_system, element)
+
+ self.assertEqual(renderer.id, 'solution_12')
+
+ # our test_system "renders" templates to a div with the repr of the context
+ xml = renderer.get_html()
+ context = extract_context(xml)
+ self.assertEqual(context, {'id' : 'solution_12'})
+
+
+class MathRenderTest(unittest.TestCase):
+ '''
+ Make sure math renders properly.
+ '''
+
+ def check_parse(self, latex_in, mathjax_out):
+ xml_str = """
{tex} """.format(tex=latex_in)
+ element = etree.fromstring(xml_str)
+
+ renderer = lookup_tag('math')(test_system, element)
+
+ self.assertEqual(renderer.mathstr, mathjax_out)
+
+ def test_parsing(self):
+ self.check_parse('$abc$', '[mathjaxinline]abc[/mathjaxinline]')
+ self.check_parse('$abc', '$abc')
+ self.check_parse(r'$\displaystyle 2+2$', '[mathjax] 2+2[/mathjax]')
+
+
+ # NOTE: not testing get_html yet because I don't understand why it's doing what it's doing.
+
diff --git a/common/lib/capa/capa/tests/test_inputtypes.py b/common/lib/capa/capa/tests/test_inputtypes.py
index 9ef642d468..b3d7702246 100644
--- a/common/lib/capa/capa/tests/test_inputtypes.py
+++ b/common/lib/capa/capa/tests/test_inputtypes.py
@@ -1,50 +1,30 @@
"""
-Tests of input types (and actually responsetypes too)
+Tests of input types.
+
+TODO:
+- test unicode in values, parameters, etc.
+- test various html escapes
+- test funny xml chars -- should never get xml parse error if things are escaped properly.
"""
-from datetime import datetime
-import json
-from mock import Mock
-from nose.plugins.skip import SkipTest
-import os
+from lxml import etree
import unittest
+import xml.sax.saxutils as saxutils
from . import test_system
from capa import inputtypes
-from lxml import etree
-
-def tst_render_template(template, context):
- """
- A test version of render to template. Renders to the repr of the context, completely ignoring the template name.
- """
- return repr(context)
+# just a handy shortcut
+lookup_tag = inputtypes.registry.get_class_for_tag
-system = Mock(render_template=tst_render_template)
+def quote_attr(s):
+ return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
class OptionInputTest(unittest.TestCase):
'''
Make sure option inputs work
'''
- def test_rendering_new(self):
- xml = """
"""
- element = etree.fromstring(xml)
-
- value = 'Down'
- status = 'answered'
- context = inputtypes._optioninput(element, value, status, test_system.render_template)
- print 'context: ', context
-
- expected = {'value': 'Down',
- 'options': [('Up', 'Up'), ('Down', 'Down')],
- 'state': 'answered',
- 'msg': '',
- 'inline': '',
- 'id': 'sky_input'}
-
- self.assertEqual(context, expected)
-
def test_rendering(self):
xml_str = """
"""
@@ -53,16 +33,451 @@ class OptionInputTest(unittest.TestCase):
state = {'value': 'Down',
'id': 'sky_input',
'status': 'answered'}
- option_input = inputtypes.OptionInput(system, element, state)
+ option_input = lookup_tag('optioninput')(test_system, element, state)
context = option_input._get_render_context()
expected = {'value': 'Down',
'options': [('Up', 'Up'), ('Down', 'Down')],
- 'state': 'answered',
+ 'status': 'answered',
'msg': '',
'inline': '',
'id': 'sky_input'}
self.assertEqual(context, expected)
+class ChoiceGroupTest(unittest.TestCase):
+ '''
+ Test choice groups, radio groups, and checkbox groups
+ '''
+
+ def check_group(self, tag, expected_input_type, expected_suffix):
+ xml_str = """
+ <{tag}>
+
This is foil One.
+
This is foil Two.
+
This is foil Three.
+ {tag}>
+ """.format(tag=tag)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': 'foil3',
+ 'id': 'sky_input',
+ 'status': 'answered'}
+
+ the_input = lookup_tag(tag)(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'sky_input',
+ 'value': 'foil3',
+ 'status': 'answered',
+ 'input_type': expected_input_type,
+ 'choices': [('foil1', '
This is foil One. '),
+ ('foil2', '
This is foil Two. '),
+ ('foil3', 'This is foil Three.'),],
+ 'name_array_suffix': expected_suffix, # what is this for??
+ }
+
+ self.assertEqual(context, expected)
+
+ def test_choicegroup(self):
+ self.check_group('choicegroup', 'radio', '')
+
+ def test_radiogroup(self):
+ self.check_group('radiogroup', 'radio', '[]')
+
+ def test_checkboxgroup(self):
+ self.check_group('checkboxgroup', 'checkbox', '[]')
+
+
+
+class JavascriptInputTest(unittest.TestCase):
+ '''
+ The javascript input is a pretty straightforward pass-thru, but test it anyway
+ '''
+
+ def test_rendering(self):
+ params = "(1,2,3)"
+
+ problem_state = "abc12',12&hi
"
+ display_class = "a_class"
+ display_file = "my_files/hi.js"
+
+ xml_str = """ """.format(
+ params=params,
+ ps=quote_attr(problem_state),
+ dc=display_class, df=display_file)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': '3',}
+ the_input = lookup_tag('javascriptinput')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'params': params,
+ 'display_file': display_file,
+ 'display_class': display_class,
+ 'problem_state': problem_state,
+ 'value': '3',
+ 'evaluation': '',}
+
+ self.assertEqual(context, expected)
+
+
+class TextLineTest(unittest.TestCase):
+ '''
+ Check that textline inputs work, with and without math.
+ '''
+
+ def test_rendering(self):
+ size = "42"
+ xml_str = """ """.format(size=size)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': 'BumbleBee',}
+ the_input = lookup_tag('textline')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': 'BumbleBee',
+ 'status': 'unanswered',
+ 'size': size,
+ 'msg': '',
+ 'hidden': False,
+ 'inline': False,
+ 'do_math': False,
+ 'preprocessor': None}
+ self.assertEqual(context, expected)
+
+
+ def test_math_rendering(self):
+ size = "42"
+ preprocessorClass = "preParty"
+ script = "foo/party.js"
+
+ xml_str = """ """.format(size=size, pp=preprocessorClass, sc=script)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': 'BumbleBee',}
+ the_input = lookup_tag('textline')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': 'BumbleBee',
+ 'status': 'unanswered',
+ 'size': size,
+ 'msg': '',
+ 'hidden': False,
+ 'inline': False,
+ 'do_math': True,
+ 'preprocessor': {'class_name': preprocessorClass,
+ 'script_src': script}}
+ self.assertEqual(context, expected)
+
+
+class FileSubmissionTest(unittest.TestCase):
+ '''
+ Check that file submission inputs work
+ '''
+
+ def test_rendering(self):
+ allowed_files = "runme.py nooooo.rb ohai.java"
+ required_files = "cookies.py"
+
+ xml_str = """ """.format(af=allowed_files,
+ rf=required_files,)
+
+
+ element = etree.fromstring(xml_str)
+
+ escapedict = {'"': '"'}
+ esc = lambda s: saxutils.escape(s, escapedict)
+
+ state = {'value': 'BumbleBee.py',
+ 'status': 'incomplete',
+ 'feedback' : {'message': '3'}, }
+ input_class = lookup_tag('filesubmission')
+ the_input = input_class(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'status': 'queued',
+ 'msg': input_class.submitted_msg,
+ 'value': 'BumbleBee.py',
+ 'queue_len': '3',
+ 'allowed_files': esc('["runme.py", "nooooo.rb", "ohai.java"]'),
+ 'required_files': esc('["cookies.py"]')}
+
+ self.assertEqual(context, expected)
+
+
+class CodeInputTest(unittest.TestCase):
+ '''
+ Check that codeinput inputs work
+ '''
+
+ def test_rendering(self):
+ mode = "parrot"
+ linenumbers = 'false'
+ rows = '37'
+ cols = '11'
+ tabsize = '7'
+
+ xml_str = """ """.format(m=mode, c=cols, r=rows, ln=linenumbers, ts=tabsize)
+
+ element = etree.fromstring(xml_str)
+
+ escapedict = {'"': '"'}
+ esc = lambda s: saxutils.escape(s, escapedict)
+
+ state = {'value': 'print "good evening"',
+ 'status': 'incomplete',
+ 'feedback' : {'message': '3'}, }
+
+ the_input = lookup_tag('codeinput')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': 'print "good evening"',
+ 'status': 'queued',
+ 'msg': 'Submitted to grader.',
+ 'mode': mode,
+ 'linenumbers': linenumbers,
+ 'rows': rows,
+ 'cols': cols,
+ 'hidden': '',
+ 'tabsize': int(tabsize),
+ 'queue_len': '3',
+ }
+
+ self.assertEqual(context, expected)
+
+
+class SchematicTest(unittest.TestCase):
+ '''
+ Check that schematic inputs work
+ '''
+
+ def test_rendering(self):
+ height = '12'
+ width = '33'
+ parts = 'resistors, capacitors, and flowers'
+ analyses = 'fast, slow, and pink'
+ initial_value = 'two large batteries'
+ submit_analyses = 'maybe'
+
+
+ xml_str = """ """.format(h=height, w=width, p=parts, a=analyses,
+ iv=initial_value, sa=submit_analyses)
+
+ element = etree.fromstring(xml_str)
+
+ value = 'three resistors and an oscilating pendulum'
+ state = {'value': value,
+ 'status': 'unsubmitted'}
+
+ the_input = lookup_tag('schematic')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': value,
+ 'initial_value': initial_value,
+ 'status': 'unsubmitted',
+ 'width': width,
+ 'height': height,
+ 'parts': parts,
+ 'analyses': analyses,
+ 'submit_analyses': submit_analyses,
+ }
+
+ self.assertEqual(context, expected)
+
+
+class ImageInputTest(unittest.TestCase):
+ '''
+ Check that image inputs work
+ '''
+
+ def check(self, value, egx, egy):
+ height = '78'
+ width = '427'
+ src = 'http://www.edx.org/cowclicker.jpg'
+
+ xml_str = """ """.format(s=src, h=height, w=width)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': value,
+ 'status': 'unsubmitted'}
+
+ the_input = lookup_tag('imageinput')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'width': width,
+ 'height': height,
+ 'src': src,
+ 'gx': egx,
+ 'gy': egy,
+ 'msg': ''}
+
+ self.assertEqual(context, expected)
+
+ def test_with_value(self):
+ # Check that compensating for the dot size works properly.
+ self.check('[50,40]', 35, 25)
+
+ def test_without_value(self):
+ self.check('', 0, 0)
+
+ def test_corrupt_values(self):
+ self.check('[12', 0, 0)
+ self.check('[12, a]', 0, 0)
+ self.check('[12 10]', 0, 0)
+ self.check('[12]', 0, 0)
+ self.check('[12 13 14]', 0, 0)
+
+
+
+class CrystallographyTest(unittest.TestCase):
+ '''
+ Check that crystallography inputs work
+ '''
+
+ def test_rendering(self):
+ height = '12'
+ width = '33'
+ size = '10'
+
+ xml_str = """ """.format(h=height, w=width, s=size)
+
+ element = etree.fromstring(xml_str)
+
+ value = 'abc'
+ state = {'value': value,
+ 'status': 'unsubmitted'}
+
+ the_input = lookup_tag('crystallography')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'size': size,
+ 'msg': '',
+ 'hidden': '',
+ 'width': width,
+ 'height': height,
+ }
+
+ self.assertEqual(context, expected)
+
+
+class VseprTest(unittest.TestCase):
+ '''
+ Check that vsepr inputs work
+ '''
+
+ def test_rendering(self):
+ height = '12'
+ width = '33'
+ molecules = "H2O, C2O"
+ geometries = "AX12,TK421"
+
+ xml_str = """ """.format(h=height, w=width, m=molecules, g=geometries)
+
+ element = etree.fromstring(xml_str)
+
+ value = 'abc'
+ state = {'value': value,
+ 'status': 'unsubmitted'}
+
+ the_input = lookup_tag('vsepr_input')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': value,
+ 'status': 'unsubmitted',
+ 'msg': '',
+ 'width': width,
+ 'height': height,
+ 'molecules': molecules,
+ 'geometries': geometries,
+ }
+
+ self.assertEqual(context, expected)
+
+
+
+class ChemicalEquationTest(unittest.TestCase):
+ '''
+ Check that chemical equation inputs work.
+ '''
+
+ def test_rendering(self):
+ size = "42"
+ xml_str = """ """.format(size=size)
+
+ element = etree.fromstring(xml_str)
+
+ state = {'value': 'H2OYeah',}
+ the_input = lookup_tag('chemicalequationinput')(test_system, element, state)
+
+ context = the_input._get_render_context()
+
+ expected = {'id': 'prob_1_2',
+ 'value': 'H2OYeah',
+ 'status': 'unanswered',
+ 'size': size,
+ 'previewer': '/static/js/capa/chemical_equation_preview.js',
+ }
+ self.assertEqual(context, expected)
+