jinja

Форк
0
/
test_ext.py 
739 строк · 25.7 Кб
1
import re
2
from io import BytesIO
3

4
import pytest
5

6
from jinja2 import DictLoader
7
from jinja2 import Environment
8
from jinja2 import nodes
9
from jinja2 import pass_context
10
from jinja2 import TemplateSyntaxError
11
from jinja2.exceptions import TemplateAssertionError
12
from jinja2.ext import Extension
13
from jinja2.lexer import count_newlines
14
from jinja2.lexer import Token
15

16
importable_object = 23
17

18
_gettext_re = re.compile(r"_\((.*?)\)", re.DOTALL)
19

20

21
i18n_templates = {
22
    "default.html": '<title>{{ page_title|default(_("missing")) }}</title>'
23
    "{% block body %}{% endblock %}",
24
    "child.html": '{% extends "default.html" %}{% block body %}'
25
    "{% trans %}watch out{% endtrans %}{% endblock %}",
26
    "plural.html": "{% trans user_count %}One user online{% pluralize %}"
27
    "{{ user_count }} users online{% endtrans %}",
28
    "plural2.html": "{% trans user_count=get_user_count() %}{{ user_count }}s"
29
    "{% pluralize %}{{ user_count }}p{% endtrans %}",
30
    "stringformat.html": '{{ _("User: %(num)s")|format(num=user_count) }}',
31
}
32

33
newstyle_i18n_templates = {
34
    "default.html": '<title>{{ page_title|default(_("missing")) }}</title>'
35
    "{% block body %}{% endblock %}",
36
    "child.html": '{% extends "default.html" %}{% block body %}'
37
    "{% trans %}watch out{% endtrans %}{% endblock %}",
38
    "plural.html": "{% trans user_count %}One user online{% pluralize %}"
39
    "{{ user_count }} users online{% endtrans %}",
40
    "stringformat.html": '{{ _("User: %(num)s", num=user_count) }}',
41
    "ngettext.html": '{{ ngettext("%(num)s apple", "%(num)s apples", apples) }}',
42
    "ngettext_long.html": "{% trans num=apples %}{{ num }} apple{% pluralize %}"
43
    "{{ num }} apples{% endtrans %}",
44
    "pgettext.html": '{{ pgettext("fruit", "Apple") }}',
45
    "npgettext.html": '{{ npgettext("fruit", "%(num)s apple", "%(num)s apples",'
46
    " apples) }}",
47
    "pgettext_block": "{% trans 'fruit' num=apples %}Apple{% endtrans %}",
48
    "npgettext_block": "{% trans 'fruit' num=apples %}{{ num }} apple"
49
    "{% pluralize %}{{ num }} apples{% endtrans %}",
50
    "transvars1.html": "{% trans %}User: {{ num }}{% endtrans %}",
51
    "transvars2.html": "{% trans num=count %}User: {{ num }}{% endtrans %}",
52
    "transvars3.html": "{% trans count=num %}User: {{ count }}{% endtrans %}",
53
    "novars.html": "{% trans %}%(hello)s{% endtrans %}",
54
    "vars.html": "{% trans %}{{ foo }}%(foo)s{% endtrans %}",
55
    "explicitvars.html": '{% trans foo="42" %}%(foo)s{% endtrans %}',
56
}
57

58

59
languages = {
60
    "de": {
61
        "missing": "fehlend",
62
        "watch out": "pass auf",
63
        "One user online": "Ein Benutzer online",
64
        "%(user_count)s users online": "%(user_count)s Benutzer online",
65
        "User: %(num)s": "Benutzer: %(num)s",
66
        "User: %(count)s": "Benutzer: %(count)s",
67
        "Apple": {None: "Apfel", "fruit": "Apple"},
68
        "%(num)s apple": {None: "%(num)s Apfel", "fruit": "%(num)s Apple"},
69
        "%(num)s apples": {None: "%(num)s Äpfel", "fruit": "%(num)s Apples"},
70
    }
71
}
72

73

74
def _get_with_context(value, ctx=None):
75
    if isinstance(value, dict):
76
        return value.get(ctx, value)
77

78
    return value
79

80

81
@pass_context
82
def gettext(context, string):
83
    language = context.get("LANGUAGE", "en")
84
    value = languages.get(language, {}).get(string, string)
