jinja

Форк
0
/
test_filters.py 
882 строки · 31.9 Кб
1
import random
2
from collections import namedtuple
3

4
import pytest
5
from markupsafe import Markup
6

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
12

13

14
class Magic:
15
    def __init__(self, value):
16
        self.value = value
17

18
    def __str__(self):
19
        return str(self.value)
20

21

22
class Magic2:
23
    def __init__(self, value1, value2):
24
        self.value1 = value1
25
        self.value2 = value2
26

27
    def __str__(self):
28
        return f"({self.value1},{self.value2})"
29

30

31
class TestFilter:
32
    def test_filter_calling(self, env):
33
        rv = env.call_filter("sum", [1, 2, 3])
34
        assert rv == 6
35

36
    def test_capitalize(self, env):
37
        tmpl = env.from_string('{{ "foo bar"|capitalize }}')
38
        assert tmpl.render() == "Foo bar"
39

40
    def test_center(self, env):
41
        tmpl = env.from_string('{{ "foo"|center(9) }}')
42
        assert tmpl.render() == "   foo   "
43

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') }}"
48
        )
49
        assert tmpl.render(given="yes") == "no|False|no|yes"
50

51
    @pytest.mark.parametrize(
52
        "args,expect",
53
        (
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)]"),
58
        ),
59
    )
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})
63
        assert out == expect
64

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)))
68
        assert out == (
69
            "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]|"
70
            "[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 'X', 'X']]"
71
        )
72

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)))
76
        assert out == (
77
            "[[0, 1, 2, 3], [4, 5, 6], [7, 8, 9]]|"
78
            "[[0, 1, 2, 3], [4, 5, 6, 'X'], [7, 8, 9, 'X']]"
79
        )
80

81
    def test_escape(self, env):
82
        tmpl = env.from_string("""{{ '<">&'|escape }}""")
83
        out = tmpl.render()
84
        assert out == "&lt;&#34;&gt;&amp;"
85

86
    @pytest.mark.parametrize(
87
        ("chars", "expect"), [(None, "..stays.."), (".", "  ..stays"), (" .", "stays")]
88
    )
89
    def test_trim(self, env, chars, expect):
90
        tmpl = env.from_string("{{ foo|trim(chars) }}")
91
        out = tmpl.render(foo="  ..stays..", chars=chars)
92
        assert out == expect
93

94
    def test_striptags(self, env):
95
        tmpl = env.from_string("""{{ foo|striptags }}""")
96
        out = tmpl.render(
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> -->"
100
        )
101
        assert out == "just a small example link to a webpage"
102

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) }}"
115
        )
116
        out = tmpl.render()
117
        assert out == (
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"
120
        )
121

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) }}"
132
        )
133
        out = tmpl.render()
134
        assert out == (
135
            "300 Bytes|3.0 kB|3.0 MB|3.0 GB|3.0 TB|300 Bytes|2.9 KiB|2.9 MiB"
136
        )
137

138
    def test_first(self, env):
139
        tmpl = env.from_string("{{ foo|first }}")
140
        out = tmpl.render(foo=list(range(10)))
141
        assert out == "0"
142

143
    @pytest.mark.parametrize(
144
        ("value", "expect"), (("42", "42.0"), ("abc", "0.0"), ("32.32", "32.32"))
145
    )
146
    def test_float(self, env, value, expect):
147
        t = env.from_string("{{ value|float }}")
148
        assert t.render(value=value) == expect
149

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"
153

154
    def test_format(self, env):
155
        tmpl = env.from_string("{{ '%s|%s'|format('a', 'b') }}")
156
        out = tmpl.render()
157
        assert out == "a|b"
158

159
    @staticmethod
160
    def _test_indent_multiline_template(env, markup=False):
161
        text = "\n".join(["", "foo bar", '"baz"', ""])
162
        if markup:
163
            text = Markup(text)
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  '
172

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"
181

182
    def test_indent_markup_input(self, env):
183
        """
184
        Tests cases where the filter input is a Markup type
185
        """
