Skip to content

Commit b037a11

Browse files
support taking in a component object
1 parent 277c095 commit b037a11

File tree

6 files changed

+98
-31
lines changed

6 files changed

+98
-31
lines changed

src/reactpy/executors/asgi/pyscript.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
pyscript_component_html,
1818
pyscript_setup_html,
1919
)
20-
from reactpy.executors.utils import html_noscript_path_to_html, vdom_head_to_html
21-
from reactpy.types import ReactPyConfig, VdomDict
20+
from reactpy.executors.utils import html_noscript_to_html, vdom_head_to_html
21+
from reactpy.types import ReactPyConfig, RootComponentConstructor, VdomDict
2222

2323

2424
class ReactPyCsr(ReactPy):
@@ -32,8 +32,9 @@ def __init__(
3232
initial: str | VdomDict = "",
3333
http_headers: dict[str, str] | None = None,
3434
html_head: VdomDict | None = None,
35-
html_noscript_str_or_path: str
35+
html_noscript: str
3636
| Path
37+
| RootComponentConstructor
3738
| None = "Enable JavaScript to view this site.",
3839
html_lang: str = "en",
3940
**settings: Unpack[ReactPyConfig],
@@ -62,8 +63,9 @@ def __init__(
6263
commonly used to render a loading animation.
6364
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6465
html_head: Additional head elements to include in the HTML response.
65-
html_noscript_str_or_path: String or Path to an HTML file whose contents are rendered within a
66-
`<noscript>` tag in the HTML body. If None, then noscript is not rendered.
66+
html_noscript: String, Path to an HTML file, or component rendered to HTML
67+
inside a `<noscript>` tag in the HTML body.
68+
If None, then noscript is not rendered.
6769
html_lang: The language of the HTML document.
6870
settings:
6971
Global ReactPy configuration settings that affect behavior and performance. Most settings
@@ -83,7 +85,7 @@ def __init__(
8385
self.extra_headers = http_headers or {}
8486
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
8587
self.html_head = html_head or html.head()
86-
self.html_noscript_str_or_path = html_noscript_str_or_path
88+
self.html_noscript = html_noscript
8789
self.html_lang = html_lang
8890

8991
def match_dispatch_path(self, scope: AsgiWebsocketScope) -> bool: # nocov
@@ -103,7 +105,7 @@ class ReactPyPyscriptApp(ReactPyApp):
103105
def render_index_html(self) -> None:
104106
"""Process the index.html and store the results in this class."""
105107
head_content = vdom_head_to_html(self.parent.html_head)
106-
noscript = html_noscript_path_to_html(self.parent.html_noscript_str_or_path)
108+
noscript = html_noscript_to_html(self.parent.html_noscript)
107109
pyscript_setup = pyscript_setup_html(
108110
extra_py=self.parent.extra_py,
109111
extra_js=self.parent.extra_js,

src/reactpy/executors/asgi/standalone.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
)
2727
from reactpy.executors.pyscript.utils import pyscript_setup_html
2828
from reactpy.executors.utils import (
29-
html_noscript_path_to_html,
29+
html_noscript_to_html,
3030
server_side_component_html,
3131
vdom_head_to_html,
3232
)
@@ -50,8 +50,9 @@ def __init__(
5050
*,
5151
http_headers: dict[str, str] | None = None,
5252
html_head: VdomDict | None = None,
53-
html_noscript_str_or_path: str
53+
html_noscript: str
5454
| Path
55+
| RootComponentConstructor
5556
| None = "Enable JavaScript to view this site.",
5657
html_lang: str = "en",
5758
pyscript_setup: bool = False,
@@ -64,8 +65,8 @@ def __init__(
6465
root_component: The root component to render. This app is typically a single page application.
6566
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6667
html_head: Additional head elements to include in the HTML response.
67-
html_noscript_str_or_path: String or Path to an HTML file whose contents are rendered within a
68-
`<noscript>` tag in the HTML body.
68+
html_noscript: String, Path to an HTML file, or component rendered to HTML
69+
inside a `<noscript>` tag in the HTML body.
6970
html_lang: The language of the HTML document.
7071
pyscript_setup: Whether to automatically load PyScript within your HTML head.
7172
pyscript_options: Options to configure PyScript behavior.
@@ -76,7 +77,7 @@ def __init__(
7677
self.extra_headers = http_headers or {}
7778
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
7879
self.html_head = html_head or html.head()
79-
self.html_noscript_str_or_path = html_noscript_str_or_path
80+
self.html_noscript = html_noscript
8081
self.html_lang = html_lang
8182

8283
if pyscript_setup:
@@ -240,7 +241,7 @@ async def __call__(
240241

241242
def render_index_html(self) -> None:
242243
"""Process the index.html and store the results in this class."""
243-
noscript = html_noscript_path_to_html(self.parent.html_noscript_str_or_path)
244+
noscript = html_noscript_to_html(self.parent.html_noscript)
244245
self._index_html = (
245246
"<!doctype html>"
246247
f'<html lang="{self.parent.html_lang}">'

src/reactpy/executors/utils.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
REACTPY_RECONNECT_MAX_INTERVAL,
1414
REACTPY_RECONNECT_MAX_RETRIES,
1515
)
16-
from reactpy.types import ReactPyConfig, VdomDict
16+
from reactpy.types import ReactPyConfig, RootComponentConstructor, VdomDict
1717
from reactpy.utils import import_dotted_path, reactpy_to_string
1818

1919
logger = logging.getLogger(__name__)
@@ -47,12 +47,16 @@ def vdom_head_to_html(head: VdomDict) -> str:
4747
raise ValueError("Head element must be constructed with `html.head`.")
4848

4949

50-
def html_noscript_path_to_html(path_or_body: str | Path | None) -> str:
51-
if path_or_body is None:
50+
def html_noscript_to_html(
51+
html_noscript: str | Path | RootComponentConstructor | None,
52+
) -> str:
53+
if html_noscript is None:
5254
return ""
53-
if isinstance(path_or_body, Path):
54-
return f"<noscript>{path_or_body.read_text()}</noscript>"
55-
return f"<noscript>{path_or_body}</noscript>"
55+
if isinstance(html_noscript, Path):
56+
html_noscript = html_noscript.read_text()
57+
elif callable(html_noscript):
58+
html_noscript = reactpy_to_string(html_noscript())
59+
return f"<noscript>{html_noscript}</noscript>"
5660

5761

5862
def process_settings(settings: ReactPyConfig) -> None:

tests/test_asgi/test_pyscript.py

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ async def test_customized_noscript(tmp_path: Path):
111111

112112
app = ReactPyCsr(
113113
Path(__file__).parent / "pyscript_components" / "root.py",
114-
html_noscript_str_or_path=noscript_file,
114+
html_noscript=noscript_file,
115115
)
116116

117117
async with BackendFixture(app) as server:
@@ -130,7 +130,7 @@ async def test_customized_noscript(tmp_path: Path):
130130
async def test_customized_noscript_string():
131131
app = ReactPyCsr(
132132
Path(__file__).parent / "pyscript_components" / "root.py",
133-
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
133+
html_noscript='<p id="noscript-message">Please enable JavaScript.</p>',
134134
)
135135

136136
async with BackendFixture(app) as server:
@@ -154,7 +154,7 @@ async def test_customized_noscript_from_file(tmp_path: Path):
154154

155155
app = ReactPyCsr(
156156
Path(__file__).parent / "pyscript_components" / "root.py",
157-
html_noscript_str_or_path=noscript_file,
157+
html_noscript=noscript_file,
158158
)
159159

160160
async with BackendFixture(app) as server:
@@ -172,7 +172,29 @@ async def test_customized_noscript_from_file(tmp_path: Path):
172172
async def test_customized_noscript_from_string():
173173
app = ReactPyCsr(
174174
Path(__file__).parent / "pyscript_components" / "root.py",
175-
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
175+
html_noscript='<p id="noscript-message">Please enable JavaScript.</p>',
176+
)
177+
178+
async with BackendFixture(app) as server:
179+
url = f"http://{server.host}:{server.port}"
180+
response = await asyncio.to_thread(
181+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
182+
)
183+
assert response.status_code == 200
184+
assert (
185+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
186+
in response.text
187+
)
188+
189+
190+
async def test_customized_noscript_from_component():
191+
@reactpy.component
192+
def noscript_message():
193+
return html.p({"id": "noscript-message"}, "Please enable JavaScript.")
194+
195+
app = ReactPyCsr(
196+
Path(__file__).parent / "pyscript_components" / "root.py",
197+
html_noscript=noscript_message,
176198
)
177199

178200
async with BackendFixture(app) as server:
@@ -216,7 +238,10 @@ async def test_noscript_omitted():
216238

217239

218240
async def test_noscript_disabled():
219-
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py", html_noscript_str_or_path=None)
241+
app = ReactPyCsr(
242+
Path(__file__).parent / "pyscript_components" / "root.py",
243+
html_noscript=None,
244+
)
220245

221246
async with BackendFixture(app) as server:
222247
url = f"http://{server.host}:{server.port}"

tests/test_asgi/test_standalone.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def sample():
152152

153153
app = ReactPy(
154154
sample,
155-
html_noscript_str_or_path=noscript_file,
155+
html_noscript=noscript_file,
156156
)
157157

158158
async with BackendFixture(app) as server:
@@ -174,7 +174,7 @@ def sample():
174174

175175
app = ReactPy(
176176
sample,
177-
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
177+
html_noscript='<p id="noscript-message">Please enable JavaScript.</p>',
178178
)
179179

180180
async with BackendFixture(app) as server:
@@ -189,6 +189,29 @@ def sample():
189189
)
190190

191191

192+
async def test_customized_noscript_from_component():
193+
@reactpy.component
194+
def sample():
195+
return html.h1("Hello World")
196+
197+
@reactpy.component
198+
def noscript_message():
199+
return html.p({"id": "noscript-message"}, "Please enable JavaScript.")
200+
201+
app = ReactPy(sample, html_noscript=noscript_message)
202+
203+
async with BackendFixture(app) as server:
204+
url = f"http://{server.host}:{server.port}"
205+
response = await asyncio.to_thread(
206+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
207+
)
208+
assert response.status_code == 200
209+
assert (
210+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
211+
in response.text
212+
)
213+
214+
192215
async def test_noscript_omitted():
193216
@reactpy.component
194217
def sample():
@@ -213,7 +236,7 @@ async def test_noscript_disabled():
213236
def sample():
214237
return html.h1("Hello World")
215238

216-
app = ReactPy(sample, html_noscript_str_or_path=None)
239+
app = ReactPy(sample, html_noscript=None)
217240

218241
async with BackendFixture(app) as server:
219242
url = f"http://{server.host}:{server.port}"

tests/test_asgi/test_utils.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import pytest
44

5-
from reactpy import config
5+
import reactpy
6+
from reactpy import config, html
67
from reactpy.executors import utils
78

89

@@ -16,21 +17,32 @@ def test_html_noscript_path_to_html(tmp_path: Path):
1617
noscript_file.write_text("<p>Please enable JavaScript.</p>", encoding="utf-8")
1718

1819
assert (
19-
utils.html_noscript_path_to_html(noscript_file)
20+
utils.html_noscript_to_html(noscript_file)
2021
== "<noscript><p>Please enable JavaScript.</p></noscript>"
2122
)
2223

2324

2425

2526
def test_html_noscript_string_to_html():
2627
assert (
27-
utils.html_noscript_path_to_html("<p>Please enable JavaScript.</p>")
28+
utils.html_noscript_to_html("<p>Please enable JavaScript.</p>")
2829
== "<noscript><p>Please enable JavaScript.</p></noscript>"
2930
)
3031

3132

33+
def test_html_noscript_component_to_html():
34+
@reactpy.component
35+
def message():
36+
return html.p({"id": "noscript-message"}, "Please enable JavaScript.")
37+
38+
assert (
39+
utils.html_noscript_to_html(message)
40+
== '<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
41+
)
42+
43+
3244
def test_html_noscript_none_to_html():
33-
assert utils.html_noscript_path_to_html(None) == ""
45+
assert utils.html_noscript_to_html(None) == ""
3446

3547

3648
def test_process_settings():

0 commit comments

Comments
 (0)