Skip to content

Commit eb9ed59

Browse files
committed
Add tests
1 parent c6bd166 commit eb9ed59

File tree

4 files changed

+246
-82
lines changed

4 files changed

+246
-82
lines changed

src/reactpy_django/hooks.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,11 @@ def use_root_id() -> str:
421421
def use_rerender() -> Callable[[], None]:
422422
"""Provides a callable that can re-render the entire component tree without disconnecting the websocket."""
423423
scope = use_scope()
424-
return scope["reactpy"]["rerender"]
424+
425+
def rerender():
426+
scope["reactpy"]["rerender"]()
427+
428+
return rerender
425429

426430

427431
def use_auth() -> UseAuthTuple:

tests/test_app/components.py

+84
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import inspect
33
from pathlib import Path
4+
from uuid import uuid4
45

56
from channels.auth import login, logout
67
from channels.db import database_sync_to_async
@@ -692,3 +693,86 @@ async def on_submit(event):
692693
html.div(f"Mutation State: (loading={user_data_mutation.loading}, error={user_data_mutation.error})"),
693694
html.div(html.input({"on_key_press": on_submit, "placeholder": "Type here to add data"})),
694695
)
696+
697+
698+
@component
699+
def use_auth():
700+
_login, _logout = reactpy_django.hooks.use_auth()
701+
uuid = hooks.use_ref(str(uuid4())).current
702+
current_user = reactpy_django.hooks.use_user()
703+
connection = reactpy_django.hooks.use_connection()
704+
705+
async def login_user(event):
706+
new_user, _created = await get_user_model().objects.aget_or_create(username="user_4")
707+
await _login(new_user)
708+
709+
async def logout_user(event):
710+
await _logout()
711+
712+
async def disconnect(event):
713+
await connection.carrier.close()
714+
715+
return html.div(
716+
{
717+
"id": "use-auth",
718+
"data-username": ("AnonymousUser" if current_user.is_anonymous else current_user.username),
719+
"data-uuid": uuid,
720+
},
721+
html.div("use_auth"),
722+
html.div(f"UUID: {uuid}"),
723+
html.button({"className": "login", "on_click": login_user}, "Login"),
724+
html.button({"className": "logout", "on_click": logout_user}, "Logout"),
725+
html.button({"className": "disconnect", "on_click": disconnect}, "disconnect"),
726+
html.div(f"User: {current_user}"),
727+
)
728+
729+
730+
@component
731+
def use_auth_no_rerender():
732+
_login, _logout = reactpy_django.hooks.use_auth()
733+
uuid = hooks.use_ref(str(uuid4())).current
734+
current_user = reactpy_django.hooks.use_user()
735+
connection = reactpy_django.hooks.use_connection()
736+
737+
async def login_user(event):
738+
new_user, _created = await get_user_model().objects.aget_or_create(username="user_5")
739+
await _login(new_user, rerender=False)
740+
741+
async def logout_user(event):
742+
await _logout(rerender=False)
743+
744+
async def disconnect(event):
745+
await connection.carrier.close()
746+
747+
return html.div(
748+
{
749+
"id": "use-auth-no-rerender",
750+
"data-username": ("AnonymousUser" if current_user.is_anonymous else current_user.username),
751+
"data-uuid": uuid,
752+
},
753+
html.div("use_auth_no_rerender"),
754+
html.div(f"UUID: {uuid}"),
755+
html.button({"className": "login", "on_click": login_user}, "Login"),
756+
html.button({"className": "logout", "on_click": logout_user}, "Logout"),
757+
html.button({"className": "disconnect", "on_click": disconnect}, "disconnect"),
758+
html.div(f"User: {current_user}"),
759+
)
760+
761+
762+
@component
763+
def use_rerender():
764+
uuid = str(uuid4())
765+
rerender = reactpy_django.hooks.use_rerender()
766+
767+
def on_click(event):
768+
rerender()
769+
770+
return html.div(
771+
{
772+
"id": "use-rerender",
773+
"data-uuid": uuid,
774+
},
775+
html.div("use_rerender"),
776+
html.div(f"UUID: {uuid}"),
777+
html.button({"on_click": on_click}, "Rerender"),
778+
)

tests/test_app/templates/base.html

+87-81
Original file line numberDiff line numberDiff line change
@@ -3,90 +3,96 @@
33
<html lang="en">
44

55
<head>
6-
<meta charset="UTF-8" />
7-
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
8-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
9-
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}" />
10-
<title>ReactPy</title>
11-
<style>
12-
iframe {
13-
width: 100%;
14-
height: 45px;
15-
}
16-
</style>
6+
<meta charset="UTF-8" />
7+
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
9+
<link rel="shortcut icon" type="image/png" href="{% static 'favicon.ico' %}" />
10+
<title>ReactPy</title>
11+
<style>
12+
iframe {
13+
width: 100%;
14+
height: 45px;
15+
}
16+
</style>
1717
</head>
1818

