From f27fc9c895e780294c51325d6b484d8f159b864a Mon Sep 17 00:00:00 2001 From: Andreas Pelme Date: Sun, 15 Sep 2024 12:47:22 +0200 Subject: [PATCH] Make it possible to trace evaluation in tests via a trace fixture. --- tests/__init__.py | 0 tests/conftest.py | 38 ++++++++++++++++++++--- tests/test_attributes.py | 10 +++--- tests/test_children.py | 67 ++++++++++++++++++++-------------------- 4 files changed, 72 insertions(+), 43 deletions(-) create mode 100644 tests/__init__.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index 0451ea7..48a7eb7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ from __future__ import annotations +import dataclasses import typing as t import pytest @@ -7,10 +8,17 @@ from htpy import Node, iter_node if t.TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Callable, Generator -RenderFixture: t.TypeAlias = t.Callable[[Node], list[str]] +@dataclasses.dataclass(frozen=True) +class Trace: + description: str + + +RenderResult: t.TypeAlias = list[str | Trace] +RenderFixture: t.TypeAlias = t.Callable[[Node], RenderResult] +TraceFixture: t.TypeAlias = t.Callable[[str], None] @pytest.fixture(scope="session") @@ -28,13 +36,33 @@ def django_env() -> None: @pytest.fixture -def render() -> Generator[RenderFixture, None, None]: +def render_result() -> RenderResult: + return [] + + +@pytest.fixture +def trace(render_result: RenderResult) -> Callable[[str], None]: + def func(description: str) -> None: + render_result.append(Trace(description=description)) + + return func + + +@pytest.fixture +def render(render_result: RenderResult) -> Generator[RenderFixture, None, None]: called = False - def func(node: Node) -> list[str]: + def func(node: Node) -> RenderResult: nonlocal called + + if called: + raise AssertionError("render() must only be called once per test") + called = True - return list(iter_node(node)) + for chunk in iter_node(node): + render_result.append(chunk) + + return render_result yield func diff --git a/tests/test_attributes.py b/tests/test_attributes.py index e833bdc..b0c5e70 100644 --- a/tests/test_attributes.py +++ b/tests/test_attributes.py @@ -178,14 +178,16 @@ def test_attrs_and_kwargs(render: RenderFixture) -> None: assert render(result) == ['
', "
"] -def test_class_priority(render: RenderFixture) -> None: - result = div(".a", {"class": "b"}, class_="c") - assert render(result) == ['
', "
"] - +def test_class_priority_dict(render: RenderFixture) -> None: result = div(".a", {"class": "b"}) assert render(result) == ['
', "
"] +def test_class_priority_dict_and_kwarg(render: RenderFixture) -> None: + result = div(".a", {"class": "b"}, class_="c") + assert render(result) == ['
', "
"] + + def test_attribute_priority(render: RenderFixture) -> None: result = div({"foo": "a"}, foo="b") assert render(result) == ['
', "
"] diff --git a/tests/test_children.py b/tests/test_children.py index 6c2e64b..fcfb818 100644 --- a/tests/test_children.py +++ b/tests/test_children.py @@ -13,12 +13,14 @@ from htpy import Element, VoidElement, dd, div, dl, dt, html, img, input, li, my_custom_element, ul +from .conftest import Trace + if t.TYPE_CHECKING: from collections.abc import Callable, Generator from htpy import Node - from .conftest import RenderFixture + from .conftest import RenderFixture, TraceFixture def test_void_element(render: RenderFixture) -> None: @@ -134,29 +136,43 @@ def test_ignored(render: RenderFixture, ignored_value: t.Any) -> None: assert render(div[ignored_value]) == ["
", "
"] -def test_sync_iter() -> None: - trace = "not started" - +def test_lazy_iter(render: RenderFixture, trace: TraceFixture) -> None: def generate_list() -> Generator[Element, None, None]: - nonlocal trace - - trace = "before yield" + trace("before yield") yield li("#a") + trace("after yield") + + result = render(ul[generate_list()]) + assert result == [ + "", + ] - trace = "done" - iterator = iter(ul[generate_list()]) +def test_lazy_callable(render: RenderFixture, trace: TraceFixture) -> None: + def parent() -> Element: + trace("in parent") + return div("#parent")[child] - assert next(iterator) == "" - assert trace == "done" + assert result == [ + "
", + Trace(description="in parent"), + '
', + Trace(description="in child"), + "child", + "
", + "
", + ] def test_iter_str(render: RenderFixture) -> None: @@ -175,23 +191,6 @@ def test_iter_markup(render: RenderFixture) -> None: assert type(child) is str -def test_callable() -> None: - called = False - - def generate_img() -> VoidElement: - nonlocal called - called = True - return img - - iterator = iter(div[generate_img]) - - assert next(iterator) == "
" - assert called is False - assert next(iterator) == "" - assert called is True - assert next(iterator) == "
" - - def test_escape_children(render: RenderFixture) -> None: result = render(div['>"']) assert result == ["
", ">"", "
"]