Down to 7 failures for Python 3. We're onto something.
[jinja2.git] / jinja2 / testsuite / ext.py
1 # -*- 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 """
11 import re
12 import unittest
13
14 from jinja2.testsuite import JinjaTestCase, filesystem_loader
15
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
21
22 # 2.x / 3.x
23 try:
24     from io import BytesIO
25 except ImportError:
26     from StringIO import StringIO as BytesIO
27
28
29 importable_object = 23
30
31 _gettext_re = re.compile(r'_\((.*?)\)(?s)')
32
33
34 templates = {
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
45 languages = {
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
57 def gettext(context, string):
58     language = context.get('LANGUAGE', 'en')
59     return languages.get(language, {}).get(string, string)
60
61
62 @contextfunction
63 def 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
70 i18n_env = Environment(
71     loader=DictLoader(templates),
72     extensions=['jinja2.ext.i18n']
73 )
74 i18n_env.globals.update({
75     '_':            gettext,
76     'gettext':      gettext,
77     'ngettext':     ngettext
78 })
79
80
81 class 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()
91         ])]).set_lineno(next(parser.stream).lineno)
92
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
102 class PreprocessorExtension(Extension):
103
104     def preprocess(self, source, name, filename=None):
105         return source.replace('[[TEST]]', '({{ foo }})')
106
107
108 class 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
141 class 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
208 class 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
232         source = BytesIO('''
233         {{ gettext('Hello World') }}
234         {% trans %}Hello World{% endtrans %}
235         {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
236         '''.encode('ascii')) # make python 3 happy
237         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
245         source = BytesIO('''
246         {# trans first #}
247         {{ gettext('Hello World') }}
248         {% trans %}Hello World{% endtrans %}{# trans second #}
249         {#: third #}
250         {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
251         '''.encode('utf-8')) # make python 3 happy
252         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
259 def suite():
260     suite = unittest.TestSuite()
261     suite.addTest(unittest.makeSuite(ExtensionsTestCase))
262     suite.addTest(unittest.makeSuite(InternationalizationTestCase))
263     return suite