Adding upstream version 0.53+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
843504222e
commit
04a4252597
14 changed files with 764 additions and 526 deletions
|
@ -45,11 +45,10 @@ def test_add_same_folder_in_same_prefix_does_nothing():
|
|||
def test_add_module_legacy():
|
||||
class Module:
|
||||
components_path = "legacy_path"
|
||||
prefix = "legacy"
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
module = Module()
|
||||
catalog.add_module(module)
|
||||
catalog.add_module(module, prefix="legacy")
|
||||
|
||||
assert "legacy_path" in catalog.prefixes["legacy"].searchpath
|
||||
|
||||
|
@ -65,19 +64,6 @@ def test_add_module_legacy_with_default_prefix():
|
|||
assert "legacy_path" in catalog.prefixes[""].searchpath
|
||||
|
||||
|
||||
def test_add_module_legacy_with_custom_prefix():
|
||||
class Module:
|
||||
components_path = "legacy_path"
|
||||
prefix = "legacy"
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
module = Module()
|
||||
catalog.add_module(module, prefix="custom")
|
||||
|
||||
assert "legacy" not in catalog.prefixes
|
||||
assert "legacy_path" in catalog.prefixes["custom"].searchpath
|
||||
|
||||
|
||||
def test_add_module_fails_with_other_modules():
|
||||
class Module:
|
||||
pass
|
||||
|
|
80
tests/test_prefix.py
Normal file
80
tests/test_prefix.py
Normal file
|
@ -0,0 +1,80 @@
|
|||
import pytest
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_prefix_namespace(catalog, folder, folder_t, autoescape):
|
||||
"""Components mounted with a prefix should be able to import other components
|
||||
from the same folder without specifying the prefix.
|
||||
"""
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
catalog.add_folder(folder_t, prefix="ui")
|
||||
|
||||
(folder / "Title.jinja").write_text("parent")
|
||||
|
||||
(folder_t / "Title.jinja").write_text("prefix")
|
||||
(folder_t / "Alert.jinja").write_text("<Title />")
|
||||
|
||||
html = catalog.render("ui.Alert")
|
||||
assert html == Markup("prefix")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_prefix_namespace_sub(catalog, folder, folder_t, autoescape):
|
||||
"""Components mounted with a prefix should be able to import other components
|
||||
from the same folder without specifying the prefix, even if those components
|
||||
are in a subfolder.
|
||||
"""
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
catalog.add_folder(folder_t, prefix="ui")
|
||||
|
||||
(folder / "sub").mkdir()
|
||||
(folder_t / "sub").mkdir()
|
||||
|
||||
(folder / "Title.jinja").write_text("parent")
|
||||
(folder / "sub" / "Title.jinja").write_text("sub/parent")
|
||||
|
||||
(folder_t / "Title.jinja").write_text("sub")
|
||||
(folder_t / "sub" / "Title.jinja").write_text("sub/prefix")
|
||||
(folder_t / "Alert.jinja").write_text("<sub.Title />")
|
||||
|
||||
html = catalog.render("ui.Alert")
|
||||
assert html == Markup("sub/prefix")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_prefix_fallback(catalog, folder, folder_t, autoescape):
|
||||
"""If a component is not found in the folder with the prefix, it should
|
||||
fallback to the no-prefix folders.
|
||||
"""
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
catalog.add_folder(folder_t, prefix="ui")
|
||||
|
||||
(folder / "Title.jinja").write_text("parent")
|
||||
(folder_t / "Alert.jinja").write_text("<Title />")
|
||||
|
||||
html = catalog.render("ui.Alert")
|
||||
assert html == Markup("parent")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_prefix_namespace_assets(catalog, folder, folder_t, autoescape):
|
||||
"""Components import without specifying the prefix should also be
|
||||
able to auto-import their assets.
|
||||
"""
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
catalog.add_folder(folder_t, prefix="ui")
|
||||
|
||||
(folder_t / "Title.jinja").write_text("prefix")
|
||||
(folder_t / "Title.css").touch()
|
||||
(folder_t / "Layout.jinja").write_text("""
|
||||
{{ catalog.render_assets() }}
|
||||
{{ content }}
|
||||
""")
|
||||
(folder_t / "Alert.jinja").write_text("<Layout><Title /></Layout>")
|
||||
|
||||
html = catalog.render("ui.Alert")
|
||||
assert html == Markup("""
|
||||
<link rel="stylesheet" href="/static/components/ui/Title.css">
|
||||
prefix
|
||||
""".strip())
|
|
@ -1,7 +1,5 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
from threading import Thread
|
||||
|
||||
import jinja2
|
||||
import pytest
|
||||
|
@ -183,74 +181,6 @@ def test_just_properties(catalog, folder, autoescape):
|
|||
)
|
||||
|
||||
|
||||
@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
|
||||
|
@ -280,11 +210,14 @@ def test_required_attr_are_required(catalog, folder, autoescape):
|
|||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_subfolder(catalog, folder, autoescape):
|
||||
"""Components can be organized in subfolders and called
|
||||
using the dot notation.
|
||||
"""
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
|
||||
sub = folder / "UI"
|
||||
sub = folder / "ui"
|
||||
sub.mkdir()
|
||||
(folder / "Meh.jinja").write_text("<UI.Tab>Meh</UI.Tab>")
|
||||
(folder / "Meh.jinja").write_text("<ui.Tab>Meh</ui.Tab>")
|
||||
(sub / "Tab.jinja").write_text('<div class="tab">{{ content }}</div>')
|
||||
|
||||
html = catalog.render("Meh")
|
||||
|
@ -437,56 +370,6 @@ def test_dict_as_attr(catalog, folder, autoescape):
|
|||
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"""
|
||||
|
@ -637,34 +520,6 @@ def test_subcomponents(catalog, folder, autoescape):
|
|||
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
|
||||
|
@ -703,7 +558,7 @@ def test_template_globals(catalog, folder, autoescape):
|
|||
<Form><Input name="foo" :value="value"/></Form>
|
||||
""")
|
||||
|
||||
html = catalog.render("Page", value="bar", __globals={"csrf_token": "abc"})
|
||||
html = catalog.render("Page", value="bar", _globals={"csrf_token": "abc"})
|
||||
print(html)
|
||||
assert """<input type="hidden" name="csrft" value="abc">""" in html
|
||||
|
||||
|
@ -717,11 +572,11 @@ def test_template_globals_update_cache(catalog, folder, autoescape):
|
|||
)
|
||||
(folder / "Page.jinja").write_text("""<CsrfToken/>""")
|
||||
|
||||
html = catalog.render("Page", __globals={"csrf_token": "abc"})
|
||||
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"})
|
||||
html = catalog.render("Page", _globals={"csrf_token": "xyz"})
|
||||
print(html)
|
||||
assert """<input type="hidden" name="csrft" value="xyz">""" in html
|
||||
|
||||
|
@ -870,50 +725,6 @@ 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 #}
|
||||
|
@ -993,149 +804,13 @@ def test_slots(catalog, folder, autoescape):
|
|||
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
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_alt_cased_component_names(catalog, folder, autoescape):
|
||||
(folder / "a_tricky-FOLDER").mkdir()
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def test_thread_safety_of_template_globals(catalog, folder):
|
||||
NUM_THREADS = 5
|
||||
(folder / "Page.jinja").write_text("{{ globalvar if globalvar is defined else 'not set' }}")
|
||||
|
||||
def render(i):
|
||||
return catalog.render("Page", __globals={"globalvar": i})
|
||||
|
||||
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):
|
||||
assert result == Markup(str(i))
|
||||
(folder / "kebab-cased.jinja").write_text("kebab")
|
||||
(folder / "a_tricky-FOLDER" / "Greeting.jinja").write_text("pascal")
|
||||
|
||||
assert catalog.render("KebabCased") == Markup("kebab")
|
||||
assert catalog.render("a_tricky-FOLDER.Greeting") == Markup("pascal")
|
||||
|
|
214
tests/test_render_assets.py
Normal file
214
tests/test_render_assets.py
Normal file
|
@ -0,0 +1,214 @@
|
|||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from markupsafe import Markup
|
||||
|
||||
|
||||
@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_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_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_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)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("autoescape", [True, False])
|
||||
def test_auto_load_assets_for_kebab_cased_names(catalog, folder, autoescape):
|
||||
catalog.jinja_env.autoescape = autoescape
|
||||
|
||||
(folder / "Layout.jinja").write_text(
|
||||
"""{{ catalog.render_assets() }}\n{{ content }}"""
|
||||
)
|
||||
|
||||
(folder / "my-component.jinja").write_text("")
|
||||
(folder / "my-component.css").touch()
|
||||
(folder / "my-component.js").touch()
|
||||
(folder / "page.jinja").write_text("<Layout><MyComponent /></Layout>")
|
||||
|
||||
html = catalog.render("Page")
|
||||
print(html)
|
||||
|
||||
assert "/static/components/my-component.css" in html
|
||||
assert "/static/components/my-component.js" in html
|
187
tests/test_thread_safety.py
Normal file
187
tests/test_thread_safety.py
Normal file
|
@ -0,0 +1,187 @@
|
|||
from threading import Thread
|
||||
|
||||
from markupsafe import Markup
|
||||
|
||||
import jinjax
|
||||
|
||||
|
||||
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("Catalog1 key:", catalog._key)
|
||||
print("Catalog2 key:", catalog2._key)
|
||||
|
||||
# Check if the context variables exist before the test
|
||||
print("Before any rendering:")
|
||||
print("Catalog1 in collected_css:", catalog._key in jinjax.catalog.collected_css)
|
||||
print("Catalog2 in collected_css:", catalog2._key in jinjax.catalog.collected_css)
|
||||
print("collected_css keys:", list(jinjax.catalog.collected_css.keys()))
|
||||
print("collected_js keys:", list(jinjax.catalog.collected_js.keys()))
|
||||
|
||||
(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()
|
||||
|
||||
# Render first component with first catalog
|
||||
html1 = catalog.render("Comp1")
|
||||
|
||||
# Check context variables after first render
|
||||
print("\nAfter first render:")
|
||||
print("Catalog1 collected_css:", catalog.collected_css)
|
||||
print("Catalog2 collected_css:", catalog2.collected_css)
|
||||
print("Catalog1 in collected_css:", catalog._key in jinjax.catalog.collected_css)
|
||||
print("Catalog2 in collected_css:", catalog2._key in jinjax.catalog.collected_css)
|
||||
print("collected_css keys:", list(jinjax.catalog.collected_css.keys()))
|
||||
|
||||
# `irender` instead of `render` so the assets are not cleared
|
||||
html2 = catalog2.irender("Comp2")
|
||||
|
||||
# Check context variables after second render
|
||||
print("\nAfter second render:")
|
||||
print("Catalog1 collected_css:", catalog.collected_css)
|
||||
print("Catalog2 collected_css:", catalog2.collected_css)
|
||||
print("Catalog1 in collected_css:", catalog._key in jinjax.catalog.collected_css)
|
||||
print("Catalog2 in collected_css:", catalog2._key in jinjax.catalog.collected_css)
|
||||
print("collected_css keys:", list(jinjax.catalog.collected_css.keys()))
|
||||
|
||||
print("\nHTML outputs:")
|
||||
print("HTML1:", html1)
|
||||
print("HTML2:", html2)
|
||||
|
||||
assert html1 == Markup(expected_1)
|
||||
assert html2 == Markup(expected_2)
|
||||
|
||||
|
||||
def test_thread_safety_of_template_globals(catalog, folder):
|
||||
NUM_THREADS = 5
|
||||
(folder / "Page.jinja").write_text(
|
||||
"{{ globalvar if globalvar is defined else 'not set' }}"
|
||||
)
|
||||
|
||||
def render(i):
|
||||
return catalog.render("Page", _globals={"globalvar": i})
|
||||
|
||||
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):
|
||||
assert result == Markup(str(i))
|
Loading…
Add table
Add a link
Reference in a new issue