2
from collections import namedtuple
5
from markupsafe import Markup
7
from jinja2 import Environment
8
from jinja2 import StrictUndefined
9
from jinja2 import TemplateRuntimeError
10
from jinja2 import UndefinedError
11
from jinja2.exceptions import TemplateAssertionError
15
def __init__(self, value):
19
return str(self.value)
23
def __init__(self, value1, value2):
28
return f"({self.value1},{self.value2})"
32
def test_filter_calling(self, env):
33
rv = env.call_filter("sum", [1, 2, 3])
36
def test_capitalize(self, env):
37
tmpl = env.from_string('{{ "foo bar"|capitalize }}')
38
assert tmpl.render() == "Foo bar"
40
def test_center(self, env):
41
tmpl = env.from_string('{{ "foo"|center(9) }}')
42
assert tmpl.render() == " foo "
44
def test_default(self, env):
45
tmpl = env.from_string(
46
"{{ missing|default('no') }}|{{ false|default('no') }}|"
47
"{{ false|default('no', true) }}|{{ given|default('no') }}"
49
assert tmpl.render(given="yes") == "no|False|no|yes"
51
@pytest.mark.parametrize(
54
("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"),
55
("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"),
56
('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"),
57
("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"),
60
def test_dictsort(self, env, args, expect):
61
t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}")
62
out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
65
def test_batch(self, env):
66
tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}")
67
out = tmpl.render(foo=list(range(10)))
69
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
70
"[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]"
73
def test_slice(self, env):
74
tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}")
75
out = tmpl.render(foo=list(range(10)))
77
"[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
78
"[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
81
def test_escape(self, env):
82
tmpl = env.from_string("""{{ '<">&'|escape }}""")
84
assert out == "<">&"
86
@pytest.mark.parametrize(
87
("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")]
89
def test_trim(self, env, chars, expect):
90
tmpl = env.from_string("{{ foo|trim(chars) }}")
91
out = tmpl.render(foo=" ..stays..", chars=chars)
94
def test_striptags(self, env):
95
tmpl = env.from_string("""{{ foo|striptags }}""")
97
foo=' <p>just a small \n <a href="#">'
98
"example</a> link</p>\n<p>to a webpage</p> "
99
"<!-- <p>and some commented stuff</p> -->"
101
assert out == "just a small example link to a webpage"
103
def test_filesizeformat(self, env):
104
tmpl = env.from_string(
105
"{{ 100|filesizeformat }}|"
106
"{{ 1000|filesizeformat }}|"
107
"{{ 1000000|filesizeformat }}|"
108
"{{ 1000000000|filesizeformat }}|"
109
"{{ 1000000000000|filesizeformat }}|"
110
"{{ 100|filesizeformat(true) }}|"
111
"{{ 1000|filesizeformat(true) }}|"
112
"{{ 1000000|filesizeformat(true) }}|"
113
"{{ 1000000000|filesizeformat(true) }}|"
114
"{{ 1000000000000|filesizeformat(true) }}"
118
"100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|"
119
"1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB"
122
def test_filesizeformat_issue59(self, env):
123
tmpl = env.from_string(
124
"{{ 300|filesizeformat }}|"
125
"{{ 3000|filesizeformat }}|"
126
"{{ 3000000|filesizeformat }}|"
127
"{{ 3000000000|filesizeformat }}|"
128
"{{ 3000000000000|filesizeformat }}|"
129
"{{ 300|filesizeformat(true) }}|"
130
"{{ 3000|filesizeformat(true) }}|"
131
"{{ 3000000|filesizeformat(true) }}"
135
"300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB"
138
def test_first(self, env):
139
tmpl = env.from_string("{{ foo|first }}")
140
out = tmpl.render(foo=list(range(10)))
143
@pytest.mark.parametrize(
144
("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"))
146
def test_float(self, env, value, expect):
147
t = env.from_string("{{ value|float }}")
148
assert t.render(value=value) == expect
150
def test_float_default(self, env):
151
t = env.from_string("{{ value|float(default=1.0) }}")
152
assert t.render(value="abc") == "1.0"
154
def test_format(self, env):
155
tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}")
160
def _test_indent_multiline_template(env, markup=False):
161
text = "\n".join(["", "foo bar", '"baz"', ""])
164
t = env.from_string("{{ foo|indent(2, false, false) }}")
165
assert t.render(foo=text) == '\n foo bar\n "baz"\n'
166
t = env.from_string("{{ foo|indent(2, false, true) }}")
167
assert t.render(foo=text) == '\n foo bar\n "baz"\n '
168
t = env.from_string("{{ foo|indent(2, true, false) }}")
169
assert t.render(foo=text) == ' \n foo bar\n "baz"\n'
170
t = env.from_string("{{ foo|indent(2, true, true) }}")
171
assert t.render(foo=text) == ' \n foo bar\n "baz"\n '
173
def test_indent(self, env):
174
self._test_indent_multiline_template(env)
175
t = env.from_string('{{ "jinja"|indent }}')
176
assert t.render() == "jinja"
177
t = env.from_string('{{ "jinja"|indent(first=true) }}')
178
assert t.render() == " jinja"
179
t = env.from_string('{{ "jinja"|indent(blank=true) }}')
180
assert t.render() == "jinja"
182
def test_indent_markup_input(self, env):
184
Tests cases where the filter input is a Markup type
186
self._test_indent_multiline_template(env, markup=True)
188
def test_indent_width_string(self, env):
189
t = env.from_string("{{ 'jinja\nflask'|indent(width='>>> ', first=True) }}")
190
assert t.render() == ">>> jinja\n>>> flask"
192
@pytest.mark.parametrize(
198
("12345678901234567890", "12345678901234567890"),
201
def test_int(self, env, value, expect):
202
t = env.from_string("{{ value|int }}")
203
assert t.render(value=value) == expect
205
@pytest.mark.parametrize(
206
("value", "base", "expect"),
207
(("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0")),
209
def test_int_base(self, env, value, base, expect):
210
t = env.from_string("{{ value|int(base=base) }}")
211
assert t.render(value=value, base=base) == expect
213
def test_int_default(self, env):
214
t = env.from_string("{{ value|int(default=1) }}")
215
assert t.render(value="abc") == "1"
217
def test_int_special_method(self, env):
222
t = env.from_string("{{ value|int }}")
223
assert t.render(value=IntIsh()) == "42"
225
def test_join(self, env):
226
tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
228
assert out == "1|2|3"
230
env2 = Environment(autoescape=True)
231
tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
232
assert tmpl.render() == "<foo><span>foo</span>"
234
def test_join_attribute(self, env):
235
User = namedtuple("User", "username")
236
tmpl = env.from_string("""{{ users|join(', ', 'username') }}""")
237
assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar"
239
def test_last(self, env):
240
tmpl = env.from_string("""{{ foo|last }}""")
241
out = tmpl.render(foo=list(range(10)))
244
def test_length(self, env):
245
tmpl = env.from_string("""{{ "hello world"|length }}""")
249
def test_lower(self, env):
250
tmpl = env.from_string("""{{ "FOO"|lower }}""")
254
def test_items(self, env):
255
d = {i: c for i, c in enumerate("abc")}
256
tmpl = env.from_string("""{{ d|items|list }}""")
257
out = tmpl.render(d=d)
258
assert out == "[(0, 'a'), (1, 'b'), (2, 'c')]"
260
def test_items_undefined(self, env):
261
tmpl = env.from_string("""{{ d|items|list }}""")
265
def test_pprint(self, env):
266
from pprint import pformat
268
tmpl = env.from_string("""{{ data|pprint }}""")
269
data = list(range(1000))
270
assert tmpl.render(data=data) == pformat(data)
272
def test_random(self, env, request):
274
state = random.getstate()
275
request.addfinalizer(lambda: random.setstate(state))
278
expected = [random.choice("1234567890") for _ in range(10)]
283
t = env.from_string('{{ "1234567890"|random }}')
285
for value in expected:
286
assert t.render() == value
288
def test_reverse(self, env):
289
tmpl = env.from_string(
290
"{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}"
292
assert tmpl.render() == "raboof|[3, 2, 1]"
294
def test_string(self, env):
296
tmpl = env.from_string("""{{ obj|string }}""")
297
assert tmpl.render(obj=x) == str(x)
299
def test_title(self, env):
300
tmpl = env.from_string("""{{ "foo bar"|title }}""")
301
assert tmpl.render() == "Foo Bar"
302
tmpl = env.from_string("""{{ "foo's bar"|title }}""")
303
assert tmpl.render() == "Foo's Bar"
304
tmpl = env.from_string("""{{ "foo bar"|title }}""")
305
assert tmpl.render() == "Foo Bar"
306
tmpl = env.from_string("""{{ "f bar f"|title }}""")
307
assert tmpl.render() == "F Bar F"
308
tmpl = env.from_string("""{{ "foo-bar"|title }}""")
309
assert tmpl.render() == "Foo-Bar"
310
tmpl = env.from_string("""{{ "foo\tbar"|title }}""")
311
assert tmpl.render() == "Foo\tBar"
312
tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""")
313
assert tmpl.render() == "Foo\tBar"
314
tmpl = env.from_string("""{{ "foo (bar)"|title }}""")
315
assert tmpl.render() == "Foo (Bar)"
316
tmpl = env.from_string("""{{ "foo {bar}"|title }}""")
317
assert tmpl.render() == "Foo {Bar}"
318
tmpl = env.from_string("""{{ "foo [bar]"|title }}""")
319
assert tmpl.render() == "Foo [Bar]"
320
tmpl = env.from_string("""{{ "foo <bar>"|title }}""")
321
assert tmpl.render() == "Foo <Bar>"
327
tmpl = env.from_string("""{{ data|title }}""")
328
out = tmpl.render(data=Foo())
329
assert out == "Foo-Bar"
331
def test_truncate(self, env):
332
tmpl = env.from_string(
333
'{{ data|truncate(15, true, ">>>") }}|'
334
'{{ data|truncate(15, false, ">>>") }}|'
335
"{{ smalldata|truncate(15) }}"
337
out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar")
338
assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar"
340
def test_truncate_very_short(self, env):
341
tmpl = env.from_string(
342
'{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}'
345
assert out == "foo bar baz|foo bar baz"
347
def test_truncate_end_length(self, env):
348
tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}')
350
assert out == "Joel..."
352
def test_upper(self, env):
353
tmpl = env.from_string('{{ "foo"|upper }}')
354
assert tmpl.render() == "FOO"
356
def test_urlize(self, env):
357
tmpl = env.from_string('{{ "foo example.org bar"|urlize }}')
358
assert tmpl.render() == (
359
'foo <a href="https://example.org" rel="noopener">' "example.org</a> bar"
361
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
362
assert tmpl.render() == (
363
'foo <a href="http://www.example.com/" rel="noopener">'
364
"http://www.example.com/</a> bar"
366
tmpl = env.from_string('{{ "foo mailto:email@example.com bar"|urlize }}')
367
assert tmpl.render() == (
368
'foo <a href="mailto:email@example.com">email@example.com</a> bar'
370
tmpl = env.from_string('{{ "foo email@example.com bar"|urlize }}')
371
assert tmpl.render() == (
372
'foo <a href="mailto:email@example.com">email@example.com</a> bar'
375
def test_urlize_rel_policy(self):
377
env.policies["urlize.rel"] = None
378
tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
379
assert tmpl.render() == (
380
'foo <a href="http://www.example.com/">http://www.example.com/</a> bar'
383
def test_urlize_target_parameter(self, env):
384
tmpl = env.from_string(
385
'{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}'
389
== 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">'
390
"http://www.example.com/</a> bar"
393
def test_urlize_extra_schemes_parameter(self, env):
394
tmpl = env.from_string(
395
'{{ "foo tel:+1-514-555-1234 ftp://localhost bar"|'
396
'urlize(extra_schemes=["tel:", "ftp:"]) }}'
398
assert tmpl.render() == (
399
'foo <a href="tel:+1-514-555-1234" rel="noopener">'
400
'tel:+1-514-555-1234</a> <a href="ftp://localhost" rel="noopener">'
401
"ftp://localhost</a> bar"
404
def test_wordcount(self, env):
405
tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
406
assert tmpl.render() == "3"
408
strict_env = Environment(undefined=StrictUndefined)
409
t = strict_env.from_string("{{ s|wordcount }}")
410
with pytest.raises(UndefinedError):
413
def test_block(self, env):
414
tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}")
415
assert tmpl.render() == "<hehe>"
417
def test_chaining(self, env):
418
tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""")
419
assert tmpl.render() == "<FOO>"
421
def test_sum(self, env):
422
tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""")
423
assert tmpl.render() == "21"
425
def test_sum_attributes(self, env):
426
tmpl = env.from_string("""{{ values|sum('value') }}""")
427
assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42"
429
def test_sum_attributes_nested(self, env):
430
tmpl = env.from_string("""{{ values|sum('real.value') }}""")
434
{"real": {"value": 23}},
435
{"real": {"value": 1}},
436
{"real": {"value": 18}},
442
def test_sum_attributes_tuple(self, env):
443
tmpl = env.from_string("""{{ values.items()|sum('1') }}""")
444
assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42"
446
def test_abs(self, env):
447
tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""")
448
assert tmpl.render() == "1|1", tmpl.render()
450
def test_round_positive(self, env):
451
tmpl = env.from_string(
452
"{{ 2.7|round }}|{{ 2.1|round }}|"
453
"{{ 2.1234|round(3, 'floor') }}|"
454
"{{ 2.1|round(0, 'ceil') }}"
456
assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render()
458
def test_round_negative(self, env):
459
tmpl = env.from_string(
460
"{{ 21.3|round(-1)}}|"
461
"{{ 21.3|round(-1, 'ceil')}}|"
462
"{{ 21.3|round(-1, 'floor')}}"
464
assert tmpl.render() == "20.0|30.0|20.0", tmpl.render()
466
def test_xmlattr(self, env):
467
tmpl = env.from_string(
468
"{{ {'foo': 42, 'bar': 23, 'fish': none, "
469
"'spam': missing, 'blub:blub': '<?>'}|xmlattr }}"
471
out = tmpl.render().split()
473
assert 'foo="42"' in out
474
assert 'bar="23"' in out
475
assert 'blub:blub="<?>"' in out
477
@pytest.mark.parametrize("sep", ("\t", "\n", "\f", " ", "/", ">", "="))
478
def test_xmlattr_key_invalid(self, env: Environment, sep: str) -> None:
479
with pytest.raises(ValueError, match="Invalid character"):
480
env.from_string("{{ {key: 'my_class'}|xmlattr }}").render(
481
key=f"class{sep}onclick=alert(1)"
484
def test_sort1(self, env):
485
tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
486
assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"
488
def test_sort2(self, env):
489
tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
490
assert tmpl.render() == "AbcD"
492
def test_sort3(self, env):
493
tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""")
494
assert tmpl.render() == "['Bar', 'blah', 'foo']"
496
def test_sort4(self, env):
497
tmpl = env.from_string("""{{ items|sort(attribute='value')|join }}""")
498
assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == "1234"
500
def test_sort5(self, env):
501
tmpl = env.from_string("""{{ items|sort(attribute='value.0')|join }}""")
502
assert tmpl.render(items=map(Magic, [[3], [2], [4], [1]])) == "[1][2][3][4]"
504
def test_sort6(self, env):
505
tmpl = env.from_string("""{{ items|sort(attribute='value1,value2')|join }}""")
509
lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
512
== "(2,1)(2,2)(2,5)(3,1)"
515
def test_sort7(self, env):
516
tmpl = env.from_string("""{{ items|sort(attribute='value2,value1')|join }}""")
520
lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
523
== "(2,1)(3,1)(2,2)(2,5)"
526
def test_sort8(self, env):
527
tmpl = env.from_string(
528
"""{{ items|sort(attribute='value1.0,value2.0')|join }}"""
533
lambda x: Magic2(x[0], x[1]),
534
[([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])],
537
== "([2],[1])([2],[2])([2],[5])([3],[1])"
540
def test_unique(self, env):
541
t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
542
assert t.render() == "bA"
544
def test_unique_case_sensitive(self, env):
545
t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
546
assert t.render() == "bAa"
548
def test_unique_attribute(self, env):
549
t = env.from_string("{{ items|unique(attribute='value')|join }}")
550
assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241"
552
@pytest.mark.parametrize(
555
('{{ ["a", "B"]|min }}', "a"),
556
('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"),
557
("{{ []|min }}", ""),
558
('{{ ["a", "B"]|max }}', "B"),
559
('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"),
560
("{{ []|max }}", ""),
563
def test_min_max(self, env, source, expect):
564
t = env.from_string(source)
565
assert t.render() == expect
567
@pytest.mark.parametrize(("name", "expect"), [("min", "1"), ("max", "9")])
568
def test_min_max_attribute(self, env, name, expect):
569
t = env.from_string("{{ items|" + name + '(attribute="value") }}')
570
assert t.render(items=map(Magic, [5, 1, 9])) == expect
572
def test_groupby(self, env):
573
tmpl = env.from_string(
575
{%- for grouper, list in [{'foo': 1, 'bar': 2},
576
{'foo': 2, 'bar': 3},
577
{'foo': 1, 'bar': 1},
578
{'foo': 3, 'bar': 4}]|groupby('foo') -%}
579
{{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
582
assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""]
584
def test_groupby_tuple_index(self, env):
585
tmpl = env.from_string(
587
{%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
588
{{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
591
assert tmpl.render() == "a:1:2|b:1|"
593
def test_groupby_multidot(self, env):
594
Date = namedtuple("Date", "day,month,year")
595
Article = namedtuple("Article", "title,date")
597
Article("aha", Date(1, 1, 1970)),
598
Article("interesting", Date(2, 1, 1970)),
599
Article("really?", Date(3, 1, 1970)),
600
Article("totally not", Date(1, 1, 1971)),
602
tmpl = env.from_string(
604
{%- for year, list in articles|groupby('date.year') -%}
605
{{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
608
assert tmpl.render(articles=articles).split("|") == [
609
"1970[aha][interesting][really?]",
614
def test_groupby_default(self, env):
615
tmpl = env.from_string(
616
"{% for city, items in users|groupby('city', default='NY') %}"
617
"{{ city }}: {{ items|map(attribute='name')|join(', ') }}\n"
622
{"name": "emma", "city": "NY"},
623
{"name": "smith", "city": "WA"},
627
assert out == "NY: emma, john\nWA: smith\n"
629
@pytest.mark.parametrize(
630
("case_sensitive", "expect"),
632
(False, "a: 1, 3\nb: 2\n"),
633
(True, "A: 3\na: 1\nb: 2\n"),
636
def test_groupby_case(self, env, case_sensitive, expect):
637
tmpl = env.from_string(
638
"{% for k, vs in data|groupby('k', case_sensitive=cs) %}"
639
"{{ k }}: {{ vs|join(', ', attribute='v') }}\n"
643
data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
648
def test_filtertag(self, env):
649
tmpl = env.from_string(
650
"{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"
652
assert tmpl.render() == "fooBAR"
654
def test_replace(self, env):
656
tmpl = env.from_string('{{ string|replace("o", 42) }}')
657
assert tmpl.render(string="<foo>") == "<f4242>"
658
env = Environment(autoescape=True)
659
tmpl = env.from_string('{{ string|replace("o", 42) }}')
660
assert tmpl.render(string="<foo>") == "<f4242>"
661
tmpl = env.from_string('{{ string|replace("<", 42) }}')
662
assert tmpl.render(string="<foo>") == "42foo>"
663
tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
664
assert tmpl.render(string=Markup("foo")) == "f>x<>x<"
666
def test_forceescape(self, env):
667
tmpl = env.from_string("{{ x|forceescape }}")
668
assert tmpl.render(x=Markup("<div />")) == "<div />"
670
def test_safe(self, env):
671
env = Environment(autoescape=True)
672
tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
673
assert tmpl.render() == "<div>foo</div>"
674
tmpl = env.from_string('{{ "<div>foo</div>" }}')
675
assert tmpl.render() == "<div>foo</div>"
677
@pytest.mark.parametrize(
680
("Hello, world!", "Hello%2C%20world%21"),
681
("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
683
([("f", 1), ("z", 2)], "f=1&z=2"),
684
({"\u203d": 1}, "%E2%80%BD=1"),
686
([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"),
687
("a b/c", "a%20b/c"),
690
def test_urlencode(self, value, expect):
691
e = Environment(autoescape=True)
692
t = e.from_string("{{ value|urlencode }}")
693
assert t.render(value=value) == expect
695
def test_simple_map(self, env):
697
tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
698
assert tmpl.render() == "6"
700
def test_map_sum(self, env):
701
tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}')
702
assert tmpl.render() == "[3, 3, 15]"
704
def test_attribute_map(self, env):
705
User = namedtuple("User", "name")
712
tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
713
assert tmpl.render(users=users) == "john|jane|mike"
715
def test_empty_map(self, env):
717
tmpl = env.from_string('{{ none|map("upper")|list }}')
718
assert tmpl.render() == "[]"
720
def test_map_default(self, env):
721
Fullname = namedtuple("Fullname", "firstname,lastname")
722
Firstname = namedtuple("Firstname", "firstname")
724
tmpl = env.from_string(
725
'{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
727
test_list = env.from_string(
728
'{{ users|map(attribute="lastname", default=["smith","x"])|join(", ") }}'
730
test_str = env.from_string(
731
'{{ users|map(attribute="lastname", default="")|join(", ") }}'
734
Fullname("john", "lennon"),
735
Fullname("jane", "edwards"),
736
Fullname("jon", None),
739
assert tmpl.render(users=users) == "lennon, edwards, None, smith"
740
assert test_list.render(users=users) == "lennon, edwards, None, ['smith', 'x']"
741
assert test_str.render(users=users) == "lennon, edwards, None, "
743
def test_simple_select(self, env):
745
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
746
assert tmpl.render() == "1|3|5"
748
def test_bool_select(self, env):
750
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
751
assert tmpl.render() == "1|2|3|4|5"
753
def test_simple_reject(self, env):
755
tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
756
assert tmpl.render() == "2|4"
758
def test_bool_reject(self, env):
760
tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
761
assert tmpl.render() == "None|False|0"
763
def test_simple_select_attr(self, env):
764
User = namedtuple("User", "name,is_active")
771
tmpl = env.from_string(
772
'{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}'
774
assert tmpl.render(users=users) == "john|jane"
776
def test_simple_reject_attr(self, env):
777
User = namedtuple("User", "name,is_active")
784
tmpl = env.from_string(
785
'{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}'
787
assert tmpl.render(users=users) == "mike"
789
def test_func_select_attr(self, env):
790
User = namedtuple("User", "id,name")
797
tmpl = env.from_string(
798
'{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}'
800
assert tmpl.render(users=users) == "john|mike"
802
def test_func_reject_attr(self, env):
803
User = namedtuple("User", "id,name")
810
tmpl = env.from_string(
811
'{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}'
813
assert tmpl.render(users=users) == "jane"
815
def test_json_dump(self):
816
env = Environment(autoescape=True)
817
t = env.from_string("{{ x|tojson }}")
818
assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}'
819
assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"'
820
assert t.render(x="<bar>") == r'"\u003cbar\u003e"'
822
def my_dumps(value, **options):
823
assert options == {"foo": "bar"}
826
env.policies["json.dumps_function"] = my_dumps
827
env.policies["json.dumps_kwargs"] = {"foo": "bar"}
828
assert t.render(x=23) == "42"
830
def test_wordwrap(self, env):
831
env.newline_sequence = "\n"
832
t = env.from_string("{{ s|wordwrap(20) }}")
833
result = t.render(s="Hello!\nThis is Jinja saying something.")
834
assert result == "Hello!\nThis is Jinja saying\nsomething."
836
def test_filter_undefined(self, env):
837
with pytest.raises(TemplateAssertionError, match="No filter named 'f'"):
838
env.from_string("{{ var|f }}")
840
def test_filter_undefined_in_if(self, env):
841
t = env.from_string("{%- if x is defined -%}{{ x|f }}{%- else -%}x{% endif %}")
842
assert t.render() == "x"
843
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
846
def test_filter_undefined_in_elif(self, env):
848
"{%- if x is defined -%}{{ x }}{%- elif y is defined -%}"
849
"{{ y|f }}{%- else -%}foo{%- endif -%}"
851
assert t.render() == "foo"
852
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
855
def test_filter_undefined_in_else(self, env):
857
"{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}"
859
assert t.render() == "foo"
860
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
863
def test_filter_undefined_in_nested_if(self, env):
865
"{%- if x is not defined -%}foo{%- else -%}{%- if y "
866
"is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}"
868
assert t.render() == "foo"
869
assert t.render(x=42) == "42"
870
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
873
def test_filter_undefined_in_condexpr(self, env):
874
t1 = env.from_string("{{ x|f if x is defined else 'foo' }}")
875
t2 = env.from_string("{{ 'foo' if x is not defined else x|f }}")
876
assert t1.render() == t2.render() == "foo"
878
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
881
with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):