85
    return _get_with_context(value)
86

87

88
@pass_context
89
def ngettext(context, s, p, n):
90
    language = context.get("LANGUAGE", "en")
91

92
    if n != 1:
93
        value = languages.get(language, {}).get(p, p)
94
        return _get_with_context(value)
95

96
    value = languages.get(language, {}).get(s, s)
97
    return _get_with_context(value)
98

99

100
@pass_context
101
def pgettext(context, c, s):
102
    language = context.get("LANGUAGE", "en")
103
    value = languages.get(language, {}).get(s, s)
104
    return _get_with_context(value, c)
105

106

107
@pass_context
108
def npgettext(context, c, s, p, n):
109
    language = context.get("LANGUAGE", "en")
110

111
    if n != 1:
112
        value = languages.get(language, {}).get(p, p)
113
        return _get_with_context(value, c)
114

115
    value = languages.get(language, {}).get(s, s)
116
    return _get_with_context(value, c)
117

118

119
i18n_env = Environment(
120
    loader=DictLoader(i18n_templates), extensions=["jinja2.ext.i18n"]
121
)
122
i18n_env.globals.update(
123
    {
124
        "_": gettext,
125
        "gettext": gettext,
126
        "ngettext": ngettext,
127
        "pgettext": pgettext,
128
        "npgettext": npgettext,
129
    }
130
)
131
i18n_env_trimmed = Environment(extensions=["jinja2.ext.i18n"])
132

133
i18n_env_trimmed.policies["ext.i18n.trimmed"] = True
134
i18n_env_trimmed.globals.update(
135
    {
136
        "_": gettext,
137
        "gettext": gettext,
138
        "ngettext": ngettext,
139
        "pgettext": pgettext,
140
        "npgettext": npgettext,
141
    }
142
)
143

144
newstyle_i18n_env = Environment(
145
    loader=DictLoader(newstyle_i18n_templates), extensions=["jinja2.ext.i18n"]
146
)
147
newstyle_i18n_env.install_gettext_callables(  # type: ignore
148
    gettext, ngettext, newstyle=True, pgettext=pgettext, npgettext=npgettext
149
)
150

151

152
class ExampleExtension(Extension):
153
    tags = {"test"}
154
    ext_attr = 42
155
    context_reference_node_cls = nodes.ContextReference
156

157
    def parse(self, parser):
158
        return nodes.Output(
159
            [
160
                self.call_method(
161
                    "_dump",
162
                    [
163
                        nodes.EnvironmentAttribute("sandboxed"),
164
                        self.attr("ext_attr"),
165
                        nodes.ImportedName(__name__ + ".importable_object"),
166
                        self.context_reference_node_cls(),
167
                    ],
168
                )
169
            ]
170
        ).set_lineno(next(parser.stream).lineno)
171

172
    def _dump(self, sandboxed, ext_attr, imported_object, context):
173
        return (
174
            f"{sandboxed}|{ext_attr}|{imported_object}|{context.blocks}"
175
            f"|{context.get('test_var')}"
176
        )
177

178

179
class DerivedExampleExtension(ExampleExtension):
180
    context_reference_node_cls = nodes.DerivedContextReference  # type: ignore
181

182

183
class PreprocessorExtension(Extension):
184
    def preprocess(self, source, name, filename=None):
185
        return source.replace("[[TEST]]", "({{ foo }})")
186

187

188
class StreamFilterExtension(Extension):
189
    def filter_stream(self, stream):
190
        for token in stream:
191
            if token.type == "data":
192
                yield from self.interpolate(token)
193
            else:
194
                yield token
195

196
    def interpolate(self, token):
197
        pos = 0
198
        end = len(token.value)
199
        lineno = token.lineno
200
        while True:
201
            match = _gettext_re.search(token.value, pos)
202
            if match is None:
203
                break
204
            value = token.value[pos : match.start()]
205
            if value:
206
                yield Token(lineno, "data", value)
207
            lineno += count_newlines(token.value)
208
            yield Token(lineno, "variable_begin", None)
209
            yield Token(lineno, "name", "gettext")
210
            yield Token(lineno, "lparen", None)
211
            yield Token(lineno, "string", match.group(1))
212
            yield Token(lineno, "rparen", None)
213
            yield Token(lineno, "variable_end", None)
214
            pos = match.end()