186
        self._test_indent_multiline_template(env, markup=True)
187

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"
191

192
    @pytest.mark.parametrize(
193
        ("value", "expect"),
194
        (
195
            ("42", "42"),
196
            ("abc", "0"),
197
            ("32.32", "32"),
198
            ("12345678901234567890", "12345678901234567890"),
199
        ),
200
    )
201
    def test_int(self, env, value, expect):
202
        t = env.from_string("{{ value|int }}")
203
        assert t.render(value=value) == expect
204

205
    @pytest.mark.parametrize(
206
        ("value", "base", "expect"),
207
        (("0x4d32", 16, "19762"), ("011", 8, "9"), ("0x33Z", 16, "0")),
208
    )
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
212

213
    def test_int_default(self, env):
214
        t = env.from_string("{{ value|int(default=1) }}")
215
        assert t.render(value="abc") == "1"
216

217
    def test_int_special_method(self, env):
218
        class IntIsh:
219
            def __int__(self):
220
                return 42
221

222
        t = env.from_string("{{ value|int }}")
223
        assert t.render(value=IntIsh()) == "42"
224

225
    def test_join(self, env):
226
        tmpl = env.from_string('{{ [1, 2, 3]|join("|") }}')
227
        out = tmpl.render()
228
        assert out == "1|2|3"
229

230
        env2 = Environment(autoescape=True)
231
        tmpl = env2.from_string('{{ ["<foo>", "<span>foo</span>"|safe]|join }}')
232
        assert tmpl.render() == "&lt;foo&gt;<span>foo</span>"
233

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"
238

239
    def test_last(self, env):
240
        tmpl = env.from_string("""{{ foo|last }}""")
241
        out = tmpl.render(foo=list(range(10)))
242
        assert out == "9"
243

244
    def test_length(self, env):
245
        tmpl = env.from_string("""{{ "hello world"|length }}""")
246
        out = tmpl.render()
247
        assert out == "11"
248

249
    def test_lower(self, env):
250
        tmpl = env.from_string("""{{ "FOO"|lower }}""")
251
        out = tmpl.render()
252
        assert out == "foo"
253

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')]"
259

260
    def test_items_undefined(self, env):
261
        tmpl = env.from_string("""{{ d|items|list }}""")
262
        out = tmpl.render()
263
        assert out == "[]"
264

265
    def test_pprint(self, env):
266
        from pprint import pformat
267

268
        tmpl = env.from_string("""{{ data|pprint }}""")
269
        data = list(range(1000))
270
        assert tmpl.render(data=data) == pformat(data)
271

272
    def test_random(self, env, request):
273
        # restore the random state when the test ends
274
        state = random.getstate()
275
        request.addfinalizer(lambda: random.setstate(state))
276
        # generate the random values from a known seed
277
        random.seed("jinja")
278
        expected = [random.choice("1234567890") for _ in range(10)]
279

280
        # check that the random sequence is generated again by a template
281
        # ensures that filter result is not constant folded
282
        random.seed("jinja")
283
        t = env.from_string('{{ "1234567890"|random }}')
284

285
        for value in expected:
286
            assert t.render() == value
287

288
    def test_reverse(self, env):
289
        tmpl = env.from_string(
290
            "{{ 'foobar'|reverse|join }}|{{ [1, 2, 3]|reverse|list }}"
291
        )
292
        assert tmpl.render() == "raboof|[3, 2, 1]"
293

294
    def test_string(self, env):
295
        x = [1, 2, 3, 4, 5]
296
        tmpl = env.from_string("""{{ obj|string }}""")
297
        assert tmpl.render(obj=x) == str(x)
298

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>"
322

323
        class Foo:
324
            def __str__(self):
325
                return "foo-bar"
326

327
        tmpl = env.from_string("""{{ data|title }}""")
328
        out = tmpl.render(data=Foo())
329
        assert out == "Foo-Bar"
330

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) }}"
336
        )
