blob: 3bb5276e3548f738fbc3b15800da5debb85da22f [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
20
21
22importable_object = 23
23
24_gettext_re = re.compile(r'_\((.*?)\)(?s)')
25
26
Armin Ronacher1fb42692010-02-09 21:14:16 +010027templates = {
28 'master.html': '<title>{{ page_title|default(_("missing")) }}</title>'
29 '{% block body %}{% endblock %}',
30 'child.html': '{% extends "master.html" %}{% block body %}'
31 '{% trans %}watch out{% endtrans %}{% endblock %}',
32 'plural.html': '{% trans user_count %}One user online{% pluralize %}'
33 '{{ user_count }} users online{% endtrans %}',
34 'stringformat.html': '{{ _("User: %d")|format(user_count) }}'
35}
36
37
38languages = {
39 'de': {
40 'missing': 'fehlend',
41 'watch out': 'pass auf',
42 'One user online': 'Ein Benutzer online',
43 '%(user_count)s users online': '%(user_count)s Benutzer online',
44 'User: %d': 'Benutzer: %d'
45 }
46}
47
48
49@contextfunction
50def gettext(context, string):
51 language = context.get('LANGUAGE', 'en')
52 return languages.get(language, {}).get(string, string)
53
54
55@contextfunction
56def ngettext(context, s, p, n):
57 language = context.get('LANGUAGE', 'en')
58 if n != 1:
59 return languages.get(language, {}).get(p, p)
60 return languages.get(language, {}).get(s, s)
61
62
63i18n_env = Environment(
64 loader=DictLoader(templates),
65 extensions=['jinja2.ext.i18n']
66)
67i18n_env.globals.update({
68 '_': gettext,
69 'gettext': gettext,
70 'ngettext': ngettext
71})
72
73
Armin Ronacher7af781c2010-02-09 16:05:08 +010074class TestExtension(Extension):
75 tags = set(['test'])
76 ext_attr = 42
77
78 def parse(self, parser):
79 return nodes.Output([self.call_method('_dump', [
80 nodes.EnvironmentAttribute('sandboxed'),
81 self.attr('ext_attr'),
82 nodes.ImportedName(__name__ + '.importable_object'),
83 nodes.ContextReference()
84 ])]).set_lineno(parser.stream.next().lineno)
85
86 def _dump(self, sandboxed, ext_attr, imported_object, context):
87 return '%s|%s|%s|%s' % (
88 sandboxed,
89 ext_attr,
90 imported_object,
91 context.blocks
92 )
93
94
95class PreprocessorExtension(Extension):
96
97 def preprocess(self, source, name, filename=None):
98 return source.replace('[[TEST]]', '({{ foo }})')
99
100
101class StreamFilterExtension(Extension):
102
103 def filter_stream(self, stream):
104 for token in stream:
105 if token.type == 'data':
106 for t in self.interpolate(token):
107 yield t
108 else:
109 yield token
110
111 def interpolate(self, token):
112 pos = 0
113 end = len(token.value)
114 lineno = token.lineno
115 while 1:
116 match = _gettext_re.search(token.value, pos)
117 if match is None:
118 break
119 value = token.value[pos:match.start()]
120 if value:
121 yield Token(lineno, 'data', value)
122 lineno += count_newlines(token.value)
123 yield Token(lineno, 'variable_begin', None)
124 yield Token(lineno, 'name', 'gettext')
125 yield Token(lineno, 'lparen', None)
126 yield Token(lineno, 'string', match.group(1))
127 yield Token(lineno, 'rparen', None)
128 yield Token(lineno, 'variable_end', None)
129 pos = match.end()
130 if pos < end:
131 yield Token(lineno, 'data', token.value[pos:])
132
133
134class ExtensionsTestCase(JinjaTestCase):
135
136 def test_loop_controls(self):
137 env = Environment(extensions=['jinja2.ext.loopcontrols'])
138
139 tmpl = env.from_string('''
140 {%- for item in [1, 2, 3, 4] %}
141 {%- if item % 2 == 0 %}{% continue %}{% endif -%}
142 {{ item }}
143 {%- endfor %}''')
144 assert tmpl.render() == '13'
145
146 tmpl = env.from_string('''
147 {%- for item in [1, 2, 3, 4] %}
148 {%- if item > 2 %}{% break %}{% endif -%}
149 {{ item }}
150 {%- endfor %}''')
151 assert tmpl.render() == '12'
152
153 def test_do(self):
154 env = Environment(extensions=['jinja2.ext.do'])
155 tmpl = env.from_string('''
156 {%- set items = [] %}
157 {%- for char in "foo" %}
158 {%- do items.append(loop.index0 ~ char) %}
159 {%- endfor %}{{ items|join(', ') }}''')
160 assert tmpl.render() == '0f, 1o, 2o'
161
162 def test_with(self):
163 env = Environment(extensions=['jinja2.ext.with_'])
164 tmpl = env.from_string('''\
165 {% with a=42, b=23 -%}
166 {{ a }} = {{ b }}
167 {% endwith -%}
168 {{ a }} = {{ b }}\
169 ''')
170 assert [x.strip() for x in tmpl.render(a=1, b=2).splitlines()] \
171 == ['42 = 23', '1 = 2']
172
173 def test_extension_nodes(self):
174 env = Environment(extensions=[TestExtension])
175 tmpl = env.from_string('{% test %}')
176 assert tmpl.render() == 'False|42|23|{}'
177
178 def test_identifier(self):
179 assert TestExtension.identifier == __name__ + '.TestExtension'
180
181 def test_rebinding(self):
182 original = Environment(extensions=[TestExtension])
183 overlay = original.overlay()
184 for env in original, overlay:
185 for ext in env.extensions.itervalues():
186 assert ext.environment is env
187
188 def test_preprocessor_extension(self):
189 env = Environment(extensions=[PreprocessorExtension])
190 tmpl = env.from_string('{[[TEST]]}')
191 assert tmpl.render(foo=42) == '{(42)}'
192
193 def test_streamfilter_extension(self):
194 env = Environment(extensions=[StreamFilterExtension])
195 env.globals['gettext'] = lambda x: x.upper()
196 tmpl = env.from_string('Foo _(bar) Baz')
197 out = tmpl.render()
198 assert out == 'Foo BAR Baz'
199
200
Armin Ronacher1fb42692010-02-09 21:14:16 +0100201class InternationalizationTestCase(JinjaTestCase):
202
203 def test_trans(self):
204 tmpl = i18n_env.get_template('child.html')
205 assert tmpl.render(LANGUAGE='de') == '<title>fehlend</title>pass auf'
206
207 def test_trans_plural(self):
208 tmpl = i18n_env.get_template('plural.html')
209 assert tmpl.render(LANGUAGE='de', user_count=1) == 'Ein Benutzer online'
210 assert tmpl.render(LANGUAGE='de', user_count=2) == '2 Benutzer online'
211
212 def test_complex_plural(self):
213 tmpl = i18n_env.from_string('{% trans foo=42, count=2 %}{{ count }} item{% '
214 'pluralize count %}{{ count }} items{% endtrans %}')
215 assert tmpl.render() == '2 items'
216 self.assert_raises(TemplateAssertionError, i18n_env.from_string,
217 '{% trans foo %}...{% pluralize bar %}...{% endtrans %}')
218
219 def test_trans_stringformatting(self):
220 tmpl = i18n_env.get_template('stringformat.html')
221 assert tmpl.render(LANGUAGE='de', user_count=5) == 'Benutzer: 5'
222
223 def test_extract(self):
224 from jinja2.ext import babel_extract
225 from StringIO import StringIO
226 source = StringIO('''
227 {{ gettext('Hello World') }}
228 {% trans %}Hello World{% endtrans %}
229 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
230 ''')
231 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), [], {})) == [
232 (2, 'gettext', u'Hello World', []),
233 (3, 'gettext', u'Hello World', []),
234 (4, 'ngettext', (u'%(users)s user', u'%(users)s users', None), [])
235 ]
236
237 def test_comment_extract(self):
238 from jinja2.ext import babel_extract
239 from StringIO import StringIO
240 source = StringIO('''
241 {# trans first #}
242 {{ gettext('Hello World') }}
243 {% trans %}Hello World{% endtrans %}{# trans second #}
244 {#: third #}
245 {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
246 ''')
247 assert list(babel_extract(source, ('gettext', 'ngettext', '_'), ['trans', ':'], {})) == [
248 (3, 'gettext', u'Hello World', ['first']),
249 (4, 'gettext', u'Hello World', ['second']),
250 (6, 'ngettext', (u'%(users)s user', u'%(users)s users', None), ['third'])
251 ]
252
253
Armin Ronacher7af781c2010-02-09 16:05:08 +0100254def suite():
255 suite = unittest.TestSuite()
256 suite.addTest(unittest.makeSuite(ExtensionsTestCase))
Armin Ronacher1fb42692010-02-09 21:14:16 +0100257 suite.addTest(unittest.makeSuite(InternationalizationTestCase))
Armin Ronacher7af781c2010-02-09 16:05:08 +0100258 return suite