1 # -*- coding: utf-8 -*-
6 Tests for the extensions.
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
14 from jinja2.testsuite import JinjaTestCase, filesystem_loader
16 from jinja2 import Environment, DictLoader, contextfunction, nodes
17 from jinja2.exceptions import TemplateAssertionError
18 from jinja2.ext import Extension
19 from jinja2.lexer import Token, count_newlines
20 from jinja2.utils import next
24 from io import BytesIO
26 from StringIO import StringIO as BytesIO
29 importable_object = 23
31 _gettext_re = re.compile(r'_\((.*?)\)(?s)')
35 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
36 '{% block body %}{% endblock %}',
37 'child.html': '{% extends "master.html" %}{% block body %}'
38 '{% trans %}watch out{% endtrans %}{% endblock %}',
39 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
40 '{{ user_count }} users online{% endtrans %}',
41 'stringformat.html': '{{ _("User: %(num)d")|format(num=user_count) }}'
44 newstyle_i18n_templates = {
45 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
46 '{% block body %}{% endblock %}',
47 'child.html': '{% extends "master.html" %}{% block body %}'
48 '{% trans %}watch out{% endtrans %}{% endblock %}',
49 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
50 '{{ user_count }} users online{% endtrans %}',
51 'stringformat.html': '{{ _("User: %(num)d", num=user_count) }}',
52 'ngettext.html': '{{ ngettext("%(num)d apple", "%(num)d apples", apples) }}'
58 'missing': u'fehlend',
59 'watch out': u'pass auf',
60 'One user online': u'Ein Benutzer online',
61 '%(user_count)s users online': u'%(user_count)s Benutzer online',
62 'User: %(num)d': u'Benutzer: %(num)d',
63 '%(num)d apple': u'%(num)d Apfel',
64 '%(num)d apples': u'%(num)d Äpfel'
70 def gettext(context, string):
71 language = context.get('LANGUAGE', 'en')
72 return languages.get(language, {}).get(string, string)
76 def ngettext(context, s, p, n):
77 language = context.get('LANGUAGE', 'en')
79 return languages.get(language, {}).get(p, p)
80 return languages.get(language, {}).get(s, s)
83 i18n_env = Environment(
84 loader=DictLoader(i18n_templates),
85 extensions=['jinja2.ext.i18n']
87 i18n_env.globals.update({
93 newstyle_i18n_env = Environment(
94 loader=DictLoader(newstyle_i18n_templates),
95 extensions=['jinja2.ext.i18n']
97 newstyle_i18n_env.install_gettext_callables(gettext, ngettext, newstyle=True)
99 class TestExtension(Extension):
103 def parse(self, parser):
104 return nodes.Output([self.call_method('_dump', [
105 nodes.EnvironmentAttribute('sandboxed'),
106 self.attr('ext_attr'),
107 nodes.ImportedName(__name__ + '.importable_object'),
108 nodes.ContextReference()
109 ])]).set_lineno(next(parser.stream).lineno)
111 def _dump(self, sandboxed, ext_attr, imported_object, context):
112 return '%s|%s|%s|%s' % (
120 class PreprocessorExtension(Extension):
122 def preprocess(self, source, name, filename=None):
123 return source.replace('[[TEST]]', '({{ foo }})')
126 class StreamFilterExtension(Extension):
128 def filter_stream(self, stream):
130 if token.type == 'data':
131 for t in self.interpolate(token):
136 def interpolate(self, token):
138 end = len(token.value)
139 lineno = token.lineno
141 match = _gettext_re.search(token.value, pos)
144 value = token.value[pos:match.start()]
146 yield Token(lineno, 'data', value)
147 lineno += count_newlines(token.value)
148 yield Token(lineno, 'variable_begin', None)
149 yield Token(lineno, 'name', 'gettext')
150 yield Token(lineno, 'lparen', None)
151 yield Token(lineno, 'string', match.group(1))
152 yield Token(lineno, 'rparen', None)
153 yield Token(lineno, 'variable_end', None)
156 yield Token(lineno, 'data', token.value[pos:])
159 class ExtensionsTestCase(JinjaTestCase):
161 def test_loop_controls(self):
162 env = Environment(extensions=['jinja2.ext.loopcontrols'])
164 tmpl = env.from_string('''
165 {%- for item in [1, 2, 3, 4] %}
166 {%- if item % 2 == 0 %}{% continue %}{% endif -%}
169 assert tmpl.render() == '13'
171 tmpl = env.from_string('''
172 {%- for item in [1, 2, 3, 4] %}
173 {%- if item > 2 %}{% break %}{% endif -%}
176 assert tmpl.render() == '12'
179 env = Environment(extensions=['jinja2.ext.do'])
180 tmpl = env.from_string('''
181 {%- set items = [] %}
182 {%- for char in "foo" %}
183 {%- do items.append(loop.index0 ~ char) %}
184 {%- endfor %}{{ items|join(', ') }}''')
185 assert tmpl.render() == '0f, 1o, 2o'
188 env = Environment(extensions=['jinja2.ext.with_'])
189 tmpl = env.from_string('''\
190 {% with a=42, b=23 -%}
195 assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
196 == ['42 = 23', '1 = 2']
198 def test_extension_nodes(self):
199 env = Environment(extensions=[TestExtension])
200 tmpl = env.from_string('{% test %}')
201 assert tmpl.render() == 'False|42|23|{}'
203 def test_identifier(self):
204 assert TestExtension.identifier == __name__ + '.TestExtension'
206 def test_rebinding(self):
207 original = Environment(extensions=[TestExtension])
208 overlay = original.overlay()
209 for env in original, overlay:
210 for ext in env.extensions.itervalues():
211 assert ext.environment is env
213 def test_preprocessor_extension(self):
214 env = Environment(extensions=[PreprocessorExtension])
215 tmpl = env.from_string('{[[TEST]]}')
216 assert tmpl.render(foo=42) == '{(42)}'
218 def test_streamfilter_extension(self):
219 env = Environment(extensions=[StreamFilterExtension])
220 env.globals['gettext'] = lambda x: x.upper()
221 tmpl = env.from_string('Foo _(bar) Baz')
223 assert out == 'Foo BAR Baz'
225 def test_extension_ordering(self):
230 env = Environment(extensions=[T1, T2])
231 ext = list(env.iter_extensions())
232 assert ext[0].__class__ is T1
233 assert ext[1].__class__ is T2
236 class InternationalizationTestCase(JinjaTestCase):
238 def test_trans(self):
239 tmpl = i18n_env.get_template('child.html')
240 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
242 def test_trans_plural(self):
243 tmpl = i18n_env.get_template('plural.html')
244 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
245 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
247 def test_complex_plural(self):
248 tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
249 'pluralize count %}{{ count }} items{% endtrans %}')
250 assert tmpl.render() == '2 items'
251 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
252 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
254 def test_trans_stringformatting(self):
255 tmpl = i18n_env.get_template('stringformat.html')
256 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
258 def test_extract(self):
259 from jinja2.ext import babel_extract
261 {{ gettext('Hello World') }}
262 {% trans %}Hello World{% endtrans %}
263 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
264 '''.encode('ascii')) # make python 3 happy
265 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
266 (2, 'gettext', u'Hello World', []),
267 (3, 'gettext', u'Hello World', []),
268 (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
271 def test_comment_extract(self):
272 from jinja2.ext import babel_extract
275 {{ gettext('Hello World') }}
276 {% trans %}Hello World{% endtrans %}{# trans second #}
278 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
279 '''.encode('utf-8')) # make python 3 happy
280 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
281 (3, 'gettext', u'Hello World', ['first']),
282 (4, 'gettext', u'Hello World', ['second']),
283 (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
287 class NewstyleInternationalizationTestCase(JinjaTestCase):
289 def test_trans(self):
290 tmpl = newstyle_i18n_env.get_template('child.html')
291 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
293 def test_trans_plural(self):
294 tmpl = newstyle_i18n_env.get_template('plural.html')
295 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
296 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
298 def test_complex_plural(self):
299 tmpl = newstyle_i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
300 'pluralize count %}{{ count }} items{% endtrans %}')
301 assert tmpl.render() == '2 items'
302 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
303 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
305 def test_trans_stringformatting(self):
306 tmpl = newstyle_i18n_env.get_template('stringformat.html')
307 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
309 def test_newstyle_plural(self):
310 tmpl = newstyle_i18n_env.get_template('ngettext.html')
311 assert tmpl.render(LANGUAGE='de', apples=1) == '1 Apfel'
312 assert tmpl.render(LANGUAGE='de', apples=5) == u'5 Äpfel'
314 def test_autoescape_support(self):
315 env = Environment(extensions=['jinja2.ext.autoescape',
317 env.install_gettext_callables(lambda x: u'<strong>Wert: %(name)s</strong>',
318 lambda s, p, n: s, newstyle=True)
319 t = env.from_string('{% autoescape ae %}{{ gettext("foo", name='
320 '"<test>") }}{% endautoescape %}')
321 assert t.render(ae=True) == '<strong>Wert: <test></strong>'
322 assert t.render(ae=False) == '<strong>Wert: <test></strong>'
325 class AutoEscapeTestCase(JinjaTestCase):
327 def test_scoped_setting(self):
328 env = Environment(extensions=['jinja2.ext.autoescape'],
330 tmpl = env.from_string('''
332 {% autoescape false %}
337 assert tmpl.render().split() == \
338 [u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
340 env = Environment(extensions=['jinja2.ext.autoescape'],
342 tmpl = env.from_string('''
344 {% autoescape true %}
349 assert tmpl.render().split() == \
350 [u'<HelloWorld>', u'<HelloWorld>', u'<HelloWorld>']
352 def test_nonvolatile(self):
353 env = Environment(extensions=['jinja2.ext.autoescape'],
355 tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
356 assert tmpl.render() == ' foo="<test>"'
357 tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
358 '|xmlattr|escape }}{% endautoescape %}')
359 assert tmpl.render() == ' foo="&lt;test&gt;"'
361 def test_volatile(self):
362 env = Environment(extensions=['jinja2.ext.autoescape'],
364 tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
365 '|xmlattr|escape }}{% endautoescape %}')
366 assert tmpl.render(foo=False) == ' foo="&lt;test&gt;"'
367 assert tmpl.render(foo=True) == ' foo="<test>"'
369 def test_scoping(self):
370 env = Environment(extensions=['jinja2.ext.autoescape'])
371 tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
372 '{% endautoescape %}{{ x }}{{ "<y>" }}')
373 assert tmpl.render(x=1) == '<x>1<y>'
375 def test_volatile_scoping(self):
376 env = Environment(extensions=['jinja2.ext.autoescape'])
382 {{ foo().__class__.__name__ }}
386 tmpl = env.from_string(tmplsource)
387 assert tmpl.render(val=True).split()[0] == 'Markup'
388 assert tmpl.render(val=False).split()[0] == unicode.__name__
390 # looking at the source we should see <testing> there in raw
391 # (and then escaped as well)
392 env = Environment(extensions=['jinja2.ext.autoescape'])
393 pysource = env.compile(tmplsource, raw=True)
394 assert '<testing>\\n' in pysource
396 env = Environment(extensions=['jinja2.ext.autoescape'],
398 pysource = env.compile(tmplsource, raw=True)
399 assert '<testing>\\n' in pysource
403 suite = unittest.TestSuite()
404 suite.addTest(unittest.makeSuite(ExtensionsTestCase))
405 suite.addTest(unittest.makeSuite(InternationalizationTestCase))
406 suite.addTest(unittest.makeSuite(NewstyleInternationalizationTestCase))
407 suite.addTest(unittest.makeSuite(AutoEscapeTestCase))