Cleanups:
- switch to using registry - pull math and solution "input types" into a separate customrender.py file - make capa_problem use new custom renderers and input types registries - remove unused imports, methods, etc - add tests for math and solution tags.
This commit is contained in:
@@ -38,6 +38,7 @@ import calc
|
||||
from correctmap import CorrectMap
|
||||
import eia
|
||||
import inputtypes
|
||||
import customrender
|
||||
from util import contextualize_text, convert_files_to_filenames
|
||||
import xqueue_interface
|
||||
|
||||
@@ -47,23 +48,8 @@ import responsetypes
|
||||
# dict of tagname, Response Class -- this should come from auto-registering
|
||||
response_tag_dict = dict([(x.response_tag, x) for x in responsetypes.__all__])
|
||||
|
||||
# Different ways students can input code
|
||||
entry_types = ['textline',
|
||||
'schematic',
|
||||
'textbox',
|
||||
'imageinput',
|
||||
'optioninput',
|
||||
'choicegroup',
|
||||
'radiogroup',
|
||||
'checkboxgroup',
|
||||
'filesubmission',
|
||||
'javascriptinput',
|
||||
'crystallography',
|
||||
'chemicalequationinput',
|
||||
'vsepr_input']
|
||||
|
||||
# extra things displayed after "show answers" is pressed
|
||||
solution_types = ['solution']
|
||||
solution_tags = ['solution']
|
||||
|
||||
# these get captured as student responses
|
||||
response_properties = ["codeparam", "responseparam", "answer"]
|
||||
@@ -309,7 +295,7 @@ class LoncapaProblem(object):
|
||||
answer_map.update(results)
|
||||
|
||||
# include solutions from <solution>...</solution> stanzas
|
||||
for entry in self.tree.xpath("//" + "|//".join(solution_types)):
|
||||
for entry in self.tree.xpath("//" + "|//".join(solution_tags)):
|
||||
answer = etree.tostring(entry)
|
||||
if answer:
|
||||
answer_map[entry.get('id')] = contextualize_text(answer, self.context)
|
||||
@@ -487,7 +473,7 @@ class LoncapaProblem(object):
|
||||
|
||||
problemid = problemtree.get('id') # my ID
|
||||
|
||||
if problemtree.tag in inputtypes.registered_input_tags():
|
||||
if problemtree.tag in inputtypes.registry.registered_tags():
|
||||
# If this is an inputtype subtree, let it render itself.
|
||||
status = "unsubmitted"
|
||||
msg = ''
|
||||
@@ -513,7 +499,7 @@ class LoncapaProblem(object):
|
||||
'hint': hint,
|
||||
'hintmode': hintmode,}}
|
||||
|
||||
input_type_cls = inputtypes.get_class_for_tag(problemtree.tag)
|
||||
input_type_cls = inputtypes.registry.get_class_for_tag(problemtree.tag)
|
||||
the_input = input_type_cls(self.system, problemtree, state)
|
||||
return the_input.get_html()
|
||||
|
||||
@@ -521,9 +507,15 @@ class LoncapaProblem(object):
|
||||
if problemtree in self.responders:
|
||||
return self.responders[problemtree].render_html(self._extract_html)
|
||||
|
||||
# let each custom renderer render itself:
|
||||
if problemtree.tag in customrender.registry.registered_tags():
|
||||
renderer_class = customrender.registry.get_class_for_tag(problemtree.tag)
|
||||
renderer = renderer_class(self.system, problemtree)
|
||||
return renderer.get_html()
|
||||
|
||||
# otherwise, render children recursively, and copy over attributes
|
||||
tree = etree.Element(problemtree.tag)
|
||||
for item in problemtree:
|
||||
# render child recursively
|
||||
item_xhtml = self._extract_html(item)
|
||||
if item_xhtml is not None:
|
||||
tree.append(item_xhtml)
|
||||
@@ -560,11 +552,12 @@ class LoncapaProblem(object):
|
||||
response_id += 1
|
||||
|
||||
answer_id = 1
|
||||
input_tags = inputtypes.registry.registered_tags()
|
||||
inputfields = tree.xpath("|".join(['//' + response.tag + '[@id=$id]//' + x
|
||||
for x in (entry_types + solution_types)]),
|
||||
for x in (input_tags + solution_tags)]),
|
||||
id=response_id_str)
|
||||
|
||||
# assign one answer_id for each entry_type or solution_type
|
||||
# assign one answer_id for each input type or solution type
|
||||
for entry in inputfields:
|
||||
entry.attrib['response_id'] = str(response_id)
|
||||
entry.attrib['answer_id'] = str(answer_id)
|
||||
|
||||
100
common/lib/capa/capa/customrender.py
Normal file
100
common/lib/capa/capa/customrender.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
This has custom renderers: classes that know how to render certain problem tags (e.g. <math> and
|
||||
<solution>) to html.
|
||||
|
||||
These tags do not have state, so they just get passed the system (for access to render_template),
|
||||
and the xml element.
|
||||
"""
|
||||
|
||||
from registry import TagRegistry
|
||||
|
||||
import logging
|
||||
import re
|
||||
import shlex # for splitting quoted strings
|
||||
import json
|
||||
|
||||
from lxml import etree
|
||||
import xml.sax.saxutils as saxutils
|
||||
from registry import TagRegistry
|
||||
|
||||
log = logging.getLogger('mitx.' + __name__)
|
||||
|
||||
registry = TagRegistry()
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
class MathRenderer(object):
|
||||
tags = ['math']
|
||||
|
||||
def __init__(self, system, xml):
|
||||
'''
|
||||
Render math using latex-like formatting.
|
||||
|
||||
Examples:
|
||||
|
||||
<math>$\displaystyle U(r)=4 U_0 $</math>
|
||||
<math>$r_0$</math>
|
||||
|
||||
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
|
||||
|
||||
TODO: use shorter tags (but this will require converting problem XML files!)
|
||||
'''
|
||||
self.system = system
|
||||
self.xml = xml
|
||||
|
||||
mathstr = re.sub('\$(.*)\$', r'[mathjaxinline]\1[/mathjaxinline]', xml.text)
|
||||
mtag = 'mathjax'
|
||||
if not r'\displaystyle' in mathstr:
|
||||
mtag += 'inline'
|
||||
else:
|
||||
mathstr = mathstr.replace(r'\displaystyle', '')
|
||||
self.mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag)
|
||||
|
||||
|
||||
def get_html(self):
|
||||
"""
|
||||
Return the contents of this tag, rendered to html, as an etree element.
|
||||
"""
|
||||
# TODO: why are there nested html tags here?? Why are there html tags at all, in fact?
|
||||
html = '<html><html>%s</html><html>%s</html></html>' % (
|
||||
self.mathstr, saxutils.escape(self.xml.tail))
|
||||
try:
|
||||
xhtml = etree.XML(html)
|
||||
except Exception as err:
|
||||
if self.system.DEBUG:
|
||||
msg = '<html><div class="inline-error"><p>Error %s</p>' % (
|
||||
str(err).replace('<', '<'))
|
||||
msg += ('<p>Failed to construct math expression from <pre>%s</pre></p>' %
|
||||
html.replace('<', '<'))
|
||||
msg += "</div></html>"
|
||||
log.error(msg)
|
||||
return etree.XML(msg)
|
||||
else:
|
||||
raise
|
||||
return xhtml
|
||||
|
||||
|
||||
registry.register(MathRenderer)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
class SolutionRenderer(object):
|
||||
'''
|
||||
A solution is just a <span>...</span> which is given an ID, that is used for displaying an
|
||||
extended answer (a problem "solution") after "show answers" is pressed.
|
||||
|
||||
Note that the solution content is NOT rendered and returned in the HTML. It is obtained by an
|
||||
ajax call.
|
||||
'''
|
||||
tags = ['solution']
|
||||
|
||||
def __init__(self, system, xml):
|
||||
self.system = system
|
||||
self.id = xml.get('id')
|
||||
|
||||
def get_html(self):
|
||||
context = {'id': self.id}
|
||||
html = self.system.render_template("solutionspan.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
registry.register(SolutionRenderer)
|
||||
|
||||
@@ -1,29 +1,3 @@
|
||||
|
||||
|
||||
# template:
|
||||
'''
|
||||
class ClassName(InputTypeBase):
|
||||
"""
|
||||
"""
|
||||
|
||||
template = "tagname.html"
|
||||
tags = ['tagname']
|
||||
|
||||
def __init__(self, system, xml, state):
|
||||
super(ClassName, self).__init__(system, xml, state)
|
||||
|
||||
|
||||
def _get_render_context(self):
|
||||
|
||||
context = {'id': self.id,
|
||||
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(ClassName)
|
||||
'''
|
||||
|
||||
|
||||
#
|
||||
# File: courseware/capa/inputtypes.py
|
||||
#
|
||||
@@ -32,11 +6,9 @@ register_input_class(ClassName)
|
||||
Module containing the problem elements which render into input objects
|
||||
|
||||
- textline
|
||||
- textbox (change this to textarea?)
|
||||
- schemmatic
|
||||
- choicegroup
|
||||
- radiogroup
|
||||
- checkboxgroup
|
||||
- textbox (aka codeinput)
|
||||
- schematic
|
||||
- choicegroup (aka radiogroup, checkboxgroup)
|
||||
- javascriptinput
|
||||
- imageinput (for clickable image)
|
||||
- optioninput (for option list)
|
||||
@@ -60,53 +32,13 @@ import json
|
||||
|
||||
from lxml import etree
|
||||
import xml.sax.saxutils as saxutils
|
||||
from registry import TagRegistry
|
||||
|
||||
log = logging.getLogger('mitx.' + __name__)
|
||||
|
||||
#########################################################################
|
||||
|
||||
_TAGS_TO_CLASSES = {}
|
||||
|
||||
def register_input_class(cls):
|
||||
"""
|
||||
Register cls as a supported input type. It is expected to have the same constructor as
|
||||
InputTypeBase, and to define cls.tags as a list of tags that it implements.
|
||||
|
||||
If an already-registered input type has claimed one of those tags, will raise ValueError.
|
||||
|
||||
If there are no tags in cls.tags, will also raise ValueError.
|
||||
"""
|
||||
|
||||
# Do all checks and complain before changing any state.
|
||||
if len(cls.tags) == 0:
|
||||
raise ValueError("No supported tags for class {0}".format(cls.__name__))
|
||||
|
||||
for t in cls.tags:
|
||||
if t in _TAGS_TO_CLASSES:
|
||||
other_cls = _TAGS_TO_CLASSES[t]
|
||||
if cls == other_cls:
|
||||
# registering the same class multiple times seems silly, but ok
|
||||
continue
|
||||
raise ValueError("Tag {0} already registered by class {1}. Can't register for class {2}"
|
||||
.format(t, other_cls.__name__, cls.__name__))
|
||||
|
||||
# Ok, should be good to change state now.
|
||||
for t in cls.tags:
|
||||
_TAGS_TO_CLASSES[t] = cls
|
||||
|
||||
def registered_input_tags():
|
||||
"""
|
||||
Get a list of all the xml tags that map to known input types.
|
||||
"""
|
||||
return _TAGS_TO_CLASSES.keys()
|
||||
|
||||
|
||||
def get_class_for_tag(tag):
|
||||
"""
|
||||
For any tag in registered_input_tags(), return the corresponding class. Otherwise, will raise KeyError.
|
||||
"""
|
||||
return _TAGS_TO_CLASSES[tag]
|
||||
|
||||
registry = TagRegistry()
|
||||
|
||||
class InputTypeBase(object):
|
||||
"""
|
||||
@@ -119,16 +51,18 @@ class InputTypeBase(object):
|
||||
"""
|
||||
Instantiate an InputType class. Arguments:
|
||||
|
||||
- system : ModuleSystem instance which provides OS, rendering, and user context. Specifically, must
|
||||
have a render_template function.
|
||||
- system : ModuleSystem instance which provides OS, rendering, and user context.
|
||||
Specifically, must have a render_template function.
|
||||
- xml : Element tree of this Input element
|
||||
- state : a dictionary with optional keys:
|
||||
* 'value' -- the current value of this input (what the student entered last time)
|
||||
* 'id' -- the id of this input, typically "{problem-location}_{response-num}_{input-num}"
|
||||
* 'value' -- the current value of this input
|
||||
(what the student entered last time)
|
||||
* 'id' -- the id of this input, typically
|
||||
"{problem-location}_{response-num}_{input-num}"
|
||||
* 'status' (answered, unanswered, unsubmitted)
|
||||
* 'feedback' (dictionary containing keys for hints, errors, or other
|
||||
feedback from previous attempt. Specifically 'message', 'hint', 'hintmode'. If 'hintmode'
|
||||
is 'always', the hint is always displayed.)
|
||||
feedback from previous attempt. Specifically 'message', 'hint',
|
||||
'hintmode'. If 'hintmode' is 'always', the hint is always displayed.)
|
||||
"""
|
||||
|
||||
self.xml = xml
|
||||
@@ -172,40 +106,13 @@ class InputTypeBase(object):
|
||||
Return the html for this input, as an etree element.
|
||||
"""
|
||||
if self.template is None:
|
||||
raise NotImplementedError("no rendering template specified for class {0}".format(self.__class__))
|
||||
raise NotImplementedError("no rendering template specified for class {0}"
|
||||
.format(self.__class__))
|
||||
|
||||
html = self.system.render_template(self.template, self._get_render_context())
|
||||
return etree.XML(html)
|
||||
|
||||
|
||||
## TODO: Remove once refactor is complete
|
||||
def make_class_for_render_function(fn):
|
||||
"""
|
||||
Take an old-style render function, return a new-style input class.
|
||||
"""
|
||||
|
||||
class Impl(InputTypeBase):
|
||||
"""
|
||||
Inherit all the constructor logic from InputTypeBase...
|
||||
"""
|
||||
tags = [fn.__name__]
|
||||
def get_html(self):
|
||||
"""...delegate to the render function to do the work"""
|
||||
return fn(self.xml, self.value, self.status, self.system.render_template, self.msg)
|
||||
|
||||
# don't want all the classes to be called Impl (confuses register_input_class).
|
||||
Impl.__name__ = fn.__name__.capitalize()
|
||||
return Impl
|
||||
|
||||
|
||||
def _reg(fn):
|
||||
"""
|
||||
Register an old-style inputtype render function as a new-style subclass of InputTypeBase.
|
||||
This will go away once converting all input types to the new format is complete. (TODO)
|
||||
"""
|
||||
register_input_class(make_class_for_render_function(fn))
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -253,7 +160,7 @@ class OptionInput(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(OptionInput)
|
||||
registry.register(OptionInput)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -346,7 +253,7 @@ def extract_choices(element):
|
||||
return choices
|
||||
|
||||
|
||||
register_input_class(ChoiceGroup)
|
||||
registry.register(ChoiceGroup)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -389,7 +296,7 @@ class JavascriptInput(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(JavascriptInput)
|
||||
registry.register(JavascriptInput)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -445,7 +352,7 @@ class TextLine(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(TextLine)
|
||||
registry.register(TextLine)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -485,7 +392,7 @@ class FileSubmission(InputTypeBase):
|
||||
'required_files': self.required_files,}
|
||||
return context
|
||||
|
||||
register_input_class(FileSubmission)
|
||||
registry.register(FileSubmission)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -542,7 +449,7 @@ class CodeInput(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(CodeInput)
|
||||
registry.register(CodeInput)
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
@@ -576,74 +483,7 @@ class Schematic(InputTypeBase):
|
||||
'submit_analyses': self.submit_analyses, }
|
||||
return context
|
||||
|
||||
register_input_class(Schematic)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
### TODO: Move out of inputtypes
|
||||
def math(element, value, status, render_template, msg=''):
|
||||
'''
|
||||
This is not really an input type. It is a convention from Lon-CAPA, used for
|
||||
displaying a math equation.
|
||||
|
||||
Examples:
|
||||
|
||||
<m display="jsmath">$\displaystyle U(r)=4 U_0 </m>
|
||||
<m>$r_0$</m>
|
||||
|
||||
We convert these to [mathjax]...[/mathjax] and [mathjaxinline]...[/mathjaxinline]
|
||||
|
||||
TODO: use shorter tags (but this will require converting problem XML files!)
|
||||
'''
|
||||
mathstr = re.sub('\$(.*)\$', '[mathjaxinline]\\1[/mathjaxinline]', element.text)
|
||||
mtag = 'mathjax'
|
||||
if not '\\displaystyle' in mathstr:
|
||||
mtag += 'inline'
|
||||
else:
|
||||
mathstr = mathstr.replace('\\displaystyle', '')
|
||||
mathstr = mathstr.replace('mathjaxinline]', '%s]' % mtag)
|
||||
|
||||
|
||||
# TODO: why are there nested html tags here?? Why are there html tags at all, in fact?
|
||||
html = '<html><html>%s</html><html>%s</html></html>' % (mathstr, saxutils.escape(element.tail))
|
||||
try:
|
||||
xhtml = etree.XML(html)
|
||||
except Exception as err:
|
||||
if False: # TODO needs to be self.system.DEBUG - but can't access system
|
||||
msg = '<html><div class="inline-error"><p>Error %s</p>' % str(err).replace('<', '<')
|
||||
msg += ('<p>Failed to construct math expression from <pre>%s</pre></p>' %
|
||||
html.replace('<', '<'))
|
||||
msg += "</div></html>"
|
||||
log.error(msg)
|
||||
return etree.XML(msg)
|
||||
else:
|
||||
raise
|
||||
# xhtml.tail = element.tail # don't forget to include the tail!
|
||||
return xhtml
|
||||
|
||||
_reg(math)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
def solution(element, value, status, render_template, msg=''):
|
||||
'''
|
||||
This is not really an input type. It is just a <span>...</span> which is given an ID,
|
||||
that is used for displaying an extended answer (a problem "solution") after "show answers"
|
||||
is pressed. Note that the solution content is NOT sent with the HTML. It is obtained
|
||||
by an ajax call.
|
||||
'''
|
||||
eid = element.get('id')
|
||||
size = element.get('size')
|
||||
context = {'id': eid,
|
||||
'value': value,
|
||||
'state': status,
|
||||
'size': size,
|
||||
'msg': msg,
|
||||
}
|
||||
html = render_template("solutionspan.html", context)
|
||||
return etree.XML(html)
|
||||
|
||||
_reg(solution)
|
||||
registry.register(Schematic)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -690,7 +530,7 @@ class ImageInput(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(ImageInput)
|
||||
registry.register(ImageInput)
|
||||
|
||||
#-----------------------------------------------------------------------------
|
||||
|
||||
@@ -730,7 +570,7 @@ class Crystallography(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(Crystallography)
|
||||
registry.register(Crystallography)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -799,4 +639,4 @@ class ChemicalEquationInput(InputTypeBase):
|
||||
}
|
||||
return context
|
||||
|
||||
register_input_class(ChemicalEquationInput)
|
||||
registry.register(ChemicalEquationInput)
|
||||
|
||||
@@ -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 <div>
|
||||
"""
|
||||
return '<div>{0}</div>'.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")),
|
||||
|
||||
76
common/lib/capa/capa/tests/test_customrender.py
Normal file
76
common/lib/capa/capa/tests/test_customrender.py
Normal file
@@ -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 = """<solution id="solution_12">{s}</solution>""".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 = """<math>{tex}</math>""".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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
Tests of input types (and actually responsetypes too).
|
||||
Tests of input types.
|
||||
|
||||
TODO:
|
||||
- test unicode in values, parameters, etc.
|
||||
@@ -7,27 +7,16 @@ TODO:
|
||||
- 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
|
||||
# just a handy shortcut
|
||||
lookup_tag = inputtypes.registry.get_class_for_tag
|
||||
|
||||
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)
|
||||
|
||||
|
||||
system = Mock(render_template=tst_render_template)
|
||||
|
||||
def quote_attr(s):
|
||||
return saxutils.quoteattr(s)[1:-1] # don't want the outer quotes
|
||||
@@ -44,7 +33,7 @@ class OptionInputTest(unittest.TestCase):
|
||||
state = {'value': 'Down',
|
||||
'id': 'sky_input',
|
||||
'status': 'answered'}
|
||||
option_input = inputtypes.get_class_for_tag('optioninput')(system, element, state)
|
||||
option_input = lookup_tag('optioninput')(test_system, element, state)
|
||||
|
||||
context = option_input._get_render_context()
|
||||
|
||||
@@ -80,7 +69,7 @@ class ChoiceGroupTest(unittest.TestCase):
|
||||
'id': 'sky_input',
|
||||
'status': 'answered'}
|
||||
|
||||
option_input = inputtypes.get_class_for_tag('choicegroup')(system, element, state)
|
||||
option_input = lookup_tag('choicegroup')(test_system, element, state)
|
||||
|
||||
context = option_input._get_render_context()
|
||||
|
||||
@@ -119,7 +108,7 @@ class ChoiceGroupTest(unittest.TestCase):
|
||||
'id': 'sky_input',
|
||||
'status': 'answered'}
|
||||
|
||||
the_input = inputtypes.get_class_for_tag(tag)(system, element, state)
|
||||
the_input = lookup_tag(tag)(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -164,7 +153,7 @@ class JavascriptInputTest(unittest.TestCase):
|
||||
element = etree.fromstring(xml_str)
|
||||
|
||||
state = {'value': '3',}
|
||||
the_input = inputtypes.get_class_for_tag('javascriptinput')(system, element, state)
|
||||
the_input = lookup_tag('javascriptinput')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -191,7 +180,7 @@ class TextLineTest(unittest.TestCase):
|
||||
element = etree.fromstring(xml_str)
|
||||
|
||||
state = {'value': 'BumbleBee',}
|
||||
the_input = inputtypes.get_class_for_tag('textline')(system, element, state)
|
||||
the_input = lookup_tag('textline')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -219,7 +208,7 @@ class TextLineTest(unittest.TestCase):
|
||||
element = etree.fromstring(xml_str)
|
||||
|
||||
state = {'value': 'BumbleBee',}
|
||||
the_input = inputtypes.get_class_for_tag('textline')(system, element, state)
|
||||
the_input = lookup_tag('textline')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -260,7 +249,7 @@ class FileSubmissionTest(unittest.TestCase):
|
||||
state = {'value': 'BumbleBee.py',
|
||||
'status': 'incomplete',
|
||||
'feedback' : {'message': '3'}, }
|
||||
the_input = inputtypes.get_class_for_tag('filesubmission')(system, element, state)
|
||||
the_input = lookup_tag('filesubmission')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -304,7 +293,7 @@ class CodeInputTest(unittest.TestCase):
|
||||
'status': 'incomplete',
|
||||
'feedback' : {'message': '3'}, }
|
||||
|
||||
the_input = inputtypes.get_class_for_tag('codeinput')(system, element, state)
|
||||
the_input = lookup_tag('codeinput')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -354,7 +343,7 @@ class SchematicTest(unittest.TestCase):
|
||||
state = {'value': value,
|
||||
'status': 'unsubmitted'}
|
||||
|
||||
the_input = inputtypes.get_class_for_tag('schematic')(system, element, state)
|
||||
the_input = lookup_tag('schematic')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -393,7 +382,7 @@ class ImageInputTest(unittest.TestCase):
|
||||
state = {'value': value,
|
||||
'status': 'unsubmitted'}
|
||||
|
||||
the_input = inputtypes.get_class_for_tag('imageinput')(system, element, state)
|
||||
the_input = lookup_tag('imageinput')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -447,7 +436,7 @@ class CrystallographyTest(unittest.TestCase):
|
||||
state = {'value': value,
|
||||
'status': 'unsubmitted'}
|
||||
|
||||
the_input = inputtypes.get_class_for_tag('crystallography')(system, element, state)
|
||||
the_input = lookup_tag('crystallography')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
@@ -476,7 +465,7 @@ class ChemicalEquationTest(unittest.TestCase):
|
||||
element = etree.fromstring(xml_str)
|
||||
|
||||
state = {'value': 'H2OYeah',}
|
||||
the_input = inputtypes.get_class_for_tag('chemicalequationinput')(system, element, state)
|
||||
the_input = lookup_tag('chemicalequationinput')(test_system, element, state)
|
||||
|
||||
context = the_input._get_render_context()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user