Adding upstream version 0.45+dfsg.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
b4efa209be
commit
eb42e29864
35 changed files with 4489 additions and 0 deletions
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
24
tests/conftest.py
Normal file
24
tests/conftest.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import pytest
|
||||
|
||||
import jinjax
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def folder(tmp_path):
|
||||
d = tmp_path / "components"
|
||||
d.mkdir()
|
||||
return d
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def folder_t(tmp_path):
|
||||
d = tmp_path / "templates"
|
||||
d.mkdir()
|
||||
return d
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def catalog(folder):
|
||||
catalog = jinjax.Catalog(auto_reload=False)
|
||||
catalog.add_folder(folder)
|
||||
return catalog
|
88
tests/test_catalog.py
Normal file
88
tests/test_catalog.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import pytest
|
||||
|
||||
import jinjax
|
||||
|
||||
|
||||
def test_add_folder_with_default_prefix():
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder("file_path")
|
||||
|
||||
assert "file_path" in catalog.prefixes[""].searchpath
|
||||
|
||||
|
||||
def test_add_folder_with_custom_prefix():
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder("file_path", prefix="custom")
|
||||
|
||||
assert "file_path" in catalog.prefixes["custom"].searchpath
|
||||
|
||||
|
||||
def test_add_folder_with_dirty_prefix():
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder("file_path", prefix="/custom.")
|
||||
|
||||
assert "/custom." not in catalog.prefixes
|
||||
assert "file_path" in catalog.prefixes["custom"].searchpath
|
||||
|
||||
|
||||
def test_add_folders_with_same_prefix():
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder("file_path1", prefix="custom")
|
||||
catalog.add_folder("file_path2", prefix="custom")
|
||||
|
||||
assert "file_path1" in catalog.prefixes["custom"].searchpath
|
||||
assert "file_path2" in catalog.prefixes["custom"].searchpath
|
||||
|
||||
|
||||
def test_add_same_folder_in_same_prefix_does_nothing():
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder("file_path", prefix="custom")
|
||||
catalog.add_folder("file_path", prefix="custom")
|
||||
|
||||
assert catalog.prefixes["custom"].searchpath.count("file_path") == 1
|
||||
|
||||
|
||||
def test_add_module_legacy():
|
||||
class Module:
|
||||
components_path = "legacy_path"
|
||||
prefix = "legacy"
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
module = Module()
|
||||
catalog.add_module(module)
|
||||
|
||||
assert "legacy_path" in catalog.prefixes["legacy"].searchpath
|
||||
|
||||
|
||||
def test_add_module_legacy_with_default_prefix():
|
||||
class Module:
|
||||
components_path = "legacy_path"
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
module = Module()
|
||||
catalog.add_module(module)
|
||||
|
||||
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
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
module = Module()
|
||||
with pytest.raises(AttributeError):
|
||||
catalog.add_module(module)
|
315
tests/test_component.py
Normal file
315
tests/test_component.py
Normal file
|
@ -0,0 +1,315 @@
|
|||
import pytest
|
||||
|
||||
from jinjax import Component, DuplicateDefDeclaration, InvalidArgument
|
||||
|
||||
|
||||
def test_load_args():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source='{#def message, lorem=4, ipsum="bar" -#}\n',
|
||||
)
|
||||
assert com.required == ["message"]
|
||||
assert com.optional == {
|
||||
"lorem": 4,
|
||||
"ipsum": "bar",
|
||||
}
|
||||
|
||||
|
||||
def test_expression_args():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="{#def expr=1 + 2 + 3, a=1 -#}\n",
|
||||
)
|
||||
assert com.required == []
|
||||
assert com.optional == {
|
||||
"expr": 6,
|
||||
"a": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_dict_args():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="{#def expr={'a': 'b', 'c': 'd'} -#}\n",
|
||||
)
|
||||
assert com.optional == {
|
||||
"expr": {"a": "b", "c": "d"},
|
||||
}
|
||||
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source='{#def a=1, expr={"a": "b", "c": "d"} -#}\n',
|
||||
)
|
||||
assert com.optional == {
|
||||
"a": 1,
|
||||
"expr": {"a": "b", "c": "d"},
|
||||
}
|
||||
|
||||
|
||||
def test_lowercase_booleans():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="{#def a=false, b=true -#}\n",
|
||||
)
|
||||
assert com.optional == {
|
||||
"a": False,
|
||||
"b": True,
|
||||
}
|
||||
|
||||
|
||||
def test_no_args():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="\n",
|
||||
)
|
||||
assert com.required == []
|
||||
assert com.optional == {}
|
||||
|
||||
|
||||
def test_fails_when_invalid_name():
|
||||
with pytest.raises(InvalidArgument):
|
||||
source = "{#def 000abc -#}\n"
|
||||
co = Component(name="", source=source)
|
||||
print(co.required, co.optional)
|
||||
|
||||
|
||||
def test_fails_when_missing_comma_between_args():
|
||||
with pytest.raises(InvalidArgument):
|
||||
source = "{#def lorem ipsum -#}\n"
|
||||
co = Component(name="", source=source)
|
||||
print(co.required, co.optional)
|
||||
|
||||
|
||||
def test_fails_when_missing_quotes_arround_default_value():
|
||||
with pytest.raises(InvalidArgument):
|
||||
source = "{#def lorem=ipsum -#}\n"
|
||||
co = Component(name="", source=source)
|
||||
print(co.required, co.optional)
|
||||
|
||||
|
||||
def test_fails_when_prop_is_expression():
|
||||
with pytest.raises(InvalidArgument):
|
||||
source = "{#def a-b -#}\n"
|
||||
co = Component(name="", source=source)
|
||||
print(co.required, co.optional)
|
||||
|
||||
|
||||
def test_fails_when_extra_comma_between_args():
|
||||
with pytest.raises(InvalidArgument):
|
||||
source = "{#def a, , b -#}\n"
|
||||
co = Component(name="", source=source)
|
||||
print(co.required, co.optional)
|
||||
|
||||
|
||||
def test_comma_in_default_value():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="{#def a='lorem, ipsum' -#}\n",
|
||||
)
|
||||
assert com.optional == {"a": "lorem, ipsum"}
|
||||
|
||||
|
||||
def test_load_assets():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
url_prefix="/static/",
|
||||
source="""
|
||||
{#css a.css, "b.css", c.css -#}
|
||||
{#js a.js, b.js, c.js -#}
|
||||
""",
|
||||
)
|
||||
assert com.css == ["/static/a.css", "/static/b.css", "/static/c.css"]
|
||||
assert com.js == ["/static/a.js", "/static/b.js", "/static/c.js"]
|
||||
|
||||
|
||||
def test_no_comma_in_assets_list_is_your_problem():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="{#js a.js b.js c.js -#}\n",
|
||||
url_prefix="/static/"
|
||||
)
|
||||
assert com.js == ["/static/a.js b.js c.js"]
|
||||
|
||||
|
||||
def test_load_metadata_in_any_order():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{#css a.css #}
|
||||
{#def lorem, ipsum=4 #}
|
||||
{#js a.js #}
|
||||
""",
|
||||
)
|
||||
assert com.required == ["lorem"]
|
||||
assert com.optional == {"ipsum": 4}
|
||||
assert com.css == ["a.css"]
|
||||
assert com.js == ["a.js"]
|
||||
|
||||
|
||||
def test_ignore_metadata_if_not_first():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
I am content
|
||||
{#css a.css #}
|
||||
{#def lorem, ipsum=4 #}
|
||||
{#js a.js #}
|
||||
""",
|
||||
)
|
||||
assert com.required == []
|
||||
assert com.optional == {}
|
||||
assert com.css == []
|
||||
assert com.js == []
|
||||
|
||||
|
||||
def test_fail_with_more_than_one_args_declaration():
|
||||
with pytest.raises(DuplicateDefDeclaration):
|
||||
Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{#def lorem, ipsum=4 #}
|
||||
{#def a, b, c, ipsum="nope" #}
|
||||
""",
|
||||
)
|
||||
|
||||
|
||||
def test_merge_repeated_css_or_js_declarations():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{#css a.css #}
|
||||
{#def lorem, ipsum=4 #}
|
||||
{#css b.css #}
|
||||
{#js a.js #}
|
||||
{#js b.js #}
|
||||
""",
|
||||
)
|
||||
assert com.required == ["lorem"]
|
||||
assert com.optional == {"ipsum": 4}
|
||||
assert com.css == ["a.css", "b.css"]
|
||||
assert com.js == ["a.js", "b.js"]
|
||||
|
||||
|
||||
def test_linejump_in_args_decl():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source='{#def\n message,\n lorem=4,\n ipsum="bar"\n#}\n',
|
||||
)
|
||||
assert com.required == ["message"]
|
||||
assert com.optional == {
|
||||
"lorem": 4,
|
||||
"ipsum": "bar",
|
||||
}
|
||||
|
||||
|
||||
def test_global_assets():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{#css a.css, /static/shared/b.css, http://example.com/cdn.css #}
|
||||
{#js "http://example.com/cdn.js", a.js, /static/shared/b.js #}
|
||||
""",
|
||||
)
|
||||
assert com.css == ["a.css", "/static/shared/b.css", "http://example.com/cdn.css"]
|
||||
assert com.js == ["http://example.com/cdn.js", "a.js", "/static/shared/b.js"]
|
||||
|
||||
|
||||
def test_types_in_args_decl():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""{# def
|
||||
ring_class: str = "ring-1 ring-black",
|
||||
rounded_class: str = "rounded-2xl md:rounded-3xl",
|
||||
|
||||
image: str | None = None,
|
||||
|
||||
title: str = "",
|
||||
p_class: str = "px-5 md:px-6 py-5 md:py-6",
|
||||
gap_class: str = "gap-4",
|
||||
content_class: str = "",
|
||||
|
||||
layer_class: str | None = None,
|
||||
layer_height: int = 4,
|
||||
#}"""
|
||||
)
|
||||
assert com.required == []
|
||||
print(com.optional)
|
||||
assert com.optional == {
|
||||
"ring_class": "ring-1 ring-black",
|
||||
"rounded_class": "rounded-2xl md:rounded-3xl",
|
||||
"image": None,
|
||||
"title": "",
|
||||
"p_class": "px-5 md:px-6 py-5 md:py-6",
|
||||
"gap_class": "gap-4",
|
||||
"content_class": "",
|
||||
"layer_class": None,
|
||||
"layer_height": 4,
|
||||
}
|
||||
|
||||
|
||||
def test_comments_in_args_decl():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""{# def
|
||||
#
|
||||
# Card style
|
||||
ring_class: str = "ring-1 ring-black",
|
||||
rounded_class: str = "rounded-2xl md:rounded-3xl",
|
||||
#
|
||||
# Image
|
||||
image: str | None = None,
|
||||
#
|
||||
# Content
|
||||
title: str = "",
|
||||
p_class: str = "px-5 md:px-6 py-5 md:py-6",
|
||||
gap_class: str = "gap-4",
|
||||
content_class: str = "",
|
||||
#
|
||||
# Decorative layer
|
||||
layer_class: str | None = None,
|
||||
layer_height: int = 4,
|
||||
#}"""
|
||||
)
|
||||
assert com.required == []
|
||||
print(com.optional)
|
||||
assert com.optional == {
|
||||
"ring_class": "ring-1 ring-black",
|
||||
"rounded_class": "rounded-2xl md:rounded-3xl",
|
||||
"image": None,
|
||||
"title": "",
|
||||
"p_class": "px-5 md:px-6 py-5 md:py-6",
|
||||
"gap_class": "gap-4",
|
||||
"content_class": "",
|
||||
"layer_class": None,
|
||||
"layer_height": 4,
|
||||
}
|
||||
|
||||
|
||||
def test_comment_after_args_decl():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{# def
|
||||
arg,
|
||||
#}
|
||||
|
||||
{#
|
||||
Some comment.
|
||||
#}
|
||||
Hi
|
||||
""".strip())
|
||||
assert com.required == ["arg"]
|
||||
assert com.optional == {}
|
||||
|
||||
|
||||
def test_fake_decl():
|
||||
com = Component(
|
||||
name="Test.jinja",
|
||||
source="""
|
||||
{# definitely not an args decl! #}
|
||||
{# def arg #}
|
||||
{# jsadfghkl are letters #}
|
||||
{# csssssss #}
|
||||
""".strip())
|
||||
assert com.required == ["arg"]
|
||||
assert com.optional == {}
|
281
tests/test_html_attrs.py
Normal file
281
tests/test_html_attrs.py
Normal file
|
@ -0,0 +1,281 @@
|
|||
import pytest
|
||||
|
||||
from jinjax.html_attrs import HTMLAttrs
|
||||
|
||||
|
||||
def test_parse_initial_attrs():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"data-position": "top",
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
"open": True,
|
||||
"disabled": False,
|
||||
"value": 0,
|
||||
"foobar": None,
|
||||
}
|
||||
)
|
||||
assert attrs.classes == "a1 b2 c3 z4"
|
||||
assert attrs.get("class") == "a1 b2 c3 z4"
|
||||
assert attrs.get("data-position") == "top"
|
||||
assert attrs.get("data_position") == "top"
|
||||
assert attrs.get("title") == "hi"
|
||||
assert attrs.get("open") is True
|
||||
assert attrs.get("disabled", "meh") == "meh"
|
||||
assert attrs.get("value") == "0"
|
||||
|
||||
assert attrs.get("disabled") is None
|
||||
assert attrs.get("foobar") is None
|
||||
|
||||
attrs.set(data_value=0)
|
||||
attrs.set(data_position=False)
|
||||
assert attrs.get("data-value") == 0
|
||||
assert attrs.get("data-position") is None
|
||||
assert attrs.get("data_position") is None
|
||||
|
||||
def test_getattr():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
"open": True,
|
||||
}
|
||||
)
|
||||
assert attrs["class"] == "a1 b2 c3 z4"
|
||||
assert attrs["title"] == "hi"
|
||||
assert attrs["open"] is True
|
||||
assert attrs["lorem"] is None
|
||||
|
||||
|
||||
def test_deltattr():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
"open": True,
|
||||
}
|
||||
)
|
||||
assert attrs["class"] == "a1 b2 c3 z4"
|
||||
del attrs["title"]
|
||||
assert attrs["title"] is None
|
||||
|
||||
|
||||
def test_render():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"data-position": "top",
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
"open": True,
|
||||
"disabled": False,
|
||||
}
|
||||
)
|
||||
assert 'class="a1 b2 c3 z4" data-position="top" title="hi" open' == attrs.render()
|
||||
|
||||
|
||||
def test_set():
|
||||
attrs = HTMLAttrs({})
|
||||
attrs.set(title="hi", data_position="top")
|
||||
attrs.set(open=True)
|
||||
assert 'data-position="top" title="hi" open' == attrs.render()
|
||||
|
||||
attrs.set(title=False, open=False)
|
||||
assert 'data-position="top"' == attrs.render()
|
||||
|
||||
|
||||
def test_class_management():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
}
|
||||
)
|
||||
attrs.set(classes="lorem bipsum lorem a1")
|
||||
|
||||
assert attrs.classes == "a1 b2 bipsum c3 lorem z4"
|
||||
|
||||
attrs.remove_class("bipsum")
|
||||
assert attrs.classes == "a1 b2 c3 lorem z4"
|
||||
|
||||
attrs.set(classes=None)
|
||||
attrs.set(classes="meh")
|
||||
assert attrs.classes == "meh"
|
||||
|
||||
|
||||
def test_setdefault():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
}
|
||||
)
|
||||
attrs.setdefault(
|
||||
title="default title",
|
||||
data_lorem="ipsum",
|
||||
open=True,
|
||||
disabled=False,
|
||||
)
|
||||
assert 'data-lorem="ipsum" title="hi"' == attrs.render()
|
||||
|
||||
|
||||
def test_as_dict():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"data-position": "top",
|
||||
"class": "z4 c3 a1 z4 b2",
|
||||
"open": True,
|
||||
"disabled": False,
|
||||
}
|
||||
)
|
||||
assert attrs.as_dict == {
|
||||
"class": "a1 b2 c3 z4",
|
||||
"data-position": "top",
|
||||
"title": "hi",
|
||||
"open": True,
|
||||
}
|
||||
|
||||
|
||||
def test_as_dict_no_classes():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"title": "hi",
|
||||
"data-position": "top",
|
||||
"open": True,
|
||||
}
|
||||
)
|
||||
assert attrs.as_dict == {
|
||||
"data-position": "top",
|
||||
"title": "hi",
|
||||
"open": True,
|
||||
}
|
||||
|
||||
|
||||
def test_render_attrs_lik_set():
|
||||
attrs = HTMLAttrs({"class": "lorem"})
|
||||
expected = 'class="ipsum lorem" data-position="top" title="hi" open'
|
||||
result = attrs.render(
|
||||
title="hi",
|
||||
data_position="top",
|
||||
classes="ipsum",
|
||||
open=True,
|
||||
)
|
||||
print(result)
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_do_not_escape_tailwind_syntax():
|
||||
attrs = HTMLAttrs({"class": "lorem [&_a]:flex"})
|
||||
expected = 'class="[&_a]:flex ipsum lorem" title="Hi&Stuff"'
|
||||
result = attrs.render(
|
||||
**{
|
||||
"title": "Hi&Stuff",
|
||||
"class": "ipsum",
|
||||
}
|
||||
)
|
||||
print(result)
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_do_escape_quotes_inside_attrs():
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"class": "lorem text-['red']",
|
||||
"title": 'I say "hey"',
|
||||
"open": True,
|
||||
}
|
||||
)
|
||||
expected = """class="lorem text-['red']" title='I say "hey"' open"""
|
||||
result = attrs.render()
|
||||
print(result)
|
||||
assert expected == result
|
||||
|
||||
|
||||
def test_additional_attributes_are_lazily_evaluated_to_strings():
|
||||
class TestObject:
|
||||
def __str__(self):
|
||||
raise RuntimeError("Should not be called unless rendered.")
|
||||
|
||||
attrs = HTMLAttrs(
|
||||
{
|
||||
"some_object": TestObject(),
|
||||
}
|
||||
)
|
||||
|
||||
with pytest.raises(RuntimeError):
|
||||
attrs.render()
|
||||
|
||||
|
||||
def test_additional_attributes_lazily_evaluated_has_string_methods():
|
||||
class TestObject:
|
||||
def __str__(self):
|
||||
return "test"
|
||||
|
||||
attrs = HTMLAttrs({"some_object": TestObject()})
|
||||
|
||||
assert attrs["some_object"].__str__
|
||||
assert attrs["some_object"].__repr__
|
||||
assert attrs["some_object"].__int__
|
||||
assert attrs["some_object"].__float__
|
||||
assert attrs["some_object"].__complex__
|
||||
assert attrs["some_object"].__hash__
|
||||
assert attrs["some_object"].__eq__
|
||||
assert attrs["some_object"].__lt__
|
||||
assert attrs["some_object"].__le__
|
||||
assert attrs["some_object"].__gt__
|
||||
assert attrs["some_object"].__ge__
|
||||
assert attrs["some_object"].__contains__
|
||||
assert attrs["some_object"].__len__
|
||||
assert attrs["some_object"].__getitem__
|
||||
assert attrs["some_object"].__add__
|
||||
assert attrs["some_object"].__radd__
|
||||
assert attrs["some_object"].__mul__
|
||||
assert attrs["some_object"].__mod__
|
||||
assert attrs["some_object"].__rmod__
|
||||
assert attrs["some_object"].capitalize
|
||||
assert attrs["some_object"].casefold
|
||||
assert attrs["some_object"].center
|
||||
assert attrs["some_object"].count
|
||||
assert attrs["some_object"].removeprefix
|
||||
assert attrs["some_object"].removesuffix
|
||||
assert attrs["some_object"].encode
|
||||
assert attrs["some_object"].endswith
|
||||
assert attrs["some_object"].expandtabs
|
||||
assert attrs["some_object"].find
|
||||
assert attrs["some_object"].format
|
||||
assert attrs["some_object"].format_map
|
||||
assert attrs["some_object"].index
|
||||
assert attrs["some_object"].isalpha
|
||||
assert attrs["some_object"].isalnum
|
||||
assert attrs["some_object"].isascii
|
||||
assert attrs["some_object"].isdecimal
|
||||
assert attrs["some_object"].isdigit
|
||||
assert attrs["some_object"].isidentifier
|
||||
assert attrs["some_object"].islower
|
||||
assert attrs["some_object"].isnumeric
|
||||
assert attrs["some_object"].isprintable
|
||||
assert attrs["some_object"].isspace
|
||||
assert attrs["some_object"].istitle
|
||||
assert attrs["some_object"].isupper
|
||||
assert attrs["some_object"].join
|
||||
assert attrs["some_object"].ljust
|
||||
assert attrs["some_object"].lower
|
||||
assert attrs["some_object"].lstrip
|
||||
assert attrs["some_object"].partition
|
||||
assert attrs["some_object"].replace
|
||||
assert attrs["some_object"].rfind
|
||||
assert attrs["some_object"].rindex
|
||||
assert attrs["some_object"].rjust
|
||||
assert attrs["some_object"].rpartition
|
||||
assert attrs["some_object"].rstrip
|
||||
assert attrs["some_object"].split
|
||||
assert attrs["some_object"].rsplit
|
||||
assert attrs["some_object"].splitlines
|
||||
assert attrs["some_object"].startswith
|
||||
assert attrs["some_object"].strip
|
||||
assert attrs["some_object"].swapcase
|
||||
assert attrs["some_object"].title
|
||||
assert attrs["some_object"].translate
|
||||
assert attrs["some_object"].upper
|
||||
assert attrs["some_object"].zfill
|
||||
|
||||
assert attrs["some_object"].upper() == "TEST"
|
||||
assert attrs["some_object"].title() == "Test"
|
152
tests/test_middleware.py
Normal file
152
tests/test_middleware.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
import typing as t
|
||||
from pathlib import Path
|
||||
|
||||
import jinjax
|
||||
|
||||
|
||||
def application(environ, start_response) -> list[bytes]:
|
||||
status = "200 OK"
|
||||
headers = [("Content-type", "text/plain")]
|
||||
start_response(status, headers)
|
||||
return [b"NOPE"]
|
||||
|
||||
|
||||
def make_environ(**kw) -> dict[str, t.Any]:
|
||||
kw.setdefault("PATH_INFO", "/")
|
||||
kw.setdefault("REQUEST_METHOD", "GET")
|
||||
return kw
|
||||
|
||||
|
||||
def mock_start_response(status: str, headers: dict[str, t.Any]):
|
||||
pass
|
||||
|
||||
|
||||
def get_catalog(folder: "str | Path", **kw) -> jinjax.Catalog:
|
||||
catalog = jinjax.Catalog(**kw)
|
||||
catalog.add_folder(folder)
|
||||
return catalog
|
||||
|
||||
|
||||
TMiddleware = t.Callable[
|
||||
[
|
||||
dict[str, t.Any],
|
||||
t.Callable[[str, dict[str, t.Any]], None],
|
||||
],
|
||||
t.Any
|
||||
]
|
||||
|
||||
def run_middleware(middleware: TMiddleware, url: str):
|
||||
return middleware(make_environ(PATH_INFO=url), mock_start_response)
|
||||
|
||||
|
||||
# Tests
|
||||
|
||||
|
||||
def test_css_is_returned(folder):
|
||||
(folder / "page.css").write_text("/* Page.css */")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/page.css")
|
||||
assert resp and not isinstance(resp, list)
|
||||
text = resp.filelike.read().strip()
|
||||
assert text == b"/* Page.css */"
|
||||
|
||||
|
||||
def test_js_is_returned(folder):
|
||||
(folder / "page.js").write_text("/* Page.js */")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/page.js")
|
||||
assert resp and not isinstance(resp, list)
|
||||
text = resp.filelike.read().strip()
|
||||
assert text == b"/* Page.js */"
|
||||
|
||||
|
||||
def test_other_file_extensions_ignored(folder):
|
||||
(folder / "Page.jinja").write_text("???")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application)
|
||||
resp = run_middleware(middleware, "/static/components/Page.jinja")
|
||||
assert resp == [b"NOPE"]
|
||||
|
||||
|
||||
def test_add_custom_extensions(folder):
|
||||
(folder / "Page.jinja").write_text("???")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application, allowed_ext=[".jinja"])
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/Page.jinja")
|
||||
assert resp and not isinstance(resp, list)
|
||||
text = resp.filelike.read().strip()
|
||||
assert text == b"???"
|
||||
|
||||
|
||||
def test_custom_root_url(folder):
|
||||
(folder / "page.css").write_text("/* Page.css */")
|
||||
catalog = get_catalog(folder, root_url="/static/co/")
|
||||
middleware = catalog.get_middleware(application)
|
||||
|
||||
resp = run_middleware(middleware, "/static/co/page.css")
|
||||
assert resp and not isinstance(resp, list)
|
||||
text = resp.filelike.read().strip()
|
||||
assert text == b"/* Page.css */"
|
||||
|
||||
|
||||
def test_autorefresh_load(folder):
|
||||
(folder / "page.css").write_text("/* Page.css */")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application, autorefresh=True)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/page.css")
|
||||
assert resp and not isinstance(resp, list)
|
||||
text = resp.filelike.read().strip()
|
||||
assert text == b"/* Page.css */"
|
||||
|
||||
|
||||
def test_autorefresh_block(folder):
|
||||
(folder / "Page.jinja").write_text("???")
|
||||
catalog = get_catalog(folder)
|
||||
middleware = catalog.get_middleware(application, autorefresh=True)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/Page.jinja")
|
||||
assert resp == [b"NOPE"]
|
||||
|
||||
|
||||
def test_multiple_folders(tmp_path):
|
||||
folder1 = tmp_path / "folder1"
|
||||
folder1.mkdir()
|
||||
(folder1 / "folder1.css").write_text("folder1")
|
||||
|
||||
folder2 = tmp_path / "folder2"
|
||||
folder2.mkdir()
|
||||
(folder2 / "folder2.css").write_text("folder2")
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder(folder1)
|
||||
catalog.add_folder(folder2)
|
||||
middleware = catalog.get_middleware(application)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/folder1.css")
|
||||
assert resp.filelike.read() == b"folder1"
|
||||
resp = run_middleware(middleware, "/static/components/folder2.css")
|
||||
assert resp.filelike.read() == b"folder2"
|
||||
|
||||
|
||||
def test_multiple_folders_precedence(tmp_path):
|
||||
folder1 = tmp_path / "folder1"
|
||||
folder1.mkdir()
|
||||
(folder1 / "name.css").write_text("folder1")
|
||||
|
||||
folder2 = tmp_path / "folder2"
|
||||
folder2.mkdir()
|
||||
(folder2 / "name.css").write_text("folder2")
|
||||
|
||||
catalog = jinjax.Catalog()
|
||||
catalog.add_folder(folder1)
|
||||
catalog.add_folder(folder2)
|
||||
middleware = catalog.get_middleware(application)
|
||||
|
||||
resp = run_middleware(middleware, "/static/components/name.css")
|
||||
assert resp.filelike.read() == b"folder1"
|
992
tests/test_render.py
Normal file
992
tests/test_render.py
Normal file
|
@ -0,0 +1,992 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
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 "" }}>×</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>×</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="Hello", world=False #}
|
||||
<Header />
|
||||
<div>{{ message }}{% if world %} World{% endif %}</div>
|
||||
</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>
|
||||
<C id="2" />
|
||||
<div id="3"></div>
|
||||
<C id="4" />
|
||||
<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>
|
||||
<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)
|
Loading…
Add table
Add a link
Reference in a new issue