Commit 0aabea24 by Chris Jerdonek

Merge 'issue_66' into development: closing issue #66 (unicode RenderEngine)

This commit removes markupsafe details from the RenderEngine class.
parents e542a896 bd304a1d
...@@ -50,6 +50,16 @@ class RenderEngine(object): ...@@ -50,6 +50,16 @@ class RenderEngine(object):
This class is meant only for internal use by the Template class. This class is meant only for internal use by the Template class.
As a rule, the code in this class operates on unicode strings where
possible rather than, say, strings of type str or markupsafe.Markup.
This means that strings obtained from "external" sources like partials
and variable tag values are immediately converted to unicode (or
escaped and converted to unicode) before being operated on further.
This makes maintaining, reasoning about, and testing the correctness
of the code much simpler. In particular, it keeps the implementation
of this class independent of the API details of one (or possibly more)
unicode subclasses (e.g. markupsafe.Markup).
""" """
tag_re = None tag_re = None
otag = '{{' otag = '{{'
...@@ -61,15 +71,30 @@ class RenderEngine(object): ...@@ -61,15 +71,30 @@ class RenderEngine(object):
""" """
Arguments: Arguments:
load_partial: a function for loading templates by name when load_partial: the function to call when loading a partial. The
loading partials. The function should accept a template name function should accept a string template name and return a
and return a unicode template string. template string of type unicode (not a subclass).
escape: a function that takes a unicode or str string, literal: the function used to convert unescaped variable tag
converts it to unicode, and escapes and returns it. values to unicode, e.g. the value corresponding to a tag
"{{{name}}}". The function should accept a string of type
literal: a function that converts a unicode or str string str or unicode (or a subclass) and return a string of type
to unicode without escaping, and returns it. unicode (but not a proper subclass of unicode).
This class will only pass basestring instances to this
function. For example, it will call str() on integer variable
values prior to passing them to this function.
escape: the function used to escape and convert variable tag
values to unicode, e.g. the value corresponding to a tag
"{{name}}". The function should obey the same properties
described above for the "literal" function argument.
This function should take care to convert any str
arguments to unicode just as the literal function should, as
this class will not pass tag values to literal prior to passing
them to this function. This allows for more flexibility,
for example using a custom escape function that handles
incoming strings of type markupssafe.Markup differently
from plain unicode strings.
""" """
self.escape = escape self.escape = escape
...@@ -78,9 +103,13 @@ class RenderEngine(object): ...@@ -78,9 +103,13 @@ class RenderEngine(object):
def render(self, template, context): def render(self, template, context):
""" """
Return a template rendered as a string with type unicode.
Arguments: Arguments:
template: a unicode template string. template: a template string of type unicode (but not a proper
subclass of unicode).
context: a Context instance. context: a Context instance.
""" """
...@@ -122,7 +151,7 @@ class RenderEngine(object): ...@@ -122,7 +151,7 @@ class RenderEngine(object):
# Then there was no match. # Then there was no match.
break break
start, tag_type, tag_name, template = parts tag_type, tag_name, template = parts[1:]
tag_name = tag_name.strip() tag_name = tag_name.strip()
func = self.modifiers[tag_type] func = self.modifiers[tag_type]
...@@ -133,6 +162,7 @@ class RenderEngine(object): ...@@ -133,6 +162,7 @@ class RenderEngine(object):
output.append(tag_value) output.append(tag_value)
output = "".join(output) output = "".join(output)
return output return output
def _render_dictionary(self, template, context): def _render_dictionary(self, template, context):
...@@ -149,35 +179,45 @@ class RenderEngine(object): ...@@ -149,35 +179,45 @@ class RenderEngine(object):
return ''.join(insides) return ''.join(insides)
@modifiers.set(None) def _get_string_context(self, tag_name):
def _render_tag(self, tag_name):
""" """
Return the value of a variable as an escaped unicode string. Get a value from the current context as a basestring instance.
""" """
raw = self.context.get(tag_name, '') val = self.context.get(tag_name)
# For methods with no return value # We use "==" rather than "is" to compare integers, as using "is"
# # relies on an implementation detail of CPython. The test about
# We use "==" rather than "is" to compare integers, as using "is" relies # rendering zeroes failed while using PyPy when using "is".
# on an implementation detail of CPython. The test about rendering
# zeroes failed while using PyPy when using "is".
# See issue #34: https://github.com/defunkt/pystache/issues/34 # See issue #34: https://github.com/defunkt/pystache/issues/34
if not raw and raw != 0: if not val and val != 0:
if tag_name == '.': if tag_name != '.':
raw = self.context.top()
else:
return '' return ''
val = self.context.top()
# If we don't first convert to a string type, the call to self._unicode_and_escape() if not isinstance(val, basestring):
# will yield an error like the following: val = str(val)
#
# TypeError: coercing to Unicode: need string or buffer, ... found
#
if not isinstance(raw, basestring):
raw = str(raw)
return self.escape(raw) return val
@modifiers.set(None)
def _render_escaped(self, tag_name):
"""
Return a variable value as an escaped unicode string.
"""
s = self._get_string_context(tag_name)
return self.escape(s)
@modifiers.set('{')
@modifiers.set('&')
def _render_literal(self, tag_name):
"""
Return a variable value as a unicode string (unescaped).
"""
s = self._get_string_context(tag_name)
return self.literal(s)
@modifiers.set('!') @modifiers.set('!')
def _render_comment(self, tag_name): def _render_comment(self, tag_name):
...@@ -185,8 +225,8 @@ class RenderEngine(object): ...@@ -185,8 +225,8 @@ class RenderEngine(object):
@modifiers.set('>') @modifiers.set('>')
def _render_partial(self, template_name): def _render_partial(self, template_name):
markup = self.load_partial(template_name) template = self.load_partial(template_name)
return self._render(markup) return self._render(template)
@modifiers.set('=') @modifiers.set('=')
def _change_delimiter(self, tag_name): def _change_delimiter(self, tag_name):
...@@ -199,20 +239,11 @@ class RenderEngine(object): ...@@ -199,20 +239,11 @@ class RenderEngine(object):
return '' return ''
@modifiers.set('{')
@modifiers.set('&')
def _render_unescaped(self, tag_name):
"""
Render a tag without escaping it.
"""
return self.literal(self.context.get(tag_name, ''))
def _render(self, template): def _render(self, template):
""" """
Arguments: Arguments:
template: a unicode template string. template: a template string with type unicode.
""" """
output = [] output = []
......
...@@ -63,9 +63,10 @@ class Renderer(object): ...@@ -63,9 +63,10 @@ class Renderer(object):
example "utf-8". See the render() method's documentation for example "utf-8". See the render() method's documentation for
more information. more information.
escape: the function used to escape mustache variable values escape: the function used to escape variable tag values when
when rendering a template. The function should accept a rendering a template. The function should accept a unicode
unicode string and return an escaped string of the same type. string (or subclass of unicode) and return an escaped string
that is again unicode (or a subclass of unicode).
This function need not handle strings of type `str` because This function need not handle strings of type `str` because
this class will only pass it unicode strings. The constructor this class will only pass it unicode strings. The constructor
assigns this function to the constructed instance's escape() assigns this function to the constructed instance's escape()
...@@ -91,14 +92,13 @@ class Renderer(object): ...@@ -91,14 +92,13 @@ class Renderer(object):
default_encoding = sys.getdefaultencoding() default_encoding = sys.getdefaultencoding()
if escape is None: if escape is None:
# TODO: use 'quote=True' with cgi.escape and add tests.
escape = markupsafe.escape if markupsafe else cgi.escape escape = markupsafe.escape if markupsafe else cgi.escape
if loader is None: if loader is None:
loader = Loader(encoding=default_encoding, decode_errors=decode_errors) loader = Loader(encoding=default_encoding, decode_errors=decode_errors)
literal = markupsafe.Markup if markupsafe else unicode self._literal = markupsafe.Markup if markupsafe else unicode
self._literal = literal
self.decode_errors = decode_errors self.decode_errors = decode_errors
self.default_encoding = default_encoding self.default_encoding = default_encoding
...@@ -106,37 +106,46 @@ class Renderer(object): ...@@ -106,37 +106,46 @@ class Renderer(object):
self.loader = loader self.loader = loader
self.output_encoding = output_encoding self.output_encoding = output_encoding
def _unicode_and_escape(self, s): def _to_unicode_soft(self, s):
if not isinstance(s, unicode): """
s = self.unicode(s) Convert an str or unicode string to a unicode string (or subclass).
return self.escape(s)
def unicode(self, s): """
return unicode(s, self.default_encoding, self.decode_errors) # Avoid the "double-decoding" TypeError.
return s if isinstance(s, unicode) else self.unicode(s)
def escape(self, u): def _to_unicode_hard(self, s):
""" """
Escape a unicode string, and return it. Convert an str or unicode string to a unicode string (not subclass).
This function is initialized as the escape function that was passed """
to the Template class's constructor when this instance was return unicode(self._to_unicode_soft(s))
constructed. See the constructor docstring for more information.
def _escape_to_unicode(self, s):
""" """
pass Convert an str or unicode string to unicode, and escape it.
Returns a unicode string (not subclass).
"""
return unicode(self.escape(self._to_unicode_soft(s)))
def literal(self, s): def unicode(self, s):
""" """
Convert the given string to a unicode string, without escaping it. Convert a string to unicode, using default_encoding and decode_errors.
Raises:
This function internally calls the built-in function unicode() and TypeError: Because this method calls Python's built-in unicode()
passes it the default_encoding and decode_errors attributes for this function, this method raises the following exception if the
Template instance. If markupsafe was importable when loading this given string is already unicode:
module, this function returns an instance of the class
markupsafe.Markup (which subclasses unicode). TypeError: decoding Unicode is not supported
""" """
return self._literal(self.unicode(s)) # TODO: Wrap UnicodeDecodeErrors with a message about setting
# the default_encoding and decode_errors attributes.
return unicode(s, self.default_encoding, self.decode_errors)
def _make_context(self, context, **kwargs): def _make_context(self, context, **kwargs):
""" """
...@@ -157,21 +166,16 @@ class Renderer(object): ...@@ -157,21 +166,16 @@ class Renderer(object):
return context return context
def _make_load_partial(self): def _make_load_partial(self):
"""
Return the load_partial function for use by RenderEngine.
"""
def load_partial(name): def load_partial(name):
template = self.loader.get(name) template = self.loader.get(name)
# Make sure the return value is unicode since RenderEngine requires
# it. Also, check that the string is not already unicode to
# avoid "double-decoding". Otherwise, we would get the following
# error:
# TypeError: decoding Unicode is not supported
if not isinstance(template, unicode):
template = self.unicode(template)
return template if template is None:
# TODO: make a TemplateNotFoundException type that provides
# the original loader as an attribute.
raise Exception("Partial not found with name: %s" % repr(name))
# RenderEngine requires that the return value be unicode.
return self._to_unicode_hard(template)
return load_partial return load_partial
...@@ -183,8 +187,8 @@ class Renderer(object): ...@@ -183,8 +187,8 @@ class Renderer(object):
load_partial = self._make_load_partial() load_partial = self._make_load_partial()
engine = RenderEngine(load_partial=load_partial, engine = RenderEngine(load_partial=load_partial,
literal=self.literal, literal=self._to_unicode_hard,
escape=self._unicode_and_escape) escape=self._escape_to_unicode)
return engine return engine
def render(self, template, context=None, **kwargs): def render(self, template, context=None, **kwargs):
...@@ -194,30 +198,32 @@ class Renderer(object): ...@@ -194,30 +198,32 @@ class Renderer(object):
Returns: Returns:
If the output_encoding attribute is None, the return value is If the output_encoding attribute is None, the return value is
a unicode string. Otherwise, the return value is encoded to a markupsafe.Markup if markup was importable and unicode if not.
string of type str using the output encoding named by the Otherwise, the return value is encoded to a string of type str
output_encoding attribute. using the output encoding named by the output_encoding attribute.
Arguments: Arguments:
template: a template string that is either unicode or of type str. template: a template string that is either unicode or of type str.
If the string has type str, it is first converted to unicode If the string has type str, it is first converted to unicode
using the default_encoding and decode_errors attributes of this using this instance's default_encoding and decode_errors
instance. See the constructor docstring for more information. attributes. See the constructor docstring for more information.
context: a dictionary, Context, or object (e.g. a View instance). context: a dictionary, Context, or object (e.g. a View instance).
**kwargs: additional key values to add to the context when rendering. **kwargs: additional key values to add to the context when
These values take precedence over the context on any key conflicts. rendering. These values take precedence over the context on
any key conflicts.
""" """
engine = self._make_render_engine() engine = self._make_render_engine()
context = self._make_context(context, **kwargs) context = self._make_context(context, **kwargs)
if not isinstance(template, unicode): # RenderEngine.render() requires that the template string be unicode.
template = self.unicode(template) template = self._to_unicode_hard(template)
rendered = engine.render(template, context) rendered = engine.render(template, context)
rendered = self._literal(rendered)
if self.output_encoding is not None: if self.output_encoding is not None:
rendered = rendered.encode(self.output_encoding) rendered = rendered.encode(self.output_encoding)
......
...@@ -16,25 +16,42 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -16,25 +16,42 @@ class RenderEngineTestCase(unittest.TestCase):
"""Test the RenderEngine class.""" """Test the RenderEngine class."""
def _engine(self): def test_init(self):
""" """
Create and return a default RenderEngine for testing. Test that __init__() stores all of the arguments correctly.
""" """
to_unicode = unicode # In real-life, these arguments would be functions
engine = RenderEngine(load_partial="foo", literal="literal", escape="escape")
self.assertEquals(engine.escape, "escape")
self.assertEquals(engine.literal, "literal")
self.assertEquals(engine.load_partial, "foo")
escape = lambda s: cgi.escape(to_unicode(s))
literal = to_unicode
engine = RenderEngine(literal=literal, escape=escape, load_partial=None) class RenderEngineEnderTestCase(unittest.TestCase):
"""Test RenderEngine.render()."""
def _engine(self):
"""
Create and return a default RenderEngine for testing.
"""
escape = lambda s: unicode(cgi.escape(s))
engine = RenderEngine(literal=unicode, escape=escape, load_partial=None)
return engine return engine
def _assert_render(self, expected, template, *context, **kwargs): def _assert_render(self, expected, template, *context, **kwargs):
"""
Test rendering the given template using the given context.
"""
partials = kwargs.get('partials') partials = kwargs.get('partials')
engine = kwargs.get('engine', self._engine()) engine = kwargs.get('engine', self._engine())
if partials is not None: if partials is not None:
engine.load_partial = lambda key: partials[key] engine.load_partial = lambda key: unicode(partials[key])
context = Context(*context) context = Context(*context)
...@@ -42,22 +59,10 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -42,22 +59,10 @@ class RenderEngineTestCase(unittest.TestCase):
self.assertEquals(actual, expected) self.assertEquals(actual, expected)
def test_init(self):
"""
Test that __init__() stores all of the arguments correctly.
"""
# In real-life, these arguments would be functions
engine = RenderEngine(load_partial="foo", literal="literal", escape="escape")
self.assertEquals(engine.load_partial, "foo")
self.assertEquals(engine.escape, "escape")
self.assertEquals(engine.literal, "literal")
def test_render(self): def test_render(self):
self._assert_render('Hi Mom', 'Hi {{person}}', {'person': 'Mom'}) self._assert_render('Hi Mom', 'Hi {{person}}', {'person': 'Mom'})
def test_render__load_partial(self): def test__load_partial(self):
""" """
Test that render() uses the load_template attribute. Test that render() uses the load_template attribute.
...@@ -65,25 +70,106 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -65,25 +70,106 @@ class RenderEngineTestCase(unittest.TestCase):
engine = self._engine() engine = self._engine()
partials = {'partial': "{{person}}"} partials = {'partial': "{{person}}"}
engine.load_partial = lambda key: partials[key] engine.load_partial = lambda key: partials[key]
self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine) self._assert_render('Hi Mom', 'Hi {{>partial}}', {'person': 'Mom'}, engine=engine)
def test_render__literal(self): def test__literal(self):
""" """
Test that render() uses the literal attribute. Test that render() uses the literal attribute.
""" """
engine = self._engine() engine = self._engine()
engine.literal = lambda s: s.upper() engine.literal = lambda s: s.upper()
self._assert_render('bar BAR', '{{foo}} {{{foo}}}', {'foo': 'bar'}, engine=engine)
def test_render__escape(self): self._assert_render('BAR', '{{{foo}}}', {'foo': 'bar'}, engine=engine)
def test__escape(self):
""" """
Test that render() uses the escape attribute. Test that render() uses the escape attribute.
""" """
engine = self._engine() engine = self._engine()
engine.escape = lambda s: "**" + s engine.escape = lambda s: "**" + s
self._assert_render('**bar bar', '{{foo}} {{{foo}}}', {'foo': 'bar'}, engine=engine)
self._assert_render('**bar', '{{foo}}', {'foo': 'bar'}, engine=engine)
def test__escape_does_not_call_literal(self):
"""
Test that render() does not call literal before or after calling escape.
"""
engine = self._engine()
engine.literal = lambda s: s.upper() # a test version
engine.escape = lambda s: "**" + s
template = 'literal: {{{foo}}} escaped: {{foo}}'
context = {'foo': 'bar'}
self._assert_render('literal: BAR escaped: **bar', template, context, engine=engine)
def test__escape_preserves_unicode_subclasses(self):
"""
Test that render() preserves unicode subclasses when passing to escape.
This is useful, for example, if one wants to respect whether a
variable value is markupsafe.Markup when escaping.
"""
class MyUnicode(unicode):
pass
def escape(s):
if type(s) is MyUnicode:
return "**" + s
else:
return s + "**"
engine = self._engine()
engine.escape = escape
template = '{{foo1}} {{foo2}}'
context = {'foo1': MyUnicode('bar'), 'foo2': 'bar'}
self._assert_render('**bar bar**', template, context, engine=engine)
def test__non_basestring__literal_and_escaped(self):
"""
Test a context value that is not a basestring instance.
"""
# We use include upper() to make sure we are actually using
# our custom function in the tests
to_unicode = lambda s: unicode(s, encoding='ascii').upper()
engine = self._engine()
engine.escape = to_unicode
engine.literal = to_unicode
self.assertRaises(TypeError, engine.literal, 100)
template = '{{text}} {{int}} {{{int}}}'
context = {'int': 100, 'text': 'foo'}
self._assert_render('FOO 100 100', template, context, engine=engine)
def test__implicit_iterator__literal(self):
"""
Test an implicit iterator in a literal tag.
"""
template = """{{#test}}{{.}}{{/test}}"""
context = {'test': ['a', 'b']}
self._assert_render('ab', template, context)
def test__implicit_iterator__escaped(self):
"""
Test an implicit iterator in a normal tag.
"""
template = """{{#test}}{{{.}}}{{/test}}"""
context = {'test': ['a', 'b']}
self._assert_render('ab', template, context)
def test_render_with_partial(self): def test_render_with_partial(self):
partials = {'partial': "{{person}}"} partials = {'partial': "{{person}}"}
...@@ -95,13 +181,11 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -95,13 +181,11 @@ class RenderEngineTestCase(unittest.TestCase):
""" """
engine = self._engine() engine = self._engine()
engine.escape = lambda s: "**" + s
engine.literal = lambda s: s.upper()
template = '{{#test}}{{foo}} {{{foo}}}{{/test}}' template = '{{#test}}unescaped: {{{foo}}} escaped: {{foo}}{{/test}}'
context = {'test': {'foo': 'bar'}} context = {'test': {'foo': '<'}}
self._assert_render('**bar BAR', template, context, engine=engine) self._assert_render('unescaped: < escaped: &lt;', template, context, engine=engine)
def test_render__partial_context_values(self): def test_render__partial_context_values(self):
""" """
...@@ -109,12 +193,12 @@ class RenderEngineTestCase(unittest.TestCase): ...@@ -109,12 +193,12 @@ class RenderEngineTestCase(unittest.TestCase):
""" """
engine = self._engine() engine = self._engine()
engine.escape = lambda s: "**" + s
engine.literal = lambda s: s.upper()
partials = {'partial': '{{foo}} {{{foo}}}'} template = '{{>partial}}'
partials = {'partial': 'unescaped: {{{foo}}} escaped: {{foo}}'}
context = {'foo': '<'}
self._assert_render('**bar BAR', '{{>partial}}', {'foo': 'bar'}, engine=engine, partials=partials) self._assert_render('unescaped: < escaped: &lt;', template, context, engine=engine, partials=partials)
def test_render__list_referencing_outer_context(self): def test_render__list_referencing_outer_context(self):
""" """
......
...@@ -13,6 +13,7 @@ from pystache import renderer ...@@ -13,6 +13,7 @@ from pystache import renderer
from pystache.renderer import Renderer from pystache.renderer import Renderer
from pystache.loader import Loader from pystache.loader import Loader
class RendererInitTestCase(unittest.TestCase): class RendererInitTestCase(unittest.TestCase):
"""A class to test the Renderer.__init__() method.""" """A class to test the Renderer.__init__() method."""
...@@ -54,7 +55,6 @@ class RendererInitTestCase(unittest.TestCase): ...@@ -54,7 +55,6 @@ class RendererInitTestCase(unittest.TestCase):
self.assertEquals(actual.__dict__, expected.__dict__) self.assertEquals(actual.__dict__, expected.__dict__)
class RendererTestCase(unittest.TestCase): class RendererTestCase(unittest.TestCase):
"""Test the Renderer class.""" """Test the Renderer class."""
...@@ -146,13 +146,13 @@ class RendererTestCase(unittest.TestCase): ...@@ -146,13 +146,13 @@ class RendererTestCase(unittest.TestCase):
renderer = Renderer(decode_errors="foo") renderer = Renderer(decode_errors="foo")
self.assertEquals(renderer.decode_errors, "foo") self.assertEquals(renderer.decode_errors, "foo")
def test_unicode(self): ## Test Renderer.unicode().
renderer = Renderer()
actual = renderer.literal("abc")
self.assertEquals(actual, "abc")
self.assertEquals(type(actual), unicode)
def test_unicode__default_encoding(self): def test_unicode__default_encoding(self):
"""
Test that the default_encoding attribute is respected.
"""
renderer = Renderer() renderer = Renderer()
s = "é" s = "é"
...@@ -163,40 +163,20 @@ class RendererTestCase(unittest.TestCase): ...@@ -163,40 +163,20 @@ class RendererTestCase(unittest.TestCase):
self.assertEquals(renderer.unicode(s), u"é") self.assertEquals(renderer.unicode(s), u"é")
def test_unicode__decode_errors(self): def test_unicode__decode_errors(self):
renderer = Renderer() """
s = "é" Test that the decode_errors attribute is respected.
"""
renderer = Renderer()
renderer.default_encoding = "ascii" renderer.default_encoding = "ascii"
renderer.decode_errors = "strict" s = "déf"
self.assertRaises(UnicodeDecodeError, renderer.unicode, s)
renderer.decode_errors = "ignore"
self.assertEquals(renderer.unicode(s), "df")
renderer.decode_errors = "replace" renderer.decode_errors = "replace"
# U+FFFD is the official Unicode replacement character. # U+FFFD is the official Unicode replacement character.
self.assertEquals(renderer.unicode(s), u'\ufffd\ufffd') self.assertEquals(renderer.unicode(s), u'd\ufffd\ufffdf')
def test_literal__with_markupsafe(self):
if not self._was_markupsafe_imported():
# Then we cannot test this case.
return
self._restore_markupsafe()
_renderer = Renderer()
_renderer.default_encoding = "utf_8"
# Check the standard case.
actual = _renderer.literal("abc")
self.assertEquals(actual, "abc")
self.assertEquals(type(actual), renderer.markupsafe.Markup)
s = "é"
# Check that markupsafe respects default_encoding.
self.assertEquals(_renderer.literal(s), u"é")
_renderer.default_encoding = "ascii"
self.assertRaises(UnicodeDecodeError, _renderer.literal, s)
# Check that markupsafe respects decode_errors.
_renderer.decode_errors = "replace"
self.assertEquals(_renderer.literal(s), u'\ufffd\ufffd')
def test_render__unicode(self): def test_render__unicode(self):
renderer = Renderer() renderer = Renderer()
...@@ -314,47 +294,176 @@ class RendererTestCase(unittest.TestCase): ...@@ -314,47 +294,176 @@ class RendererTestCase(unittest.TestCase):
# TypeError: decoding Unicode is not supported # TypeError: decoding Unicode is not supported
self.assertEquals(load_partial("partial"), "foo") self.assertEquals(load_partial("partial"), "foo")
# By testing that Renderer.render() constructs the RenderEngine instance
# correctly, we no longer need to test the rendering code paths through # By testing that Renderer.render() constructs the right RenderEngine,
# the Renderer. We can test rendering paths through only the RenderEngine # we no longer need to exercise all rendering code paths through
# for the same amount of code coverage. # the Renderer. It suffices to test rendering paths through the
def test_make_render_engine__load_partial(self): # RenderEngine for the same amount of code coverage.
class Renderer_MakeRenderEngineTests(unittest.TestCase):
""" """
Test that _make_render_engine() constructs and passes load_partial correctly. Check the RenderEngine returned by Renderer._make_render_engine().
""" """
partials = {'partial': 'foo'}
renderer = Renderer(loader=partials) ## Test the engine's load_partial attribute.
renderer.unicode = lambda s: s.upper() # a test version.
def test__load_partial__returns_unicode(self):
"""
Check that load_partial returns unicode (and not a subclass).
"""
class MyUnicode(unicode):
pass
renderer = Renderer()
renderer.default_encoding = 'ascii'
renderer.loader = {'str': 'foo', 'subclass': MyUnicode('abc')}
engine = renderer._make_render_engine()
actual = engine.load_partial('str')
self.assertEquals(actual, "foo")
self.assertEquals(type(actual), unicode)
# Check that unicode subclasses are not preserved.
actual = engine.load_partial('subclass')
self.assertEquals(actual, "abc")
self.assertEquals(type(actual), unicode)
def test__load_partial__not_found(self):
"""
Check that load_partial provides a nice message when a template is not found.
"""
renderer = Renderer()
renderer.loader = {}
engine = renderer._make_render_engine() engine = renderer._make_render_engine()
# Make sure it calls unicode. load_partial = engine.load_partial
self.assertEquals(engine.load_partial('partial'), "FOO")
try:
load_partial("foo")
raise Exception("Shouldn't get here")
except Exception, err:
self.assertEquals(str(err), "Partial not found with name: 'foo'")
## Test the engine's literal attribute.
def test_make_render_engine__literal(self): def test__literal__uses_renderer_unicode(self):
""" """
Test that _make_render_engine() passes the right literal. Test that literal uses the renderer's unicode function.
""" """
renderer = Renderer() renderer = Renderer()
renderer.literal = "foo" # in real life, this would be a function. renderer.unicode = lambda s: s.upper()
engine = renderer._make_render_engine() engine = renderer._make_render_engine()
self.assertEquals(engine.literal, "foo") literal = engine.literal
def test_make_render_engine__escape(self): self.assertEquals(literal("foo"), "FOO")
def test__literal__handles_unicode(self):
""" """
Test that _make_render_engine() passes the right escape. Test that literal doesn't try to "double decode" unicode.
""" """
renderer = Renderer() renderer = Renderer()
renderer.unicode = lambda s: s.upper() # a test version. renderer.default_encoding = 'ascii'
renderer.escape = lambda s: "**" + s # a test version.
engine = renderer._make_render_engine()
literal = engine.literal
self.assertEquals(literal(u"foo"), "foo")
def test__literal__returns_unicode(self):
"""
Test that literal returns unicode (and not a subclass).
"""
renderer = Renderer()
renderer.default_encoding = 'ascii'
engine = renderer._make_render_engine()
literal = engine.literal
self.assertEquals(type(literal("foo")), unicode)
class MyUnicode(unicode):
pass
s = MyUnicode("abc")
self.assertEquals(type(s), MyUnicode)
self.assertTrue(isinstance(s, unicode))
self.assertEquals(type(literal(s)), unicode)
## Test the engine's escape attribute.
def test__escape__uses_renderer_escape(self):
"""
Test that escape uses the renderer's escape function.
"""
renderer = Renderer()
renderer.escape = lambda s: "**" + s
engine = renderer._make_render_engine() engine = renderer._make_render_engine()
escape = engine.escape escape = engine.escape
self.assertEquals(escape(u"foo"), "**foo") self.assertEquals(escape("foo"), "**foo")
def test__escape__uses_renderer_unicode(self):
"""
Test that escape uses the renderer's unicode function.
"""
renderer = Renderer()
renderer.unicode = lambda s: s.upper()
engine = renderer._make_render_engine()
escape = engine.escape
self.assertEquals(escape("foo"), "FOO")
def test__escape__has_access_to_original_unicode_subclass(self):
"""
Test that escape receives strings with the unicode subclass intact.
"""
renderer = Renderer()
renderer.escape = lambda s: type(s).__name__
engine = renderer._make_render_engine()
escape = engine.escape
class MyUnicode(unicode):
pass
self.assertEquals(escape("foo"), "unicode")
self.assertEquals(escape(u"foo"), "unicode")
self.assertEquals(escape(MyUnicode("foo")), "MyUnicode")
def test__escape__returns_unicode(self):
"""
Test that literal returns unicode (and not a subclass).
"""
renderer = Renderer()
renderer.default_encoding = 'ascii'
engine = renderer._make_render_engine()
escape = engine.escape
self.assertEquals(type(escape("foo")), unicode)
# Check that literal doesn't preserve unicode subclasses.
class MyUnicode(unicode):
pass
s = MyUnicode("abc")
self.assertEquals(type(s), MyUnicode)
self.assertTrue(isinstance(s, unicode))
self.assertEquals(type(escape(s)), unicode)
# Test that escape converts str strings to unicode first.
self.assertEquals(escape("foo"), "**FOO")
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