1 # -*- coding: utf-8 -*-
3 jinja2.testsuite.lexnparse
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
6 All the unittests regarding lexing, parsing and syntax.
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
14 from jinja2
.testsuite
import JinjaTestCase
16 from jinja2
import Environment
, Template
, TemplateSyntaxError
, \
22 # how does a string look like in jinja syntax?
23 if sys
.version_info
< (3, 0):
24 def jinja_string_repr(string
):
25 return repr(string
)[1:]
27 jinja_string_repr
= repr
30 class LexerTestCase(JinjaTestCase
):
33 tmpl
= env
.from_string('{% raw %}foo{% endraw %}|'
34 '{%raw%}{{ bar }}|{% baz %}{% endraw %}')
35 assert tmpl
.render() == 'foo|{{ bar }}|{% baz %}'
38 tmpl
= env
.from_string('1 {%- raw -%} 2 {%- endraw -%} 3')
39 assert tmpl
.render() == '123'
41 def test_balancing(self
):
42 env
= Environment('{%', '%}', '${', '}')
43 tmpl
= env
.from_string('''{% for item in seq
44 %}${{'foo': item}|upper}{% endfor %}''')
45 assert tmpl
.render(seq
=range(3)) == "{'FOO': 0}{'FOO': 1}{'FOO': 2}"
47 def test_comments(self
):
48 env
= Environment('<!--', '-->', '{', '}')
49 tmpl
= env
.from_string('''\
51 <!--- for item in seq -->
55 assert tmpl
.render(seq
=range(3)) == ("<ul>\n <li>0</li>\n "
56 "<li>1</li>\n <li>2</li>\n</ul>")
58 def test_string_escapes(self
):
59 for char
in u
'\0', u
'\u2668', u
'\xe4', u
'\t', u
'\r', u
'\n':
60 tmpl
= env
.from_string('{{ %s }}' % jinja_string_repr(char
))
61 assert tmpl
.render() == char
62 assert env
.from_string('{{ "\N{HOT SPRINGS}" }}').render() == u
'\u2668'
64 def test_bytefallback(self
):
65 from pprint
import pformat
66 tmpl
= env
.from_string(u
'''{{ 'foo'|pprint }}|{{ 'bär'|pprint }}''')
67 assert tmpl
.render() == pformat('foo') + '|' + pformat(u
'bär')
69 def test_operators(self
):
70 from jinja2
.lexer
import operators
71 for test
, expect
in operators
.iteritems():
74 stream
= env
.lexer
.tokenize('{{ %s }}' % test
)
76 assert stream
.current
.type == expect
78 def test_normalizing(self
):
79 for seq
in '\r', '\r\n', '\n':
80 env
= Environment(newline_sequence
=seq
)
81 tmpl
= env
.from_string('1\n2\r\n3\n4\n')
82 result
= tmpl
.render()
83 assert result
.replace(seq
, 'X') == '1X2X3X4'
86 class ParserTestCase(JinjaTestCase
):
88 def test_php_syntax(self
):
89 env
= Environment('<?', '?>', '<?=', '?>', '<!--', '-->')
90 tmpl
= env
.from_string('''\
91 <!-- I'm a comment, I'm not interesting -->\
92 <? for item in seq -?>
95 assert tmpl
.render(seq
=range(5)) == '01234'
97 def test_erb_syntax(self
):
98 env
= Environment('<%', '%>', '<%=', '%>', '<%#', '%>')
99 tmpl
= env
.from_string('''\
100 <%# I'm a comment, I'm not interesting %>\
101 <% for item in seq -%>
104 assert tmpl
.render(seq
=range(5)) == '01234'
106 def test_comment_syntax(self
):
107 env
= Environment('<!--', '-->', '${', '}', '<!--#', '-->')
108 tmpl
= env
.from_string('''\
109 <!--# I'm a comment, I'm not interesting -->\
110 <!-- for item in seq --->
113 assert tmpl
.render(seq
=range(5)) == '01234'
115 def test_balancing(self
):
116 tmpl
= env
.from_string('''{{{'foo':'bar'}.foo}}''')
117 assert tmpl
.render() == 'bar'
119 def test_start_comment(self
):
120 tmpl
= env
.from_string('''{# foo comment
122 {% macro blub() %}foo{% endmacro %}
124 assert tmpl
.render().strip() == 'foo'
126 def test_line_syntax(self
):
127 env
= Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
128 tmpl
= env
.from_string('''\
129 <%# regular comment %>
133 assert [int(x
.strip()) for x
in tmpl
.render(seq
=range(5)).split()] == \
136 env
= Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
137 tmpl
= env
.from_string('''\
138 <%# regular comment %>
140 ${item} ## the rest of the stuff
142 assert [int(x
.strip()) for x
in tmpl
.render(seq
=range(5)).split()] == \
145 def test_line_syntax_priority(self
):
146 # XXX: why is the whitespace there in front of the newline?
147 env
= Environment('{%', '%}', '${', '}', '/*', '*/', '##', '#')
148 tmpl
= env
.from_string('''\
150 I'm a multiline comment */
152 * ${item} # this is just extra stuff
154 assert tmpl
.render(seq
=[1, 2]).strip() == '* 1\n* 2'
155 env
= Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
156 tmpl
= env
.from_string('''\
158 I'm a multiline comment */
160 * ${item} ## this is just extra stuff
161 ## extra stuff i just want to ignore
163 assert tmpl
.render(seq
=[1, 2]).strip() == '* 1\n\n* 2'
165 def test_error_messages(self
):
166 def assert_error(code
, expected
):
169 except TemplateSyntaxError
, e
:
170 assert str(e
) == expected
, 'unexpected error message'
172 assert False, 'that was suposed to be an error'
174 assert_error('{% for item in seq %}...{% endif %}',
175 "Encountered unknown tag 'endif'. Jinja was looking "
176 "for the following tags: 'endfor' or 'else'. The "
177 "innermost block that needs to be closed is 'for'.")
178 assert_error('{% if foo %}{% for item in seq %}...{% endfor %}{% endfor %}',
179 "Encountered unknown tag 'endfor'. Jinja was looking for "
180 "the following tags: 'elif' or 'else' or 'endif'. The "
181 "innermost block that needs to be closed is 'if'.")
182 assert_error('{% if foo %}',
183 "Unexpected end of template. Jinja was looking for the "
184 "following tags: 'elif' or 'else' or 'endif'. The "
185 "innermost block that needs to be closed is 'if'.")
186 assert_error('{% for item in seq %}',
187 "Unexpected end of template. Jinja was looking for the "
188 "following tags: 'endfor' or 'else'. The innermost block "
189 "that needs to be closed is 'for'.")
190 assert_error('{% block foo-bar-baz %}',
191 "Block names in Jinja have to be valid Python identifiers "
192 "and may not contain hypens, use an underscore instead.")
193 assert_error('{% unknown_tag %}',
194 "Encountered unknown tag 'unknown_tag'.")
197 class SyntaxTestCase(JinjaTestCase
):
201 env
.globals['foo'] = lambda a
, b
, c
, e
, g
: a
+ b
+ c
+ e
+ g
202 tmpl
= env
.from_string("{{ foo('a', c='d', e='f', *['b'], **{'g': 'h'}) }}")
203 assert tmpl
.render() == 'abdfh'
205 def test_slicing(self
):
206 tmpl
= env
.from_string('{{ [1, 2, 3][:] }}|{{ [1, 2, 3][::-1] }}')
207 assert tmpl
.render() == '[1, 2, 3]|[3, 2, 1]'
210 tmpl
= env
.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
211 assert tmpl
.render(foo
={'bar': 42}) == '42|42'
213 def test_subscript(self
):
214 tmpl
= env
.from_string("{{ foo[0] }}|{{ foo[-1] }}")
215 assert tmpl
.render(foo
=[0, 1, 2]) == '0|2'
217 def test_tuple(self
):
218 tmpl
= env
.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
219 assert tmpl
.render() == '()|(1,)|(1, 2)'
222 tmpl
= env
.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
223 assert tmpl
.render() == '1.5|8'
226 tmpl
= env
.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
227 assert tmpl
.render() == '1|1.5|1'
229 def test_unary(self
):
230 tmpl
= env
.from_string('{{ +3 }}|{{ -3 }}')
231 assert tmpl
.render() == '3|-3'
233 def test_concat(self
):
234 tmpl
= env
.from_string("{{ [1, 2] ~ 'foo' }}")
235 assert tmpl
.render() == '[1, 2]foo'
237 def test_compare(self
):
238 tmpl
= env
.from_string('{{ 1 > 0 }}|{{ 1 >= 1 }}|{{ 2 < 3 }}|'
239 '{{ 2 == 2 }}|{{ 1 <= 1 }}')
240 assert tmpl
.render() == 'True|True|True|True|True'
243 tmpl
= env
.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
244 assert tmpl
.render() == 'True|False'
246 def test_literals(self
):
247 tmpl
= env
.from_string('{{ [] }}|{{ {} }}|{{ () }}')
248 assert tmpl
.render().lower() == '[]|{}|()'
251 tmpl
= env
.from_string('{{ true and false }}|{{ false '
252 'or true }}|{{ not false }}')
253 assert tmpl
.render() == 'False|True|True'
255 def test_grouping(self
):
256 tmpl
= env
.from_string('{{ (true and false) or (false and true) and not false }}')
257 assert tmpl
.render() == 'False'
259 def test_django_attr(self
):
260 tmpl
= env
.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
261 assert tmpl
.render() == '1|1'
263 def test_conditional_expression(self
):
264 tmpl
= env
.from_string('''{{ 0 if true else 1 }}''')
265 assert tmpl
.render() == '0'
267 def test_short_conditional_expression(self
):
268 tmpl
= env
.from_string('<{{ 1 if false }}>')
269 assert tmpl
.render() == '<>'
271 tmpl
= env
.from_string('<{{ (1 if false).bar }}>')
272 self
.assert_raises(UndefinedError
, tmpl
.render
)
274 def test_filter_priority(self
):
275 tmpl
= env
.from_string('{{ "foo"|upper + "bar"|upper }}')
276 assert tmpl
.render() == 'FOOBAR'
278 def test_function_calls(self
):
281 (True, '*foo, *bar'),
282 (True, '*foo, bar=42'),
283 (True, '**foo, *bar'),
284 (True, '**foo, bar'),
286 (False, 'foo, bar=42'),
287 (False, 'foo, bar=23, *args'),
288 (False, 'a, b=c, *d, **e'),
289 (False, '*foo, **bar')
291 for should_fail
, sig
in tests
:
293 self
.assert_raises(TemplateSyntaxError
,
294 env
.from_string
, '{{ foo(%s) }}' % sig
)
296 env
.from_string('foo(%s)' % sig
)
298 def test_tuple_expr(self
):
305 '{% for foo, bar in seq %}...{% endfor %}',
306 '{% for x in foo, bar %}...{% endfor %}',
307 '{% for x in foo, %}...{% endfor %}'
309 assert env
.from_string(tmpl
)
311 def test_trailing_comma(self
):
312 tmpl
= env
.from_string('{{ (1, 2,) }}|{{ [1, 2,] }}|{{ {1: 2,} }}')
313 assert tmpl
.render().lower() == '(1, 2)|[1, 2]|{1: 2}'
315 def test_block_end_name(self
):
316 env
.from_string('{% block foo %}...{% endblock foo %}')
317 self
.assert_raises(TemplateSyntaxError
, env
.from_string
,
318 '{% block x %}{% endblock y %}')
320 def test_contant_casing(self
):
321 for const
in True, False, None:
322 tmpl
= env
.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % (
323 str(const
), str(const
).lower(), str(const
).upper()
325 assert tmpl
.render() == '%s|%s|' % (const
, const
)
327 def test_test_chaining(self
):
328 self
.assert_raises(TemplateSyntaxError
, env
.from_string
,
329 '{{ foo is string is sequence }}')
330 env
.from_string('{{ 42 is string or 42 is number }}'
333 def test_string_concatenation(self
):
334 tmpl
= env
.from_string('{{ "foo" "bar" "baz" }}')
335 assert tmpl
.render() == 'foobarbaz'
337 def test_notin(self
):
339 tmpl
= env
.from_string('''{{ not 42 in bar }}''')
340 assert tmpl
.render(bar
=bar
) == unicode(not 42 in bar
)
342 def test_implicit_subscribed_tuple(self
):
344 def __getitem__(self
, x
):
346 t
= env
.from_string('{{ foo[1, 2] }}')
347 assert t
.render(foo
=Foo()) == u
'(1, 2)'
350 tmpl
= env
.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
351 assert tmpl
.render() == '{{ FOO }} and {% BAR %}'
353 def test_const(self
):
354 tmpl
= env
.from_string('{{ true }}|{{ false }}|{{ none }}|'
355 '{{ none is defined }}|{{ missing is defined }}')
356 assert tmpl
.render() == 'True|False|None|True|False'
358 def test_neg_filter_priority(self
):
359 node
= env
.parse('{{ -1|foo }}')
360 assert isinstance(node
.body
[0].nodes
[0], nodes
.Filter
)
361 assert isinstance(node
.body
[0].nodes
[0].node
, nodes
.Neg
)
363 def test_const_assign(self
):
364 constass1
= '''{% set true = 42 %}'''
365 constass2
= '''{% for none in seq %}{% endfor %}'''
366 for tmpl
in constass1
, constass2
:
367 self
.assert_raises(TemplateSyntaxError
, env
.from_string
, tmpl
)
369 def test_localset(self
):
370 tmpl
= env
.from_string('''{% set foo = 0 %}\
371 {% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
373 assert tmpl
.render() == '0'
375 def test_parse_unary(self
):
376 tmpl
= env
.from_string('{{ -foo["bar"] }}')
377 assert tmpl
.render(foo
={'bar': 42}) == '-42'
378 tmpl
= env
.from_string('{{ -foo["bar"]|abs }}')
379 assert tmpl
.render(foo
={'bar': 42}) == '42'
383 suite
= unittest
.TestSuite()
384 suite
.addTest(unittest
.makeSuite(LexerTestCase
))
385 suite
.addTest(unittest
.makeSuite(ParserTestCase
))
386 suite
.addTest(unittest
.makeSuite(SyntaxTestCase
))