1919
<body>
20-
<h1>ReactPy Test Page</h1>
21-
<hr>
22-
{% component "test_app.components.hello_world" class="hello-world" %}
23-
<hr>
24-
{% component "test_app.components.button" class="button" %}
25-
<hr>
26-
{% component "test_app.components.parameterized_component" class="parametarized-component" x=123 y=456 %}
27-
<hr>
28-
{% component "test_app.components.object_in_templatetag" my_object %}
29-
<hr>
30-
{% component "test_app.components.button_from_js_module" %}
31-
<hr>
32-
{% component "test_app.components.use_connection" %}
33-
<hr>
34-
{% component "test_app.components.use_scope" %}
35-
<hr>
36-
{% component "test_app.components.use_location" %}
37-
<hr>
38-
{% component "test_app.components.use_origin" %}
39-
<hr>
40-
{% component "test_app.components.django_css" %}
41-
<hr>
42-
{% component "test_app.components.django_js" %}
43-
<hr>
44-
{% component "test_app.components.unauthorized_user" %}
45-
<hr>
46-
{% component "test_app.components.authorized_user" %}
47-
<hr>
48-
{% component "test_app.components.relational_query" %}
49-
<hr>
50-
{% component "test_app.components.async_relational_query" %}
51-
<hr>
52-
{% component "test_app.components.todo_list" %}
53-
<hr>
54-
{% component "test_app.components.async_todo_list" %}
55-
<hr>
56-
{% component "test_app.components.view_to_component_sync_func" %}
57-
<hr>
58-
{% component "test_app.components.view_to_component_async_func" %}
59-
<hr>
60-
{% component "test_app.components.view_to_component_sync_class" %}
61-
<hr>
62-
{% component "test_app.components.view_to_component_async_class" %}
63-
<hr>
64-
{% component "test_app.components.view_to_component_template_view_class" %}
65-
<hr>
66-
{% component "test_app.components.view_to_component_script" %}
67-
<hr>
68-
{% component "test_app.components.view_to_component_request" %}
69-
<hr>
70-
{% component "test_app.components.view_to_component_args" %}
71-
<hr>
72-
{% component "test_app.components.view_to_component_kwargs" %}
73-
<hr>
74-
{% component "test_app.components.view_to_iframe_sync_func" %}
75-
<hr>
76-
{% component "test_app.components.view_to_iframe_async_func" %}
77-
<hr>
78-
{% component "test_app.components.view_to_iframe_sync_class" %}
79-
<hr>
80-
{% component "test_app.components.view_to_iframe_async_class" %}
81-
<hr>
82-
{% component "test_app.components.view_to_iframe_template_view_class" %}
83-
<hr>
84-
{% component "test_app.components.view_to_iframe_args" %}
85-
<hr>
86-
{% component "test_app.components.use_user_data" %}
87-
<hr>
88-
{% component "test_app.components.use_user_data_with_default" %}
89-
<hr>
20+
<h1>ReactPy Test Page</h1>
21+
<hr>
22+
{% component "test_app.components.hello_world" class="hello-world" %}
23+
<hr>
24+
{% component "test_app.components.button" class="button" %}
25+
<hr>
26+
{% component "test_app.components.parameterized_component" class="parametarized-component" x=123 y=456 %}
27+
<hr>
28+
{% component "test_app.components.object_in_templatetag" my_object %}
29+
<hr>
30+
{% component "test_app.components.button_from_js_module" %}
31+
<hr>
32+
{% component "test_app.components.use_connection" %}
33+
<hr>
34+
{% component "test_app.components.use_scope" %}
35+
<hr>
36+
{% component "test_app.components.use_location" %}
37+
<hr>
38+
{% component "test_app.components.use_origin" %}
39+
<hr>
40+
{% component "test_app.components.django_css" %}
41+
<hr>
42+
{% component "test_app.components.django_js" %}
43+
<hr>
44+
{% component "test_app.components.unauthorized_user" %}
45+
<hr>
46+
{% component "test_app.components.authorized_user" %}
47+
<hr>
48+
{% component "test_app.components.relational_query" %}
49+
<hr>
50+
{% component "test_app.components.async_relational_query" %}
51+
<hr>
52+
{% component "test_app.components.todo_list" %}
53+
<hr>
54+
{% component "test_app.components.async_todo_list" %}
55+
<hr>
56+
{% component "test_app.components.view_to_component_sync_func" %}
57+
<hr>
58+
{% component "test_app.components.view_to_component_async_func" %}
59+
<hr>
60+
{% component "test_app.components.view_to_component_sync_class" %}
61+
<hr>
62+
{% component "test_app.components.view_to_component_async_class" %}
63+
<hr>
64+
{% component "test_app.components.view_to_component_template_view_class" %}
65+
<hr>
66+
{% component "test_app.components.view_to_component_script" %}
67+
<hr>
68+
{% component "test_app.components.view_to_component_request" %}
69+
<hr>
70+
{% component "test_app.components.view_to_component_args" %}
71+
<hr>
72+
{% component "test_app.components.view_to_component_kwargs" %}
73+
<hr>
74+
{% component "test_app.components.view_to_iframe_sync_func" %}
75+
<hr>
76+
{% component "test_app.components.view_to_iframe_async_func" %}
77+
<hr>
78+
{% component "test_app.components.view_to_iframe_sync_class" %}
79+
<hr>
80+
{% component "test_app.components.view_to_iframe_async_class" %}
81+
<hr>
82+
{% component "test_app.components.view_to_iframe_template_view_class" %}
83+
<hr>
84+
{% component "test_app.components.view_to_iframe_args" %}
85+
<hr>
86+
{% component "test_app.components.use_user_data" %}
87+
<hr>
88+
{% component "test_app.components.use_user_data_with_default" %}
89+
<hr>
90+
{% component "test_app.components.use_auth" %}
91+
<hr>
92+
{% component "test_app.components.use_auth_no_rerender" %}
93+
<hr>
94+
{% component "test_app.components.use_rerender" %}
95+
<hr>
9096
</body>
9197

