fix matching typo in tests, restore missing assert
[jinja2.git] / jinja2 / testsuite / lexnparse.py
1 # -*- coding: utf-8 -*-
2 """
3     jinja2.testsuite.lexnparse
4     ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6     All the unittests regarding lexing, parsing and syntax.
7
8     :copyright: (c) 2010 by the Jinja Team.
9     :license: BSD, see LICENSE for more details.
10 """
11 import sys
12 import unittest
13
14 from jinja2.testsuite import JinjaTestCase
15
16 from jinja2 import Environment, Template, TemplateSyntaxError, \
17      UndefinedError, nodes
18
19 env = Environment()
20
21
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:]
26 else:
27     jinja_string_repr = repr
28
29
30 class LexerTestCase(JinjaTestCase):
31
32     def test_raw1(self):
33         tmpl = env.from_string('{% raw %}foo{% endraw %}|'
34                                '{%raw%}{{ bar }}|{% baz %}{%       endraw    %}')
35         assert tmpl.render() == 'foo|{{ bar }}|{% baz %}'
36
37     def test_raw2(self):
38         tmpl = env.from_string('1  {%- raw -%}   2   {%- endraw -%}   3')
39         assert tmpl.render() == '123'
40
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}"
46
47     def test_comments(self):
48         env = Environment('<!--', '-->', '{', '}')
49         tmpl = env.from_string('''\
50 <ul>
51 <!--- for item in seq -->
52   <li>{item}</li>
53 <!--- endfor -->
54 </ul>''')
55         assert tmpl.render(seq=range(3)) == ("<ul>\n  <li>0</li>\n  "
56                                              "<li>1</li>\n  <li>2</li>\n</ul>")
57
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'
63
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')
68
69     def test_operators(self):
70         from jinja2.lexer import operators
71         for test, expect in operators.iteritems():
72             if test in '([{}])':
73                 continue
74             stream = env.lexer.tokenize('{{ %s }}' % test)
75             stream.next()
76             assert stream.current.type == expect
77
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'
84
85
86 class ParserTestCase(JinjaTestCase):
87
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 -?>
93     <?= item ?>
94 <?- endfor ?>''')
95         assert tmpl.render(seq=range(5)) == '01234'
96
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 -%>
102     <%= item %>
103 <%- endfor %>''')
104         assert tmpl.render(seq=range(5)) == '01234'
105
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 --->
111     ${item}
112 <!--- endfor -->''')
113         assert tmpl.render(seq=range(5)) == '01234'
114
115     def test_balancing(self):
116         tmpl = env.from_string('''{{{'foo':'bar'}.foo}}''')
117         assert tmpl.render() == 'bar'
118
119     def test_start_comment(self):
120         tmpl = env.from_string('''{# foo comment
121 and bar comment #}
122 {% macro blub() %}foo{% endmacro %}
123 {{ blub() }}''')
124         assert tmpl.render().strip() == 'foo'
125
126     def test_line_syntax(self):
127         env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%')
128         tmpl = env.from_string('''\
129 <%# regular comment %>
130 % for item in seq:
131     ${item}
132 % endfor''')
133         assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
134                range(5)
135
136         env = Environment('<%', '%>', '${', '}', '<%#', '%>', '%', '##')
137         tmpl = env.from_string('''\
138 <%# regular comment %>
139 % for item in seq:
140     ${item} ## the rest of the stuff
141 % endfor''')
142         assert [int(x.strip()) for x in tmpl.render(seq=range(5)).split()] == \
143                 range(5)
144
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('''\
149 /* ignore me.
150    I'm a multiline comment */
151 ## for item in seq:
152 * ${item}          # this is just extra stuff
153 ## endfor''')
154         assert tmpl.render(seq=[1, 2]).strip() == '* 1\n* 2'
155         env = Environment('{%', '%}', '${', '}', '/*', '*/', '#', '##')
156         tmpl = env.from_string('''\
157 /* ignore me.
158    I'm a multiline comment */
159 # for item in seq:
160 * ${item}          ## this is just extra stuff
161     ## extra stuff i just want to ignore
162 # endfor''')
163         assert tmpl.render(seq=[1, 2]).strip() == '* 1\n\n* 2'
164
165     def test_error_messages(self):
166         def assert_error(code, expected):
167             try:
168                 Template(code)
169             except TemplateSyntaxError, e:
170                 assert str(e) == expected, 'unexpected error message'
171             else:
172                 assert False, 'that was supposed to be an error'
173
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 hyphens, use an underscore instead.")
193         assert_error('{% unknown_tag %}',
194                      "Encountered unknown tag 'unknown_tag'.")
195
196
197 class SyntaxTestCase(JinjaTestCase):
198
199     def test_call(self):
200         env = Environment()
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'
204
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]'
208
209     def test_attr(self):
210         tmpl = env.from_string("{{ foo.bar }}|{{ foo['bar'] }}")
211         assert tmpl.render(foo={'bar': 42}) == '42|42'
212
213     def test_subscript(self):
214         tmpl = env.from_string("{{ foo[0] }}|{{ foo[-1] }}")
215         assert tmpl.render(foo=[0, 1, 2]) == '0|2'
216
217     def test_tuple(self):
218         tmpl = env.from_string('{{ () }}|{{ (1,) }}|{{ (1, 2) }}')
219         assert tmpl.render() == '()|(1,)|(1, 2)'
220
221     def test_math(self):
222         tmpl = env.from_string('{{ (1 + 1 * 2) - 3 / 2 }}|{{ 2**3 }}')
223         assert tmpl.render() == '1.5|8'
224
225     def test_div(self):
226         tmpl = env.from_string('{{ 3 // 2 }}|{{ 3 / 2 }}|{{ 3 % 2 }}')
227         assert tmpl.render() == '1|1.5|1'
228
229     def test_unary(self):
230         tmpl = env.from_string('{{ +3 }}|{{ -3 }}')
231         assert tmpl.render() == '3|-3'
232
233     def test_concat(self):
234         tmpl = env.from_string("{{ [1, 2] ~ 'foo' }}")
235         assert tmpl.render() == '[1, 2]foo'
236
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'
241
242     def test_inop(self):
243         tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
244         assert tmpl.render() == 'True|False'
245
246     def test_literals(self):
247         tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}')
248         assert tmpl.render().lower() == '[]|{}|()'
249
250     def test_bool(self):
251         tmpl = env.from_string('{{ true and false }}|{{ false '
252                                'or true }}|{{ not false }}')
253         assert tmpl.render() == 'False|True|True'
254
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'
258
259     def test_django_attr(self):
260         tmpl = env.from_string('{{ [1, 2, 3].0 }}|{{ [[1]].0.0 }}')
261         assert tmpl.render() == '1|1'
262
263     def test_conditional_expression(self):
264         tmpl = env.from_string('''{{ 0 if true else 1 }}''')
265         assert tmpl.render() == '0'
266
267     def test_short_conditional_expression(self):
268         tmpl = env.from_string('<{{ 1 if false }}>')
269         assert tmpl.render() == '<>'
270
271         tmpl = env.from_string('<{{ (1 if false).bar }}>')
272         self.assert_raises(UndefinedError, tmpl.render)
273
274     def test_filter_priority(self):
275         tmpl = env.from_string('{{ "foo"|upper + "bar"|upper }}')
276         assert tmpl.render() == 'FOOBAR'
277
278     def test_function_calls(self):
279         tests = [
280             (True, '*foo, bar'),
281             (True, '*foo, *bar'),
282             (True, '*foo, bar=42'),
283             (True, '**foo, *bar'),
284             (True, '**foo, bar'),
285             (False, 'foo, bar'),
286             (False, 'foo, bar=42'),
287             (False, 'foo, bar=23, *args'),
288             (False, 'a, b=c, *d, **e'),
289             (False, '*foo, **bar')
290         ]
291         for should_fail, sig in tests:
292             if should_fail:
293                 self.assert_raises(TemplateSyntaxError,
294                     env.from_string, '{{ foo(%s) }}' % sig)
295             else:
296                 env.from_string('foo(%s)' % sig)
297
298     def test_tuple_expr(self):
299         for tmpl in [
300             '{{ () }}',
301             '{{ (1, 2) }}',
302             '{{ (1, 2,) }}',
303             '{{ 1, }}',
304             '{{ 1, 2 }}',
305             '{% for foo, bar in seq %}...{% endfor %}',
306             '{% for x in foo, bar %}...{% endfor %}',
307             '{% for x in foo, %}...{% endfor %}'
308         ]:
309             assert env.from_string(tmpl)
310
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}'
314
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 %}')
319
320     def test_constant_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()
324             ))
325             assert tmpl.render() == '%s|%s|' % (const, const)
326
327     def test_test_chaining(self):
328         self.assert_raises(TemplateSyntaxError, env.from_string,
329                            '{{ foo is string is sequence }}')
330         assert env.from_string('{{ 42 is string or 42 is number }}'
331             ).render() == 'True'
332
333     def test_string_concatenation(self):
334         tmpl = env.from_string('{{ "foo" "bar" "baz" }}')
335         assert tmpl.render() == 'foobarbaz'
336
337     def test_notin(self):
338         bar = xrange(100)
339         tmpl = env.from_string('''{{ not 42 in bar }}''')
340         assert tmpl.render(bar=bar) == unicode(not 42 in bar)
341
342     def test_implicit_subscribed_tuple(self):
343         class Foo(object):
344             def __getitem__(self, x):
345                 return x
346         t = env.from_string('{{ foo[1, 2] }}')
347         assert t.render(foo=Foo()) == u'(1, 2)'
348
349     def test_raw2(self):
350         tmpl = env.from_string('{% raw %}{{ FOO }} and {% BAR %}{% endraw %}')
351         assert tmpl.render() == '{{ FOO }} and {% BAR %}'
352
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'
357
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)
362
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)
368
369     def test_localset(self):
370         tmpl = env.from_string('''{% set foo = 0 %}\
371 {% for item in [1, 2] %}{% set foo = 1 %}{% endfor %}\
372 {{ foo }}''')
373         assert tmpl.render() == '0'
374
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'
380
381
382 def suite():
383     suite = unittest.TestSuite()
384     suite.addTest(unittest.makeSuite(LexerTestCase))
385     suite.addTest(unittest.makeSuite(ParserTestCase))
386     suite.addTest(unittest.makeSuite(SyntaxTestCase))
387     return suite