215
        if pos < end:
216
            yield Token(lineno, "data", token.value[pos:])
217

218

219
class TestExtensions:
220
    def test_extend_late(self):
221
        env = Environment()
222
        t = env.from_string('{% autoescape true %}{{ "<test>" }}{% endautoescape %}')
223
        assert t.render() == "&lt;test&gt;"
224

225
    def test_loop_controls(self):
226
        env = Environment(extensions=["jinja2.ext.loopcontrols"])
227

228
        tmpl = env.from_string(
229
            """
230
            {%- for item in [1, 2, 3, 4] %}
231
                {%- if item % 2 == 0 %}{% continue %}{% endif -%}
232
                {{ item }}
233
            {%- endfor %}"""
234
        )
235
        assert tmpl.render() == "13"
236

237
        tmpl = env.from_string(
238
            """
239
            {%- for item in [1, 2, 3, 4] %}
240
                {%- if item > 2 %}{% break %}{% endif -%}
241
                {{ item }}
242
            {%- endfor %}"""
243
        )
244
        assert tmpl.render() == "12"
245

246
    def test_do(self):
247
        env = Environment(extensions=["jinja2.ext.do"])
248
        tmpl = env.from_string(
249
            """
250
            {%- set items = [] %}
251
            {%- for char in "foo" %}
252
                {%- do items.append(loop.index0 ~ char) %}
253
            {%- endfor %}{{ items|join(', ') }}"""
254
        )
255
        assert tmpl.render() == "0f, 1o, 2o"
256

257
    def test_extension_nodes(self):
258
        env = Environment(extensions=[ExampleExtension])
259
        tmpl = env.from_string("{% test %}")
260
        assert tmpl.render() == "False|42|23|{}|None"
261

262
    def test_contextreference_node_passes_context(self):
263
        env = Environment(extensions=[ExampleExtension])
264
        tmpl = env.from_string('{% set test_var="test_content" %}{% test %}')
265
        assert tmpl.render() == "False|42|23|{}|test_content"
266

267
    def test_contextreference_node_can_pass_locals(self):
268
        env = Environment(extensions=[DerivedExampleExtension])
269
        tmpl = env.from_string(
270
            '{% for test_var in ["test_content"] %}{% test %}{% endfor %}'
271
        )
272
        assert tmpl.render() == "False|42|23|{}|test_content"
273

274
    def test_identifier(self):
275
        assert ExampleExtension.identifier == __name__ + ".ExampleExtension"
276

277
    def test_rebinding(self):
278
        original = Environment(extensions=[ExampleExtension])
279
        overlay = original.overlay()
280
        for env in original, overlay:
281
            for ext in env.extensions.values():
282
                assert ext.environment is env
283

284
    def test_preprocessor_extension(self):
285
        env = Environment(extensions=[PreprocessorExtension])
286
        tmpl = env.from_string("{[[TEST]]}")
287
        assert tmpl.render(foo=42) == "{(42)}"
288

289
    def test_streamfilter_extension(self):
290
        env = Environment(extensions=[StreamFilterExtension])
291
        env.globals["gettext"] = lambda x: x.upper()
292
        tmpl = env.from_string("Foo _(bar) Baz")
293
        out = tmpl.render()
294
        assert out == "Foo BAR Baz"
295

296
    def test_extension_ordering(self):
297
        class T1(Extension):
298
            priority = 1
299

300
        class T2(Extension):
301
            priority = 2
302

303
        env = Environment(extensions=[T1, T2])
304
        ext = list(env.iter_extensions())
305
        assert ext[0].__class__ is T1
306
        assert ext[1].__class__ is T2
307

308
    def test_debug(self):
309
        env = Environment(extensions=["jinja2.ext.debug"])
310
        t = env.from_string("Hello\n{% debug %}\nGoodbye")
311
        out = t.render()
312

313
        for value in ("context", "cycler", "filters", "abs", "tests", "!="):
314
            assert f"'{value}'" in out
315

316

317
class TestInternationalization:
318
    def test_trans(self):
319
        tmpl = i18n_env.get_template("child.html")
320
        assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
321

322
    def test_trans_plural(self):
323
        tmpl = i18n_env.get_template("plural.html")
324
        assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
325
        assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
326

327
    def test_trans_plural_with_functions(self):