9298
</html>

tests/test_app/tests/test_components.py

+70
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,76 @@ def test_component_use_user_data_with_default(self):
320320
)
321321
assert "Data: {'default1': 'value', 'default2': 'value2', 'default3': 'value3'}" in user_data_div.text_content()
322322

323+
@navigate_to_page("/")
324+
def test_component_use_auth(self):
325+
uuid = self.page.wait_for_selector("#use-auth").get_attribute("data-uuid")
326+
assert len(uuid) == 36
327+
328+
login_btn = self.page.wait_for_selector("#use-auth .login")
329+
login_btn.click()
330+
331+
# Wait for #use-auth[data-username="user_4"] to appear
332+
self.page.wait_for_selector("#use-auth[data-username='user_4']")
333+
self.page.wait_for_selector(f"#use-auth[data-uuid='{uuid}']")
334+
335+
# Press disconnect and wait for #use-auth[data-uuid=...] to disappear
336+
self.page.wait_for_selector("#use-auth .disconnect").click()
337+
expect(self.page.locator(f"#use-auth[data-uuid='{uuid}']")).to_have_count(0)
338+
339+
# Double check that the same user is logged in
340+
self.page.wait_for_selector("#use-auth[data-username='user_4']")
341+
342+
# Press logout and wait for #use-auth[data-username="AnonymousUser"] to appear
343+
self.page.wait_for_selector("#use-auth .logout").click()
344+
self.page.wait_for_selector("#use-auth[data-username='AnonymousUser']")
345+
346+
# Press disconnect and wait for #use-auth[data-uuid=...] to disappear
347+
self.page.wait_for_selector("#use-auth .disconnect").click()
348+
expect(self.page.locator(f"#use-auth[data-uuid='{uuid}']")).to_have_count(0)
349+
350+
# Double check that the user stayed logged out
351+
self.page.wait_for_selector("#use-auth[data-username='AnonymousUser']")
352+
353+
@navigate_to_page("/")
354+
def test_component_use_auth_no_rerender(self):
355+
uuid = self.page.wait_for_selector("#use-auth-no-rerender").get_attribute("data-uuid")
356+
assert len(uuid) == 36
357+
358+
login_btn = self.page.wait_for_selector("#use-auth-no-rerender .login")
359+
login_btn.click()
360+
361+
# Make sure #use-auth[data-username="user_5"] does not appear
362+
with pytest.raises(TimeoutError):
363+
self.page.wait_for_selector("#use-auth-no-rerender[data-username='user_5']", timeout=1)
364+
365+
# Press disconnect and see if #use-auth[data-username="user_5"] appears
366+
self.page.wait_for_selector("#use-auth-no-rerender .disconnect").click()
367+
self.page.wait_for_selector("#use-auth-no-rerender[data-username='user_5']")
368+
369+
# Press logout and make sure #use-auth[data-username="AnonymousUser"] does not appear
370+
with pytest.raises(TimeoutError):
371+
self.page.wait_for_selector("#use-auth-no-rerender[data-username='AnonymousUser']", timeout=1)
372+
373+
# Press disconnect and see if #use-auth[data-username="AnonymousUser"] appears
374+
self.page.wait_for_selector("#use-auth-no-rerender .disconnect").click()
375+
376+
@navigate_to_page("/")
377+
def test_component_use_rerender(self):
378+
initial_uuid = self.page.wait_for_selector("#use-rerender").get_attribute("data-uuid")
379+
assert len(initial_uuid) == 36
380+
381+
rerender_button = self.page.wait_for_selector("#use-rerender button")
382+
rerender_button.click()
383+
384+
# Wait for #use-rerender[data-uuid=...] to disappear
385+
expect(self.page.locator(f"#use-rerender[data-uuid='{initial_uuid}']")).to_have_count(0)
386+
387+
# Find the new #use-rerender[data-uuid=...]
388+
self.page.wait_for_selector("#use-rerender")
389+
new_uuid = self.page.wait_for_selector("#use-rerender").get_attribute("data-uuid")
390+
assert len(new_uuid) == 36
391+
assert new_uuid != initial_uuid
392+
323393
###################
324394
# Prerender Tests #
325395
###################

0 commit comments

Comments
 (0)