337
        out = tmpl.render(data="foobar baz bar" * 1000, smalldata="foobar baz bar")
338
        assert out == "foobar baz b>>>|foobar baz>>>|foobar baz bar"
339

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) }}'
343
        )
344
        out = tmpl.render()
345
        assert out == "foo bar baz|foo bar baz"
346

347
    def test_truncate_end_length(self, env):
348
        tmpl = env.from_string('{{ "Joel is a slug"|truncate(7, true) }}')
349
        out = tmpl.render()
350
        assert out == "Joel..."
351

352
    def test_upper(self, env):
353
        tmpl = env.from_string('{{ "foo"|upper }}')
354
        assert tmpl.render() == "FOO"
355

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"
360
        )
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"
365
        )
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'
369
        )
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'
373
        )
374

375
    def test_urlize_rel_policy(self):
376
        env = Environment()
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'
381
        )
382

383
    def test_urlize_target_parameter(self, env):
384
        tmpl = env.from_string(
385
            '{{ "foo http://www.example.com/ bar"|urlize(target="_blank") }}'
386
        )
387
        assert (
388
            tmpl.render()
389
            == 'foo <a href="http://www.example.com/" rel="noopener" target="_blank">'
390
            "http://www.example.com/</a> bar"
391
        )
392

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:"]) }}'
397
        )
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"
402
        )
403

404
    def test_wordcount(self, env):
405
        tmpl = env.from_string('{{ "foo bar baz"|wordcount }}')
406
        assert tmpl.render() == "3"
407

408
        strict_env = Environment(undefined=StrictUndefined)
409
        t = strict_env.from_string("{{ s|wordcount }}")
410
        with pytest.raises(UndefinedError):
411
            t.render()
412

413
    def test_block(self, env):
414
        tmpl = env.from_string("{% filter lower|escape %}<HEHE>{% endfilter %}")
415
        assert tmpl.render() == "&lt;hehe&gt;"
416

417
    def test_chaining(self, env):
418
        tmpl = env.from_string("""{{ ['<foo>', '<bar>']|first|upper|escape }}""")
419
        assert tmpl.render() == "&lt;FOO&gt;"
420

421
    def test_sum(self, env):
422
        tmpl = env.from_string("""{{ [1, 2, 3, 4, 5, 6]|sum }}""")
423
        assert tmpl.render() == "21"
424

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"
428

429
    def test_sum_attributes_nested(self, env):
430
        tmpl = env.from_string("""{{ values|sum('real.value') }}""")
431
        assert (
432
            tmpl.render(
433
                values=[
434
                    {"real": {"value": 23}},
435
                    {"real": {"value": 1}},
436
                    {"real": {"value": 18}},
437
                ]
438
            )
439
            == "42"
440
        )
441

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"
445

446
    def test_abs(self, env):
447
        tmpl = env.from_string("""{{ -1|abs }}|{{ 1|abs }}""")
448
        assert tmpl.render() == "1|1", tmpl.render()
449

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') }}"
455
        )
456
        assert tmpl.render() == "3.0|2.0|2.123|3.0", tmpl.render()
457

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')}}"
463
        )
464
        assert tmpl.render() == "20.0|30.0|20.0", tmpl.render()
465

466
    def test_xmlattr(self, env):
467
        tmpl = env.from_string(
468
            "{{ {'foo': 42, 'bar': 23, 'fish': none, "
469
            "'spam': missing, 'blub:blub': '<?>'}|xmlattr }}"
470
        )
471
        out = tmpl.render().split()
472
        assert len(out) == 3
473
        assert 'foo="42"' in out
474
        assert 'bar="23"' in out
475
        assert 'blub:blub="&lt;?&gt;"' in out
476

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)"
482
            )
483

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]"
487

488
    def test_sort2(self, env):
489
        tmpl = env.from_string('{{ "".join(["c", "A", "b", "D"]|sort) }}')
490
        assert tmpl.render() == "AbcD"
491

492
    def test_sort3(self, env):
