1
0
Fork 0
jinjax/tests/test_render.py
Daniel Baumann 5ea73fcab5
Adding upstream version 0.46.
Signed-off-by: Daniel Baumann <daniel@debian.org>
2025-02-05 18:42:15 +01:00

1120 lines
29 KiB
Python

import time
from pathlib import Path
from textwrap import dedent
from threading import Thread
import jinja2
import pytest
from jinja2.exceptions import TemplateSyntaxError
from markupsafe import Markup
import jinjax
@pytest.mark.parametrize("autoescape", [True, False])
def test_render_simple(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text(
"""
{#def message #}
<div class="greeting [&_a]:flex">{{ message }}</div>
"""
)
html = catalog.render("Greeting", message="Hello world!")
assert html == Markup('<div class="greeting [&_a]:flex">Hello world!</div>')
@pytest.mark.parametrize("autoescape", [True, False])
def test_render_source(catalog, autoescape):
catalog.jinja_env.autoescape = autoescape
source = '{#def message #}\n<div class="greeting [&_a]:flex">{{ message }}</div>'
expected = Markup('<div class="greeting [&_a]:flex">Hello world!</div>')
html = catalog.render("Greeting", message="Hello world!", _source=source)
assert expected == html
# Legacy
html = catalog.render("Greeting", message="Hello world!", __source=source)
assert expected == html
@pytest.mark.parametrize("autoescape", [True, False])
def test_render_content(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Card.jinja").write_text("""
<section class="card">
{{ content }}
</section>
""")
content = '<button type="button">Close</button>'
expected = Markup(f'<section class="card">\n{content}\n</section>')
html = catalog.render("Card", _content=content)
print(html)
assert expected == html
# Legacy
html = catalog.render("Card", __content=content)
assert expected == html
@pytest.mark.parametrize("autoescape", [True, False])
@pytest.mark.parametrize(
"source, expected",
[
("<Title>Hi</Title><Title>Hi</Title>", "<h1>Hi</h1><h1>Hi</h1>"),
("<Icon /><Icon />", '<i class="icon"></i><i class="icon"></i>'),
("<Title>Hi</Title><Icon />", '<h1>Hi</h1><i class="icon"></i>'),
("<Icon /><Title>Hi</Title>", '<i class="icon"></i><h1>Hi</h1>'),
],
)
def test_render_mix_of_contentful_and_contentless_components(
catalog,
folder,
source,
expected,
autoescape,
):
catalog.jinja_env.autoescape = autoescape
(folder / "Icon.jinja").write_text('<i class="icon"></i>')
(folder / "Title.jinja").write_text("<h1>{{ content }}</h1>")
(folder / "Page.jinja").write_text(source)
html = catalog.render("Page")
assert html == Markup(expected)
@pytest.mark.parametrize("autoescape", [True, False])
def test_composition(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text(
"""
{#def message #}
<div class="greeting [&_a]:flex">{{ message }}</div>
"""
)
(folder / "CloseBtn.jinja").write_text(
"""
{#def disabled=False -#}
<button type="button"{{ " disabled" if disabled else "" }}>&times;</button>
"""
)
(folder / "Card.jinja").write_text(
"""
<section class="card">
{{ content }}
<CloseBtn disabled />
</section>
"""
)
(folder / "Page.jinja").write_text(
"""
{#def message #}
<Card>
<Greeting :message="message" />
<button type="button">Close</button>
</Card>
"""
)
html = catalog.render("Page", message="Hello")
print(html)
assert (
"""
<section class="card">
<div class="greeting [&_a]:flex">Hello</div>
<button type="button">Close</button>
<button type="button" disabled>&times;</button>
</section>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_just_properties(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Lorem.jinja").write_text(
"""
{#def ipsum=False #}
<p>lorem {{ "ipsum" if ipsum else "lorem" }}</p>
"""
)
(folder / "Layout.jinja").write_text(
"""
<main>
{{ content }}
</main>
"""
)
(folder / "Page.jinja").write_text(
"""
<Layout>
<Lorem ipsum />
<p>meh</p>
<Lorem />
</Layout>
"""
)
html = catalog.render("Page")
print(html)
assert (
"""
<main>
<p>lorem ipsum</p>
<p>meh</p>
<p>lorem lorem</p>
</main>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_render_assets(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text(
"""
{#def message #}
{#css greeting.css, http://example.com/super.css #}
{#js greeting.js #}
<div class="greeting [&_a]:flex">{{ message }}</div>
"""
)
(folder / "Card.jinja").write_text(
"""
{#css https://somewhere.com/style.css, card.css #}
{#js card.js, shared.js #}
<section class="card">
{{ content }}
</section>
"""
)
(folder / "Layout.jinja").write_text(
"""
<html>
{{ catalog.render_assets() }}
{{ content }}
</html>
"""
)
(folder / "Page.jinja").write_text(
"""
{#def message #}
{#js https://somewhere.com/blabla.js, shared.js #}
<Layout>
<Card>
<Greeting :message="message" />
<button type="button">Close</button>
</Card>
</Layout>
"""
)
html = catalog.render("Page", message="Hello")
print(html)
assert (
"""
<html>
<link rel="stylesheet" href="https://somewhere.com/style.css">
<link rel="stylesheet" href="/static/components/card.css">
<link rel="stylesheet" href="/static/components/greeting.css">
<link rel="stylesheet" href="http://example.com/super.css">
<script type="module" src="https://somewhere.com/blabla.js"></script>
<script type="module" src="/static/components/shared.js"></script>
<script type="module" src="/static/components/card.js"></script>
<script type="module" src="/static/components/greeting.js"></script>
<section class="card">
<div class="greeting [&_a]:flex">Hello</div>
<button type="button">Close</button>
</section>
</html>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_global_values(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Global.jinja").write_text("""{{ globalvar }}""")
message = "Hello world!"
catalog.jinja_env.globals["globalvar"] = message
html = catalog.render("Global")
print(html)
assert message in html
@pytest.mark.parametrize("autoescape", [True, False])
def test_required_attr_are_required(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text(
"""
{#def message #}
<div class="greeting">{{ message }}</div>
"""
)
with pytest.raises(jinjax.MissingRequiredArgument):
catalog.render("Greeting")
@pytest.mark.parametrize("autoescape", [True, False])
def test_subfolder(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
sub = folder / "UI"
sub.mkdir()
(folder / "Meh.jinja").write_text("<UI.Tab>Meh</UI.Tab>")
(sub / "Tab.jinja").write_text('<div class="tab">{{ content }}</div>')
html = catalog.render("Meh")
assert html == Markup('<div class="tab">Meh</div>')
@pytest.mark.parametrize("autoescape", [True, False])
def test_default_attr(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text(
"""
{#def message="Hello", world=False #}
<div>{{ message }}{% if world %} World{% endif %}</div>
"""
)
(folder / "Page.jinja").write_text(
"""
<Greeting />
<Greeting message="Hi" />
<Greeting :world="False" />
<Greeting :world="True" />
<Greeting world />
"""
)
html = catalog.render("Page", message="Hello")
print(html)
assert (
"""
<div>Hello</div>
<div>Hi</div>
<div>Hello</div>
<div>Hello World</div>
<div>Hello World</div>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_raw_content(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Code.jinja").write_text("""
<pre class="code">
{{ content|e }}
</pre>
""")
(folder / "Page.jinja").write_text("""
<Code>
{% raw -%}
{#def message="Hello", world=False #}
<Header />
<div>{{ message }}{% if world %} World{% endif %}</div>
{%- endraw %}
</Code>
""")
html = catalog.render("Page")
print(html)
assert (
"""
<pre class="code">
{#def message=&#34;Hello&#34;, world=False #}
&lt;Header /&gt;
&lt;div&gt;{{ message }}{% if world %} World{% endif %}&lt;/div&gt;
</pre>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_multiple_raw(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "C.jinja").write_text("""
<div {{ attrs.render() }}></div>
""")
(folder / "Page.jinja").write_text("""
<C id="1" />
{% raw -%}
<C id="2" />
{%- endraw %}
<C id="3" />
{% raw %}<C id="4" />{% endraw %}
<C id="5" />
""")
html = catalog.render("Page", message="Hello")
print(html)
assert (
"""
<div id="1"></div>
&lt;C id=&#34;2&#34; /&gt;
<div id="3"></div>
&lt;C id=&#34;4&#34; /&gt;
<div id="5"></div>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_check_for_unclosed(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Lorem.jinja").write_text("""
{#def ipsum=False #}
<p>lorem {{ "ipsum" if ipsum else "lorem" }}</p>
""")
(folder / "Page.jinja").write_text("""
<main>
<Lorem ipsum>
</main>
""")
with pytest.raises(TemplateSyntaxError):
try:
catalog.render("Page")
except TemplateSyntaxError as err:
print(err)
raise
@pytest.mark.parametrize("autoescape", [True, False])
def test_dict_as_attr(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "CitiesList.jinja").write_text("""
{#def cities #}
{% for city, country in cities.items() -%}
<p>{{ city }}, {{ country }}</p>
{%- endfor %}
""")
(folder / "Page.jinja").write_text("""
<CitiesList :cities="{
'Lima': 'Peru',
'New York': 'USA',
}" />
""")
html = catalog.render("Page")
assert html == Markup("<p>Lima, Peru</p><p>New York, USA</p>")
@pytest.mark.parametrize("autoescape", [True, False])
def test_cleanup_assets(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Layout.jinja").write_text("""
<html>
{{ catalog.render_assets() }}
{{ content }}
</html>
""")
(folder / "Foo.jinja").write_text("""
{#js foo.js #}
<Layout>
<p>Foo</p>
</Layout>
""")
(folder / "Bar.jinja").write_text("""
{#js bar.js #}
<Layout>
<p>Bar</p>
</Layout>
""")
html = catalog.render("Foo")
print(html, "\n")
assert (
"""
<html>
<script type="module" src="/static/components/foo.js"></script>
<p>Foo</p>
</html>
""".strip()
in html
)
html = catalog.render("Bar")
print(html)
assert (
"""
<html>
<script type="module" src="/static/components/bar.js"></script>
<p>Bar</p>
</html>
""".strip()
in html
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_do_not_mess_with_external_jinja_env(folder_t, folder, autoescape):
"""https://github.com/jpsca/jinjax/issues/19"""
(folder_t / "greeting.html").write_text("Jinja still works")
(folder / "Greeting.jinja").write_text("JinjaX works")
jinja_env = jinja2.Environment(
loader=jinja2.FileSystemLoader(folder_t),
extensions=["jinja2.ext.i18n"],
)
jinja_env.globals = {"glo": "bar"}
jinja_env.filters = {"fil": lambda x: x}
jinja_env.tests = {"tes": lambda x: x}
jinja_env.autoescape = autoescape
catalog = jinjax.Catalog(
jinja_env=jinja_env,
extensions=["jinja2.ext.debug"],
globals={"xglo": "foo"},
filters={"xfil": lambda x: x},
tests={"xtes": lambda x: x},
)
catalog.add_folder(folder)
html = catalog.render("Greeting")
assert html == Markup("JinjaX works")
assert catalog.jinja_env.globals["catalog"] == catalog
assert catalog.jinja_env.globals["glo"] == "bar"
assert catalog.jinja_env.globals["xglo"] == "foo"
assert catalog.jinja_env.filters["fil"]
assert catalog.jinja_env.filters["xfil"]
assert catalog.jinja_env.tests["tes"]
assert catalog.jinja_env.tests["xtes"]
assert "jinja2.ext.InternationalizationExtension" in catalog.jinja_env.extensions
assert "jinja2.ext.DebugExtension" in catalog.jinja_env.extensions
assert "jinja2.ext.ExprStmtExtension" in catalog.jinja_env.extensions
tmpl = jinja_env.get_template("greeting.html")
assert tmpl.render() == "Jinja still works"
assert jinja_env.globals["catalog"] == catalog
assert jinja_env.globals["glo"] == "bar"
assert "xglo" not in jinja_env.globals
assert jinja_env.filters["fil"]
assert "xfil" not in jinja_env.filters
assert jinja_env.tests["tes"]
assert "xtes" not in jinja_env.tests
assert "jinja2.ext.InternationalizationExtension" in jinja_env.extensions
assert "jinja2.ext.DebugExtension" not in jinja_env.extensions
@pytest.mark.parametrize("autoescape", [True, False])
def test_auto_reload(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Layout.jinja").write_text("""
<html>
{{ content }}
</html>
""")
(folder / "Foo.jinja").write_text("""
<Layout>
<p>Foo</p>
<Bar></Bar>
</Layout>
""")
bar_file = folder / "Bar.jinja"
bar_file.write_text("<p>Bar</p>")
html1 = catalog.render("Foo")
print(bar_file.stat().st_mtime)
print(html1, "\n")
assert (
"""
<html>
<p>Foo</p>
<p>Bar</p>
</html>
""".strip()
in html1
)
# Give it some time so the st_mtime are different
time.sleep(0.1)
catalog.auto_reload = False
bar_file.write_text("<p>Ignored</p>")
print(bar_file.stat().st_mtime)
html2 = catalog.render("Foo")
print(html2, "\n")
catalog.auto_reload = True
bar_file.write_text("<p>Updated</p>")
print(bar_file.stat().st_mtime)
html3 = catalog.render("Foo")
print(html3, "\n")
assert html1 == html2
assert (
"""
<html>
<p>Foo</p>
<p>Updated</p>
</html>
""".strip()
in html3
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_subcomponents(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
"""Issue https://github.com/jpsca/jinjax/issues/32"""
(folder / "Page.jinja").write_text("""
{#def message #}
<html>
<p>lorem ipsum</p>
<Subcomponent />
{{ message }}
</html>
""")
(folder / "Subcomponent.jinja").write_text("""
<p>foo bar</p>
""")
html = catalog.render("Page", message="<3")
if autoescape:
expected = """
<html>
<p>lorem ipsum</p>
<p>foo bar</p>
&lt;3
</html>"""
else:
expected = """
<html>
<p>lorem ipsum</p>
<p>foo bar</p>
<3
</html>"""
assert html == Markup(expected.strip())
@pytest.mark.parametrize("autoescape", [True, False])
def test_fingerprint_assets(catalog, folder: Path, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Layout.jinja").write_text("""
<html>
{{ catalog.render_assets() }}
{{ content }}
</html>
""")
(folder / "Page.jinja").write_text("""
{#css app.css, http://example.com/super.css #}
{#js app.js #}
<Layout>Hi</Layout>
""")
(folder / "app.css").write_text("...")
catalog.fingerprint = True
html = catalog.render("Page", message="Hello")
print(html)
assert 'src="/static/components/app.js"' in html
assert 'href="/static/components/app-' in html
assert 'href="http://example.com/super.css' in html
@pytest.mark.parametrize("autoescape", [True, False])
def test_colon_in_attrs(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "C.jinja").write_text("""
<div {{ attrs.render() }}></div>
""")
(folder / "Page.jinja").write_text("""
<C hx-on:click="show = !show" />
""")
html = catalog.render("Page", message="Hello")
print(html)
assert """<div hx-on:click="show = !show"></div>""" in html
@pytest.mark.parametrize("autoescape", [True, False])
def test_template_globals(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Input.jinja").write_text("""
{# def name, value #}<input type="text" name="{{name}}" value="{{value}}">
""")
(folder / "CsrfToken.jinja").write_text("""
<input type="hidden" name="csrft" value="{{csrf_token}}">
""")
(folder / "Form.jinja").write_text("""
<form><CsrfToken/>{{content}}</form>
""")
(folder / "Page.jinja").write_text("""
{# def value #}
<Form><Input name="foo" :value="value"/></Form>
""")
html = catalog.render("Page", value="bar", __globals={"csrf_token": "abc"})
print(html)
assert """<input type="hidden" name="csrft" value="abc">""" in html
@pytest.mark.parametrize("autoescape", [True, False])
def test_template_globals_update_cache(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "CsrfToken.jinja").write_text(
"""<input type="hidden" name="csrft" value="{{csrf_token}}">"""
)
(folder / "Page.jinja").write_text("""<CsrfToken/>""")
html = catalog.render("Page", __globals={"csrf_token": "abc"})
print(html)
assert """<input type="hidden" name="csrft" value="abc">""" in html
html = catalog.render("Page", __globals={"csrf_token": "xyz"})
print(html)
assert """<input type="hidden" name="csrft" value="xyz">""" in html
@pytest.mark.parametrize("autoescape", [True, False])
def test_alpine_sintax(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Greeting.jinja").write_text("""
{#def message #}
<button @click="alert('{{ message }}')">Say Hi</button>""")
html = catalog.render("Greeting", message="Hello world!")
print(html)
expected = """<button @click="alert('Hello world!')">Say Hi</button>"""
assert html == Markup(expected)
@pytest.mark.parametrize("autoescape", [True, False])
def test_alpine_sintax_in_component(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Button.jinja").write_text(
"""<button {{ attrs.render() }}>{{ content }}</button>"""
)
(folder / "Greeting.jinja").write_text(
"""<Button @click="alert('Hello world!')">Say Hi</Button>"""
)
html = catalog.render("Greeting")
print(html)
expected = """<button @click="alert('Hello world!')">Say Hi</button>"""
assert html == Markup(expected)
@pytest.mark.parametrize("autoescape", [True, False])
def test_autoescaped_attrs(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "CheckboxItem.jinja").write_text(
"""<div {{ attrs.render(class="relative") }}></div>"""
)
(folder / "Page.jinja").write_text(
"""<CheckboxItem class="border border-red-500" />"""
)
html = catalog.render("Page")
print(html)
expected = """<div class="border border-red-500 relative"></div>"""
assert html == Markup(expected)
@pytest.mark.parametrize(
"template",
[
pytest.param(
dedent(
"""
{# def
href,
hx_target="#maincontent",
hx_swap="innerHTML show:body:top",
hx_push_url=true,
#}
<a href="{{href}}" hx-get="{{href}}" hx-target="{{hx_target}}"
hx-swap="{{hx_swap}}"
{% if hx_push_url %}hx-push-url="true"{% endif %}>
{{- content -}}
</a>
"""
),
id="no comment",
),
pytest.param(
dedent(
"""
{# def
href,
hx_target="#maincontent", # css selector
hx_swap="innerHTML show:body:top",
hx_push_url=true,
#}
<a href="{{href}}" hx-get="{{href}}" hx-target="{{hx_target}}"
hx-swap="{{hx_swap}}"
{% if hx_push_url %}hx-push-url="true"{% endif %}>
{{- content -}}
</a>
"""
),
id="comment with # on line",
),
pytest.param(
dedent(
"""
{# def
href, # url of the target page
hx_target="#maincontent", # css selector
hx_swap="innerHTML show:body:top", # browse on top of the page
hx_push_url=true, # replace the url of the browser
#}
<a href="{{href}}" hx-get="{{href}}" hx-target="{{hx_target}}"
hx-swap="{{hx_swap}}"
{% if hx_push_url %}hx-push-url="true"{% endif %}>
{{- content -}}
</a>
"""
),
id="many comments",
),
pytest.param(
dedent(
"""
{# def
href: str, # url of the target page
hx_target: str = "#maincontent", # css selector
hx_swap: str = "innerHTML show:body:top", # browse on top of the page
hx_push_url: bool = true, # replace the url
#}
<a href="{{href}}" hx-get="{{href}}" hx-target="{{hx_target}}"
hx-swap="{{hx_swap}}"
{% if hx_push_url %}hx-push-url="true"{% endif %}>
{{- content -}}
</a>
"""
),
id="many comments and typing",
),
],
)
@pytest.mark.parametrize("autoescape", [True, False])
def test_strip_comment(catalog, folder, autoescape, template):
catalog.jinja_env.autoescape = autoescape
(folder / "A.jinja").write_text(template)
(folder / "Page.jinja").write_text("""<A href="/yolo">Yolo</A>""")
html = catalog.render("Page")
print(html)
expected = """
<a href="/yolo" hx-get="/yolo" hx-target="#maincontent"
hx-swap="innerHTML show:body:top"
hx-push-url="true">Yolo</a>""".strip()
assert html == Markup(expected)
@pytest.mark.parametrize("autoescape", [True, False])
def test_auto_load_assets_with_same_name(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Layout.jinja").write_text(
"""{{ catalog.render_assets() }}\n{{ content }}"""
)
(folder / "FooBar.css").touch()
(folder / "common").mkdir()
(folder / "common" / "Form.jinja").write_text(
"""
{#js "shared.js" #}
<form></form>"""
)
(folder / "common" / "Form.css").touch()
(folder / "common" / "Form.js").touch()
(folder / "Page.jinja").write_text(
"""
{#css "Page.css" #}
<Layout><common.Form></common.Form></Layout>"""
)
(folder / "Page.css").touch()
(folder / "Page.js").touch()
html = catalog.render("Page")
print(html)
expected = """
<link rel="stylesheet" href="/static/components/Page.css">
<link rel="stylesheet" href="/static/components/common/Form.css">
<script type="module" src="/static/components/Page.js"></script>
<script type="module" src="/static/components/shared.js"></script>
<script type="module" src="/static/components/common/Form.js"></script>
<form></form>
""".strip()
assert html == Markup(expected)
def test_vue_like_syntax(catalog, folder):
(folder / "Test.jinja").write_text("""
{#def a, b, c, d #}
{{ a }} {{ b }} {{ c }} {{ d }}
""")
(folder / "Caller.jinja").write_text(
"""<Test :a="2+2" b="2+2" :c="{'lorem': 'ipsum'}" :d="false" />"""
)
html = catalog.render("Caller")
print(html)
expected = """4 2+2 {'lorem': 'ipsum'} False""".strip()
assert html == Markup(expected)
def test_jinja_like_syntax(catalog, folder):
(folder / "Test.jinja").write_text("""
{#def a, b, c, d #}
{{ a }} {{ b }} {{ c }} {{ d }}
""")
(folder / "Caller.jinja").write_text(
"""<Test a={{ 2+2 }} b="2+2" c={{ {'lorem': 'ipsum'} }} d={{ false }} />"""
)
html = catalog.render("Caller")
print(html)
expected = """4 2+2 {'lorem': 'ipsum'} False""".strip()
assert html == Markup(expected)
def test_mixed_syntax(catalog, folder):
(folder / "Test.jinja").write_text("""
{#def a, b, c, d #}
{{ a }} {{ b }} {{ c }} {{ d }}
""")
(folder / "Caller.jinja").write_text(
"""<Test :a={{ 2+2 }} b="{{2+2}}" :c={{ {'lorem': 'ipsum'} }} :d={{ false }} />"""
)
html = catalog.render("Caller")
print(html)
expected = """4 {{2+2}} {'lorem': 'ipsum'} False""".strip()
assert html == Markup(expected)
@pytest.mark.parametrize("autoescape", [True, False])
def test_slots(catalog, folder, autoescape):
catalog.jinja_env.autoescape = autoescape
(folder / "Component.jinja").write_text(
"""
<p>{{ content }}</p>
<p>{{ content("first") }}</p>
<p>{{ content("second") }}</p>
<p>{{ content("antoher") }}</p>
<p>{{ content() }}</p>
""".strip()
)
(folder / "Messages.jinja").write_text(
"""
<Component>
{% if _slot == "first" %}Hello World
{%- elif _slot == "second" %}Lorem Ipsum
{%- elif _slot == "meh" %}QWERTYUIOP
{%- else %}Default{% endif %}
</Component>
""".strip()
)
html = catalog.render("Messages")
print(html)
expected = """
<p>Default</p>
<p>Hello World</p>
<p>Lorem Ipsum</p>
<p>Default</p>
<p>Default</p>
""".strip()
assert html == Markup(expected)
class ThreadWithReturnValue(Thread):
def __init__(self, group=None, target=None, name=None, args=None, kwargs=None):
args = args or ()
kwargs = kwargs or {}
Thread.__init__(
self,
group=group,
target=target,
name=name,
args=args,
kwargs=kwargs,
)
self._target = target
self._args = args
self._kwargs = kwargs
self._return = None
def run(self):
if self._target is not None:
self._return = self._target(*self._args, **self._kwargs)
def join(self, *args):
Thread.join(self, *args)
return self._return
def test_thread_safety_of_render_assets(catalog, folder):
NUM_THREADS = 5
child_tmpl = """
{#css "c{i}.css" #}
{#js "c{i}.js" #}
<p>Child {i}</p>""".strip()
parent_tmpl = """
{{ catalog.render_assets() }}
{{ content }}""".strip()
comp_tmpl = """
{#css "a{i}.css", "b{i}.css" #}
{#js "a{i}.js", "b{i}.js" #}
<Parent{i}><Child{i} /></Parent{i}>""".strip()
expected_tmpl = """
<link rel="stylesheet" href="/static/components/a{i}.css">
<link rel="stylesheet" href="/static/components/b{i}.css">
<link rel="stylesheet" href="/static/components/c{i}.css">
<script type="module" src="/static/components/a{i}.js"></script>
<script type="module" src="/static/components/b{i}.js"></script>
<script type="module" src="/static/components/c{i}.js"></script>
<p>Child {i}</p>""".strip()
def render(i):
return catalog.render(f"Page{i}")
for i in range(NUM_THREADS):
si = str(i)
child_name = f"Child{i}.jinja"
child_src = child_tmpl.replace("{i}", si)
parent_name = f"Parent{i}.jinja"
parent_src = parent_tmpl.replace("{i}", si)
comp_name = f"Page{i}.jinja"
comp_src = comp_tmpl.replace("{i}", si)
(folder / child_name).write_text(child_src)
(folder / comp_name).write_text(comp_src)
(folder / parent_name).write_text(parent_src)
threads = []
for i in range(NUM_THREADS):
thread = ThreadWithReturnValue(target=render, args=(i,))
threads.append(thread)
thread.start()
results = [thread.join() for thread in threads]
for i, result in enumerate(results):
expected = expected_tmpl.replace("{i}", str(i))
print(f"---- EXPECTED {i}----")
print(expected)
print(f"---- RESULT {i}----")
print(result)
assert result == Markup(expected)
def test_same_thread_assets_independence(catalog, folder):
catalog2 = jinjax.Catalog()
catalog2.add_folder(folder)
print(catalog._key)
print(catalog2._key)
(folder / "Parent.jinja").write_text("""
{{ catalog.render_assets() }}
{{ content }}""".strip())
(folder / "Comp1.jinja").write_text("""
{#css "a.css" #}
{#js "a.js" #}
<Parent />""".strip())
(folder / "Comp2.jinja").write_text("""
{#css "b.css" #}
{#js "b.js" #}
<Parent />""".strip())
expected_1 = """
<link rel="stylesheet" href="/static/components/a.css">
<script type="module" src="/static/components/a.js"></script>""".strip()
expected_2 = """
<link rel="stylesheet" href="/static/components/b.css">
<script type="module" src="/static/components/b.js"></script>""".strip()
html1 = catalog.render("Comp1")
# `irender` instead of `render` so the assets are not cleared
html2 = catalog2.irender("Comp2")
print(html1)
print(html2)
assert html1 == Markup(expected_1)
assert html2 == Markup(expected_2)