Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified src/js/bun.lockb
Binary file not shown.
5 changes: 3 additions & 2 deletions src/reactpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from reactpy import config, logging, reactjs, types, web, widgets
from reactpy._html import html
from reactpy._html import h, html
from reactpy.core import hooks
from reactpy.core.component import component
from reactpy.core.events import event
Expand All @@ -23,7 +23,7 @@
from reactpy.utils import Ref, reactpy_to_string, string_to_reactpy

__author__ = "The Reactive Python Team"
__version__ = "2.0.0b9"
__version__ = "2.0.0b10"

__all__ = [
"Ref",
Expand All @@ -32,6 +32,7 @@
"config",
"create_context",
"event",
"h",
"hooks",
"html",
"logging",
Expand Down
17 changes: 11 additions & 6 deletions src/reactpy/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from lxml import etree
from lxml.html import fromstring

from reactpy import html
from reactpy import h
from reactpy.transforms import RequiredTransforms, attributes_to_reactjs
from reactpy.types import Component, VdomDict

Expand Down Expand Up @@ -105,9 +105,14 @@ def string_to_reactpy(
event handler that prevents the browser from navigating to the link. This is
useful if you would rather have `reactpy-router` handle your URL navigation.
"""
if not isinstance(html, str): # nocov
if not isinstance(html, str):
msg = f"Expected html to be a string, not {type(html).__name__}"
raise TypeError(msg)
if not html.strip():
return h.fragment()
if "<" not in html or ">" not in html:
msg = "Expected html string to contain HTML tags, but no tags were found."
raise ValueError(msg)

# If the user provided a string, convert it to a list of lxml.etree nodes
try:
Expand Down Expand Up @@ -153,7 +158,7 @@ def _etree_to_vdom(
attributes = attributes_to_reactjs(dict(node.items()))

# Convert the lxml node to a VDOM dict
constructor = getattr(html, str(node.tag))
constructor = getattr(h, str(node.tag))
el = constructor(attributes, children)

# Perform necessary transformations on the VDOM attributes to meet VDOM spec
Expand Down Expand Up @@ -236,13 +241,13 @@ def component_to_vdom(component: Component) -> VdomDict:
"""Convert the first render of a component into a VDOM dictionary"""
result = component.render()

if result is None:
return h.fragment()
if isinstance(result, dict):
return result
if hasattr(result, "render"):
return component_to_vdom(cast(Component, result))
elif isinstance(result, str):
return html.div(result)
return html()
return h.div(result) if isinstance(result, str) else h.div()


def _react_attribute_to_html(key: str, value: Any) -> tuple[str, str]:
Expand Down
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
)
from reactpy.testing.display import _playwright_visible

from . import pytestmark # noqa: F401

REACTPY_ASYNC_RENDERING.set_current(True)
REACTPY_DEBUG.set_current(True)

Expand Down
2 changes: 2 additions & 0 deletions tests/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
from tests.tooling.common import DEFAULT_TYPE_DELAY
from tests.tooling.hooks import use_counter

from . import pytestmark # noqa: F401

JS_DIR = Path(__file__).parent / "js"


Expand Down
2 changes: 2 additions & 0 deletions tests/test_core/test_component.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import reactpy
from reactpy.testing import DisplayFixture

from .. import pytestmark # noqa: F401


def test_component_repr():
@reactpy.component
Expand Down
2 changes: 2 additions & 0 deletions tests/test_core/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from reactpy.types import Event
from tests.tooling.common import DEFAULT_TYPE_DELAY

from .. import pytestmark # noqa: F401


def test_event_handler_repr():
handler = EventHandler(lambda: None)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_core/test_hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from reactpy.utils import Ref
from tests.tooling.common import DEFAULT_TYPE_DELAY, update_message

from .. import pytestmark # noqa: F401


async def test_must_be_rendering_in_layout_to_use_hooks():
@reactpy.component
Expand Down
2 changes: 2 additions & 0 deletions tests/test_html.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from tests.tooling.common import DEFAULT_TYPE_DELAY
from tests.tooling.hooks import use_counter

from . import pytestmark # noqa: F401


async def test_script_re_run_on_content_change(display: DisplayFixture):
@component
Expand Down
2 changes: 2 additions & 0 deletions tests/test_pyscript/test_components.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from reactpy.testing import BackendFixture, DisplayFixture
from reactpy.testing.backend import root_hotswap_component

from .. import pytestmark # noqa: F401


@pytest.fixture(scope="module")
async def display(browser):
Expand Down
2 changes: 2 additions & 0 deletions tests/test_reactjs/test_modules.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from reactpy.reactjs import component_from_string, import_reactjs
from reactpy.testing import BackendFixture, DisplayFixture

from .. import pytestmark # noqa: F401

JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures"


Expand Down
2 changes: 2 additions & 0 deletions tests/test_sample.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from reactpy.testing import DisplayFixture
from tests.sample import SampleApp

from . import pytestmark # noqa: F401


async def test_sample_app(display: DisplayFixture):
await display.show(SampleApp)
Expand Down
2 changes: 2 additions & 0 deletions tests/test_testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from reactpy.testing.display import DisplayFixture
from tests.sample import SampleApp

from . import pytestmark # noqa: F401


def test_assert_reactpy_logged_does_not_suppress_errors():
with pytest.raises(RuntimeError, match=r"expected error"):
Expand Down
17 changes: 17 additions & 0 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,23 @@ def test_string_to_reactpy(case):
assert utils.string_to_reactpy(case["source"]) == case["model"]


@pytest.mark.parametrize("source", ["", " ", "\n\t "])
def test_string_to_reactpy_empty_source(source):
assert utils.string_to_reactpy(source) == html.fragment()


@pytest.mark.parametrize("source", [123, None, object()])
def test_string_to_reactpy_non_string_source(source):
with pytest.raises(TypeError):
utils.string_to_reactpy(source) # type: ignore[arg-type]


@pytest.mark.parametrize("source", ["no tags", "plain text", "just words"])
def test_string_to_reactpy_missing_tags(source):
with pytest.raises(ValueError):
utils.string_to_reactpy(source)


@pytest.mark.parametrize(
"case",
[
Expand Down
2 changes: 2 additions & 0 deletions tests/test_web/test_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
)
from reactpy.types import InlineJavaScript

from .. import pytestmark # noqa: F401

JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures"


Expand Down
Loading