test_regression.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. import pytest
  2. from jinja2 import DictLoader
  3. from jinja2 import Environment
  4. from jinja2 import PrefixLoader
  5. from jinja2 import Template
  6. from jinja2 import TemplateAssertionError
  7. from jinja2 import TemplateNotFound
  8. from jinja2 import TemplateSyntaxError
  9. from jinja2.utils import pass_context
  10. class TestCorner:
  11. def test_assigned_scoping(self, env):
  12. t = env.from_string(
  13. """
  14. {%- for item in (1, 2, 3, 4) -%}
  15. [{{ item }}]
  16. {%- endfor %}
  17. {{- item -}}
  18. """
  19. )
  20. assert t.render(item=42) == "[1][2][3][4]42"
  21. t = env.from_string(
  22. """
  23. {%- for item in (1, 2, 3, 4) -%}
  24. [{{ item }}]
  25. {%- endfor %}
  26. {%- set item = 42 %}
  27. {{- item -}}
  28. """
  29. )
  30. assert t.render() == "[1][2][3][4]42"
  31. t = env.from_string(
  32. """
  33. {%- set item = 42 %}
  34. {%- for item in (1, 2, 3, 4) -%}
  35. [{{ item }}]
  36. {%- endfor %}
  37. {{- item -}}
  38. """
  39. )
  40. assert t.render() == "[1][2][3][4]42"
  41. def test_closure_scoping(self, env):
  42. t = env.from_string(
  43. """
  44. {%- set wrapper = "<FOO>" %}
  45. {%- for item in (1, 2, 3, 4) %}
  46. {%- macro wrapper() %}[{{ item }}]{% endmacro %}
  47. {{- wrapper() }}
  48. {%- endfor %}
  49. {{- wrapper -}}
  50. """
  51. )
  52. assert t.render() == "[1][2][3][4]<FOO>"
  53. t = env.from_string(
  54. """
  55. {%- for item in (1, 2, 3, 4) %}
  56. {%- macro wrapper() %}[{{ item }}]{% endmacro %}
  57. {{- wrapper() }}
  58. {%- endfor %}
  59. {%- set wrapper = "<FOO>" %}
  60. {{- wrapper -}}
  61. """
  62. )
  63. assert t.render() == "[1][2][3][4]<FOO>"
  64. t = env.from_string(
  65. """
  66. {%- for item in (1, 2, 3, 4) %}
  67. {%- macro wrapper() %}[{{ item }}]{% endmacro %}
  68. {{- wrapper() }}
  69. {%- endfor %}
  70. {{- wrapper -}}
  71. """
  72. )
  73. assert t.render(wrapper=23) == "[1][2][3][4]23"
  74. class TestBug:
  75. def test_keyword_folding(self, env):
  76. env = Environment()
  77. env.filters["testing"] = lambda value, some: value + some
  78. assert (
  79. env.from_string("{{ 'test'|testing(some='stuff') }}").render()
  80. == "teststuff"
  81. )
  82. def test_extends_output_bugs(self, env):
  83. env = Environment(
  84. loader=DictLoader({"parent.html": "(({% block title %}{% endblock %}))"})
  85. )
  86. t = env.from_string(
  87. '{% if expr %}{% extends "parent.html" %}{% endif %}'
  88. "[[{% block title %}title{% endblock %}]]"
  89. "{% for item in [1, 2, 3] %}({{ item }}){% endfor %}"
  90. )
  91. assert t.render(expr=False) == "[[title]](1)(2)(3)"
  92. assert t.render(expr=True) == "((title))"
  93. def test_urlize_filter_escaping(self, env):
  94. tmpl = env.from_string('{{ "http://www.example.org/<foo"|urlize }}')
  95. assert (
  96. tmpl.render() == '<a href="http://www.example.org/&lt;foo" rel="noopener">'
  97. "http://www.example.org/&lt;foo</a>"
  98. )
  99. def test_urlize_filter_closing_punctuation(self, env):
  100. tmpl = env.from_string(
  101. '{{ "(see http://www.example.org/?page=subj_<desc.h>)"|urlize }}'
  102. )
  103. assert tmpl.render() == (
  104. '(see <a href="http://www.example.org/?page=subj_&lt;desc.h&gt;" '
  105. 'rel="noopener">http://www.example.org/?page=subj_&lt;desc.h&gt;</a>)'
  106. )
  107. def test_loop_call_loop(self, env):
  108. tmpl = env.from_string(
  109. """
  110. {% macro test() %}
  111. {{ caller() }}
  112. {% endmacro %}
  113. {% for num1 in range(5) %}
  114. {% call test() %}
  115. {% for num2 in range(10) %}
  116. {{ loop.index }}
  117. {% endfor %}
  118. {% endcall %}
  119. {% endfor %}
  120. """
  121. )
  122. assert tmpl.render().split() == [str(x) for x in range(1, 11)] * 5
  123. def test_weird_inline_comment(self, env):
  124. env = Environment(line_statement_prefix="%")
  125. pytest.raises(
  126. TemplateSyntaxError,
  127. env.from_string,
  128. "% for item in seq {# missing #}\n...% endfor",
  129. )
  130. def test_old_macro_loop_scoping_bug(self, env):
  131. tmpl = env.from_string(
  132. "{% for i in (1, 2) %}{{ i }}{% endfor %}"
  133. "{% macro i() %}3{% endmacro %}{{ i() }}"
  134. )
  135. assert tmpl.render() == "123"
  136. def test_partial_conditional_assignments(self, env):
  137. tmpl = env.from_string("{% if b %}{% set a = 42 %}{% endif %}{{ a }}")
  138. assert tmpl.render(a=23) == "23"
  139. assert tmpl.render(b=True) == "42"
  140. def test_stacked_locals_scoping_bug(self, env):
  141. env = Environment(line_statement_prefix="#")
  142. t = env.from_string(
  143. """\
  144. # for j in [1, 2]:
  145. # set x = 1
  146. # for i in [1, 2]:
  147. # print x
  148. # if i % 2 == 0:
  149. # set x = x + 1
  150. # endif
  151. # endfor
  152. # endfor
  153. # if a
  154. # print 'A'
  155. # elif b
  156. # print 'B'
  157. # elif c == d
  158. # print 'C'
  159. # else
  160. # print 'D'
  161. # endif
  162. """
  163. )
  164. assert t.render(a=0, b=False, c=42, d=42.0) == "1111C"
  165. def test_stacked_locals_scoping_bug_twoframe(self, env):
  166. t = Template(
  167. """
  168. {% set x = 1 %}
  169. {% for item in foo %}
  170. {% if item == 1 %}
  171. {% set x = 2 %}
  172. {% endif %}
  173. {% endfor %}
  174. {{ x }}
  175. """
  176. )
  177. rv = t.render(foo=[1]).strip()
  178. assert rv == "1"
  179. def test_call_with_args(self, env):
  180. t = Template(
  181. """{% macro dump_users(users) -%}
  182. <ul>
  183. {%- for user in users -%}
  184. <li><p>{{ user.username|e }}</p>{{ caller(user) }}</li>
  185. {%- endfor -%}
  186. </ul>
  187. {%- endmacro -%}
  188. {% call(user) dump_users(list_of_user) -%}
  189. <dl>
  190. <dl>Realname</dl>
  191. <dd>{{ user.realname|e }}</dd>
  192. <dl>Description</dl>
  193. <dd>{{ user.description }}</dd>
  194. </dl>
  195. {% endcall %}"""
  196. )
  197. assert [
  198. x.strip()
  199. for x in t.render(
  200. list_of_user=[
  201. {
  202. "username": "apo",
  203. "realname": "something else",
  204. "description": "test",
  205. }
  206. ]
  207. ).splitlines()
  208. ] == [
  209. "<ul><li><p>apo</p><dl>",
  210. "<dl>Realname</dl>",
  211. "<dd>something else</dd>",
  212. "<dl>Description</dl>",
  213. "<dd>test</dd>",
  214. "</dl>",
  215. "</li></ul>",
  216. ]
  217. def test_empty_if_condition_fails(self, env):
  218. pytest.raises(TemplateSyntaxError, Template, "{% if %}....{% endif %}")
  219. pytest.raises(
  220. TemplateSyntaxError, Template, "{% if foo %}...{% elif %}...{% endif %}"
  221. )
  222. pytest.raises(TemplateSyntaxError, Template, "{% for x in %}..{% endfor %}")
  223. def test_recursive_loop_compile(self, env):
  224. Template(
  225. """
  226. {% for p in foo recursive%}
  227. {{p.bar}}
  228. {% for f in p.fields recursive%}
  229. {{f.baz}}
  230. {{p.bar}}
  231. {% if f.rec %}
  232. {{ loop(f.sub) }}
  233. {% endif %}
  234. {% endfor %}
  235. {% endfor %}
  236. """
  237. )
  238. Template(
  239. """
  240. {% for p in foo%}
  241. {{p.bar}}
  242. {% for f in p.fields recursive%}
  243. {{f.baz}}
  244. {{p.bar}}
  245. {% if f.rec %}
  246. {{ loop(f.sub) }}
  247. {% endif %}
  248. {% endfor %}
  249. {% endfor %}
  250. """
  251. )
  252. def test_else_loop_bug(self, env):
  253. t = Template(
  254. """
  255. {% for x in y %}
  256. {{ loop.index0 }}
  257. {% else %}
  258. {% for i in range(3) %}{{ i }}{% endfor %}
  259. {% endfor %}
  260. """
  261. )
  262. assert t.render(y=[]).strip() == "012"
  263. def test_correct_prefix_loader_name(self, env):
  264. env = Environment(loader=PrefixLoader({"foo": DictLoader({})}))
  265. with pytest.raises(TemplateNotFound) as e:
  266. env.get_template("foo/bar.html")
  267. assert e.value.name == "foo/bar.html"
  268. def test_pass_context_callable_class(self, env):
  269. class CallableClass:
  270. @pass_context
  271. def __call__(self, ctx):
  272. return ctx.resolve("hello")
  273. tpl = Template("""{{ callableclass() }}""")
  274. output = tpl.render(callableclass=CallableClass(), hello="TEST")
  275. expected = "TEST"
  276. assert output == expected
  277. def test_block_set_with_extends(self):
  278. env = Environment(
  279. loader=DictLoader({"main": "{% block body %}[{{ x }}]{% endblock %}"})
  280. )
  281. t = env.from_string('{% extends "main" %}{% set x %}42{% endset %}')
  282. assert t.render() == "[42]"
  283. def test_nested_for_else(self, env):
  284. tmpl = env.from_string(
  285. "{% for x in y %}{{ loop.index0 }}{% else %}"
  286. "{% for i in range(3) %}{{ i }}{% endfor %}"
  287. "{% endfor %}"
  288. )
  289. assert tmpl.render() == "012"
  290. def test_macro_var_bug(self, env):
  291. tmpl = env.from_string(
  292. """
  293. {% set i = 1 %}
  294. {% macro test() %}
  295. {% for i in range(0, 10) %}{{ i }}{% endfor %}
  296. {% endmacro %}{{ test() }}
  297. """
  298. )
  299. assert tmpl.render().strip() == "0123456789"
  300. def test_macro_var_bug_advanced(self, env):
  301. tmpl = env.from_string(
  302. """
  303. {% macro outer() %}
  304. {% set i = 1 %}
  305. {% macro test() %}
  306. {% for i in range(0, 10) %}{{ i }}{% endfor %}
  307. {% endmacro %}{{ test() }}
  308. {% endmacro %}{{ outer() }}
  309. """
  310. )
  311. assert tmpl.render().strip() == "0123456789"
  312. def test_callable_defaults(self):
  313. env = Environment()
  314. env.globals["get_int"] = lambda: 42
  315. t = env.from_string(
  316. """
  317. {% macro test(a, b, c=get_int()) -%}
  318. {{ a + b + c }}
  319. {%- endmacro %}
  320. {{ test(1, 2) }}|{{ test(1, 2, 3) }}
  321. """
  322. )
  323. assert t.render().strip() == "45|6"
  324. def test_macro_escaping(self):
  325. env = Environment(autoescape=lambda x: False)
  326. template = "{% macro m() %}<html>{% endmacro %}"
  327. template += "{% autoescape true %}{{ m() }}{% endautoescape %}"
  328. assert env.from_string(template).render()
  329. def test_macro_scoping(self, env):
  330. tmpl = env.from_string(
  331. """
  332. {% set n=[1,2,3,4,5] %}
  333. {% for n in [[1,2,3], [3,4,5], [5,6,7]] %}
  334. {% macro x(l) %}
  335. {{ l.pop() }}
  336. {% if l %}{{ x(l) }}{% endif %}
  337. {% endmacro %}
  338. {{ x(n) }}
  339. {% endfor %}
  340. """
  341. )
  342. assert list(map(int, tmpl.render().split())) == [3, 2, 1, 5, 4, 3, 7, 6, 5]
  343. def test_scopes_and_blocks(self):
  344. env = Environment(
  345. loader=DictLoader(
  346. {
  347. "a.html": """
  348. {%- set foo = 'bar' -%}
  349. {% include 'x.html' -%}
  350. """,
  351. "b.html": """
  352. {%- set foo = 'bar' -%}
  353. {% block test %}{% include 'x.html' %}{% endblock -%}
  354. """,
  355. "c.html": """
  356. {%- set foo = 'bar' -%}
  357. {% block test %}{% set foo = foo
  358. %}{% include 'x.html' %}{% endblock -%}
  359. """,
  360. "x.html": """{{ foo }}|{{ test }}""",
  361. }
  362. )
  363. )
  364. a = env.get_template("a.html")
  365. b = env.get_template("b.html")
  366. c = env.get_template("c.html")
  367. assert a.render(test="x").strip() == "bar|x"
  368. assert b.render(test="x").strip() == "bar|x"
  369. assert c.render(test="x").strip() == "bar|x"
  370. def test_scopes_and_include(self):
  371. env = Environment(
  372. loader=DictLoader(
  373. {
  374. "include.html": "{{ var }}",
  375. "base.html": '{% include "include.html" %}',
  376. "child.html": '{% extends "base.html" %}{% set var = 42 %}',
  377. }
  378. )
  379. )
  380. t = env.get_template("child.html")
  381. assert t.render() == "42"
  382. def test_caller_scoping(self, env):
  383. t = env.from_string(
  384. """
  385. {% macro detail(icon, value) -%}
  386. {% if value -%}
  387. <p><span class="fa fa-fw fa-{{ icon }}"></span>
  388. {%- if caller is undefined -%}
  389. {{ value }}
  390. {%- else -%}
  391. {{ caller(value, *varargs) }}
  392. {%- endif -%}</p>
  393. {%- endif %}
  394. {%- endmacro %}
  395. {% macro link_detail(icon, value, href) -%}
  396. {% call(value, href) detail(icon, value, href) -%}
  397. <a href="{{ href }}">{{ value }}</a>
  398. {%- endcall %}
  399. {%- endmacro %}
  400. """
  401. )
  402. assert t.module.link_detail("circle", "Index", "/") == (
  403. '<p><span class="fa fa-fw fa-circle"></span><a href="/">Index</a></p>'
  404. )
  405. def test_variable_reuse(self, env):
  406. t = env.from_string("{% for x in x.y %}{{ x }}{% endfor %}")
  407. assert t.render(x={"y": [0, 1, 2]}) == "012"
  408. t = env.from_string("{% for x in x.y %}{{ loop.index0 }}|{{ x }}{% endfor %}")
  409. assert t.render(x={"y": [0, 1, 2]}) == "0|01|12|2"
  410. t = env.from_string("{% for x in x.y recursive %}{{ x }}{% endfor %}")
  411. assert t.render(x={"y": [0, 1, 2]}) == "012"
  412. def test_double_caller(self, env):
  413. t = env.from_string(
  414. "{% macro x(caller=none) %}[{% if caller %}"
  415. "{{ caller() }}{% endif %}]{% endmacro %}"
  416. "{{ x() }}{% call x() %}aha!{% endcall %}"
  417. )
  418. assert t.render() == "[][aha!]"
  419. def test_double_caller_no_default(self, env):
  420. with pytest.raises(TemplateAssertionError) as exc_info:
  421. env.from_string(
  422. "{% macro x(caller) %}[{% if caller %}"
  423. "{{ caller() }}{% endif %}]{% endmacro %}"
  424. )
  425. assert exc_info.match(
  426. r'"caller" argument must be omitted or ' r"be given a default"
  427. )
  428. t = env.from_string(
  429. "{% macro x(caller=none) %}[{% if caller %}"
  430. "{{ caller() }}{% endif %}]{% endmacro %}"
  431. )
  432. with pytest.raises(TypeError) as exc_info:
  433. t.module.x(None, caller=lambda: 42)
  434. assert exc_info.match(
  435. r"\'x\' was invoked with two values for the " r"special caller argument"
  436. )
  437. def test_macro_blocks(self, env):
  438. t = env.from_string(
  439. "{% macro x() %}{% block foo %}x{% endblock %}{% endmacro %}{{ x() }}"
  440. )
  441. assert t.render() == "x"
  442. def test_scoped_block(self, env):
  443. t = env.from_string(
  444. "{% set x = 1 %}{% with x = 2 %}{% block y scoped %}"
  445. "{{ x }}{% endblock %}{% endwith %}"
  446. )
  447. assert t.render() == "2"
  448. def test_recursive_loop_filter(self, env):
  449. t = env.from_string(
  450. """
  451. <?xml version="1.0" encoding="UTF-8"?>
  452. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  453. {%- for page in [site.root] if page.url != this recursive %}
  454. <url><loc>{{ page.url }}</loc></url>
  455. {{- loop(page.children) }}
  456. {%- endfor %}
  457. </urlset>
  458. """
  459. )
  460. sm = t.render(
  461. this="/foo",
  462. site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}},
  463. )
  464. lines = [x.strip() for x in sm.splitlines() if x.strip()]
  465. assert lines == [
  466. '<?xml version="1.0" encoding="UTF-8"?>',
  467. '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
  468. "<url><loc>/</loc></url>",
  469. "<url><loc>/bar</loc></url>",
  470. "</urlset>",
  471. ]
  472. def test_empty_if(self, env):
  473. t = env.from_string("{% if foo %}{% else %}42{% endif %}")
  474. assert t.render(foo=False) == "42"
  475. def test_subproperty_if(self, env):
  476. t = env.from_string(
  477. "{% if object1.subproperty1 is eq object2.subproperty2 %}42{% endif %}"
  478. )
  479. assert (
  480. t.render(
  481. object1={"subproperty1": "value"}, object2={"subproperty2": "value"}
  482. )
  483. == "42"
  484. )
  485. def test_set_and_include(self):
  486. env = Environment(
  487. loader=DictLoader(
  488. {
  489. "inc": "bar",
  490. "main": '{% set foo = "foo" %}{{ foo }}{% include "inc" %}',
  491. }
  492. )
  493. )
  494. assert env.get_template("main").render() == "foobar"
  495. def test_loop_include(self):
  496. env = Environment(
  497. loader=DictLoader(
  498. {
  499. "inc": "{{ i }}",
  500. "main": '{% for i in [1, 2, 3] %}{% include "inc" %}{% endfor %}',
  501. }
  502. )
  503. )
  504. assert env.get_template("main").render() == "123"
  505. def test_grouper_repr(self):
  506. from jinja2.filters import _GroupTuple
  507. t = _GroupTuple("foo", [1, 2])
  508. assert t.grouper == "foo"
  509. assert t.list == [1, 2]
  510. assert repr(t) == "('foo', [1, 2])"
  511. assert str(t) == "('foo', [1, 2])"
  512. def test_custom_context(self, env):
  513. from jinja2.runtime import Context
  514. class MyContext(Context):
  515. pass
  516. class MyEnvironment(Environment):
  517. context_class = MyContext
  518. loader = DictLoader({"base": "{{ foobar }}", "test": '{% extends "base" %}'})
  519. env = MyEnvironment(loader=loader)
  520. assert env.get_template("test").render(foobar="test") == "test"
  521. def test_recursive_loop_bug(self, env):
  522. tmpl = env.from_string(
  523. "{%- for value in values recursive %}1{% else %}0{% endfor -%}"
  524. )
  525. assert tmpl.render(values=[]) == "0"
  526. def test_markup_and_chainable_undefined(self):
  527. from markupsafe import Markup
  528. from jinja2.runtime import ChainableUndefined
  529. assert str(Markup(ChainableUndefined())) == ""
  530. def test_scoped_block_loop_vars(self, env):
  531. tmpl = env.from_string(
  532. """\
  533. Start
  534. {% for i in ["foo", "bar"] -%}
  535. {% block body scoped -%}
  536. {{ loop.index }}) {{ i }}{% if loop.last %} last{% endif -%}
  537. {%- endblock %}
  538. {% endfor -%}
  539. End"""
  540. )
  541. assert tmpl.render() == "Start\n1) foo\n2) bar last\nEnd"
  542. def test_pass_context_loop_vars(self, env):
  543. @pass_context
  544. def test(ctx):
  545. return f"{ctx['i']}{ctx['j']}"
  546. tmpl = env.from_string(
  547. """\
  548. {% set i = 42 %}
  549. {%- for idx in range(2) -%}
  550. {{ i }}{{ j }}
  551. {% set i = idx -%}
  552. {%- set j = loop.index -%}
  553. {{ test() }}
  554. {{ i }}{{ j }}
  555. {% endfor -%}
  556. {{ i }}{{ j }}"""
  557. )
  558. tmpl.globals["test"] = test
  559. assert tmpl.render() == "42\n01\n01\n42\n12\n12\n42"
  560. def test_pass_context_scoped_loop_vars(self, env):
  561. @pass_context
  562. def test(ctx):
  563. return f"{ctx['i']}"
  564. tmpl = env.from_string(
  565. """\
  566. {% set i = 42 %}
  567. {%- for idx in range(2) -%}
  568. {{ i }}
  569. {%- set i = loop.index0 -%}
  570. {% block body scoped %}
  571. {{ test() }}
  572. {% endblock -%}
  573. {% endfor -%}
  574. {{ i }}"""
  575. )
  576. tmpl.globals["test"] = test
  577. assert tmpl.render() == "42\n0\n42\n1\n42"
  578. def test_pass_context_in_blocks(self, env):
  579. @pass_context
  580. def test(ctx):
  581. return f"{ctx['i']}"
  582. tmpl = env.from_string(
  583. """\
  584. {%- set i = 42 -%}
  585. {{ i }}
  586. {% block body -%}
  587. {% set i = 24 -%}
  588. {{ test() }}
  589. {% endblock -%}
  590. {{ i }}"""
  591. )
  592. tmpl.globals["test"] = test
  593. assert tmpl.render() == "42\n24\n42"
  594. def test_pass_context_block_and_loop(self, env):
  595. @pass_context
  596. def test(ctx):
  597. return f"{ctx['i']}"
  598. tmpl = env.from_string(
  599. """\
  600. {%- set i = 42 -%}
  601. {% for idx in range(2) -%}
  602. {{ test() }}
  603. {%- set i = idx -%}
  604. {% block body scoped %}
  605. {{ test() }}
  606. {% set i = 24 -%}
  607. {{ test() }}
  608. {% endblock -%}
  609. {{ test() }}
  610. {% endfor -%}
  611. {{ test() }}"""
  612. )
  613. tmpl.globals["test"] = test
  614. # values set within a block or loop should not
  615. # show up outside of it
  616. assert tmpl.render() == "42\n0\n24\n0\n42\n1\n24\n1\n42"
  617. @pytest.mark.parametrize("op", ["extends", "include"])
  618. def test_cached_extends(self, op):
  619. env = Environment(
  620. loader=DictLoader(
  621. {"base": "{{ x }} {{ y }}", "main": f"{{% {op} 'base' %}}"}
  622. )
  623. )
  624. env.globals["x"] = "x"
  625. env.globals["y"] = "y"
  626. # template globals overlay env globals
  627. tmpl = env.get_template("main", globals={"x": "bar"})
  628. assert tmpl.render() == "bar y"
  629. # base was loaded indirectly, it just has env globals
  630. tmpl = env.get_template("base")
  631. assert tmpl.render() == "x y"
  632. # set template globals for base, no longer uses env globals
  633. tmpl = env.get_template("base", globals={"x": 42})
  634. assert tmpl.render() == "42 y"
  635. # templates are cached, they keep template globals set earlier
  636. tmpl = env.get_template("main")
  637. assert tmpl.render() == "bar y"
  638. tmpl = env.get_template("base")
  639. assert tmpl.render() == "42 y"
  640. def test_nested_loop_scoping(self, env):
  641. tmpl = env.from_string(
  642. "{% set output %}{% for x in [1,2,3] %}hello{% endfor %}"
  643. "{% endset %}{{ output }}"
  644. )
  645. assert tmpl.render() == "hellohellohello"
  646. @pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"])
  647. def test_unicode_whitespace(env, unicode_char):
  648. content = "Lorem ipsum\n" + unicode_char + "\nMore text"
  649. tmpl = env.from_string(content)
  650. assert tmpl.render() == content