blob: 09b2b857af15f5d45974e334ad163ce582012928 [file] [log] [blame]
Armin Ronacher7af781c2010-02-09 16:05:08 +01001# -*- coding: utf-8 -*-
2"""
3 jinja2.testsuite.ext
4 ~~~~~~~~~~~~~~~~~~~~
5
6 Tests for the extensions.
7
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
10"""
11import re
12import unittest
13
14from jinja2.testsuite import JinjaTestCase, filesystem_loader
15
Armin Ronacher1fb42692010-02-09 21:14:16 +010016from jinja2 import Environment, DictLoader, contextfunction, nodes
17from jinja2.exceptions import TemplateAssertionError
Armin Ronacher7af781c2010-02-09 16:05:08 +010018from jinja2.ext import Extension
19from jinja2.lexer import Token, count_newlines
Armin Ronacher0d242be2010-02-10 01:35:13 +010020from jinja2.utils import next
21
22# 2.x / 3.x
23try:
24 from io import BytesIO
25except ImportError:
26 from StringIO import StringIO as BytesIO
Armin Ronacher7af781c2010-02-09 16:05:08 +010027
28
29importable_object = 23
30
31_gettext_re = re.compile(r'_\((.*?)\)(?s)')
32
33
Armin Ronacher1fb42692010-02-09 21:14:16 +010034templates = {
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: %d")|format(user_count) }}'
42}
43
44
45languages = {
46 'de': {
47 'missing': 'fehlend',
48 'watch out': 'pass auf',
49 'One user online': 'Ein Benutzer online',
50 '%(user_count)s users online': '%(user_count)s Benutzer online',
51 'User: %d': 'Benutzer: %d'
52 }
53}
54
55
56@contextfunction
57def gettext(context, string):
58 language = context.get('LANGUAGE', 'en')
59 return languages.get(language, {}).get(string, string)
60
61
62@contextfunction
63def ngettext(context, s, p, n):
64 language = context.get('LANGUAGE', 'en')
65 if n != 1:
66 return languages.get(language, {}).get(p, p)
67 return languages.get(language, {}).get(s, s)
68
69
70i18n_env = Environment(
71 loader=DictLoader(templates),
72 extensions=['jinja2.ext.i18n']
73)
74i18n_env.globals.update({
75 '_': gettext,
76 'gettext': gettext,
77 'ngettext': ngettext
78})
79
80
Armin Ronacher7af781c2010-02-09 16:05:08 +010081class TestExtension(Extension):
82 tags = set(['test'])
83 ext_attr = 42
84
85 def parse(self, parser):
86 return nodes.Output([self.call_method('_dump', [
87 nodes.EnvironmentAttribute('sandboxed'),
88 self.attr('ext_attr'),
89 nodes.ImportedName(__name__ + '.importable_object'),
90 nodes.ContextReference()
Armin Ronacher0d242be2010-02-10 01:35:13 +010091 ])]).set_lineno(next(parser.stream).lineno)
Armin Ronacher7af781c2010-02-09 16:05:08 +010092
93 def _dump(self, sandboxed, ext_attr, imported_object, context):
94 return '%s|%s|%s|%s' % (
95 sandboxed,
96 ext_attr,
97 imported_object,
98 context.blocks
99 )
100
101
102class PreprocessorExtension(Extension):
103
104 def preprocess(self, source, name, filename=None):
105 return source.replace('[[TEST]]', '({{ foo }})')
106
107
108class StreamFilterExtension(Extension):
109
110 def filter_stream(self, stream):
111 for token in stream:
112 if token.type == 'data':
113 for t in self.interpolate(token):
114 yield t
115 else:
116 yield token
117
118 def interpolate(self, token):
119 pos = 0
120 end = len(token.value)
121 lineno = token.lineno
122 while 1:
123 match = _gettext_re.search(token.value, pos)
124 if match is None:
125 break
126 value = token.value[pos:match.start()]
127 if value:
128 yield Token(lineno, 'data', value)
129 lineno += count_newlines(token.value)
130 yield Token(lineno, 'variable_begin', None)
131 yield Token(lineno, 'name', 'gettext')
132 yield Token(lineno, 'lparen', None)
133 yield Token(lineno, 'string', match.group(1))
134 yield Token(lineno, 'rparen', None)
135 yield Token(lineno, 'variable_end', None)
136 pos = match.end()
137 if pos < end:
138 yield Token(lineno, 'data', token.value[pos:])
139
140
141class ExtensionsTestCase(JinjaTestCase):
142
143 def test_loop_controls(self):
144 env = Environment(extensions=['jinja2.ext.loopcontrols'])
145
146 tmpl = env.from_string('''
147 {%- for item in [1, 2, 3, 4] %}
148 {%- if item % 2 == 0 %}{% continue %}{% endif -%}
149 {{ item }}
150 {%- endfor %}''')
151 assert tmpl.render() == '13'
152
153 tmpl = env.from_string('''
154 {%- for item in [1, 2, 3, 4] %}
155 {%- if item > 2 %}{% break %}{% endif -%}
156 {{ item }}
157 {%- endfor %}''')
158 assert tmpl.render() == '12'
159
160 def test_do(self):
161 env = Environment(extensions=['jinja2.ext.do'])
162 tmpl = env.from_string('''
163 {%- set items = [] %}
164 {%- for char in "foo" %}
165 {%- do items.append(loop.index0 ~ char) %}
166 {%- endfor %}{{ items|join(', ') }}''')
167 assert tmpl.render() == '0f, 1o, 2o'
168
169 def test_with(self):
170 env = Environment(extensions=['jinja2.ext.with_'])
171 tmpl = env.from_string('''\
172 {% with a=42, b=23 -%}
173 {{ a }} = {{ b }}
174 {% endwith -%}
175 {{ a }} = {{ b }}\
176 ''')
177 assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
178 == ['42 = 23', '1 = 2']
179
180 def test_extension_nodes(self):
181 env = Environment(extensions=[TestExtension])
182 tmpl = env.from_string('{% test %}')
183 assert tmpl.render() == 'False|42|23|{}'
184
185 def test_identifier(self):
186 assert TestExtension.identifier == __name__ + '.TestExtension'
187
188 def test_rebinding(self):
189 original = Environment(extensions=[TestExtension])
190 overlay = original.overlay()
191 for env in original, overlay:
192 for ext in env.extensions.itervalues():
193 assert ext.environment is env
194
195 def test_preprocessor_extension(self):
196 env = Environment(extensions=[PreprocessorExtension])
197 tmpl = env.from_string('{[[TEST]]}')
198 assert tmpl.render(foo=42) == '{(42)}'
199
200 def test_streamfilter_extension(self):
201 env = Environment(extensions=[StreamFilterExtension])
202 env.globals['gettext'] = lambda x: x.upper()
203 tmpl = env.from_string('Foo _(bar) Baz')
204 out = tmpl.render()
205 assert out == 'Foo BAR Baz'
206
207
Armin Ronacher1fb42692010-02-09 21:14:16 +0100208class InternationalizationTestCase(JinjaTestCase):
209
210 def test_trans(self):
211 tmpl = i18n_env.get_template('child.html')
212 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
213
214 def test_trans_plural(self):
215 tmpl = i18n_env.get_template('plural.html')
216 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
217 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
218
219 def test_complex_plural(self):
220 tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
221 'pluralize count %}{{ count }} items{% endtrans %}')
222 assert tmpl.render() == '2 items'
223 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
224 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
225
226 def test_trans_stringformatting(self):
227 tmpl = i18n_env.get_template('stringformat.html')
228 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
229
230 def test_extract(self):
231 from jinja2.ext import babel_extract
Armin Ronacher0d242be2010-02-10 01:35:13 +0100232 source = BytesIO('''
Armin Ronacher1fb42692010-02-09 21:14:16 +0100233 {{ gettext('Hello World') }}
234 {% trans %}Hello World{% endtrans %}
235 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
Armin Ronacher0d242be2010-02-10 01:35:13 +0100236 '''.encode('ascii')) # make python 3 happy
Armin Ronacher1fb42692010-02-09 21:14:16 +0100237 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
238 (2, 'gettext', u'Hello World', []),
239 (3, 'gettext', u'Hello World', []),
240 (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
241 ]
242
243 def test_comment_extract(self):
244 from jinja2.ext import babel_extract
Armin Ronacher0d242be2010-02-10 01:35:13 +0100245 source = BytesIO('''
Armin Ronacher1fb42692010-02-09 21:14:16 +0100246 {# trans first #}
247 {{ gettext('Hello World') }}
248 {% trans %}Hello World{% endtrans %}{# trans second #}
249 {#: third #}
250 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
Armin Ronacher0d242be2010-02-10 01:35:13 +0100251 '''.encode('utf-8')) # make python 3 happy
Armin Ronacher1fb42692010-02-09 21:14:16 +0100252 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
253 (3, 'gettext', u'Hello World', ['first']),
254 (4, 'gettext', u'Hello World', ['second']),
255 (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
256 ]
257
258
Armin Ronacher8346bd72010-03-14 19:43:47 +0100259class AutoEscapeTestCase(JinjaTestCase):
260
261 def test_scoped_setting(self):
262 env = Environment(extensions=['jinja2.ext.autoescape'],
263 autoescape=True)
264 tmpl = env.from_string('''
265 {{ "<HelloWorld>" }}
266 {% autoescape false %}
267 {{ "<HelloWorld>" }}
268 {% endautoescape %}
269 {{ "<HelloWorld>" }}
270 ''')
271 assert tmpl.render().split() == \
272 [u'&lt;HelloWorld&gt;', u'<HelloWorld>', u'&lt;HelloWorld&gt;']
273
274 env = Environment(extensions=['jinja2.ext.autoescape'],
275 autoescape=False)
276 tmpl = env.from_string('''
277 {{ "<HelloWorld>" }}
278 {% autoescape true %}
279 {{ "<HelloWorld>" }}
280 {% endautoescape %}
281 {{ "<HelloWorld>" }}
282 ''')
283 assert tmpl.render().split() == \
284 [u'<HelloWorld>', u'&lt;HelloWorld&gt;', u'<HelloWorld>']
285
286 def test_nonvolatile(self):
287 env = Environment(extensions=['jinja2.ext.autoescape'],
288 autoescape=True)
289 tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
290 assert tmpl.render() == ' foo="&lt;test&gt;"'
291 tmpl = env.from_string('{% autoescape false %}{{ {"foo": "<test>"}'
292 '|xmlattr|escape }}{% endautoescape %}')
293 assert tmpl.render() == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
294
295 def test_volatile(self):
296 env = Environment(extensions=['jinja2.ext.autoescape'],
297 autoescape=True)
298 tmpl = env.from_string('{% autoescape foo %}{{ {"foo": "<test>"}'
299 '|xmlattr|escape }}{% endautoescape %}')
300 assert tmpl.render(foo=False) == ' foo=&#34;&amp;lt;test&amp;gt;&#34;'
301 assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
302
303 def test_scoping(self):
304 env = Environment(extensions=['jinja2.ext.autoescape'])
305 tmpl = env.from_string('{% autoescape true %}{% set x = "<x>" %}{{ x }}'
306 '{% endautoescape %}{{ x }}{{ "<y>" }}')
307 assert tmpl.render(x=1) == '&lt;x&gt;1<y>'
308
309
Armin Ronacher7af781c2010-02-09 16:05:08 +0100310def suite():
311 suite = unittest.TestSuite()
312 suite.addTest(unittest.makeSuite(ExtensionsTestCase))
Armin Ronacher1fb42692010-02-09 21:14:16 +0100313 suite.addTest(unittest.makeSuite(InternationalizationTestCase))
Armin Ronacher8346bd72010-03-14 19:43:47 +0100314 suite.addTest(unittest.makeSuite(AutoEscapeTestCase))
Armin Ronacher7af781c2010-02-09 16:05:08 +0100315 return suite