493
        tmpl = env.from_string("""{{ ['foo', 'Bar', 'blah']|sort }}""")
494
        assert tmpl.render() == "['Bar', 'blah', 'foo']"
495

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"
499

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]"
503

504
    def test_sort6(self, env):
505
        tmpl = env.from_string("""{{ items|sort(attribute='value1,value2')|join }}""")
506
        assert (
507
            tmpl.render(
508
                items=map(
509
                    lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
510
                )
511
            )
512
            == "(2,1)(2,2)(2,5)(3,1)"
513
        )
514

515
    def test_sort7(self, env):
516
        tmpl = env.from_string("""{{ items|sort(attribute='value2,value1')|join }}""")
517
        assert (
518
            tmpl.render(
519
                items=map(
520
                    lambda x: Magic2(x[0], x[1]), [(3, 1), (2, 2), (2, 1), (2, 5)]
521
                )
522
            )
523
            == "(2,1)(3,1)(2,2)(2,5)"
524
        )
525

526
    def test_sort8(self, env):
527
        tmpl = env.from_string(
528
            """{{ items|sort(attribute='value1.0,value2.0')|join }}"""
529
        )
530
        assert (
531
            tmpl.render(
532
                items=map(
533
                    lambda x: Magic2(x[0], x[1]),
534
                    [([3], [1]), ([2], [2]), ([2], [1]), ([2], [5])],
535
                )
536
            )
537
            == "([2],[1])([2],[2])([2],[5])([3],[1])"
538
        )
539

540
    def test_unique(self, env):
541
        t = env.from_string('{{ "".join(["b", "A", "a", "b"]|unique) }}')
542
        assert t.render() == "bA"
543

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"
547

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"
551

552
    @pytest.mark.parametrize(
553
        "source,expect",
554
        (
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 }}", ""),
561
        ),
562
    )
563
    def test_min_max(self, env, source, expect):
564
        t = env.from_string(source)
565
        assert t.render() == expect
566

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
571

572
    def test_groupby(self, env):
573
        tmpl = env.from_string(
574
            """
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 %}|
580
        {%- endfor %}"""
581
        )
582
        assert tmpl.render().split("|") == ["1: 1, 2: 1, 1", "2: 2, 3", "3: 3, 4", ""]
583

584
    def test_groupby_tuple_index(self, env):
585
        tmpl = env.from_string(
586
            """
587
        {%- for grouper, list in [('a', 1), ('a', 2), ('b', 1)]|groupby(0) -%}
588
            {{ grouper }}{% for x in list %}:{{ x.1 }}{% endfor %}|
589
        {%- endfor %}"""
590
        )
591
        assert tmpl.render() == "a:1:2|b:1|"
592

593
    def test_groupby_multidot(self, env):
594
        Date = namedtuple("Date", "day,month,year")
595
        Article = namedtuple("Article", "title,date")
596
        articles = [
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)),
601
        ]
602
        tmpl = env.from_string(
603
            """
604
        {%- for year, list in articles|groupby('date.year') -%}
605
            {{ year }}{% for x in list %}[{{ x.title }}]{% endfor %}|
606
        {%- endfor %}"""
607
        )
608
        assert tmpl.render(articles=articles).split("|") == [
609
            "1970[aha][interesting][really?]",
610
            "1971[totally not]",
611
            "",
612
        ]
613

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"
618
            "{% endfor %}"
619
        )
620
        out = tmpl.render(
621
            users=[
622
                {"name": "emma", "city": "NY"},
623
                {"name": "smith", "city": "WA"},
624
                {"name": "john"},
625
            ]
626
        )
627
        assert out == "NY: emma, john\nWA: smith\n"
628

629
    @pytest.mark.parametrize(
630
        ("case_sensitive", "expect"),
631
        [
632
            (False, "a: 1, 3\nb: 2\n"),
633
            (True, "A: 3\na: 1\nb: 2\n"),
634
        ],
635
    )
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"
640
            "{% endfor %}"
641
        )
