blob: 8087a248148a8c25c7bdcb3e36491c584ad7813d [file] [log] [blame]
IRIS YANG31213572020-08-18 13:17:02 +00001import random
2from collections import namedtuple
3
4import pytest
5
6from jinja2 import Environment
7from jinja2 import Markup
8from jinja2 import StrictUndefined
9from jinja2 import UndefinedError
10
11
12class Magic:
13 def __init__(self, value):
14 self.value = value
15
16 def __str__(self):
17 return str(self.value)
18
19
20class Magic2:
21 def __init__(self, value1, value2):
22 self.value1 = value1
23 self.value2 = value2
24
25 def __str__(self):
26 return f"({self.value1},{self.value2})"
27
28
29class TestFilter:
30 def test_filter_calling(self, env):
31 rv = env.call_filter("sum", [1, 2, 3])
32 assert rv == 6
33
34 def test_capitalize(self, env):
35 tmpl = env.from_string('{{ "foo bar"|capitalize }}')
36 assert tmpl.render() == "Foo bar"
37
38 def test_center(self, env):
39 tmpl = env.from_string('{{ "foo"|center(9) }}')
40 assert tmpl.render() == " foo "
41
42 def test_default(self, env):
43 tmpl = env.from_string(
44 "{{ missing|default('no') }}|{{ false|default('no') }}|"
45 "{{ false|default('no', true) }}|{{ given|default('no') }}"
46 )
47 assert tmpl.render(given="yes") == "no|False|no|yes"
48
49 @pytest.mark.parametrize(
50 "args,expect",
51 (
52 ("", "[('aa', 0), ('AB', 3), ('b', 1), ('c', 2)]"),
53 ("true", "[('AB', 3), ('aa', 0), ('b', 1), ('c', 2)]"),
54 ('by="value"', "[('aa', 0), ('b', 1), ('c', 2), ('AB', 3)]"),
55 ("reverse=true", "[('c', 2), ('b', 1), ('AB', 3), ('aa', 0)]"),
56 ),
57 )
58 def test_dictsort(self, env, args, expect):
59 t = env.from_string(f"{{{{ foo|dictsort({args}) }}}}")
60 out = t.render(foo={"aa": 0, "b": 1, "c": 2, "AB": 3})
61 assert out == expect
62
63 def test_batch(self, env):
64 tmpl = env.from_string("{{ foo|batch(3)|list }}|{{ foo|batch(3, 'X')|list }}")
65 out = tmpl.render(foo=list(range(10)))
66 assert out == (
67 "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
68 "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]"
69 )
70
71 def test_slice(self, env):
72 tmpl = env.from_string("{{ foo|slice(3)|list }}|{{ foo|slice(3, 'X')|list }}")
73 out = tmpl.render(foo=list(range(10)))
74 assert out == (
75 "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
76 "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
77 )
78
79 def test_escape(self, env):
80 tmpl = env.from_string("""{{ '<">&'|escape }}""")
81 out = tmpl.render()
82 assert out == "&lt;&#34;&gt;&amp;"
83
84 @pytest.mark.parametrize(
85 ("chars", "expect"), [(None, "..stays.."), (".", " ..stays"), (" .", "stays")]
86 )
87 def test_trim(self, env, chars, expect):
88 tmpl = env.from_string("{{ foo|trim(chars) }}")
89 out = tmpl.render(foo=" ..stays..", chars=chars)
90 assert out == expect
91
92 def test_striptags(self, env):
93 tmpl = env.from_string("""{{ foo|striptags }}""")
94 out = tmpl.render(
95 foo=' <p>just a small \n <a href="#">'
96 "example</a> link</p>\n<p>to a webpage</p> "
97 "<!-- <p>and some commented stuff</p> -->"
98 )
99 assert out == "just a small example link to a webpage"
100
101 def test_filesizeformat(self, env):
102 tmpl = env.from_string(
103 "{{ 100|filesizeformat }}|"
104 "{{ 1000|filesizeformat }}|"
105 "{{ 1000000|filesizeformat }}|"
106 "{{ 1000000000|filesizeformat }}|"
107 "{{ 1000000000000|filesizeformat }}|"
108 "{{ 100|filesizeformat(true) }}|"
109 "{{ 1000|filesizeformat(true) }}|"
110 "{{ 1000000|filesizeformat(true) }}|"
111 "{{ 1000000000|filesizeformat(true) }}|"
112 "{{ 1000000000000|filesizeformat(true) }}"
113 )
114 out = tmpl.render()
115 assert out == (
116 "100 Bytes|1.0 kB|1.0 MB|1.0 GB|1.0 TB|100 Bytes|"
117 "1000 Bytes|976.6 KiB|953.7 MiB|931.3 GiB"
118 )
119
120 def test_filesizeformat_issue59(self, env):
121 tmpl = env.from_string(
122 "{{ 300|filesizeformat }}|"
123 "{{ 3000|filesizeformat }}|"
124 "{{ 3000000|filesizeformat }}|"
125 "{{ 3000000000|filesizeformat }}|"
126 "{{ 3000000000000|filesizeformat }}|"
127 "{{ 300|filesizeformat(true) }}|"
128 "{{ 3000|filesizeformat(true) }}|"
129 "{{ 3000000|filesizeformat(true) }}"
130 )
131 out = tmpl.render()
132 assert out == (
133 "300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB"
134 )
135
136 def test_first(self, env):
137 tmpl = env.from_string("{{ foo|first }}")
138 out = tmpl.render(foo=list(range(10)))
139 assert out == "0"
140
141 @pytest.mark.parametrize(
142 ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"))
143 )
144 def test_float(self, env, value, expect):
145 t = env.from_string("{{ value|float }}")
146 assert t.render(value=value) == expect
147
148 def test_float_default(self, env):
149 t = env.from_string("{{ value|float(default=1.0) }}")
150 assert t.render(value="abc") == "1.0"
151
152 def test_format(self, env):
153 tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}")
154 out = tmpl.render()
155 assert out == "a|b"
156
157 @staticmethod
158 def _test_indent_multiline_template(env, markup=False):
159 text = "\n".join(["", "foo bar", '"baz"', ""])
160 if markup:
161 text = Markup(text)
162 t = env.from_string("{{ foo|indent(2, false, false) }}")
163 assert t.render(foo=text) == '\n foo bar\n "baz"\n'
164 t = env.from_string("{{ foo|indent(2, false, true) }}")
165 assert t.render(foo=text) == '\n foo bar\n "baz"\n '
166 t = env.from_string("{{ foo|indent(2, true, false) }}")
167 assert t.render(foo=text) == ' \n foo bar\n "baz"\n'
168 t = env.from_string("{{ foo|indent(2, true, true) }}")
169 assert t.render(foo=text) == ' \n foo bar\n "baz"\n '
170
171 def test_indent(self, env):
172 self._test_indent_multiline_template(env)
173 t = env.from_string('{{ "jinja"|indent }}')
174 assert t.render() == "jinja"
175 t = env.from_string('{{ "jinja"|indent(first=true) }}')
176 assert t.render() == " jinja"
177 t = env.from_string('{{ "jinja"|indent(blank=true) }}')
178 assert t.render() == "jinja"
179
180 def test_indent_markup_input(self, env):
181 """
182 Tests cases where the filter input is a Markup type
183 """
184 self._test_indent_multiline_template(env, markup=True)
185
186 @pytest.mark.parametrize(
187 ("value", "expect"),
188 (
189 ("42", "42"),
190 ("abc", "0"),
191 ("32.32", "32"),
192 ("12345678901234567890", "12345678901234567890"),
193 ),
194 )
195 def test_int(self, env, value, expect):
196 t = env.from_string("{{ value|int }}")
197 assert t.render(value=value) == expect
198
199 @pytest.mark.parametrize(
200 ("value", "base", "expect"),
201 (("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0"),),
202 )
203 def test_int_base(self, env, value, base, expect):
204 t = env.from_string("{{ value|int(base=base) }}")
205 assert t.render(value=value, base=base) == expect
206
207 def test_int_default(self, env):
208 t = env.from_string("{{ value|int(default=1) }}")
209 assert t.render(value="abc") == "1"
210
211 def test_int_special_method(self, env):
212 class IntIsh:
213 def __int__(self):
214 return 42
215
216 t = env.from_string("{{ value|int }}")
217 assert t.render(value=IntIsh()) == "42"
218
219 def test_join(self, env):
220 tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
221 out = tmpl.render()
222 assert out == "1|2|3"
223
224 env2 = Environment(autoescape=True)
225 tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
226 assert tmpl.render() == "&lt;foo&gt;<span>foo</span>"
227
228 def test_join_attribute(self, env):
229 User = namedtuple("User", "username")
230 tmpl = env.from_string("""{{ users|join(', ', 'username') }}""")
231 assert tmpl.render(users=map(User, ["foo", "bar"])) == "foo, bar"
232
233 def test_last(self, env):
234 tmpl = env.from_string("""{{ foo|last }}""")
235 out = tmpl.render(foo=list(range(10)))
236 assert out == "9"
237
238 def test_length(self, env):
239 tmpl = env.from_string("""{{ "hello world"|length }}""")
240 out = tmpl.render()
241 assert out == "11"
242
243 def test_lower(self, env):
244 tmpl = env.from_string("""{{ "FOO"|lower }}""")
245 out = tmpl.render()
246 assert out == "foo"
247
248 def test_pprint(self, env):
249 from pprint import pformat
250
251 tmpl = env.from_string("""{{ data|pprint }}""")
252 data = list(range(1000))
253 assert tmpl.render(data=data) == pformat(data)
254
255 def test_random(self, env, request):
256 # restore the random state when the test ends
257 state = random.getstate()
258 request.addfinalizer(lambda: random.setstate(state))
259 # generate the random values from a known seed
260 random.seed("jinja")
261 expected = [random.choice("1234567890") for _ in range(10)]
262
263 # check that the random sequence is generated again by a template
264 # ensures that filter result is not constant folded
265 random.seed("jinja")
266 t = env.from_string('{{ "1234567890"|random }}')
267
268 for value in expected:
269 assert t.render() == value
270
271 def test_reverse(self, env):
272 tmpl = env.from_string(
273 "{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}"
274 )
275 assert tmpl.render() == "raboof|[3, 2, 1]"
276
277 def test_string(self, env):
278 x = [1, 2, 3, 4, 5]
279 tmpl = env.from_string("""{{ obj|string }}""")
280 assert tmpl.render(obj=x) == str(x)
281
282 def test_title(self, env):
283 tmpl = env.from_string("""{{ "foo bar"|title }}""")
284 assert tmpl.render() == "Foo Bar"
285 tmpl = env.from_string("""{{ "foo's bar"|title }}""")
286 assert tmpl.render() == "Foo's Bar"
287 tmpl = env.from_string("""{{ "foo bar"|title }}""")
288 assert tmpl.render() == "Foo Bar"
289 tmpl = env.from_string("""{{ "f bar f"|title }}""")
290 assert tmpl.render() == "F Bar F"
291 tmpl = env.from_string("""{{ "foo-bar"|title }}""")
292 assert tmpl.render() == "Foo-Bar"
293 tmpl = env.from_string("""{{ "foo\tbar"|title }}""")
294 assert tmpl.render() == "Foo\tBar"
295 tmpl = env.from_string("""{{ "FOO\tBAR"|title }}""")
296 assert tmpl.render() == "Foo\tBar"
297 tmpl = env.from_string("""{{ "foo (bar)"|title }}""")
298 assert tmpl.render() == "Foo (Bar)"
299 tmpl = env.from_string("""{{ "foo {bar}"|title }}""")
300 assert tmpl.render() == "Foo {Bar}"
301 tmpl = env.from_string("""{{ "foo [bar]"|title }}""")
302 assert tmpl.render() == "Foo [Bar]"
303 tmpl = env.from_string("""{{ "foo <bar>"|title }}""")
304 assert tmpl.render() == "Foo <Bar>"
305
306 class Foo:
307 def __str__(self):
308 return "foo-bar"
309
310 tmpl = env.from_string("""{{ data|title }}""")
311 out = tmpl.render(data=Foo())
312 assert out == "Foo-Bar"
313
314 def test_truncate(self, env):
315 tmpl = env.from_string(
316 '{{ data|truncate(15, true, ">>>") }}|'
317 '{{ data|truncate(15, false, ">>>") }}|'
318 "{{ smalldata|truncate(15) }}"
319 )
320 out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar")
321 assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar"
322
323 def test_truncate_very_short(self, env):
324 tmpl = env.from_string(
325 '{{ "foo bar baz"|truncate(9) }}|{{ "foo bar baz"|truncate(9, true) }}'
326 )
327 out = tmpl.render()
328 assert out == "foo bar baz|foo bar baz"
329
330 def test_truncate_end_length(self, env):
331 tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}')
332 out = tmpl.render()
333 assert out == "Joel..."
334
335 def test_upper(self, env):
336 tmpl = env.from_string('{{ "foo"|upper }}')
337 assert tmpl.render() == "FOO"
338
339 def test_urlize(self, env):
340 tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
341 assert tmpl.render() == (
342 'foo <a href="http://www.example.com/" rel="noopener">'
343 "http://www.example.com/</a> bar"
344 )
345
346 def test_urlize_rel_policy(self):
347 env = Environment()
348 env.policies["urlize.rel"] = None
349 tmpl = env.from_string('{{ "foo http://www.example.com/ bar"|urlize }}')
350 assert tmpl.render() == (
351 'foo <a href="http://www.example.com/">http://www.example.com/</a> bar'
352 )
353
354 def test_urlize_target_parameter(self, env):
355 tmpl = env.from_string(
356 '{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}'
357 )
358 assert (
359 tmpl.render()
360 == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">'
361 "http://www.example.com/</a> bar"
362 )
363
364 def test_wordcount(self, env):
365 tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
366 assert tmpl.render() == "3"
367
368 strict_env = Environment(undefined=StrictUndefined)
369 t = strict_env.from_string("{{ s|wordcount }}")
370 with pytest.raises(UndefinedError):
371 t.render()
372
373 def test_block(self, env):
374 tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}")
375 assert tmpl.render() == "&lt;hehe&gt;"
376
377 def test_chaining(self, env):
378 tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""")
379 assert tmpl.render() == "&lt;FOO&gt;"
380
381 def test_sum(self, env):
382 tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""")
383 assert tmpl.render() == "21"
384
385 def test_sum_attributes(self, env):
386 tmpl = env.from_string("""{{ values|sum('value') }}""")
387 assert tmpl.render(values=[{"value": 23}, {"value": 1}, {"value": 18}]) == "42"
388
389 def test_sum_attributes_nested(self, env):
390 tmpl = env.from_string("""{{ values|sum('real.value') }}""")
391 assert (
392 tmpl.render(
393 values=[
394 {"real": {"value": 23}},
395 {"real": {"value": 1}},
396 {"real": {"value": 18}},
397 ]
398 )
399 == "42"
400 )
401
402 def test_sum_attributes_tuple(self, env):
403 tmpl = env.from_string("""{{ values.items()|sum('1') }}""")
404 assert tmpl.render(values={"foo": 23, "bar": 1, "baz": 18}) == "42"
405
406 def test_abs(self, env):
407 tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""")
408 assert tmpl.render() == "1|1", tmpl.render()
409
410 def test_round_positive(self, env):
411 tmpl = env.from_string(
412 "{{ 2.7|round }}|{{ 2.1|round }}|"
413 "{{ 2.1234|round(3, 'floor') }}|"
414 "{{ 2.1|round(0, 'ceil') }}"
415 )
416 assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render()
417
418 def test_round_negative(self, env):
419 tmpl = env.from_string(
420 "{{ 21.3|round(-1)}}|"
421 "{{ 21.3|round(-1, 'ceil')}}|"
422 "{{ 21.3|round(-1, 'floor')}}"
423 )
424 assert tmpl.render() == "20.0|30.0|20.0", tmpl.render()
425
426 def test_xmlattr(self, env):
427 tmpl = env.from_string(
428 "{{ {'foo': 42, 'bar': 23, 'fish': none, "
429 "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}"
430 )
431 out = tmpl.render().split()
432 assert len(out) == 3
433 assert 'foo="42"' in out
434 assert 'bar="23"' in out
435 assert 'blub:blub="&lt;?&gt;"' in out
436
437 def test_sort1(self, env):
438 tmpl = env.from_string("{{ [2, 3, 1]|sort }}|{{ [2, 3, 1]|sort(true) }}")
439 assert tmpl.render() == "[1, 2, 3]|[3, 2, 1]"
440
441 def test_sort2(self, env):
442 tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
443 assert tmpl.render() == "AbcD"
444
445 def test_sort3(self, env):
446 tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""")
447 assert tmpl.render() == "['Bar', 'blah', 'foo']"
448
449 def test_sort4(self, env):
450 tmpl = env.from_string("""{{ items|sort(attribute='value')|join }}""")
451 assert tmpl.render(items=map(Magic, [3, 2, 4, 1])) == "1234"
452
453 def test_sort5(self, env):
454 tmpl = env.from_string("""{{ items|sort(attribute='value.0')|join }}""")
455 assert tmpl.render(items=map(Magic, [[3], [2], [4], [1]])) == "[1][2][3][4]"
456
457 def test_sort6(self, env):
458 tmpl = env.from_string("""{{ items|sort(attribute='value1,value2')|join }}""")
459 assert (
460 tmpl.render(
461 items=map(
462 lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
463 )
464 )
465 == "(2,1)(2,2)(2,5)(3,1)"
466 )
467
468 def test_sort7(self, env):
469 tmpl = env.from_string("""{{ items|sort(attribute='value2,value1')|join }}""")
470 assert (
471 tmpl.render(
472 items=map(
473 lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
474 )
475 )
476 == "(2,1)(3,1)(2,2)(2,5)"
477 )
478
479 def test_sort8(self, env):
480 tmpl = env.from_string(
481 """{{ items|sort(attribute='value1.0,value2.0')|join }}"""
482 )
483 assert (
484 tmpl.render(
485 items=map(
486 lambda x: Magic2(x[0], x[1]),
487 [([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])],
488 )
489 )
490 == "([2],[1])([2],[2])([2],[5])([3],[1])"
491 )
492
493 def test_unique(self, env):
494 t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
495 assert t.render() == "bA"
496
497 def test_unique_case_sensitive(self, env):
498 t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique(true)) }}')
499 assert t.render() == "bAa"
500
501 def test_unique_attribute(self, env):
502 t = env.from_string("{{ items|unique(attribute='value')|join }}")
503 assert t.render(items=map(Magic, [3, 2, 4, 1, 2])) == "3241"
504
505 @pytest.mark.parametrize(
506 "source,expect",
507 (
508 ('{{ ["a", "B"]|min }}', "a"),
509 ('{{ ["a", "B"]|min(case_sensitive=true) }}', "B"),
510 ("{{ []|min }}", ""),
511 ('{{ ["a", "B"]|max }}', "B"),
512 ('{{ ["a", "B"]|max(case_sensitive=true) }}', "a"),
513 ("{{ []|max }}", ""),
514 ),
515 )
516 def test_min_max(self, env, source, expect):
517 t = env.from_string(source)
518 assert t.render() == expect
519
520 @pytest.mark.parametrize("name,expect", (("min", "1"), ("max", "9"),))
521 def test_min_max_attribute(self, env, name, expect):
522 t = env.from_string("{{ items|" + name + '(attribute="value") }}')
523 assert t.render(items=map(Magic, [5, 1, 9])) == expect
524
525 def test_groupby(self, env):
526 tmpl = env.from_string(
527 """
528 {%- for grouper, list in [{'foo': 1, 'bar': 2},
529 {'foo': 2, 'bar': 3},
530 {'foo': 1, 'bar': 1},
531 {'foo': 3, 'bar': 4}]|groupby('foo') -%}
532 {{ grouper }}{% for x in list %}: {{ x.foo }}, {{ x.bar }}{% endfor %}|
533 {%- endfor %}"""
534 )
535 assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""]
536
537 def test_groupby_tuple_index(self, env):
538 tmpl = env.from_string(
539 """
540 {%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
541 {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
542 {%- endfor %}"""
543 )
544 assert tmpl.render() == "a:1:2|b:1|"
545
546 def test_groupby_multidot(self, env):
547 Date = namedtuple("Date", "day,month,year")
548 Article = namedtuple("Article", "title,date")
549 articles = [
550 Article("aha", Date(1, 1, 1970)),
551 Article("interesting", Date(2, 1, 1970)),
552 Article("really?", Date(3, 1, 1970)),
553 Article("totally not", Date(1, 1, 1971)),
554 ]
555 tmpl = env.from_string(
556 """
557 {%- for year, list in articles|groupby('date.year') -%}
558 {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
559 {%- endfor %}"""
560 )
561 assert tmpl.render(articles=articles).split("|") == [
562 "1970[aha][interesting][really?]",
563 "1971[totally not]",
564 "",
565 ]
566
567 def test_filtertag(self, env):
568 tmpl = env.from_string(
569 "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"
570 )
571 assert tmpl.render() == "fooBAR"
572
573 def test_replace(self, env):
574 env = Environment()
575 tmpl = env.from_string('{{ string|replace("o", 42) }}')
576 assert tmpl.render(string="<foo>") == "<f4242>"
577 env = Environment(autoescape=True)
578 tmpl = env.from_string('{{ string|replace("o", 42) }}')
579 assert tmpl.render(string="<foo>") == "&lt;f4242&gt;"
580 tmpl = env.from_string('{{ string|replace("<", 42) }}')
581 assert tmpl.render(string="<foo>") == "42foo&gt;"
582 tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
583 assert tmpl.render(string=Markup("foo")) == "f&gt;x&lt;&gt;x&lt;"
584
585 def test_forceescape(self, env):
586 tmpl = env.from_string("{{ x|forceescape }}")
587 assert tmpl.render(x=Markup("<div />")) == "&lt;div /&gt;"
588
589 def test_safe(self, env):
590 env = Environment(autoescape=True)
591 tmpl = env.from_string('{{ "<div>foo</div>"|safe }}')
592 assert tmpl.render() == "<div>foo</div>"
593 tmpl = env.from_string('{{ "<div>foo</div>" }}')
594 assert tmpl.render() == "&lt;div&gt;foo&lt;/div&gt;"
595
596 @pytest.mark.parametrize(
597 ("value", "expect"),
598 [
599 ("Hello, world!", "Hello%2C%20world%21"),
600 ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
601 ({"f": 1}, "f=1"),
602 ([("f", 1), ("z", 2)], "f=1&amp;z=2"),
603 ({"\u203d": 1}, "%E2%80%BD=1"),
604 ({0: 1}, "0=1"),
605 ([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"),
606 ("a b/c", "a%20b/c"),
607 ],
608 )
609 def test_urlencode(self, value, expect):
610 e = Environment(autoescape=True)
611 t = e.from_string("{{ value|urlencode }}")
612 assert t.render(value=value) == expect
613
614 def test_simple_map(self, env):
615 env = Environment()
616 tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
617 assert tmpl.render() == "6"
618
619 def test_map_sum(self, env):
620 tmpl = env.from_string('{{ [[1,2], [3], [4,5,6]]|map("sum")|list }}')
621 assert tmpl.render() == "[3, 3, 15]"
622
623 def test_attribute_map(self, env):
624 User = namedtuple("User", "name")
625 env = Environment()
626 users = [
627 User("john"),
628 User("jane"),
629 User("mike"),
630 ]
631 tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
632 assert tmpl.render(users=users) == "john|jane|mike"
633
634 def test_empty_map(self, env):
635 env = Environment()
636 tmpl = env.from_string('{{ none|map("upper")|list }}')
637 assert tmpl.render() == "[]"
638
639 def test_map_default(self, env):
640 Fullname = namedtuple("Fullname", "firstname,lastname")
641 Firstname = namedtuple("Firstname", "firstname")
642 env = Environment()
643 tmpl = env.from_string(
644 '{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
645 )
646 users = [
647 Fullname("john", "lennon"),
648 Fullname("jane", "edwards"),
649 Fullname("jon", None),
650 Firstname("mike"),
651 ]
652 assert tmpl.render(users=users) == "lennon, edwards, None, smith"
653
654 def test_simple_select(self, env):
655 env = Environment()
656 tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
657 assert tmpl.render() == "1|3|5"
658
659 def test_bool_select(self, env):
660 env = Environment()
661 tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
662 assert tmpl.render() == "1|2|3|4|5"
663
664 def test_simple_reject(self, env):
665 env = Environment()
666 tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
667 assert tmpl.render() == "2|4"
668
669 def test_bool_reject(self, env):
670 env = Environment()
671 tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
672 assert tmpl.render() == "None|False|0"
673
674 def test_simple_select_attr(self, env):
675 User = namedtuple("User", "name,is_active")
676 env = Environment()
677 users = [
678 User("john", True),
679 User("jane", True),
680 User("mike", False),
681 ]
682 tmpl = env.from_string(
683 '{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}'
684 )
685 assert tmpl.render(users=users) == "john|jane"
686
687 def test_simple_reject_attr(self, env):
688 User = namedtuple("User", "name,is_active")
689 env = Environment()
690 users = [
691 User("john", True),
692 User("jane", True),
693 User("mike", False),
694 ]
695 tmpl = env.from_string(
696 '{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}'
697 )
698 assert tmpl.render(users=users) == "mike"
699
700 def test_func_select_attr(self, env):
701 User = namedtuple("User", "id,name")
702 env = Environment()
703 users = [
704 User(1, "john"),
705 User(2, "jane"),
706 User(3, "mike"),
707 ]
708 tmpl = env.from_string(
709 '{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}'
710 )
711 assert tmpl.render(users=users) == "john|mike"
712
713 def test_func_reject_attr(self, env):
714 User = namedtuple("User", "id,name")
715 env = Environment()
716 users = [
717 User(1, "john"),
718 User(2, "jane"),
719 User(3, "mike"),
720 ]
721 tmpl = env.from_string(
722 '{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}'
723 )
724 assert tmpl.render(users=users) == "jane"
725
726 def test_json_dump(self):
727 env = Environment(autoescape=True)
728 t = env.from_string("{{ x|tojson }}")
729 assert t.render(x={"foo": "bar"}) == '{"foo": "bar"}'
730 assert t.render(x="\"ba&r'") == r'"\"ba\u0026r\u0027"'
731 assert t.render(x="<bar>") == r'"\u003cbar\u003e"'
732
733 def my_dumps(value, **options):
734 assert options == {"foo": "bar"}
735 return "42"
736
737 env.policies["json.dumps_function"] = my_dumps
738 env.policies["json.dumps_kwargs"] = {"foo": "bar"}
739 assert t.render(x=23) == "42"
740
741 def test_wordwrap(self, env):
742 env.newline_sequence = "\n"
743 t = env.from_string("{{ s|wordwrap(20) }}")
744 result = t.render(s="Hello!\nThis is Jinja saying something.")
745 assert result == "Hello!\nThis is Jinja saying\nsomething."