diff --git a/src/js/packages/@reactpy/client/src/vdom.tsx b/src/js/packages/@reactpy/client/src/vdom.tsx index e57ea4594..8b289fceb 100644 --- a/src/js/packages/@reactpy/client/src/vdom.tsx +++ b/src/js/packages/@reactpy/client/src/vdom.tsx @@ -1,4 +1,5 @@ import eventToObject from "event-to-object"; +import { Fragment } from "preact"; import type { ReactPyVdom, ReactPyVdomImportSource, @@ -93,7 +94,7 @@ function createImportSourceElement(props: { } } } else { - type = props.model.tagName; + type = props.model.tagName === "" ? Fragment : props.model.tagName; } return props.binding.create( type, diff --git a/tests/test_reactjs/js_fixtures/nest-custom-under-web.js b/tests/test_reactjs/js_fixtures/nest-custom-under-web.js new file mode 100644 index 000000000..027c9a2b7 --- /dev/null +++ b/tests/test_reactjs/js_fixtures/nest-custom-under-web.js @@ -0,0 +1,14 @@ +import React from "https://esm.sh/react@19.0" +import ReactDOM from "https://esm.sh/react-dom@19.0/client" +import {Container} from "https://esm.sh/react-bootstrap@2.10.10/?deps=react@19.0,react-dom@19.0,react-is@19.0&exports=Container"; +export {Container}; + +export function bind(node, config) { + const root = ReactDOM.createRoot(node); + return { + create: (type, props, children) => + React.createElement(type, props, children), + render: (element) => root.render(element, node), + unmount: () => root.unmount() + }; +} \ No newline at end of file diff --git a/tests/test_reactjs/test_modules.py b/tests/test_reactjs/test_modules.py index 8b3992ef0..7a458defa 100644 --- a/tests/test_reactjs/test_modules.py +++ b/tests/test_reactjs/test_modules.py @@ -1,3 +1,5 @@ +from pathlib import Path + import pytest import reactpy @@ -5,6 +7,8 @@ from reactpy.reactjs import component_from_string, import_reactjs from reactpy.testing import BackendFixture, DisplayFixture +JS_FIXTURES_DIR = Path(__file__).parent / "js_fixtures" + @pytest.fixture(scope="module") async def display(browser): @@ -89,3 +93,24 @@ def App(): elements = await display.page.query_selector_all(".component-c") assert len(elements) == 2 await display.page.wait_for_selector("#deep-server") + + +async def test_nest_custom_component_under_web_component(display: DisplayFixture): + """ + Fix https://github.com/reactive-python/reactpy/discussions/1323 + + Custom components (i.e those wrapped in the component decorator) were not able to + be nested under web components. + """ + Container = reactpy.reactjs.component_from_file( + JS_FIXTURES_DIR / "nest-custom-under-web.js", "Container", name="nest-custom-under-web" + ) + + @reactpy.component + def CustomComponent(): + return reactpy.html.div(reactpy.html.h1({"id": "my-header"}, "Header 1")) + + await display.show(lambda: Container(CustomComponent())) + + element = await display.page.wait_for_selector("#my-header", state="attached") + assert await element.inner_text() == "Header 1" diff --git a/tests/test_web/__init__.py b/tests/test_web/__init__.py index e69de29bb..6a5447f43 100644 --- a/tests/test_web/__init__.py +++ b/tests/test_web/__init__.py @@ -0,0 +1,4 @@ +""" +THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. +THE CONTENTS OF THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" diff --git a/tests/test_web/test_module.py b/tests/test_web/test_module.py index cddcc86dc..34d857b7f 100644 --- a/tests/test_web/test_module.py +++ b/tests/test_web/test_module.py @@ -1,5 +1,7 @@ -# THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. -# THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" +THESE ARE TESTS FOR THE LEGACY API. SEE tests/test_reactjs/* FOR THE NEW API TESTS. +THE CONTENTS OF THIS MODULE WILL BE MIGRATED OR DELETED ONCE THE LEGACY API IS REMOVED. +""" from pathlib import Path import pytest