642
        out = tmpl.render(
643
            data=[{"k": "a", "v": 1}, {"k": "b", "v": 2}, {"k": "A", "v": 3}],
644
            cs=case_sensitive,
645
        )
646
        assert out == expect
647

648
    def test_filtertag(self, env):
649
        tmpl = env.from_string(
650
            "{% filter upper|replace('FOO', 'foo') %}foobar{% endfilter %}"
651
        )
652
        assert tmpl.render() == "fooBAR"
653

654
    def test_replace(self, env):
655
        env = Environment()
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>") == "&lt;f4242&gt;"
661
        tmpl = env.from_string('{{ string|replace("<", 42) }}')
662
        assert tmpl.render(string="<foo>") == "42foo&gt;"
663
        tmpl = env.from_string('{{ string|replace("o", ">x<") }}')
664
        assert tmpl.render(string=Markup("foo")) == "f&gt;x&lt;&gt;x&lt;"
665

666
    def test_forceescape(self, env):
667
        tmpl = env.from_string("{{ x|forceescape }}")
668
        assert tmpl.render(x=Markup("<div />")) == "&lt;div /&gt;"
669

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() == "&lt;div&gt;foo&lt;/div&gt;"
676

677
    @pytest.mark.parametrize(
678
        ("value", "expect"),
679
        [
680
            ("Hello, world!", "Hello%2C%20world%21"),
681
            ("Hello, world\u203d", "Hello%2C%20world%E2%80%BD"),
682
            ({"f": 1}, "f=1"),
683
            ([("f", 1), ("z", 2)], "f=1&amp;z=2"),
684
            ({"\u203d": 1}, "%E2%80%BD=1"),
685
            ({0: 1}, "0=1"),
686
            ([("a b/c", "a b/c")], "a+b%2Fc=a+b%2Fc"),
687
            ("a b/c", "a%20b/c"),
688
        ],
689
    )
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
694

695
    def test_simple_map(self, env):
696
        env = Environment()
697
        tmpl = env.from_string('{{ ["1", "2", "3"]|map("int")|sum }}')
698
        assert tmpl.render() == "6"
699

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]"
703

704
    def test_attribute_map(self, env):
705
        User = namedtuple("User", "name")
706
        env = Environment()
707
        users = [
708
            User("john"),
709
            User("jane"),
710
            User("mike"),
711
        ]
712
        tmpl = env.from_string('{{ users|map(attribute="name")|join("|") }}')
713
        assert tmpl.render(users=users) == "john|jane|mike"
714

715
    def test_empty_map(self, env):
716
        env = Environment()
717
        tmpl = env.from_string('{{ none|map("upper")|list }}')
718
        assert tmpl.render() == "[]"
719

720
    def test_map_default(self, env):
721
        Fullname = namedtuple("Fullname", "firstname,lastname")
722
        Firstname = namedtuple("Firstname", "firstname")
723
        env = Environment()
724
        tmpl = env.from_string(
725
            '{{ users|map(attribute="lastname", default="smith")|join(", ") }}'
726
        )
727
        test_list = env.from_string(
728
            '{{ users|map(attribute="lastname", default=["smith","x"])|join(", ") }}'
729
        )
730
        test_str = env.from_string(
731
            '{{ users|map(attribute="lastname", default="")|join(", ") }}'
732
        )
733
        users = [
734
            Fullname("john", "lennon"),
735
            Fullname("jane", "edwards"),
736
            Fullname("jon", None),
737
            Firstname("mike"),
738
        ]
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, "
742

743
    def test_simple_select(self, env):
744
        env = Environment()
745
        tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|select("odd")|join("|") }}')
746
        assert tmpl.render() == "1|3|5"
747

748
    def test_bool_select(self, env):
749
        env = Environment()
750
        tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|select|join("|") }}')
751
        assert tmpl.render() == "1|2|3|4|5"
752

753
    def test_simple_reject(self, env):
754
        env = Environment()
755
        tmpl = env.from_string('{{ [1, 2, 3, 4, 5]|reject("odd")|join("|") }}')
