Skip to content

Commit d09f96d

Browse files
clean up and add tests
1 parent fdf1770 commit d09f96d

File tree

6 files changed

+190
-17
lines changed

6 files changed

+190
-17
lines changed

src/reactpy/executors/asgi/pyscript.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(
3232
initial: str | VdomDict = "",
3333
http_headers: dict[str, str] | None = None,
3434
html_head: VdomDict | None = None,
35-
html_noscript_path: str | Path | None = None,
35+
html_noscript_str_or_path: str | Path | None = "Enable JavaScript to view this site.",
3636
html_lang: str = "en",
3737
**settings: Unpack[ReactPyConfig],
3838
) -> None:
@@ -60,8 +60,8 @@ def __init__(
6060
commonly used to render a loading animation.
6161
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6262
html_head: Additional head elements to include in the HTML response.
63-
html_noscript_path: Path to an HTML file whose contents are rendered within a
64-
`<noscript>` tag in the HTML body.
63+
html_noscript_str_or_path: String or Path to an HTML file whose contents are rendered within a
64+
`<noscript>` tag in the HTML body. If None, then noscript is not rendered.
6565
html_lang: The language of the HTML document.
6666
settings:
6767
Global ReactPy configuration settings that affect behavior and performance. Most settings
@@ -81,7 +81,7 @@ def __init__(
8181
self.extra_headers = http_headers or {}
8282
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
8383
self.html_head = html_head or html.head()
84-
self.html_noscript_path = html_noscript_path
84+
self.html_noscript_str_or_path = html_noscript_str_or_path
8585
self.html_lang = html_lang
8686

8787
def match_dispatch_path(self, scope: AsgiWebsocketScope) -> bool: # nocov
@@ -101,7 +101,7 @@ class ReactPyPyscriptApp(ReactPyApp):
101101
def render_index_html(self) -> None:
102102
"""Process the index.html and store the results in this class."""
103103
head_content = vdom_head_to_html(self.parent.html_head)
104-
noscript = html_noscript_path_to_html(self.parent.html_noscript_path or "")
104+
noscript = html_noscript_path_to_html(self.parent.html_noscript_str_or_path)
105105
pyscript_setup = pyscript_setup_html(
106106
extra_py=self.parent.extra_py,
107107
extra_js=self.parent.extra_js,

src/reactpy/executors/asgi/standalone.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def __init__(
5050
*,
5151
http_headers: dict[str, str] | None = None,
5252
html_head: VdomDict | None = None,
53-
html_noscript_path: str | Path | None = None,
53+
html_noscript_str_or_path: str | Path | None = "Enable JavaScript to view this site.",
5454
html_lang: str = "en",
5555
pyscript_setup: bool = False,
5656
pyscript_options: PyScriptOptions | None = None,
@@ -62,7 +62,7 @@ def __init__(
6262
root_component: The root component to render. This app is typically a single page application.
6363
http_headers: Additional headers to include in the HTTP response for the base HTML document.
6464
html_head: Additional head elements to include in the HTML response.
65-
html_noscript_path: Path to an HTML file whose contents are rendered within a
65+
html_noscript_str_or_path: String or Path to an HTML file whose contents are rendered within a
6666
`<noscript>` tag in the HTML body.
6767
html_lang: The language of the HTML document.
6868
pyscript_setup: Whether to automatically load PyScript within your HTML head.
@@ -74,7 +74,7 @@ def __init__(
7474
self.extra_headers = http_headers or {}
7575
self.dispatcher_pattern = re.compile(f"^{self.dispatcher_path}?")
7676
self.html_head = html_head or html.head()
77-
self.html_noscript_path = html_noscript_path
77+
self.html_noscript_str_or_path = html_noscript_str_or_path
7878
self.html_lang = html_lang
7979

8080
if pyscript_setup:
@@ -238,7 +238,7 @@ async def __call__(
238238

239239
def render_index_html(self) -> None:
240240
"""Process the index.html and store the results in this class."""
241-
noscript = html_noscript_path_to_html(self.parent.html_noscript_path)
241+
noscript = html_noscript_path_to_html(self.parent.html_noscript_str_or_path)
242242
self._index_html = (
243243
"<!doctype html>"
244244
f'<html lang="{self.parent.html_lang}">'

src/reactpy/executors/utils.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,12 @@ 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: str | Path | None = "", default_text="Please enable JavaScript to view this site.") -> str:
51-
if path:
52-
return f"<noscript>{Path(path).read_text(encoding='utf-8')}</noscript>"
53-
return f"<noscript>{default_text}</noscript>"
50+
def html_noscript_path_to_html(path_or_body: str | Path | None) -> str:
51+
if path_or_body is None:
52+
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>"
5456

5557

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

tests/test_asgi/test_pyscript.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33
from pathlib import Path
44

55
import pytest
6-
from requests import request
76
from jinja2 import Environment as JinjaEnvironment
87
from jinja2 import FileSystemLoader as JinjaFileSystemLoader
8+
from requests import request
99
from starlette.applications import Starlette
1010
from starlette.routing import Route
1111
from starlette.templating import Jinja2Templates
1212

13+
import reactpy
1314
from reactpy import html
1415
from reactpy.executors.asgi.pyscript import ReactPyCsr
1516
from reactpy.testing import BackendFixture, DisplayFixture
@@ -110,7 +111,68 @@ async def test_customized_noscript(tmp_path: Path):
110111

111112
app = ReactPyCsr(
112113
Path(__file__).parent / "pyscript_components" / "root.py",
113-
html_noscript_path=noscript_file,
114+
html_noscript_str_or_path=noscript_file,
115+
)
116+
117+
async with BackendFixture(app) as server:
118+
url = f"http://{server.host}:{server.port}"
119+
response = await asyncio.to_thread(
120+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
121+
)
122+
assert response.status_code == 200
123+
assert (
124+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
125+
in response.text
126+
)
127+
128+
129+
130+
async def test_customized_noscript_string():
131+
app = ReactPyCsr(
132+
Path(__file__).parent / "pyscript_components" / "root.py",
133+
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
134+
)
135+
136+
async with BackendFixture(app) as server:
137+
url = f"http://{server.host}:{server.port}"
138+
response = await asyncio.to_thread(
139+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
140+
)
141+
assert response.status_code == 200
142+
assert (
143+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
144+
in response.text
145+
)
146+
147+
148+
async def test_customized_noscript_from_file(tmp_path: Path):
149+
noscript_file = tmp_path / "noscript.html"
150+
noscript_file.write_text(
151+
'<p id="noscript-message">Please enable JavaScript.</p>',
152+
encoding="utf-8",
153+
)
154+
155+
app = ReactPyCsr(
156+
Path(__file__).parent / "pyscript_components" / "root.py",
157+
html_noscript_str_or_path=noscript_file,
158+
)
159+
160+
async with BackendFixture(app) as server:
161+
url = f"http://{server.host}:{server.port}"
162+
response = await asyncio.to_thread(
163+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
164+
)
165+
assert response.status_code == 200
166+
assert (
167+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
168+
in response.text
169+
)
170+
171+
172+
async def test_customized_noscript_from_string():
173+
app = ReactPyCsr(
174+
Path(__file__).parent / "pyscript_components" / "root.py",
175+
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
114176
)
115177

116178
async with BackendFixture(app) as server:
@@ -125,6 +187,46 @@ async def test_customized_noscript(tmp_path: Path):
125187
)
126188

127189

190+
async def test_default_noscript_rendered():
191+
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py")
192+
193+
async with BackendFixture(app) as server:
194+
url = f"http://{server.host}:{server.port}"
195+
response = await asyncio.to_thread(
196+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
197+
)
198+
assert response.status_code == 200
199+
assert "<noscript>Enable JavaScript to view this site.</noscript>" in response.text
200+
201+
202+
203+
async def test_noscript_omitted():
204+
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py")
205+
206+
async with BackendFixture(app) as server:
207+
url = f"http://{server.host}:{server.port}"
208+
response = await asyncio.to_thread(
209+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
210+
)
211+
assert response.status_code == 200
212+
assert (
213+
'<noscript><p id="noscript-message">Enable JavaScript to view this site.</p></noscript>'
214+
in response.text
215+
)
216+
217+
218+
async def test_noscript_disabled():
219+
app = ReactPyCsr(Path(__file__).parent / "pyscript_components" / "root.py", html_noscript_str_or_path=None)
220+
221+
async with BackendFixture(app) as server:
222+
url = f"http://{server.host}:{server.port}"
223+
response = await asyncio.to_thread(
224+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
225+
)
226+
assert response.status_code == 200
227+
assert "<noscript>" not in response.text
228+
229+
128230
async def test_jinja_template_tag(jinja_display: DisplayFixture):
129231
await jinja_display.goto("/")
130232

tests/test_asgi/test_standalone.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def sample():
139139
assert (await new_display.page.title()) == custom_title
140140

141141

142-
async def test_customized_noscript(tmp_path: Path):
142+
async def test_customized_noscript_from_file(tmp_path: Path):
143143
@reactpy.component
144144
def sample():
145145
return html.h1("Hello World")
@@ -152,7 +152,7 @@ def sample():
152152

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

158158
async with BackendFixture(app) as server:
@@ -167,6 +167,63 @@ def sample():
167167
)
168168

169169

170+
async def test_customized_noscript_from_string():
171+
@reactpy.component
172+
def sample():
173+
return html.h1("Hello World")
174+
175+
app = ReactPy(
176+
sample,
177+
html_noscript_str_or_path='<p id="noscript-message">Please enable JavaScript.</p>',
178+
)
179+
180+
async with BackendFixture(app) as server:
181+
url = f"http://{server.host}:{server.port}"
182+
response = await asyncio.to_thread(
183+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
184+
)
185+
assert response.status_code == 200
186+
assert (
187+
'<noscript><p id="noscript-message">Please enable JavaScript.</p></noscript>'
188+
in response.text
189+
)
190+
191+
192+
async def test_noscript_omitted():
193+
@reactpy.component
194+
def sample():
195+
return html.h1("Hello World")
196+
197+
app = ReactPy(sample)
198+
199+
async with BackendFixture(app) as server:
200+
url = f"http://{server.host}:{server.port}"
201+
response = await asyncio.to_thread(
202+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
203+
)
204+
assert response.status_code == 200
205+
assert (
206+
'<noscript><p id="noscript-message">Enable JavaScript to view this site.</p></noscript>'
207+
in response.text
208+
)
209+
210+
211+
async def test_noscript_disabled():
212+
@reactpy.component
213+
def sample():
214+
return html.h1("Hello World")
215+
216+
app = ReactPy(sample, html_noscript_str_or_path=None)
217+
218+
async with BackendFixture(app) as server:
219+
url = f"http://{server.host}:{server.port}"
220+
response = await asyncio.to_thread(
221+
request, "GET", url, timeout=REACTPY_TESTS_DEFAULT_TIMEOUT.current
222+
)
223+
assert response.status_code == 200
224+
assert "<noscript>" not in response.text
225+
226+
170227
async def test_head_request():
171228
@reactpy.component
172229
def sample():

tests/test_asgi/test_utils.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ def test_html_noscript_path_to_html(tmp_path: Path):
2121
)
2222

2323

24+
25+
def test_html_noscript_string_to_html():
26+
assert (
27+
utils.html_noscript_path_to_html("<p>Please enable JavaScript.</p>")
28+
== "<noscript><p>Please enable JavaScript.</p></noscript>"
29+
)
30+
31+
32+
def test_html_noscript_none_to_html():
33+
assert utils.html_noscript_path_to_html(None) == ""
34+
35+
2436
def test_process_settings():
2537
utils.process_settings({"async_rendering": False})
2638
assert config.REACTPY_ASYNC_RENDERING.current is False

0 commit comments

Comments
 (0)