328
        tmpl = i18n_env.get_template("plural2.html")
329

330
        def get_user_count():
331
            get_user_count.called += 1
332
            return 1
333

334
        get_user_count.called = 0
335
        assert tmpl.render(LANGUAGE="de", get_user_count=get_user_count) == "1s"
336
        assert get_user_count.called == 1
337

338
    def test_complex_plural(self):
339
        tmpl = i18n_env.from_string(
340
            "{% trans foo=42, count=2 %}{{ count }} item{% "
341
            "pluralize count %}{{ count }} items{% endtrans %}"
342
        )
343
        assert tmpl.render() == "2 items"
344
        pytest.raises(
345
            TemplateAssertionError,
346
            i18n_env.from_string,
347
            "{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
348
        )
349

350
    def test_trans_stringformatting(self):
351
        tmpl = i18n_env.get_template("stringformat.html")
352
        assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
353

354
    def test_trimmed(self):
355
        tmpl = i18n_env.from_string(
356
            "{%- trans trimmed %}  hello\n  world  {% endtrans -%}"
357
        )
358
        assert tmpl.render() == "hello world"
359

360
    def test_trimmed_policy(self):
361
        s = "{%- trans %}  hello\n  world  {% endtrans -%}"
362
        tmpl = i18n_env.from_string(s)
363
        trimmed_tmpl = i18n_env_trimmed.from_string(s)
364
        assert tmpl.render() == "  hello\n  world  "
365
        assert trimmed_tmpl.render() == "hello world"
366

367
    def test_trimmed_policy_override(self):
368
        tmpl = i18n_env_trimmed.from_string(
369
            "{%- trans notrimmed %}  hello\n  world  {% endtrans -%}"
370
        )
371
        assert tmpl.render() == "  hello\n  world  "
372

373
    def test_trimmed_vars(self):
374
        tmpl = i18n_env.from_string(
375
            '{%- trans trimmed x="world" %}  hello\n  {{ x }} {% endtrans -%}'
376
        )
377
        assert tmpl.render() == "hello world"
378

379
    def test_trimmed_varname_trimmed(self):
380
        # unlikely variable name, but when used as a variable
381
        # it should not enable trimming
382
        tmpl = i18n_env.from_string(
383
            "{%- trans trimmed = 'world' %}  hello\n  {{ trimmed }}  {% endtrans -%}"
384
        )
385
        assert tmpl.render() == "  hello\n  world  "
386

387
    def test_extract(self):
388
        from jinja2.ext import babel_extract
389

390
        source = BytesIO(
391
            b"""
392
            {{ gettext('Hello World') }}
393
            {% trans %}Hello World{% endtrans %}
394
            {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
395
            """
396
        )
397
        assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
398
            (2, "gettext", "Hello World", []),
399
            (3, "gettext", "Hello World", []),
400
            (4, "ngettext", ("%(users)s user", "%(users)s users", None), []),
401
        ]
402

403
    def test_extract_trimmed(self):
404
        from jinja2.ext import babel_extract
405

406
        source = BytesIO(
407
            b"""
408
            {{ gettext(' Hello  \n  World') }}
409
            {% trans trimmed %} Hello  \n  World{% endtrans %}
410
            {% trans trimmed %}{{ users }} \n user
411
            {%- pluralize %}{{ users }} \n users{% endtrans %}
412
            """
413
        )