756
        assert tmpl.render() == "2|4"
757

758
    def test_bool_reject(self, env):
759
        env = Environment()
760
        tmpl = env.from_string('{{ [none, false, 0, 1, 2, 3, 4, 5]|reject|join("|") }}')
761
        assert tmpl.render() == "None|False|0"
762

763
    def test_simple_select_attr(self, env):
764
        User = namedtuple("User", "name,is_active")
765
        env = Environment()
766
        users = [
767
            User("john", True),
768
            User("jane", True),
769
            User("mike", False),
770
        ]
771
        tmpl = env.from_string(
772
            '{{ users|selectattr("is_active")|map(attribute="name")|join("|") }}'
773
        )
774
        assert tmpl.render(users=users) == "john|jane"
775

776
    def test_simple_reject_attr(self, env):
777
        User = namedtuple("User", "name,is_active")
778
        env = Environment()
779
        users = [
780
            User("john", True),
781
            User("jane", True),
782
            User("mike", False),
783
        ]
784
        tmpl = env.from_string(
785
            '{{ users|rejectattr("is_active")|map(attribute="name")|join("|") }}'
786
        )
787
        assert tmpl.render(users=users) == "mike"
788

789
    def test_func_select_attr(self, env):
790
        User = namedtuple("User", "id,name")
791
        env = Environment()
792
        users = [
793
            User(1, "john"),
794
            User(2, "jane"),
795
            User(3, "mike"),
796
        ]
797
        tmpl = env.from_string(
798
            '{{ users|selectattr("id", "odd")|map(attribute="name")|join("|") }}'
799
        )
800
        assert tmpl.render(users=users) == "john|mike"
801

802
    def test_func_reject_attr(self, env):
803
        User = namedtuple("User", "id,name")
804
        env = Environment()
805
        users = [
806
            User(1, "john"),
807
            User(2, "jane"),
808
            User(3, "mike"),
809
        ]
810
        tmpl = env.from_string(
811
            '{{ users|rejectattr("id", "odd")|map(attribute="name")|join("|") }}'
812
        )
813
        assert tmpl.render(users=users) == "jane"
814

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"'
821

822
        def my_dumps(value, **options):
823
            assert options == {"foo": "bar"}
824
            return "42"
825

826
        env.policies["json.dumps_function"] = my_dumps
827
        env.policies["json.dumps_kwargs"] = {"foo": "bar"}
828
        assert t.render(x=23) == "42"
829

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."
835

836
    def test_filter_undefined(self, env):
837
        with pytest.raises(TemplateAssertionError, match="No filter named 'f'"):
838
            env.from_string("{{ var|f }}")
839

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'"):
844
            t.render(x=42)
845

846
    def test_filter_undefined_in_elif(self, env):
847
        t = env.from_string(
848
            "{%- if x is defined -%}{{ x }}{%- elif y is defined -%}"
849
            "{{ y|f }}{%- else -%}foo{%- endif -%}"
850
        )
851
        assert t.render() == "foo"
852
        with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
853
            t.render(y=42)
854

855
    def test_filter_undefined_in_else(self, env):
856
        t = env.from_string(
857
            "{%- if x is not defined -%}foo{%- else -%}{{ x|f }}{%- endif -%}"
858
        )
859
        assert t.render() == "foo"
860
        with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
861
            t.render(x=42)
862

863
    def test_filter_undefined_in_nested_if(self, env):
864
        t = env.from_string(
865
            "{%- if x is not defined -%}foo{%- else -%}{%- if y "
866
            "is defined -%}{{ y|f }}{%- endif -%}{{ x }}{%- endif -%}"
867
        )
868
        assert t.render() == "foo"
869
        assert t.render(x=42) == "42"
870
        with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
871
            t.render(x=24, y=42)
872

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"
877

878
        with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
879
            t1.render(x=42)
880

881
        with pytest.raises(TemplateRuntimeError, match="No filter named 'f'"):
882
            t2.render(x=42)
883

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.