457 lines
19 KiB
Python
457 lines
19 KiB
Python
import lxml.html
|
|
|
|
from django.contrib.messages import get_messages
|
|
from django.test import override_settings, TestCase
|
|
from django.urls import reverse
|
|
from django.utils import html
|
|
|
|
from apps.dashboard.forms import (
|
|
DUPLICATE_ITEM_ERROR,
|
|
EMPTY_ITEM_ERROR,
|
|
)
|
|
from apps.dashboard.models import Applet, Item, List, UserApplet
|
|
from apps.lyric.models import User
|
|
|
|
|
|
class HomePageTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(self.user)
|
|
Applet.objects.get_or_create(slug="new-list", defaults={"name": "New List"})
|
|
|
|
def test_uses_home_template(self):
|
|
response = self.client.get('/')
|
|
self.assertTemplateUsed(response, 'apps/dashboard/home.html')
|
|
|
|
def test_renders_input_form(self):
|
|
response = self.client.get('/')
|
|
parsed = lxml.html.fromstring(response.content)
|
|
forms = parsed.cssselect('form[method=POST]')
|
|
self.assertIn("/dashboard/new_list", [form.get("action") for form in forms])
|
|
[form] = [form for form in forms if form.get("action") == "/dashboard/new_list"]
|
|
inputs = form.cssselect("input")
|
|
self.assertIn("text", [input.get("name") for input in inputs])
|
|
|
|
class NewListTest(TestCase):
|
|
def setUp(self):
|
|
user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(user)
|
|
|
|
def test_can_save_a_POST_request(self):
|
|
self.client.post("/dashboard/new_list", data={"text": "A new list item"})
|
|
self.assertEqual(Item.objects.count(), 1)
|
|
new_item = Item.objects.get()
|
|
self.assertEqual(new_item.text, "A new list item")
|
|
|
|
def test_redirects_after_POST(self):
|
|
response = self.client.post("/dashboard/new_list", data={"text": "A new list item"})
|
|
new_list = List.objects.get()
|
|
self.assertRedirects(response, f"/dashboard/list/{new_list.id}/")
|
|
|
|
# Post invalid input helper
|
|
def post_invalid_input(self):
|
|
return self.client.post("/dashboard/new_list", data={"text": ""})
|
|
|
|
def test_for_invalid_input_nothing_saved_to_db(self):
|
|
self.post_invalid_input()
|
|
self.assertEqual(Item.objects.count(), 0)
|
|
|
|
def test_for_invalid_input_renders_list_template(self):
|
|
response = self.post_invalid_input()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "apps/dashboard/home.html")
|
|
|
|
def test_for_invalid_input_shows_error_on_page(self):
|
|
response = self.post_invalid_input()
|
|
self.assertContains(response, html.escape(EMPTY_ITEM_ERROR))
|
|
|
|
class ListViewTest(TestCase):
|
|
def test_uses_list_template(self):
|
|
mylist = List.objects.create()
|
|
response = self.client.get(f"/dashboard/list/{mylist.id}/")
|
|
self.assertTemplateUsed(response, "apps/dashboard/list.html")
|
|
|
|
def test_renders_input_form(self):
|
|
mylist = List.objects.create()
|
|
url = f"/dashboard/list/{mylist.id}/"
|
|
response = self.client.get(url)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
forms = parsed.cssselect("form[method=POST]")
|
|
self.assertIn(url, [form.get("action") for form in forms])
|
|
[form] = [form for form in forms if form.get("action") == url]
|
|
inputs = form.cssselect("input")
|
|
self.assertIn("text", [input.get("name") for input in inputs])
|
|
|
|
def test_displays_only_items_for_that_list(self):
|
|
# Given/Arrange
|
|
correct_list = List.objects.create()
|
|
Item.objects.create(text="itemey 1", list=correct_list)
|
|
Item.objects.create(text="itemey 2", list=correct_list)
|
|
other_list = List.objects.create()
|
|
Item.objects.create(text="other list item", list=other_list)
|
|
# When/Act
|
|
response = self.client.get(f"/dashboard/list/{correct_list.id}/")
|
|
# Then/Assert
|
|
self.assertContains(response, "itemey 1")
|
|
self.assertContains(response, "itemey 2")
|
|
self.assertNotContains(response, "other list item")
|
|
|
|
def test_can_save_a_POST_request_to_an_existing_list(self):
|
|
other_list = List.objects.create()
|
|
correct_list = List.objects.create()
|
|
|
|
self.client.post(
|
|
f"/dashboard/list/{correct_list.id}/",
|
|
data={"text": "A new item for an existing list"},
|
|
)
|
|
|
|
self.assertEqual(Item.objects.count(), 1)
|
|
new_item = Item.objects.get()
|
|
self.assertEqual(new_item.text, "A new item for an existing list")
|
|
self.assertEqual(new_item.list, correct_list)
|
|
|
|
def test_POST_redirects_to_list_view(self):
|
|
other_list = List.objects.create()
|
|
correct_list = List.objects.create()
|
|
|
|
response = self.client.post(
|
|
f"/dashboard/list/{correct_list.id}/",
|
|
data={"text": "A new item for an existing list"},
|
|
)
|
|
|
|
self.assertRedirects(response, f"/dashboard/list/{correct_list.id}/")
|
|
|
|
# Post invalid input helper
|
|
def post_invalid_input(self):
|
|
mylist = List.objects.create()
|
|
return self.client.post(f"/dashboard/list/{mylist.id}/", data={"text": ""})
|
|
|
|
def test_for_invalid_input_nothing_saved_to_db(self):
|
|
self.post_invalid_input()
|
|
self.assertEqual(Item.objects.count(), 0)
|
|
|
|
def test_for_invalid_input_renders_list_template(self):
|
|
response = self.post_invalid_input()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertTemplateUsed(response, "apps/dashboard/list.html")
|
|
|
|
def test_for_invalid_input_shows_error_on_page(self):
|
|
response = self.post_invalid_input()
|
|
self.assertContains(response, html.escape(EMPTY_ITEM_ERROR))
|
|
|
|
def test_for_invalid_input_sets_is_invalid_class(self):
|
|
response = self.post_invalid_input()
|
|
parsed = lxml.html.fromstring(response.content)
|
|
[input] = parsed.cssselect("input[name=text]")
|
|
self.assertIn("is-invalid", set(input.classes))
|
|
|
|
def test_duplicate_item_validation_errors_end_up_on_lists_page(self):
|
|
list1 = List.objects.create()
|
|
Item.objects.create(list=list1, text="lorem ipsum")
|
|
|
|
response = self.client.post(
|
|
f"/dashboard/list/{list1.id}/",
|
|
data={"text": "lorem ipsum"},
|
|
)
|
|
|
|
expected_error = html.escape(DUPLICATE_ITEM_ERROR)
|
|
self.assertContains(response, expected_error)
|
|
self.assertTemplateUsed(response, "apps/dashboard/list.html")
|
|
self.assertEqual(Item.objects.all().count(), 1)
|
|
|
|
class MyListsTest(TestCase):
|
|
def test_my_lists_url_renders_my_lists_template(self):
|
|
user = User.objects.create(email="a@b.cde")
|
|
self.client.force_login(user)
|
|
response = self.client.get(f"/dashboard/users/{user.id}/")
|
|
self.assertTemplateUsed(response, "apps/dashboard/my_lists.html")
|
|
|
|
def test_passes_correct_owner_to_template(self):
|
|
User.objects.create(email="wrongowner@example.com")
|
|
correct_user = User.objects.create(email="a@b.cde")
|
|
self.client.force_login(correct_user)
|
|
response = self.client.get(f"/dashboard/users/{correct_user.id}/")
|
|
self.assertEqual(response.context["owner"], correct_user)
|
|
|
|
def test_list_owner_is_saved_if_user_is_authenticated(self):
|
|
user = User.objects.create(email="a@b.cde")
|
|
self.client.force_login(user)
|
|
self.client.post("/dashboard/new_list", data={"text": "new item"})
|
|
new_list = List.objects.get()
|
|
self.assertEqual(new_list.owner, user)
|
|
|
|
def test_my_lists_redirects_if_not_logged_in(self):
|
|
user = User.objects.create(email="a@b.cde")
|
|
response = self.client.get(f"/dashboard/users/{user.id}/")
|
|
self.assertRedirects(response, "/")
|
|
|
|
def test_my_lists_returns_403_for_wrong_user(self):
|
|
# create two users, login as user_a, request user_b's my_lists url
|
|
user1 = User.objects.create(email="a@b.cde")
|
|
user2 = User.objects.create(email="wrongowner@example.com")
|
|
self.client.force_login(user2)
|
|
response = self.client.get(f"/dashboard/users/{user1.id}/")
|
|
# assert 403
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
class ShareListTest(TestCase):
|
|
def test_post_to_share_list_url_redirects_to_list(self):
|
|
our_list = List.objects.create()
|
|
alice = User.objects.create(email="alice@example.com")
|
|
response = self.client.post(
|
|
f"/dashboard/list/{our_list.id}/share_list",
|
|
data={"recipient": "alice@example.com"},
|
|
)
|
|
self.assertRedirects(response, f"/dashboard/list/{our_list.id}/")
|
|
|
|
def test_post_with_email_adds_user_to_shared_with(self):
|
|
our_list = List.objects.create()
|
|
alice = User.objects.create(email="alice@example.com")
|
|
self.client.post(
|
|
f"/dashboard/list/{our_list.id}/share_list",
|
|
data={"recipient": "alice@example.com"},
|
|
)
|
|
self.assertIn(alice, our_list.shared_with.all())
|
|
|
|
def test_post_with_nonexistent_email_redirects_to_list(self):
|
|
our_list = List.objects.create()
|
|
response = self.client.post(
|
|
f"/dashboard/list/{our_list.id}/share_list",
|
|
data={"recipient": "nobody@example.com"},
|
|
)
|
|
self.assertRedirects(
|
|
response,
|
|
f"/dashboard/list/{our_list.id}/",
|
|
fetch_redirect_response=False,
|
|
)
|
|
|
|
def test_share_list_does_not_add_owner_as_recipient(self):
|
|
owner = User.objects.create(email="owner@example.com")
|
|
our_list = List.objects.create(owner=owner)
|
|
self.client.force_login(owner)
|
|
self.client.post(reverse("share_list", args=[our_list.id]),
|
|
data={"recipient": "owner@example.com"})
|
|
self.assertNotIn(owner, our_list.shared_with.all())
|
|
|
|
@override_settings(MESSAGE_STORAGE='django.contrib.messages.storage.session.SessionStorage')
|
|
def test_share_list_shows_privacy_safe_message(self):
|
|
our_list = List.objects.create()
|
|
response = self.client.post(
|
|
f"/dashboard/list/{our_list.id}/share_list",
|
|
data={"recipient": "nobody@example.com"},
|
|
follow=True,
|
|
)
|
|
messages = list(get_messages(response.wsgi_request))
|
|
self.assertEqual(
|
|
str(messages[0]),
|
|
"An invite has been sent if that address is registered.",
|
|
)
|
|
|
|
class ViewAuthListTest(TestCase):
|
|
def setUp(self):
|
|
self.owner = User.objects.create(email="disco@example.com")
|
|
self.our_list = List.objects.create(owner=self.owner)
|
|
|
|
def test_anonymous_user_is_redirected(self):
|
|
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
|
|
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")
|
|
self.client.force_login(stranger)
|
|
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
|
|
self.assertEqual(response.status_code, 403)
|
|
|
|
def test_shared_with_user_can_access_list(self):
|
|
guest = User.objects.create(email="guest@example.com")
|
|
self.our_list.shared_with.add(guest)
|
|
self.client.force_login(guest)
|
|
response = self.client.get(reverse("view_list", args=[self.our_list.id]))
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
@override_settings(COMPRESS_ENABLED=False)
|
|
class SetPaletteTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="a@b.cde")
|
|
self.client.force_login(self.user)
|
|
self.url = reverse("home")
|
|
Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
|
|
|
|
def test_anonymous_user_is_redirected_home(self):
|
|
response = self.client.post("/dashboard/set_palette")
|
|
self.assertRedirects(response, "/", fetch_redirect_response=False)
|
|
|
|
def test_set_palette_updates_user_palette(self):
|
|
User.objects.filter(pk=self.user.pk).update(palette="palette-sheol")
|
|
self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
|
self.user.refresh_from_db()
|
|
self.assertEqual(self.user.palette, "palette-default")
|
|
|
|
def test_locked_palette_is_rejected(self):
|
|
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-nirvana"})
|
|
self.user.refresh_from_db()
|
|
self.assertEqual(self.user.palette, "palette-default")
|
|
self.assertRedirects(response, "/", fetch_redirect_response=False)
|
|
|
|
def test_set_palette_redirects_home(self):
|
|
response = self.client.post("/dashboard/set_palette", data={"palette": "palette-default"})
|
|
self.assertRedirects(response, "/", fetch_redirect_response=False)
|
|
|
|
def test_my_lists_contains_set_palette_form(self):
|
|
response = self.client.get(self.url)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
forms = parsed.cssselect('form[action="/dashboard/set_palette"]')
|
|
self.assertEqual(len(forms), 1)
|
|
|
|
def test_active_palette_swatch_has_active_class(self):
|
|
response = self.client.get(self.url)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
[active] = parsed.cssselect(".swatch.active")
|
|
self.assertIn("palette-default", active.classes)
|
|
|
|
def test_locked_palettes_are_not_forms(self):
|
|
response = self.client.get(self.url)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
locked = parsed.cssselect(".swatch.locked")
|
|
expected_locked = [p for p in response.context["palettes"] if p["locked"]]
|
|
self.assertEqual(len(locked), len(expected_locked))
|
|
# they mustn't be button els
|
|
for swatch in locked:
|
|
self.assertNotEqual(swatch.tag, "button")
|
|
|
|
def test_palette_picker_count_matches_context(self):
|
|
response = self.client.get(self.url)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
swatches = parsed.cssselect(".swatch")
|
|
self.assertEqual(len(swatches), len(response.context["palettes"]))
|
|
|
|
@override_settings(COMPRESS_ENABLED=False)
|
|
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", fetch_redirect_response=False)
|
|
|
|
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("@", applet.text_content())
|
|
[input_el] = parsed.cssselect("#id_new_username")
|
|
self.assertEqual("", input_el.get("value"))
|
|
|
|
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)
|
|
[username_input] = parsed.cssselect("#id_new_username")
|
|
self.assertEqual("discoman", username_input.get("value"))
|
|
|
|
class ToggleAppletsViewTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(self.user)
|
|
self.username_applet, _ = Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
|
|
self.palette_applet, _ = Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
|
|
self.url = reverse("toggle_applets")
|
|
|
|
def test_unauthenticated_user_is_redirected(self):
|
|
self.client.logout()
|
|
response = self.client.post(self.url)
|
|
self.assertRedirects(
|
|
response, f"/?next={self.url}", fetch_redirect_response=False
|
|
)
|
|
|
|
def test_unchecked_applet_gets_user_applet_with_visible_false(self):
|
|
self.client.post(self.url, {"applets": ["username"]})
|
|
ua = UserApplet.objects.get(user=self.user, applet=self.palette_applet)
|
|
self.assertFalse(ua.visible)
|
|
|
|
def test_redirects_on_normal_post(self):
|
|
response = self.client.post(
|
|
self.url, {"applets": ["username", "palette"]}
|
|
)
|
|
self.assertRedirects(response, reverse("home"), fetch_redirect_response=False)
|
|
|
|
def test_returns_200_on_htmx_post(self):
|
|
response = self.client.post(
|
|
self.url,
|
|
{"applets": ["username", "palette"]},
|
|
HTTP_HX_REQUEST="true",
|
|
)
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_htmx_post_renders_visible_applets_only(self):
|
|
response = self.client.post(
|
|
self.url,
|
|
{"applets": ["username"]},
|
|
HTTP_HX_REQUEST="true",
|
|
)
|
|
parsed = lxml.html.fromstring(response.content)
|
|
self.assertEqual(len(parsed.cssselect("#id_applet_username")), 1)
|
|
self.assertEqual(len(parsed.cssselect("#id_applet_palette")), 0)
|
|
|
|
class AppletVisibilityContextTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(self.user)
|
|
self.username_applet, _ = Applet.objects.get_or_create(slug="username", defaults={"name": "Username"})
|
|
self.palette_applet, _ = Applet.objects.get_or_create(slug="palette", defaults={"name": "Palette"})
|
|
UserApplet.objects.create(user=self.user, applet=self.palette_applet, visible=False)
|
|
|
|
def test_dash_reflects_user_applet_visibility(self):
|
|
response = self.client.get("/")
|
|
applet_map = {entry["applet"].slug: entry["visible"] for entry in response.context["applets"]}
|
|
self.assertFalse(applet_map["palette"])
|
|
self.assertTrue(applet_map["username"])
|
|
|
|
class FooterNavTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(self.user)
|
|
|
|
def test_footer_nav_present_on_dashboard(self):
|
|
response = self.client.get("/")
|
|
parsed = lxml.html.fromstring(response.content)
|
|
[nav] = parsed.cssselect("#id_footer_nav")
|
|
self.assertIsNotNone(nav)
|
|
|
|
def test_footer_nav_has_dashboard_link(self):
|
|
response = self.client.get("/")
|
|
parsed = lxml.html.fromstring(response.content)
|
|
[nav] = parsed.cssselect("#id_footer_nav")
|
|
links = [a.get("href") for a in nav.cssselect("a")]
|
|
self.assertIn("/", links)
|
|
|
|
def test_footer_nav_has_gameboard_link(self):
|
|
response = self.client.get("/")
|
|
parsed = lxml.html.fromstring(response.content)
|
|
[nav] = parsed.cssselect("#id_footer_nav")
|
|
links = [a.get("href") for a in nav.cssselect("a")]
|
|
self.assertIn("/gameboard/", links)
|
|
|
|
class WalletAppletTest(TestCase):
|
|
def setUp(self):
|
|
self.user = User.objects.create(email="disco@test.io")
|
|
self.client.force_login(self.user)
|
|
Applet.objects.get_or_create(slug="wallet", defaults={"name": "Wallet"})
|
|
response = self.client.get("/")
|
|
self.parsed = lxml.html.fromstring(response.content)
|
|
|
|
def test_wallet_applet_present_on_dash(self):
|
|
[_] = self.parsed.cssselect("#id_applet_wallet")
|
|
|
|
def test_wallet_applet_has_manage_link(self):
|
|
[link] = self.parsed.cssselect("#id_applet_wallet a.wallet-manage-link")
|
|
self.assertEqual(link.get("href"), "/dashboard/wallet/")
|