414
        assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], {})) == [
415
            (2, "gettext", " Hello  \n  World", []),
416
            (4, "gettext", "Hello World", []),
417
            (6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
418
        ]
419

420
    def test_extract_trimmed_option(self):
421
        from jinja2.ext import babel_extract
422

423
        source = BytesIO(
424
            b"""
425
            {{ gettext(' Hello  \n  World') }}
426
            {% trans %} Hello  \n  World{% endtrans %}
427
            {% trans %}{{ users }} \n user
428
            {%- pluralize %}{{ users }} \n users{% endtrans %}
429
            """
430
        )
431
        opts = {"trimmed": "true"}
432
        assert list(babel_extract(source, ("gettext", "ngettext", "_"), [], opts)) == [
433
            (2, "gettext", " Hello  \n  World", []),
434
            (4, "gettext", "Hello World", []),
435
            (6, "ngettext", ("%(users)s user", "%(users)s users", None), []),
436
        ]
437

438
    def test_comment_extract(self):
439
        from jinja2.ext import babel_extract
440

441
        source = BytesIO(
442
            b"""
443
            {# trans first #}
444
            {{ gettext('Hello World') }}
445
            {% trans %}Hello World{% endtrans %}{# trans second #}
446
            {#: third #}
447
            {% trans %}{{ users }} user{% pluralize %}{{ users }} users{% endtrans %}
448
            """
449
        )
450
        assert list(
451
            babel_extract(source, ("gettext", "ngettext", "_"), ["trans", ":"], {})
452
        ) == [
453
            (3, "gettext", "Hello World", ["first"]),
454
            (4, "gettext", "Hello World", ["second"]),
455
            (6, "ngettext", ("%(users)s user", "%(users)s users", None), ["third"]),
456
        ]
457

458
    def test_extract_context(self):
459
        from jinja2.ext import babel_extract
460

461
        source = BytesIO(
462
            b"""
463
             {{ pgettext("babel", "Hello World") }}
464
             {{ npgettext("babel", "%(users)s user", "%(users)s users", users) }}
465
             """
466
        )
467
        assert list(babel_extract(source, ("pgettext", "npgettext", "_"), [], {})) == [
468
            (2, "pgettext", ("babel", "Hello World"), []),
469
            (3, "npgettext", ("babel", "%(users)s user", "%(users)s users", None), []),
470
        ]
471

472
    def test_nested_trans_error(self):
473
        s = "{% trans %}foo{% trans %}{% endtrans %}"
474
        with pytest.raises(TemplateSyntaxError) as excinfo:
475
            i18n_env.from_string(s)
476
        assert "trans blocks can't be nested" in str(excinfo.value)
477

478
    def test_trans_block_error(self):
479
        s = "{% trans %}foo{% wibble bar %}{% endwibble %}{% endtrans %}"
480
        with pytest.raises(TemplateSyntaxError) as excinfo:
481
            i18n_env.from_string(s)
482
        assert "saw `wibble`" in str(excinfo.value)
483

484

485
class TestScope:
486
    def test_basic_scope_behavior(self):
487
        # This is what the old with statement compiled down to
488
        class ScopeExt(Extension):
489
            tags = {"scope"}
490

491
            def parse(self, parser):
492
                node = nodes.Scope(lineno=next(parser.stream).lineno)
493
                assignments = []
494
                while parser.stream.current.type != "block_end":
495
                    lineno = parser.stream.current.lineno
496
                    if assignments:
497
                        parser.stream.expect("comma")
498
                    target = parser.parse_assign_target()
499
                    parser.stream.expect("assign")
500
                    expr = parser.parse_expression()
501
                    assignments.append(nodes.Assign(target, expr, lineno=lineno))
502
                node.body = assignments + list(
503
                    parser.parse_statements(("name:endscope",), drop_needle=True)
504
                )
505
                return node
506

507
        env = Environment(extensions=[ScopeExt])
508
        tmpl = env.from_string(
509
            """\
510
        {%- scope a=1, b=2, c=b, d=e, e=5 -%}
511
            {{ a }}|{{ b }}|{{ c }}|{{ d }}|{{ e }}
512
        {%- endscope -%}
513
        """
514
        )
515
        assert tmpl.render(b=3, e=4) == "1|2|2|4|5"
516

517

518
class TestNewstyleInternationalization:
519
    def test_trans(self):
520
        tmpl = newstyle_i18n_env.get_template("child.html")
521
        assert tmpl.render(LANGUAGE="de") == "<title>fehlend</title>pass auf"
522

523
    def test_trans_plural(self):
524
        tmpl = newstyle_i18n_env.get_template("plural.html")
525
        assert tmpl.render(LANGUAGE="de", user_count=1) == "Ein Benutzer online"
526
        assert tmpl.render(LANGUAGE="de", user_count=2) == "2 Benutzer online"
527

528
    def test_complex_plural(self):
529
        tmpl = newstyle_i18n_env.from_string(
530
            "{% trans foo=42, count=2 %}{{ count }} item{% "
531
            "pluralize count %}{{ count }} items{% endtrans %}"
532
        )
533
        assert tmpl.render() == "2 items"
534
        pytest.raises(
535
            TemplateAssertionError,
536
            i18n_env.from_string,
537
            "{% trans foo %}...{% pluralize bar %}...{% endtrans %}",
538
        )
539

540
    def test_trans_stringformatting(self):
541
        tmpl = newstyle_i18n_env.get_template("stringformat.html")
542
        assert tmpl.render(LANGUAGE="de", user_count=5) == "Benutzer: 5"
543

544
    def test_newstyle_plural(self):
545
        tmpl = newstyle_i18n_env.get_template("ngettext.html")
546
        assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apfel"
547
        assert tmpl.render(LANGUAGE="de", apples=5) == "5 Äpfel"
548

549
    def test_autoescape_support(self):
550
        env = Environment(extensions=["jinja2.ext.i18n"])
551
        env.install_gettext_callables(
552
            lambda x: "<strong>Wert: %(name)s</strong>",
553
            lambda s, p, n: s,
554
            newstyle=True,
555
        )
556
        t = env.from_string(
557
            '{% autoescape ae %}{{ gettext("foo", name='
558
            '"<test>") }}{% endautoescape %}'
559
        )
560
        assert t.render(ae=True) == "<strong>Wert: &lt;test&gt;</strong>"
561
        assert t.render(ae=False) == "<strong>Wert: <test></strong>"
562

563
    def test_autoescape_macros(self):
564
        env = Environment(autoescape=False)
565
        template = (
566
            "{% macro m() %}<html>{% endmacro %}"
567
            "{% autoescape true %}{{ m() }}{% endautoescape %}"
568
        )
569
        assert env.from_string(template).render() == "<html>"
570

571
    def test_num_used_twice(self):
572
        tmpl = newstyle_i18n_env.get_template("ngettext_long.html")
573
        assert tmpl.render(apples=5, LANGUAGE="de") == "5 Äpfel"
574

575
    def test_num_called_num(self):
576
        source = newstyle_i18n_env.compile(
577
            """
578
            {% trans num=3 %}{{ num }} apple{% pluralize
579
            %}{{ num }} apples{% endtrans %}
580
        """,
581
            raw=True,
582
        )
583
        # quite hacky, but the only way to properly test that.  The idea is
584
        # that the generated code does not pass num twice (although that
585
        # would work) for better performance.  This only works on the
586
        # newstyle gettext of course
587
        assert (
588
            re.search(r"u?'%\(num\)s apple', u?'%\(num\)s apples', 3", source)
589
            is not None
590
        )
591

592
    def test_trans_vars(self):
593
        t1 = newstyle_i18n_env.get_template("transvars1.html")
594
        t2 = newstyle_i18n_env.get_template("transvars2.html")
595
        t3 = newstyle_i18n_env.get_template("transvars3.html")
596
        assert t1.render(num=1, LANGUAGE="de") == "Benutzer: 1"
597
        assert t2.render(count=23, LANGUAGE="de") == "Benutzer: 23"
598
        assert t3.render(num=42, LANGUAGE="de") == "Benutzer: 42"
599

600
    def test_novars_vars_escaping(self):
601
        t = newstyle_i18n_env.get_template("novars.html")
602
        assert t.render() == "%(hello)s"
603
        t = newstyle_i18n_env.get_template("vars.html")
604
        assert t.render(foo="42") == "42%(foo)s"
605
        t = newstyle_i18n_env.get_template("explicitvars.html")
606
        assert t.render() == "%(foo)s"
607

608
    def test_context(self):
609
        tmpl = newstyle_i18n_env.get_template("pgettext.html")
610
        assert tmpl.render(LANGUAGE="de") == "Apple"
611

612
    def test_context_plural(self):
613
        tmpl = newstyle_i18n_env.get_template("npgettext.html")
614
        assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
615
        assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
616

617
    def test_context_block(self):
618
        tmpl = newstyle_i18n_env.get_template("pgettext_block")
619
        assert tmpl.render(LANGUAGE="de") == "Apple"
620

621
    def test_context_plural_block(self):
622
        tmpl = newstyle_i18n_env.get_template("npgettext_block")
623
        assert tmpl.render(LANGUAGE="de", apples=1) == "1 Apple"
624
        assert tmpl.render(LANGUAGE="de", apples=5) == "5 Apples"
625

626

627
class TestAutoEscape:
628
    def test_scoped_setting(self):
629
        env = Environment(autoescape=True)
630
        tmpl = env.from_string(
631
            """
632
            {{ "<HelloWorld>" }}
633
            {% autoescape false %}
634
                {{ "<HelloWorld>" }}
635
            {% endautoescape %}
636
            {{ "<HelloWorld>" }}
637
        """
638
        )
639
        assert tmpl.render().split() == [
640
            "&lt;HelloWorld&gt;",
641
            "<HelloWorld>",
642
            "&lt;HelloWorld&gt;",
643
        ]
644

645
        env = Environment(autoescape=False)
646
        tmpl = env.from_string(
647
            """
648
            {{ "<HelloWorld>" }}
649
            {% autoescape true %}
650
                {{ "<HelloWorld>" }}
651
            {% endautoescape %}
652
            {{ "<HelloWorld>" }}
653
        """
654
        )
655
        assert tmpl.render().split() == [
656
            "<HelloWorld>",
657
            "&lt;HelloWorld&gt;",
658
            "<HelloWorld>",
659
        ]
660

661
    def test_nonvolatile(self):
662
        env = Environment(autoescape=True)
663
        tmpl = env.from_string('{{ {"foo": "<test>"}|xmlattr|escape }}')
664
        assert tmpl.render() == ' foo="&lt;test&gt;"'
665
        tmpl = env.from_string(
666
            '{% autoescape false %}{{ {"foo": "<test>"}'
667
            "|xmlattr|escape }}{% endautoescape %}"
668
        )
669
        assert tmpl.render() == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
670

671
    def test_volatile(self):
672
        env = Environment(autoescape=True)
673
        tmpl = env.from_string(
674
            '{% autoescape foo %}{{ {"foo": "<test>"}'
675
            "|xmlattr|escape }}{% endautoescape %}"
676
        )
677
        assert tmpl.render(foo=False) == " foo=&#34;&amp;lt;test&amp;gt;&#34;"
678
        assert tmpl.render(foo=True) == ' foo="&lt;test&gt;"'
679

680
    def test_scoping(self):
681
        env = Environment()
682
        tmpl = env.from_string(
683
            '{% autoescape true %}{% set x = "<x>" %}{{ x }}'
684
            '{% endautoescape %}{{ x }}{{ "<y>" }}'
685
        )
686
        assert tmpl.render(x=1) == "&lt;x&gt;1<y>"
687

688
    def test_volatile_scoping(self):
689
        env = Environment()
690
        tmplsource = """
691
        {% autoescape val %}
692
            {% macro foo(x) %}
693
                [{{ x }}]
694
            {% endmacro %}
695
            {{ foo().__class__.__name__ }}
696
        {% endautoescape %}
697
        {{ '<testing>' }}
698
        """
699
        tmpl = env.from_string(tmplsource)
700
        assert tmpl.render(val=True).split()[0] == "Markup"
701
        assert tmpl.render(val=False).split()[0] == "str"
702

703
        # looking at the source we should see <testing> there in raw
704
        # (and then escaped as well)
705
        env = Environment()
706
        pysource = env.compile(tmplsource, raw=True)
707
        assert "<testing>\\n" in pysource
708

709
        env = Environment(autoescape=True)
710
        pysource = env.compile(tmplsource, raw=True)
711
        assert "&lt;testing&gt;\\n" in pysource
712

713
    def test_overlay_scopes(self):
714
        class MagicScopeExtension(Extension):
715
            tags = {"overlay"}
716

717
            def parse(self, parser):
718
                node = nodes.OverlayScope(lineno=next(parser.stream).lineno)
719
                node.body = list(
720
                    parser.parse_statements(("name:endoverlay",), drop_needle=True)
721
                )
722
                node.context = self.call_method("get_scope")
723
                return node
724

725
            def get_scope(self):
726
                return {"x": [1, 2, 3]}
727

728
        env = Environment(extensions=[MagicScopeExtension])
729

730
        tmpl = env.from_string(
731
            """
732
            {{- x }}|{% set z = 99 %}
733
            {%- overlay %}
734
                {{- y }}|{{ z }}|{% for item in x %}[{{ item }}]{% endfor %}
735
            {%- endoverlay %}|
736
            {{- x -}}
737
        """
738
        )
739
        assert tmpl.render(x=42, y=23) == "42|23|99|[1][2][3]|42"
740

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

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

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

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