linkding/bookmarks/tests/test_bookmarks_list_template.py
Sascha Ißbrücker c929e8f11c
Speed up navigation (#824)
* use client-side navigation

* update tests

* add setting for enabling link prefetching

* do not prefetch bookmark details

* theme progress bar

* cleanup behaviors

* update test
2024-09-14 11:32:19 +02:00

963 lines
34 KiB
Python

import datetime
from typing import Type
from dateutil.relativedelta import relativedelta
from django.contrib.auth.models import AnonymousUser
from django.http import HttpResponse
from django.template import Template, RequestContext
from django.test import TestCase, RequestFactory
from django.urls import reverse
from django.utils import timezone, formats
from bookmarks.middlewares import LinkdingMiddleware
from bookmarks.models import Bookmark, UserProfile, User
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.views.partials import contexts
class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
def assertBookmarksLink(
self, html: str, bookmark: Bookmark, link_target: str = "_blank"
):
favicon_img = (
f'<img class="favicon" src="/static/{bookmark.favicon_file}" alt="">'
if bookmark.favicon_file
else ""
)
self.assertInHTML(
f"""
{favicon_img}
<a href="{bookmark.url}"
target="{link_target}"
rel="noopener">
<span>{bookmark.resolved_title}</span>
</a>
""",
html,
)
def assertWebArchiveLink(
self, html: str, label_content: str, url: str, link_target: str = "_blank"
):
self.assertInHTML(
f"""
<a href="{url}"
title="Show snapshot on the Internet Archive Wayback Machine" target="{link_target}" rel="noopener">
{label_content}
</a>
<span>|</span>
""",
html,
)
def assertViewLink(
self, html: str, bookmark: Bookmark, return_url=reverse("bookmarks:index")
):
self.assertViewLinkCount(html, bookmark, return_url=return_url)
def assertNoViewLink(
self, html: str, bookmark: Bookmark, return_url=reverse("bookmarks:index")
):
self.assertViewLinkCount(html, bookmark, count=0, return_url=return_url)
def assertViewLinkCount(
self,
html: str,
bookmark: Bookmark,
count=1,
return_url=reverse("bookmarks:index"),
):
details_url = reverse("bookmarks:details", args=[bookmark.id])
details_modal_url = reverse("bookmarks:details_modal", args=[bookmark.id])
self.assertInHTML(
f"""
<a ld-fetch="{details_modal_url}?return_url={return_url}"
ld-on="click" ld-target="body|append"
data-turbo-prefetch="false"
href="{details_url}">View</a>
""",
html,
count=count,
)
def assertEditLinkCount(self, html: str, bookmark: Bookmark, count=1):
edit_url = reverse("bookmarks:edit", args=[bookmark.id])
self.assertInHTML(
f"""
<a href="{edit_url}?return_url=/bookmarks">Edit</a>
""",
html,
count=count,
)
def assertArchiveLinkCount(self, html: str, bookmark: Bookmark, count=1):
self.assertInHTML(
f"""
<button type="submit" name="archive" value="{bookmark.id}"
class="btn btn-link btn-sm">Archive</button>
""",
html,
count=count,
)
def assertDeleteLinkCount(self, html: str, bookmark: Bookmark, count=1):
self.assertInHTML(
f"""
<button ld-confirm-button type="submit" name="remove" value="{bookmark.id}"
class="btn btn-link btn-sm">Remove</button>
""",
html,
count=count,
)
def assertBookmarkActions(self, html: str, bookmark: Bookmark):
self.assertBookmarkActionsCount(html, bookmark, count=1)
def assertNoBookmarkActions(self, html: str, bookmark: Bookmark):
self.assertBookmarkActionsCount(html, bookmark, count=0)
def assertBookmarkActionsCount(self, html: str, bookmark: Bookmark, count=1):
self.assertEditLinkCount(html, bookmark, count=count)
self.assertArchiveLinkCount(html, bookmark, count=count)
self.assertDeleteLinkCount(html, bookmark, count=count)
def assertShareInfo(self, html: str, bookmark: Bookmark):
self.assertShareInfoCount(html, bookmark, 1)
def assertNoShareInfo(self, html: str, bookmark: Bookmark):
self.assertShareInfoCount(html, bookmark, 0)
def assertShareInfoCount(self, html: str, bookmark: Bookmark, count=1):
# Shared by link
self.assertInHTML(
f"""
<span>Shared by
<a href="?user={bookmark.owner.username}">{bookmark.owner.username}</a>
</span>
""",
html,
count=count,
)
def assertFaviconVisible(self, html: str, bookmark: Bookmark):
self.assertFavicon(html, bookmark, True)
def assertFaviconHidden(self, html: str, bookmark: Bookmark):
self.assertFavicon(html, bookmark, False)
def assertFavicon(self, html: str, bookmark: Bookmark, visible=True):
soup = self.make_soup(html)
favicon = soup.select_one(".favicon")
if not visible:
self.assertIsNone(favicon)
return
url = f"/static/{bookmark.favicon_file}"
self.assertIsNotNone(favicon)
self.assertEqual(favicon["src"], url)
def assertPreviewImageVisible(self, html: str, bookmark: Bookmark):
self.assertPreviewImage(html, bookmark, True)
def assertPreviewImageHidden(self, html: str, bookmark: Bookmark):
self.assertPreviewImage(html, bookmark, False)
def assertPreviewImage(self, html: str, bookmark: Bookmark, visible=True):
soup = self.make_soup(html)
preview_image = soup.select_one(".preview-image")
if not visible:
self.assertIsNone(preview_image)
return
url = f"/static/{bookmark.preview_image_file}"
self.assertIsNotNone(preview_image)
self.assertEqual(preview_image["src"], url)
def assertBookmarkURLCount(
self, html: str, bookmark: Bookmark, link_target: str = "_blank", count=0
):
self.assertInHTML(
f"""
<div class="url-path truncate">
<a href="{bookmark.url}" target="{link_target}" rel="noopener"
class="url-display">
{bookmark.url}
</a>
</div>
""",
html,
count,
)
def assertBookmarkURLVisible(self, html: str, bookmark: Bookmark):
self.assertBookmarkURLCount(html, bookmark, count=1)
def assertBookmarkURLHidden(
self, html: str, bookmark: Bookmark, link_target: str = "_blank"
):
self.assertBookmarkURLCount(html, bookmark, count=0)
def assertNotes(self, html: str, notes_html: str, count=1):
self.assertInHTML(
f"""
<div class="notes">
<div class="markdown">
{notes_html}
</div>
</div>
""",
html,
count=count,
)
def assertNotesToggle(self, html: str, count=1):
self.assertInHTML(
f"""
<button type="button" class="btn btn-link btn-sm btn-icon toggle-notes">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<use xlink:href="#ld-icon-note"></use>
</svg>
Notes
</button>
""",
html,
count=count,
)
def assertUnshareButton(self, html: str, bookmark: Bookmark, count=1):
self.assertInHTML(
f"""
<button type="submit" name="unshare" value="{bookmark.id}"
class="btn btn-link btn-sm btn-icon"
ld-confirm-button ld-confirm-icon="ld-icon-unshare" ld-confirm-question="Unshare?">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<use xlink:href="#ld-icon-share"></use>
</svg>
Shared
</button>
""",
html,
count=count,
)
def assertMarkAsReadButton(self, html: str, bookmark: Bookmark, count=1):
self.assertInHTML(
f"""
<button type="submit" name="mark_as_read" value="{bookmark.id}"
class="btn btn-link btn-sm btn-icon"
ld-confirm-button ld-confirm-icon="ld-icon-read" ld-confirm-question="Mark as read?">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
<use xlink:href="#ld-icon-unread"></use>
</svg>
Unread
</button>
""",
html,
count=count,
)
def render_template(
self,
url="/bookmarks",
context_type: Type[
contexts.BookmarkListContext
] = contexts.ActiveBookmarkListContext,
user: User | AnonymousUser = None,
) -> str:
rf = RequestFactory()
request = rf.get(url)
request.user = user or self.get_or_create_test_user()
middleware = LinkdingMiddleware(lambda r: HttpResponse())
middleware(request)
bookmark_list_context = context_type(request)
context = RequestContext(request, {"bookmark_list": bookmark_list_context})
template = Template("{% include 'bookmarks/bookmark_list.html' %}")
return template.render(context)
def setup_date_format_test(
self, date_display_setting: str, web_archive_url: str = ""
):
bookmark = self.setup_bookmark()
bookmark.date_added = timezone.now() - relativedelta(days=8)
bookmark.web_archive_snapshot_url = web_archive_url
bookmark.save()
user = self.get_or_create_test_user()
user.profile.bookmark_date_display = date_display_setting
user.profile.save()
return bookmark
def inline_bookmark_description_test(self, bookmark):
html = self.render_template()
soup = self.make_soup(html)
has_description = bool(bookmark.description)
has_tags = len(bookmark.tags.all()) > 0
# inline description block exists
description = soup.select_one(".description.inline.truncate")
self.assertIsNotNone(description)
# separate description block does not exist
separate_description = soup.select_one(".description.separate")
self.assertIsNone(separate_description)
# one direct child element per description or tags
children = description.find_all(recursive=False)
expected_child_count = (
0 + (1 if has_description else 0) + (1 if has_tags else 0)
)
self.assertEqual(len(children), expected_child_count)
# has separator between description and tags
if has_description and has_tags:
self.assertTrue("|" in description.text)
# contains description text, without leading/trailing whitespace
if has_description:
description_text = description.find("span", text=bookmark.description)
self.assertIsNotNone(description_text)
if not has_tags:
# no tags element
tags = soup.select_one(".tags")
self.assertIsNone(tags)
else:
# tags element exists
tags = soup.select_one(".tags")
self.assertIsNotNone(tags)
# one link for each tag
tag_links = tags.find_all("a")
self.assertEqual(len(tag_links), len(bookmark.tags.all()))
for tag in bookmark.tags.all():
tag_link = tags.find("a", text=f"#{tag.name}")
self.assertIsNotNone(tag_link)
self.assertEqual(tag_link["href"], f"?q=%23{tag.name}")
def test_inline_bookmark_description(self):
profile = self.get_or_create_test_user().profile
profile.bookmark_description_display = (
UserProfile.BOOKMARK_DESCRIPTION_DISPLAY_INLINE
)
profile.save()
# no description, no tags
bookmark = self.setup_bookmark(description="")
self.inline_bookmark_description_test(bookmark)
# with description, no tags
bookmark = self.setup_bookmark(description="Test description")
self.inline_bookmark_description_test(bookmark)
# no description, with tags
Bookmark.objects.all().delete()
bookmark = self.setup_bookmark(
description="", tags=[self.setup_tag(), self.setup_tag(), self.setup_tag()]
)
self.inline_bookmark_description_test(bookmark)
# with description, with tags
Bookmark.objects.all().delete()
bookmark = self.setup_bookmark(
description="Test description",
tags=[self.setup_tag(), self.setup_tag(), self.setup_tag()],
)
self.inline_bookmark_description_test(bookmark)
def separate_bookmark_description_test(self, bookmark):
html = self.render_template()
soup = self.make_soup(html)
has_description = bool(bookmark.description)
has_tags = len(bookmark.tags.all()) > 0
# inline description block does not exist
inline_description = soup.select_one(".description.inline")
self.assertIsNone(inline_description)
if not has_description:
# no description element
description = soup.select_one(".description")
self.assertIsNone(description)
else:
# contains description text, without leading/trailing whitespace
description = soup.select_one(".description.separate")
self.assertIsNotNone(description)
self.assertEqual(description.text, bookmark.description)
if not has_tags:
# no tags element
tags = soup.select_one(".tags")
self.assertIsNone(tags)
else:
# tags element exists
tags = soup.select_one(".tags")
self.assertIsNotNone(tags)
# one link for each tag
tag_links = tags.find_all("a")
self.assertEqual(len(tag_links), len(bookmark.tags.all()))
for tag in bookmark.tags.all():
tag_link = tags.find("a", text=f"#{tag.name}")
self.assertIsNotNone(tag_link)
self.assertEqual(tag_link["href"], f"?q=%23{tag.name}")
def test_separate_bookmark_description(self):
profile = self.get_or_create_test_user().profile
profile.bookmark_description_display = (
UserProfile.BOOKMARK_DESCRIPTION_DISPLAY_SEPARATE
)
profile.save()
# no description, no tags
bookmark = self.setup_bookmark(description="")
self.separate_bookmark_description_test(bookmark)
# with description, no tags
bookmark = self.setup_bookmark(description="Test description")
self.separate_bookmark_description_test(bookmark)
# no description, with tags
Bookmark.objects.all().delete()
bookmark = self.setup_bookmark(
description="", tags=[self.setup_tag(), self.setup_tag(), self.setup_tag()]
)
self.separate_bookmark_description_test(bookmark)
# with description, with tags
Bookmark.objects.all().delete()
bookmark = self.setup_bookmark(
description="Test description",
tags=[self.setup_tag(), self.setup_tag(), self.setup_tag()],
)
self.separate_bookmark_description_test(bookmark)
def test_bookmark_description_max_lines(self):
self.setup_bookmark()
html = self.render_template()
soup = self.make_soup(html)
bookmark_list = soup.select_one("ul.bookmark-list")
style = bookmark_list["style"]
self.assertIn("--ld-bookmark-description-max-lines:1;", style)
profile = self.get_or_create_test_user().profile
profile.bookmark_description_max_lines = 3
profile.save()
html = self.render_template()
soup = self.make_soup(html)
bookmark_list = soup.select_one("ul.bookmark-list")
style = bookmark_list["style"]
self.assertIn("--ld-bookmark-description-max-lines:3;", style)
def test_bookmark_tag_ordering(self):
bookmark = self.setup_bookmark()
tag3 = self.setup_tag(name="tag3")
tag1 = self.setup_tag(name="tag1")
tag2 = self.setup_tag(name="tag2")
bookmark.tags.add(tag3, tag1, tag2)
html = self.render_template()
soup = self.make_soup(html)
tags = soup.select_one(".tags")
tag_links = tags.find_all("a")
self.assertEqual(len(tag_links), 3)
self.assertEqual(tag_links[0].text, "#tag1")
self.assertEqual(tag_links[1].text, "#tag2")
self.assertEqual(tag_links[2].text, "#tag3")
def test_should_render_web_archive_link_with_absolute_date_setting(self):
bookmark = self.setup_date_format_test(
UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE,
"https://web.archive.org/web/20210811214511/https://wanikani.com/",
)
html = self.render_template()
formatted_date = formats.date_format(bookmark.date_added, "SHORT_DATE_FORMAT")
self.assertWebArchiveLink(
html, formatted_date, bookmark.web_archive_snapshot_url
)
def test_should_render_web_archive_link_with_relative_date_setting(self):
bookmark = self.setup_date_format_test(
UserProfile.BOOKMARK_DATE_DISPLAY_RELATIVE,
"https://web.archive.org/web/20210811214511/https://wanikani.com/",
)
html = self.render_template()
self.assertWebArchiveLink(html, "1 week ago", bookmark.web_archive_snapshot_url)
def test_should_render_generated_web_archive_link_without_saved_snapshot_url(self):
user = self.get_or_create_test_user()
user.profile.bookmark_date_display = UserProfile.BOOKMARK_DATE_DISPLAY_ABSOLUTE
user.profile.save()
date_added = timezone.datetime(
2023, 8, 11, 21, 45, 11, tzinfo=datetime.timezone.utc
)
bookmark = self.setup_bookmark(
url="https://example.com/article", added=date_added
)
html = self.render_template()
formatted_date = formats.date_format(bookmark.date_added, "SHORT_DATE_FORMAT")
self.assertWebArchiveLink(
html,
formatted_date,
"https://web.archive.org/web/20230811214511/https://example.com/article",
)
def test_bookmark_link_target_should_be_blank_by_default(self):
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertBookmarksLink(html, bookmark, link_target="_blank")
def test_bookmark_link_target_should_respect_user_profile(self):
profile = self.get_or_create_test_user().profile
profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF
profile.save()
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertBookmarksLink(html, bookmark, link_target="_self")
def test_web_archive_link_target_should_be_blank_by_default(self):
bookmark = self.setup_bookmark()
bookmark.date_added = timezone.now() - relativedelta(days=8)
bookmark.web_archive_snapshot_url = "https://example.com"
bookmark.save()
html = self.render_template()
self.assertWebArchiveLink(
html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_blank"
)
def test_web_archive_link_target_should_respect_user_profile(self):
profile = self.get_or_create_test_user().profile
profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF
profile.save()
bookmark = self.setup_bookmark()
bookmark.date_added = timezone.now() - relativedelta(days=8)
bookmark.web_archive_snapshot_url = "https://example.com"
bookmark.save()
html = self.render_template()
self.assertWebArchiveLink(
html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_self"
)
def test_should_reflect_unread_state_as_css_class(self):
self.setup_bookmark(unread=True)
html = self.render_template()
self.assertIn('<li ld-bookmark-item class="unread">', html)
def test_should_reflect_shared_state_as_css_class(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = True
profile.save()
self.setup_bookmark(shared=True)
html = self.render_template()
self.assertIn('<li ld-bookmark-item class="shared">', html)
def test_should_reflect_both_unread_and_shared_state_as_css_class(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = True
profile.save()
self.setup_bookmark(unread=True, shared=True)
html = self.render_template()
self.assertIn('<li ld-bookmark-item class="unread shared">', html)
def test_show_bookmark_actions_for_owned_bookmarks(self):
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertViewLink(html, bookmark)
self.assertBookmarkActions(html, bookmark)
self.assertNoShareInfo(html, bookmark)
def test_hide_view_link(self):
bookmark = self.setup_bookmark()
profile = self.get_or_create_test_user().profile
profile.display_view_bookmark_action = False
profile.save()
html = self.render_template()
self.assertViewLinkCount(html, bookmark, count=0)
self.assertEditLinkCount(html, bookmark, count=1)
self.assertArchiveLinkCount(html, bookmark, count=1)
self.assertDeleteLinkCount(html, bookmark, count=1)
def test_hide_edit_link(self):
bookmark = self.setup_bookmark()
profile = self.get_or_create_test_user().profile
profile.display_edit_bookmark_action = False
profile.save()
html = self.render_template()
self.assertViewLinkCount(html, bookmark, count=1)
self.assertEditLinkCount(html, bookmark, count=0)
self.assertArchiveLinkCount(html, bookmark, count=1)
self.assertDeleteLinkCount(html, bookmark, count=1)
def test_hide_archive_link(self):
bookmark = self.setup_bookmark()
profile = self.get_or_create_test_user().profile
profile.display_archive_bookmark_action = False
profile.save()
html = self.render_template()
self.assertViewLinkCount(html, bookmark, count=1)
self.assertEditLinkCount(html, bookmark, count=1)
self.assertArchiveLinkCount(html, bookmark, count=0)
self.assertDeleteLinkCount(html, bookmark, count=1)
def test_hide_remove_link(self):
bookmark = self.setup_bookmark()
profile = self.get_or_create_test_user().profile
profile.display_remove_bookmark_action = False
profile.save()
html = self.render_template()
self.assertViewLinkCount(html, bookmark, count=1)
self.assertEditLinkCount(html, bookmark, count=1)
self.assertArchiveLinkCount(html, bookmark, count=1)
self.assertDeleteLinkCount(html, bookmark, count=0)
def test_show_share_info_for_non_owned_bookmarks(self):
other_user = User.objects.create_user(
"otheruser", "otheruser@example.com", "password123"
)
other_user.profile.enable_sharing = True
other_user.profile.save()
bookmark = self.setup_bookmark(user=other_user, shared=True)
html = self.render_template(context_type=contexts.SharedBookmarkListContext)
self.assertViewLink(html, bookmark, return_url=reverse("bookmarks:shared"))
self.assertNoBookmarkActions(html, bookmark)
self.assertShareInfo(html, bookmark)
def test_share_info_user_link_keeps_query_params(self):
other_user = User.objects.create_user(
"otheruser", "otheruser@example.com", "password123"
)
other_user.profile.enable_sharing = True
other_user.profile.save()
bookmark = self.setup_bookmark(user=other_user, shared=True, title="foo")
html = self.render_template(
url="/bookmarks?q=foo", context_type=contexts.SharedBookmarkListContext
)
self.assertInHTML(
f"""
<span>Shared by
<a href="?q=foo&user={bookmark.owner.username}">{bookmark.owner.username}</a>
</span>
""",
html,
)
def test_preview_image_should_be_visible_when_preview_images_enabled(self):
profile = self.get_or_create_test_user().profile
profile.enable_preview_images = True
profile.save()
bookmark = self.setup_bookmark(preview_image_file="preview.png")
html = self.render_template()
self.assertPreviewImageVisible(html, bookmark)
def test_preview_image_should_be_hidden_when_preview_images_disabled(self):
profile = self.get_or_create_test_user().profile
profile.enable_preview_images = False
profile.save()
bookmark = self.setup_bookmark(preview_image_file="preview.png")
html = self.render_template()
self.assertPreviewImageHidden(html, bookmark)
def test_preview_image_should_be_hidden_when_there_is_no_preview_image(self):
profile = self.get_or_create_test_user().profile
profile.enable_preview_images = True
profile.save()
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertPreviewImageHidden(html, bookmark)
def test_favicon_should_be_visible_when_favicons_enabled(self):
profile = self.get_or_create_test_user().profile
profile.enable_favicons = True
profile.save()
bookmark = self.setup_bookmark(favicon_file="https_example_com.png")
html = self.render_template()
self.assertFaviconVisible(html, bookmark)
def test_favicon_should_be_hidden_when_there_is_no_icon(self):
profile = self.get_or_create_test_user().profile
profile.enable_favicons = True
profile.save()
bookmark = self.setup_bookmark(favicon_file="")
html = self.render_template()
self.assertFaviconHidden(html, bookmark)
def test_favicon_should_be_hidden_when_favicons_disabled(self):
profile = self.get_or_create_test_user().profile
profile.enable_favicons = False
profile.save()
bookmark = self.setup_bookmark(favicon_file="https_example_com.png")
html = self.render_template()
self.assertFaviconHidden(html, bookmark)
def test_bookmark_url_should_be_hidden_by_default(self):
profile = self.get_or_create_test_user().profile
profile.save()
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertBookmarkURLHidden(html, bookmark)
def test_show_bookmark_url_when_enabled(self):
profile = self.get_or_create_test_user().profile
profile.display_url = True
profile.save()
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertBookmarkURLVisible(html, bookmark)
def test_hide_bookmark_url_when_disabled(self):
profile = self.get_or_create_test_user().profile
profile.display_url = False
profile.save()
bookmark = self.setup_bookmark()
html = self.render_template()
self.assertBookmarkURLHidden(html, bookmark)
def test_show_mark_as_read_when_unread(self):
bookmark = self.setup_bookmark(unread=True)
html = self.render_template()
self.assertMarkAsReadButton(html, bookmark)
def test_hide_mark_as_read_when_read(self):
bookmark = self.setup_bookmark(unread=False)
html = self.render_template()
self.assertMarkAsReadButton(html, bookmark, count=0)
def test_hide_mark_as_read_for_non_owned_bookmarks(self):
other_user = self.setup_user(enable_sharing=True)
bookmark = self.setup_bookmark(user=other_user, shared=True, unread=True)
html = self.render_template(context_type=contexts.SharedBookmarkListContext)
self.assertBookmarksLink(html, bookmark)
self.assertMarkAsReadButton(html, bookmark, count=0)
def test_show_unshare_button_when_shared(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = True
profile.save()
bookmark = self.setup_bookmark(shared=True)
html = self.render_template()
self.assertUnshareButton(html, bookmark)
def test_hide_unshare_button_when_not_shared(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = True
profile.save()
bookmark = self.setup_bookmark(shared=False)
html = self.render_template()
self.assertUnshareButton(html, bookmark, count=0)
def test_hide_unshare_button_when_sharing_is_disabled(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = False
profile.save()
bookmark = self.setup_bookmark(shared=True)
html = self.render_template()
self.assertUnshareButton(html, bookmark, count=0)
def test_hide_unshare_for_non_owned_bookmarks(self):
other_user = self.setup_user(enable_sharing=True)
bookmark = self.setup_bookmark(user=other_user, shared=True)
html = self.render_template(context_type=contexts.SharedBookmarkListContext)
self.assertBookmarksLink(html, bookmark)
self.assertUnshareButton(html, bookmark, count=0)
def test_without_notes(self):
self.setup_bookmark()
html = self.render_template()
self.assertNotes(html, "", 0)
self.assertNotesToggle(html, 0)
def test_with_notes(self):
self.setup_bookmark(notes="Test note")
html = self.render_template()
note_html = "<p>Test note</p>"
self.assertNotes(html, note_html, 1)
def test_note_renders_markdown(self):
self.setup_bookmark(notes='**Example:** `print("Hello world!")`')
html = self.render_template()
note_html = (
'<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
)
self.assertNotes(html, note_html, 1)
def test_note_cleans_html(self):
self.setup_bookmark(notes='<script>alert("test")</script>')
self.setup_bookmark(
notes='<b ld-fetch="https://example.com" ld-on="click">bold text</b>'
)
html = self.render_template()
note_html = '&lt;script&gt;alert("test")&lt;/script&gt;'
self.assertIn(note_html, html, 1)
note_html = "<b>bold text</b>"
self.assertIn(note_html, html, 1)
def test_notes_are_hidden_initially_by_default(self):
self.setup_bookmark(notes="Test note")
html = self.render_template()
soup = self.make_soup(html)
bookmark_list = soup.select_one("ul.bookmark-list.show-notes")
self.assertIsNone(bookmark_list)
def test_notes_are_hidden_initially_with_permanent_notes_disabled(self):
profile = self.get_or_create_test_user().profile
profile.permanent_notes = False
profile.save()
self.setup_bookmark(notes="Test note")
html = self.render_template()
soup = self.make_soup(html)
bookmark_list = soup.select_one("ul.bookmark-list.show-notes")
self.assertIsNone(bookmark_list)
def test_notes_are_visible_initially_with_permanent_notes_enabled(self):
profile = self.get_or_create_test_user().profile
profile.permanent_notes = True
profile.save()
self.setup_bookmark(notes="Test note")
html = self.render_template()
soup = self.make_soup(html)
bookmark_list = soup.select_one("ul.bookmark-list.show-notes")
self.assertIsNotNone(bookmark_list)
def test_toggle_notes_is_visible_by_default(self):
self.setup_bookmark(notes="Test note")
html = self.render_template()
self.assertNotesToggle(html, 1)
def test_toggle_notes_is_visible_with_permanent_notes_disabled(self):
profile = self.get_or_create_test_user().profile
profile.permanent_notes = False
profile.save()
self.setup_bookmark(notes="Test note")
html = self.render_template()
self.assertNotesToggle(html, 1)
def test_toggle_notes_is_hidden_with_permanent_notes_enabled(self):
profile = self.get_or_create_test_user().profile
profile.permanent_notes = True
profile.save()
self.setup_bookmark(notes="Test note")
html = self.render_template()
self.assertNotesToggle(html, 0)
def test_with_anonymous_user(self):
profile = self.get_or_create_test_user().profile
profile.enable_sharing = True
profile.enable_public_sharing = True
profile.save()
bookmark = self.setup_bookmark()
bookmark.date_added = timezone.now() - relativedelta(days=8)
bookmark.web_archive_snapshot_url = (
"https://web.archive.org/web/20230531200136/https://example.com"
)
bookmark.notes = '**Example:** `print("Hello world!")`'
bookmark.favicon_file = "https_example_com.png"
bookmark.shared = True
bookmark.unread = True
bookmark.save()
html = self.render_template(
context_type=contexts.SharedBookmarkListContext, user=AnonymousUser()
)
self.assertBookmarksLink(html, bookmark, link_target="_blank")
self.assertWebArchiveLink(
html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_blank"
)
self.assertViewLink(html, bookmark, return_url=reverse("bookmarks:shared"))
self.assertNoBookmarkActions(html, bookmark)
self.assertShareInfo(html, bookmark)
self.assertMarkAsReadButton(html, bookmark, count=0)
self.assertUnshareButton(html, bookmark, count=0)
note_html = (
'<p><strong>Example:</strong> <code>print("Hello world!")</code></p>'
)
self.assertNotes(html, note_html, 1)
self.assertFaviconVisible(html, bookmark)
def test_empty_state(self):
html = self.render_template()
self.assertInHTML(
'<p class="empty-title h5">You have no bookmarks yet</p>', html
)