Skip to content

Commit 7792156

Browse files
committed
Add tests
1 parent ef56ac1 commit 7792156

File tree

3 files changed

+136
-38
lines changed

3 files changed

+136
-38
lines changed

src/reactpy/web/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
from reactpy.web.module import (
22
export,
3+
import_js_from_file,
4+
import_js_from_string,
5+
import_js_from_url,
36
module_from_file,
47
module_from_string,
58
module_from_url,
69
)
710

811
__all__ = [
912
"export",
13+
"import_js_from_file",
14+
"import_js_from_string",
15+
"import_js_from_url",
1016
"module_from_file",
1117
"module_from_string",
1218
"module_from_url",

src/reactpy/web/module.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ def module_from_url(
189189
resolve_exports: bool | None = None,
190190
resolve_exports_depth: int = 5,
191191
unmount_before_update: bool = False,
192-
) -> WebModule:
192+
) -> WebModule: # pragma: no cover
193193
warn(
194194
"module_from_url is deprecated, use import_js_from_url instead",
195195
DeprecationWarning,
@@ -211,7 +211,7 @@ def module_from_file(
211211
resolve_exports_depth: int = 5,
212212
unmount_before_update: bool = False,
213213
symlink: bool = False,
214-
) -> WebModule:
214+
) -> WebModule: # pragma: no cover
215215
warn(
216216
"module_from_file is deprecated, use import_js_from_file instead",
217217
DeprecationWarning,
@@ -234,7 +234,7 @@ def module_from_string(
234234
resolve_exports: bool | None = None,
235235
resolve_exports_depth: int = 5,
236236
unmount_before_update: bool = False,
237-
) -> WebModule:
237+
) -> WebModule: # pragma: no cover
238238
warn(
239239
"module_from_string is deprecated, use import_js_from_string instead",
240240
DeprecationWarning,
@@ -466,7 +466,7 @@ def export(
466466
export_names: str | list[str] | tuple[str, ...],
467467
fallback: Any | None = None,
468468
allow_children: bool = True,
469-
) -> VdomConstructor | list[VdomConstructor]:
469+
) -> VdomConstructor | list[VdomConstructor]: # pragma: no cover
470470
warn(
471471
"export is deprecated, use import_js_from_* functions instead",
472472
DeprecationWarning,

tests/test_web/test_module.py

Lines changed: 126 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from servestatic import ServeStaticASGI
55

66
import reactpy
7+
import reactpy.web.module
78
from reactpy.executors.asgi.standalone import ReactPy
89
from reactpy.testing import (
910
BackendFixture,
@@ -19,8 +20,8 @@
1920

2021

2122
async def test_that_js_module_unmount_is_called(display: DisplayFixture):
22-
SomeComponent = reactpy.web.export(
23-
reactpy.web.module_from_file(
23+
SomeComponent = reactpy.web.module._vdom_from_web_module(
24+
reactpy.web.module._module_from_file(
2425
"set-flag-when-unmount-is-called",
2526
JS_FIXTURES_DIR / "set-flag-when-unmount-is-called.js",
2627
),
@@ -52,8 +53,10 @@ def ShowCurrentComponent():
5253

5354

5455
async def test_module_from_url(browser):
55-
SimpleButton = reactpy.web.export(
56-
reactpy.web.module_from_url("/static/simple-button.js", resolve_exports=False),
56+
SimpleButton = reactpy.web.module._vdom_from_web_module(
57+
reactpy.web.module._module_from_url(
58+
"/static/simple-button.js", resolve_exports=False
59+
),
5760
"SimpleButton",
5861
)
5962

@@ -72,8 +75,8 @@ def ShowSimpleButton():
7275

7376

7477
async def test_module_from_file(display: DisplayFixture):
75-
SimpleButton = reactpy.web.export(
76-
reactpy.web.module_from_file(
78+
SimpleButton = reactpy.web.module._vdom_from_web_module(
79+
reactpy.web.module._module_from_file(
7780
"simple-button", JS_FIXTURES_DIR / "simple-button.js"
7881
),
7982
"SimpleButton",
@@ -98,30 +101,30 @@ def test_module_from_file_source_conflict(tmp_path):
98101
first_file = tmp_path / "first.js"
99102

100103
with pytest.raises(FileNotFoundError, match="does not exist"):
101-
reactpy.web.module_from_file("temp", first_file)
104+
reactpy.web.module._module_from_file("temp", first_file)
102105

103106
first_file.touch()
104107

105-
reactpy.web.module_from_file("temp", first_file)
108+
reactpy.web.module._module_from_file("temp", first_file)
106109

107110
second_file = tmp_path / "second.js"
108111
second_file.touch()
109112

110113
# ok, same content
111-
reactpy.web.module_from_file("temp", second_file)
114+
reactpy.web.module._module_from_file("temp", second_file)
112115

113116
third_file = tmp_path / "third.js"
114117
third_file.write_text("something-different")
115118

116119
with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):
117-
reactpy.web.module_from_file("temp", third_file)
120+
reactpy.web.module._module_from_file("temp", third_file)
118121

119122

120123
def test_web_module_from_file_symlink(tmp_path):
121124
file = tmp_path / "temp.js"
122125
file.touch()
123126

124-
module = reactpy.web.module_from_file("temp", file, symlink=True)
127+
module = reactpy.web.module._module_from_file("temp", file, symlink=True)
125128

126129
assert module.file.resolve().read_text() == ""
127130

@@ -134,44 +137,44 @@ def test_web_module_from_file_symlink_twice(tmp_path):
134137
file_1 = tmp_path / "temp_1.js"
135138
file_1.touch()
136139

137-
reactpy.web.module_from_file("temp", file_1, symlink=True)
140+
reactpy.web.module._module_from_file("temp", file_1, symlink=True)
138141

139142
with assert_reactpy_did_not_log(r"Existing web module .* will be replaced with"):
140-
reactpy.web.module_from_file("temp", file_1, symlink=True)
143+
reactpy.web.module._module_from_file("temp", file_1, symlink=True)
141144

142145
file_2 = tmp_path / "temp_2.js"
143146
file_2.write_text("something")
144147

145148
with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):
146-
reactpy.web.module_from_file("temp", file_2, symlink=True)
149+
reactpy.web.module._module_from_file("temp", file_2, symlink=True)
147150

148151

149152
def test_web_module_from_file_replace_existing(tmp_path):
150153
file1 = tmp_path / "temp1.js"
151154
file1.touch()
152155

153-
reactpy.web.module_from_file("temp", file1)
156+
reactpy.web.module._module_from_file("temp", file1)
154157

155158
file2 = tmp_path / "temp2.js"
156159
file2.write_text("something")
157160

158161
with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):
159-
reactpy.web.module_from_file("temp", file2)
162+
reactpy.web.module._module_from_file("temp", file2)
160163

161164

162165
def test_module_missing_exports():
163166
module = WebModule("test", NAME_SOURCE, None, {"a", "b", "c"}, None, False)
164167

165168
with pytest.raises(ValueError, match="does not export 'x'"):
166-
reactpy.web.export(module, "x")
169+
reactpy.web.module._vdom_from_web_module(module, "x")
167170

168171
with pytest.raises(ValueError, match=r"does not export \['x', 'y'\]"):
169-
reactpy.web.export(module, ["x", "y"])
172+
reactpy.web.module._vdom_from_web_module(module, ["x", "y"])
170173

171174

172175
async def test_module_exports_multiple_components(display: DisplayFixture):
173-
Header1, Header2 = reactpy.web.export(
174-
reactpy.web.module_from_file(
176+
Header1, Header2 = reactpy.web.module._vdom_from_web_module(
177+
reactpy.web.module._module_from_file(
175178
"exports-two-components", JS_FIXTURES_DIR / "exports-two-components.js"
176179
),
177180
["Header1", "Header2"],
@@ -187,10 +190,12 @@ async def test_module_exports_multiple_components(display: DisplayFixture):
187190

188191

189192
async def test_imported_components_can_render_children(display: DisplayFixture):
190-
module = reactpy.web.module_from_file(
193+
module = reactpy.web.module._module_from_file(
191194
"component-can-have-child", JS_FIXTURES_DIR / "component-can-have-child.js"
192195
)
193-
Parent, Child = reactpy.web.export(module, ["Parent", "Child"])
196+
Parent, Child = reactpy.web.module._vdom_from_web_module(
197+
module, ["Parent", "Child"]
198+
)
194199

195200
await display.show(
196201
lambda: Parent(
@@ -219,10 +224,10 @@ async def test_keys_properly_propagated(display: DisplayFixture):
219224
This property is required for certain JS components, such as the GridLayout from
220225
react-grid-layout.
221226
"""
222-
module = reactpy.web.module_from_file(
227+
module = reactpy.web.module._module_from_file(
223228
"keys-properly-propagated", JS_FIXTURES_DIR / "keys-properly-propagated.js"
224229
)
225-
GridLayout = reactpy.web.export(module, "GridLayout")
230+
GridLayout = reactpy.web.module._vdom_from_web_module(module, "GridLayout")
226231

227232
await display.show(
228233
lambda: GridLayout(
@@ -273,12 +278,14 @@ async def test_keys_properly_propagated(display: DisplayFixture):
273278

274279

275280
async def test_subcomponent_notation_as_str_attrs(display: DisplayFixture):
276-
module = reactpy.web.module_from_file(
281+
module = reactpy.web.module._module_from_file(
277282
"subcomponent-notation",
278283
JS_FIXTURES_DIR / "subcomponent-notation.js",
279284
)
280-
InputGroup, InputGroupText, FormControl, FormLabel = reactpy.web.export(
281-
module, ["InputGroup", "InputGroup.Text", "Form.Control", "Form.Label"]
285+
InputGroup, InputGroupText, FormControl, FormLabel = (
286+
reactpy.web.module._vdom_from_web_module(
287+
module, ["InputGroup", "InputGroup.Text", "Form.Control", "Form.Label"]
288+
)
282289
)
283290

284291
content = reactpy.html.div(
@@ -333,11 +340,13 @@ async def test_subcomponent_notation_as_str_attrs(display: DisplayFixture):
333340

334341

335342
async def test_subcomponent_notation_as_obj_attrs(display: DisplayFixture):
336-
module = reactpy.web.module_from_file(
343+
module = reactpy.web.module._module_from_file(
337344
"subcomponent-notation",
338345
JS_FIXTURES_DIR / "subcomponent-notation.js",
339346
)
340-
InputGroup, Form = reactpy.web.export(module, ["InputGroup", "Form"])
347+
InputGroup, Form = reactpy.web.module._vdom_from_web_module(
348+
module, ["InputGroup", "Form"]
349+
)
341350

342351
content = reactpy.html.div(
343352
{"id": "the-parent"},
@@ -391,10 +400,10 @@ async def test_subcomponent_notation_as_obj_attrs(display: DisplayFixture):
391400

392401

393402
async def test_callable_prop_with_javacript(display: DisplayFixture):
394-
module = reactpy.web.module_from_file(
403+
module = reactpy.web.module._module_from_file(
395404
"callable-prop", JS_FIXTURES_DIR / "callable-prop.js"
396405
)
397-
Component = reactpy.web.export(module, "Component")
406+
Component = reactpy.web.module._vdom_from_web_module(module, "Component")
398407

399408
@reactpy.component
400409
def App():
@@ -411,7 +420,90 @@ def App():
411420
assert await my_div.inner_text() == "PREFIX TEXT: TEST 123"
412421

413422

414-
def test_module_from_string():
415-
reactpy.web.module_from_string("temp", "old")
423+
def test_import_js_from_string():
424+
reactpy.web.import_js_from_string("temp", "old", "Component", resolve_exports=False)
425+
reactpy.web.module._STRING_WEB_MODULE_CACHE.clear()
416426
with assert_reactpy_did_log(r"Existing web module .* will be replaced with"):
417-
reactpy.web.module_from_string("temp", "new")
427+
reactpy.web.import_js_from_string(
428+
"temp", "new", "Component", resolve_exports=False
429+
)
430+
431+
432+
async def test_import_js_from_url(browser):
433+
SimpleButton = reactpy.web.import_js_from_url(
434+
"/static/simple-button.js", "SimpleButton", resolve_exports=False
435+
)
436+
437+
@reactpy.component
438+
def ShowSimpleButton():
439+
return SimpleButton({"id": "my-button"})
440+
441+
app = ReactPy(ShowSimpleButton)
442+
app = ServeStaticASGI(app, JS_FIXTURES_DIR, "/static/")
443+
444+
async with BackendFixture(app) as server:
445+
async with DisplayFixture(server, browser) as display:
446+
await display.show(ShowSimpleButton)
447+
448+
await display.page.wait_for_selector("#my-button")
449+
450+
451+
async def test_import_js_from_file(display: DisplayFixture):
452+
SimpleButton = reactpy.web.import_js_from_file(
453+
"simple-button", JS_FIXTURES_DIR / "simple-button.js", "SimpleButton"
454+
)
455+
456+
is_clicked = reactpy.Ref(False)
457+
458+
@reactpy.component
459+
def ShowSimpleButton():
460+
return SimpleButton(
461+
{"id": "my-button", "onClick": lambda event: is_clicked.set_current(True)}
462+
)
463+
464+
await display.show(ShowSimpleButton)
465+
466+
button = await display.page.wait_for_selector("#my-button")
467+
await button.click()
468+
await poll(lambda: is_clicked.current).until_is(True)
469+
470+
471+
def test_import_js_from_url_caching():
472+
url = "https://example.com/module.js"
473+
reactpy.web.module._URL_WEB_MODULE_CACHE.clear()
474+
475+
# First import
476+
reactpy.web.import_js_from_url(url, "Component", resolve_exports=False)
477+
assert url in reactpy.web.module._URL_WEB_MODULE_CACHE
478+
module1 = reactpy.web.module._URL_WEB_MODULE_CACHE[url]
479+
480+
# Second import
481+
reactpy.web.import_js_from_url(url, "Component", resolve_exports=False)
482+
assert reactpy.web.module._URL_WEB_MODULE_CACHE[url] is module1
483+
484+
485+
def test_import_js_from_file_caching(tmp_path):
486+
file = tmp_path / "test.js"
487+
file.write_text("export function Component() {}")
488+
name = "test-file-module"
489+
reactpy.web.module._FILE_WEB_MODULE_CACHE.clear()
490+
491+
reactpy.web.import_js_from_file(name, file, "Component")
492+
assert name in reactpy.web.module._FILE_WEB_MODULE_CACHE
493+
module1 = reactpy.web.module._FILE_WEB_MODULE_CACHE[name]
494+
495+
reactpy.web.import_js_from_file(name, file, "Component")
496+
assert reactpy.web.module._FILE_WEB_MODULE_CACHE[name] is module1
497+
498+
499+
def test_import_js_from_string_caching():
500+
name = "test-string-module"
501+
content = "export function Component() {}"
502+
reactpy.web.module._STRING_WEB_MODULE_CACHE.clear()
503+
504+
reactpy.web.import_js_from_string(name, content, "Component")
505+
assert name in reactpy.web.module._STRING_WEB_MODULE_CACHE
506+
module1 = reactpy.web.module._STRING_WEB_MODULE_CACHE[name]
507+
508+
reactpy.web.import_js_from_string(name, content, "Component")
509+
assert reactpy.web.module._STRING_WEB_MODULE_CACHE[name] is module1

0 commit comments

Comments
 (0)