diff --git a/src/apps/dashboard/tests/integrated/test_views.py b/src/apps/dashboard/tests/integrated/test_views.py index 94af23b..87c1529 100644 --- a/src/apps/dashboard/tests/integrated/test_views.py +++ b/src/apps/dashboard/tests/integrated/test_views.py @@ -241,7 +241,7 @@ class ViewAuthListTest(TestCase): def test_anonymous_user_is_redirected(self): response = self.client.get(reverse("view_list", args=[self.our_list.id])) - self.assertRedirects(response, "/") + self.assertRedirects(response, "/", fetch_redirect_response=False) def test_non_owner_non_shared_user_gets_403(self): stranger = User.objects.create(email="stranger@example.com") @@ -308,3 +308,36 @@ class SetThemeTest(TestCase): parsed = lxml.html.fromstring(response.content) swatches = parsed.cssselect(".swatch") self.assertEqual(len(swatches), len(response.context["themes"])) + +class ProfileViewTest(TestCase): + def setUp(self): + self.user = User.objects.create(email="discoman@example.com") + self.client.force_login(self.user) + + def test_post_username_saves_to_user(self): + self.client.post("/dashboard/set_profile", data={"username": "discoman"}) + self.user.refresh_from_db() + self.assertEqual(self.user.username, "discoman") + + def test_post_username_requires_login(self): + self.client.logout() + response = self.client.post("/dashboard/set_profile", data={"username": "somnambulist"}) + self.assertRedirects(response, "/?next=/dashboard/set_profile") + + def test_dash_renders_username_applet(self): + response = self.client.get("/") + parsed = lxml.html.fromstring(response.content) + [applet] = parsed.cssselect("#id_applet_username") + self.assertIn("di…an@e…e.com", applet.text_content()) + [_] = parsed.cssselect("#id_new_username") + + def test_dash_shows_display_name_in_applet(self): + self.user.username = "discoman" + self.user.save() + response = self.client.get("/") + parsed = lxml.html.fromstring(response.content) + [applet] = parsed.cssselect("#id_applet_username") + self.assertIn("discoman", applet.text_content()) + [username_input] = parsed.cssselect("#id_new_username") + self.assertEqual("discoman", username_input.get("value")) + diff --git a/src/apps/dashboard/urls.py b/src/apps/dashboard/urls.py index 62d7af8..24a65dd 100644 --- a/src/apps/dashboard/urls.py +++ b/src/apps/dashboard/urls.py @@ -4,7 +4,8 @@ from . import views urlpatterns = [ path('new_list', views.new_list, name='new_list'), path('list//', views.view_list, name='view_list'), - path('users//', views.my_lists, name='my_lists'), path('list//share_list', views.share_list, name="share_list"), path('set_theme', views.set_theme, name='set_theme'), + path('set_profile', views.set_profile, name='set_profile'), + path('users//', views.my_lists, name='my_lists'), ] diff --git a/src/apps/dashboard/views.py b/src/apps/dashboard/views.py index be240eb..d8c3b58 100644 --- a/src/apps/dashboard/views.py +++ b/src/apps/dashboard/views.py @@ -1,4 +1,5 @@ from django.contrib import messages +from django.contrib.auth.decorators import login_required from django.http import HttpResponseForbidden from django.shortcuts import redirect, render @@ -51,7 +52,7 @@ def view_list(request, list_id): form.save() return redirect(our_list) return render(request, "apps/dashboard/list.html", {"list": our_list, "form": form}) - + def my_lists(request, user_id): owner = User.objects.get(id=user_id) if not request.user.is_authenticated: @@ -72,12 +73,19 @@ def share_list(request, list_id): messages.success(request, "An invite has been sent if that address is registered.") return redirect(our_list) +@login_required(login_url="/") def set_theme(request): - if not request.user.is_authenticated: - return redirect("home") if request.method == "POST": theme = request.POST.get("theme", "") if theme in UNLOCKED_THEMES: request.user.theme = theme request.user.save(update_fields=["theme"]) return redirect("home") + +@login_required(login_url="/") +def set_profile(request): + if request.method == "POST": + username = request.POST.get("username", "") + request.user.username = username + request.user.save(update_fields=["username"]) + return redirect("/") diff --git a/src/functional_tests/base.py b/src/functional_tests/base.py index d0f91b1..2658a2c 100644 --- a/src/functional_tests/base.py +++ b/src/functional_tests/base.py @@ -65,7 +65,7 @@ class FunctionalTest(StaticLiveServerTestCase): def dump_html(self): path = SCREEN_DUMP_LOCATION / self._get_filename("html") print("dumping page html to", path) - path.write_text(self.browser.page_source) + path.write_text(self.browser.page_source, encoding="utf-8") def _get_filename(self, extension): timestamp = datetime.now().isoformat().replace(":", ".") diff --git a/src/functional_tests/test_dashboard.py b/src/functional_tests/test_dashboard.py new file mode 100644 index 0000000..0c5ffbb --- /dev/null +++ b/src/functional_tests/test_dashboard.py @@ -0,0 +1,39 @@ +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +from .base import FunctionalTest + + +class DashboardMaintenanceTest(FunctionalTest): + def test_user_without_username_can_claim_unclaimed_username(self): + # 1. Create a pre-authenticated session for discoman@example.com + self.create_pre_authenticated_session("discoman@example.com") + # 2. Navigate to self.live_server_url + "/" + self.browser.get(self.live_server_url) + # 3. Find the username applet on the page; look for a
or
with id="id_username_applet" + self.browser.find_element(By.ID, "id_applet_username") + # 4. Assert it shows the current display name (truncated email: di…an@e…e.com) + self.assertIn("di…an@e…e.com", self.browser.find_element(By.ID, "id_applet_username").text) + # 5. Find the username input field inside the applet & type a username + username_input = self.browser.find_element(By.CSS_SELECTOR, "#id_new_username") + # 6. Type a username, e.g., discoman + username_input.send_keys("discoman") + self.wait_for( + lambda: self.browser.find_element(By.CSS_SELECTOR, "#id_new_username:valid") + ) + # 7. Submit the form (click a btn or press Enter) + username_input.send_keys(Keys.ENTER) + # 8. Without a page reload, wait for the navbar to update; user wait_for() to check that the navbar text now contains "discoman" + self.wait_for( + lambda: self.assertIn( + "discoman", + self.browser.find_element(By.CLASS_NAME, "navbar-text").text + ) + ) + # 9. Also assert the applet input now shows "discoman" as its value + self.wait_for( + lambda: self.assertEqual( + "discoman", + self.browser.find_element(By.CSS_SELECTOR, "#id_new_username").get_attribute("value") + ) + ) diff --git a/src/templates/apps/dashboard/home.html b/src/templates/apps/dashboard/home.html index 70a1323..1852bd4 100644 --- a/src/templates/apps/dashboard/home.html +++ b/src/templates/apps/dashboard/home.html @@ -1,4 +1,5 @@ {% extends "core/base.html" %} +{% load lyric_extras %} {% block title_text %}Start a new to-do list{% endblock title_text %} {% block header_text %}Start a new to-do list{% endblock header_text %} @@ -29,5 +30,14 @@
{% endfor %}
+
+

{{ user|display_name }}

+
+
+ {% csrf_token %} + +
+
+
{% endif %} {% endblock content %}