Speed up response times for certain actions (#829)

* return updated HTML from bookmark actions

* open details through URL

* fix details update

* improve modal behavior

* use a frame

* make behaviors properly destroy themselves

* remove page and details params from tag urls

* use separate behavior for details and tags

* remove separate details view

* make it work with other views

* add asset actions

* remove asset refresh for now

* remove details partial

* fix tests

* remove old partials

* update tests

* cache and reuse tags

* extract search autocomplete behavior

* remove details param from pagination

* fix tests

* only return details modal when navigating in frame

* fix link target

* remove unused behaviors

* use auto submit behavior for user select

* fix import
This commit is contained in:
Sascha Ißbrücker
2024-09-16 12:48:19 +02:00
committed by GitHub
parent db225d5267
commit ffaaf0521d
65 changed files with 1419 additions and 1444 deletions

View File

@@ -2,6 +2,7 @@ import random
import logging
from datetime import datetime
from typing import List
from unittest import TestCase
from bs4 import BeautifulSoup
from django.contrib.auth.models import User
@@ -220,6 +221,75 @@ class HtmlTestMixin:
return BeautifulSoup(html, features="html.parser")
class BookmarkListTestMixin(TestCase, HtmlTestMixin):
def assertVisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
bookmark_list = soup.select_one(
f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]'
)
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select("li[ld-bookmark-item]")
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks:
bookmark_item = bookmark_list.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNotNone(bookmark_item)
def assertInvisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
for bookmark in bookmarks:
bookmark_item = soup.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNone(bookmark_item)
class TagCloudTestMixin(TestCase, HtmlTestMixin):
def assertVisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one("div.tag-cloud")
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select("a[data-is-tag-item]")
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select("a[data-is-tag-item]")
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertFalse(tag.name in tag_item_names)
def assertSelectedTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
selected_tags = soup.select_one("p.selected-tags")
self.assertIsNotNone(selected_tags)
tag_list = selected_tags.select("a")
self.assertEqual(len(tag_list), len(tags))
for tag in tags:
self.assertTrue(
tag.name in selected_tags.text,
msg=f"Selected tags do not contain: {tag.name}",
)
class LinkdingApiTestCase(APITestCase):
def get(self, url, expected_status_code=status.HTTP_200_OK):
response = self.client.get(url)

View File

@@ -1,13 +1,24 @@
from unittest.mock import patch
from django.contrib.auth.models import User
from django.core.files.uploadedfile import SimpleUploadedFile
from django.forms import model_to_dict
from django.test import TestCase
from django.http import HttpResponse
from django.test import TestCase, override_settings
from django.urls import reverse
from bookmarks.models import Bookmark
from bookmarks.tests.helpers import BookmarkFactoryMixin
from bookmarks.models import Bookmark, BookmarkAsset
from bookmarks.services import tasks, bookmarks
from bookmarks.tests.helpers import (
BookmarkFactoryMixin,
BookmarkListTestMixin,
TagCloudTestMixin,
)
class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
class BookmarkActionViewTestCase(
TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin
):
def setUp(self) -> None:
user = self.get_or_create_test_user()
@@ -156,6 +167,129 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertEqual(response.status_code, 404)
self.assertTrue(bookmark.shared)
@override_settings(LD_ENABLE_SNAPSHOTS=True)
def test_create_html_snapshot(self):
bookmark = self.setup_bookmark()
with patch.object(tasks, "_create_html_snapshot_task"):
self.client.post(
reverse("bookmarks:index.action"),
{
"create_html_snapshot": [bookmark.id],
},
)
self.assertEqual(bookmark.bookmarkasset_set.count(), 1)
asset = bookmark.bookmarkasset_set.first()
self.assertEqual(asset.asset_type, BookmarkAsset.TYPE_SNAPSHOT)
@override_settings(LD_ENABLE_SNAPSHOTS=True)
def test_can_only_create_html_snapshot_for_own_bookmarks(self):
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
with patch.object(tasks, "_create_html_snapshot_task"):
response = self.client.post(
reverse("bookmarks:index.action"),
{
"create_html_snapshot": [bookmark.id],
},
)
self.assertEqual(response.status_code, 404)
self.assertEqual(bookmark.bookmarkasset_set.count(), 0)
def test_upload_asset(self):
bookmark = self.setup_bookmark()
file_content = b"file content"
upload_file = SimpleUploadedFile("test.txt", file_content)
with patch.object(bookmarks, "upload_asset") as mock_upload_asset:
response = self.client.post(
reverse("bookmarks:index.action"),
{"upload_asset": bookmark.id, "upload_asset_file": upload_file},
)
self.assertEqual(response.status_code, 302)
mock_upload_asset.assert_called_once()
args, _ = mock_upload_asset.call_args
self.assertEqual(args[0], bookmark)
upload_file = args[1]
self.assertEqual(upload_file.name, "test.txt")
def test_can_only_upload_asset_for_own_bookmarks(self):
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
file_content = b"file content"
upload_file = SimpleUploadedFile("test.txt", file_content)
with patch.object(bookmarks, "upload_asset") as mock_upload_asset:
response = self.client.post(
reverse("bookmarks:index.action"),
{"upload_asset": bookmark.id, "upload_asset_file": upload_file},
)
self.assertEqual(response.status_code, 404)
mock_upload_asset.assert_not_called()
def test_remove_asset(self):
bookmark = self.setup_bookmark()
asset = self.setup_asset(bookmark)
response = self.client.post(
reverse("bookmarks:index.action"), {"remove_asset": asset.id}
)
self.assertEqual(response.status_code, 302)
self.assertFalse(BookmarkAsset.objects.filter(id=asset.id).exists())
def test_can_only_remove_own_asset(self):
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
asset = self.setup_asset(bookmark)
response = self.client.post(
reverse("bookmarks:index.action"), {"remove_asset": asset.id}
)
self.assertEqual(response.status_code, 404)
self.assertTrue(BookmarkAsset.objects.filter(id=asset.id).exists())
def test_update_state(self):
bookmark = self.setup_bookmark()
response = self.client.post(
reverse("bookmarks:index.action"),
{
"update_state": bookmark.id,
"is_archived": "on",
"unread": "on",
"shared": "on",
},
)
self.assertEqual(response.status_code, 302)
bookmark.refresh_from_db()
self.assertTrue(bookmark.unread)
self.assertTrue(bookmark.is_archived)
self.assertTrue(bookmark.shared)
def test_can_only_update_own_bookmark_state(self):
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
response = self.client.post(
reverse("bookmarks:index.action"),
{
"update_state": bookmark.id,
"is_archived": "on",
"unread": "on",
"shared": "on",
},
)
self.assertEqual(response.status_code, 404)
bookmark.refresh_from_db()
self.assertFalse(bookmark.unread)
self.assertFalse(bookmark.is_archived)
self.assertFalse(bookmark.shared)
def test_bulk_archive(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
@@ -791,58 +925,119 @@ class BookmarkActionViewTestCase(TestCase, BookmarkFactoryMixin):
self.assertBookmarksAreUnmodified([bookmark1, bookmark2, bookmark3])
def test_should_redirect_to_return_url(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
bookmark3 = self.setup_bookmark()
def test_index_action_redirects_to_index_with_query_params(self):
url = reverse("bookmarks:index.action") + "?q=foo&page=2"
redirect_url = reverse("bookmarks:index") + "?q=foo&page=2"
response = self.client.post(url)
url = (
reverse("bookmarks:index.action")
+ "?return_url="
+ reverse("bookmarks:settings.index")
)
response = self.client.post(
url,
{
"bulk_action": ["bulk_archive"],
"bulk_execute": [""],
"bookmark_id": [
str(bookmark1.id),
str(bookmark2.id),
str(bookmark3.id),
],
},
)
self.assertRedirects(response, redirect_url)
self.assertRedirects(response, reverse("bookmarks:settings.index"))
def test_archived_action_redirects_to_archived_with_query_params(self):
url = reverse("bookmarks:archived.action") + "?q=foo&page=2"
redirect_url = reverse("bookmarks:archived") + "?q=foo&page=2"
response = self.client.post(url)
def test_should_not_redirect_to_external_url(self):
bookmark1 = self.setup_bookmark()
bookmark2 = self.setup_bookmark()
bookmark3 = self.setup_bookmark()
self.assertRedirects(response, redirect_url)
def post_with(return_url, follow=None):
url = reverse("bookmarks:index.action") + f"?return_url={return_url}"
return self.client.post(
url,
{
"bulk_action": ["bulk_archive"],
"bulk_execute": [""],
"bookmark_id": [
str(bookmark1.id),
str(bookmark2.id),
str(bookmark3.id),
],
},
follow=follow,
def test_shared_action_redirects_to_shared_with_query_params(self):
url = reverse("bookmarks:shared.action") + "?q=foo&page=2"
redirect_url = reverse("bookmarks:shared") + "?q=foo&page=2"
response = self.client.post(url)
self.assertRedirects(response, redirect_url)
def bookmark_update_fixture(self):
user = self.get_or_create_test_user()
profile = user.profile
profile.enable_sharing = True
profile.save()
return {
"active": self.setup_numbered_bookmarks(3),
"archived": self.setup_numbered_bookmarks(3, archived=True),
"shared": self.setup_numbered_bookmarks(3, shared=True),
}
def assertBookmarkUpdateResponse(self, response: HttpResponse):
self.assertEqual(response.status_code, 200)
html = response.content.decode("utf-8")
soup = self.make_soup(html)
# bookmark list update
self.assertIsNotNone(
soup.select_one(
"turbo-stream[action='update'][target='bookmark-list-container']"
)
)
response = post_with("https://example.com")
self.assertRedirects(response, reverse("bookmarks:index"))
response = post_with("//example.com")
self.assertRedirects(response, reverse("bookmarks:index"))
response = post_with("://example.com")
self.assertRedirects(response, reverse("bookmarks:index"))
# tag cloud update
self.assertIsNotNone(
soup.select_one(
"turbo-stream[action='update'][target='tag-cloud-container']"
)
)
response = post_with("/foo//example.com", follow=True)
self.assertEqual(response.status_code, 404)
# update event
self.assertInHTML(
"""
<script>
document.dispatchEvent(new CustomEvent('bookmark-list-updated'));
</script>
""",
html,
)
def test_index_action_with_turbo_returns_bookmark_update(self):
fixture = self.bookmark_update_fixture()
response = self.client.post(
reverse("bookmarks:index.action"),
HTTP_ACCEPT="text/vnd.turbo-stream.html",
)
visible_tags = self.get_tags_from_bookmarks(
fixture["active"] + fixture["shared"]
)
invisible_tags = self.get_tags_from_bookmarks(fixture["archived"])
self.assertBookmarkUpdateResponse(response)
self.assertVisibleBookmarks(response, fixture["active"] + fixture["shared"])
self.assertInvisibleBookmarks(response, fixture["archived"])
self.assertVisibleTags(response, visible_tags)
self.assertInvisibleTags(response, invisible_tags)
def test_archived_action_with_turbo_returns_bookmark_update(self):
fixture = self.bookmark_update_fixture()
response = self.client.post(
reverse("bookmarks:archived.action"),
HTTP_ACCEPT="text/vnd.turbo-stream.html",
)
visible_tags = self.get_tags_from_bookmarks(fixture["archived"])
invisible_tags = self.get_tags_from_bookmarks(
fixture["active"] + fixture["shared"]
)
self.assertBookmarkUpdateResponse(response)
self.assertVisibleBookmarks(response, fixture["archived"])
self.assertInvisibleBookmarks(response, fixture["active"] + fixture["shared"])
self.assertVisibleTags(response, visible_tags)
self.assertInvisibleTags(response, invisible_tags)
def test_shared_action_with_turbo_returns_bookmark_update(self):
fixture = self.bookmark_update_fixture()
response = self.client.post(
reverse("bookmarks:shared.action"),
HTTP_ACCEPT="text/vnd.turbo-stream.html",
)
visible_tags = self.get_tags_from_bookmarks(fixture["shared"])
invisible_tags = self.get_tags_from_bookmarks(
fixture["active"] + fixture["archived"]
)
self.assertBookmarkUpdateResponse(response)
self.assertVisibleBookmarks(response, fixture["shared"])
self.assertInvisibleBookmarks(response, fixture["active"] + fixture["archived"])
self.assertVisibleTags(response, visible_tags)
self.assertInvisibleTags(response, invisible_tags)

View File

@@ -1,89 +1,26 @@
import urllib.parse
from typing import List
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.models import BookmarkSearch, UserProfile
from bookmarks.tests.helpers import (
BookmarkFactoryMixin,
HtmlTestMixin,
BookmarkListTestMixin,
TagCloudTestMixin,
collapse_whitespace,
)
class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
class BookmarkArchivedViewTestCase(
TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin
):
def setUp(self) -> None:
user = self.get_or_create_test_user()
self.client.force_login(user)
def assertVisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
bookmark_list = soup.select_one(
f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]'
)
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select("li[ld-bookmark-item]")
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks:
bookmark_item = bookmark_list.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNotNone(bookmark_item)
def assertInvisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
for bookmark in bookmarks:
bookmark_item = soup.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNone(bookmark_item)
def assertVisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one("div.tag-cloud")
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select("a[data-is-tag-item]")
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select("a[data-is-tag-item]")
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertFalse(tag.name in tag_item_names)
def assertSelectedTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
selected_tags = soup.select_one("p.selected-tags")
self.assertIsNotNone(selected_tags)
tag_list = selected_tags.select("a")
self.assertEqual(len(tag_list), len(tags))
for tag in tags:
self.assertTrue(
tag.name in selected_tags.text,
msg=f"Selected tags do not contain: {tag.name}",
)
def assertEditLink(self, response, url):
html = response.content.decode()
self.assertInHTML(
@@ -307,24 +244,21 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
base_url = reverse("bookmarks:archived")
# without params
return_url = urllib.parse.quote_plus(base_url)
url = f"{action_url}?return_url={return_url}"
url = f"{action_url}"
response = self.client.get(base_url)
self.assertBulkActionForm(response, url)
# with query
url_params = "?q=foo"
return_url = urllib.parse.quote_plus(base_url + url_params)
url = f"{action_url}?q=foo&return_url={return_url}"
url = f"{action_url}?q=foo"
response = self.client.get(base_url + url_params)
self.assertBulkActionForm(response, url)
# with query and sort
url_params = "?q=foo&sort=title_asc"
return_url = urllib.parse.quote_plus(base_url + url_params)
url = f"{action_url}?q=foo&sort=title_asc&return_url={return_url}"
url = f"{action_url}?q=foo&sort=title_asc"
response = self.client.get(base_url + url_params)
self.assertBulkActionForm(response, url)
@@ -527,7 +461,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertEqual(
actions_form.attrs["action"],
"/bookmarks/archived/action?q=%23foo&return_url=%2Fbookmarks%2Farchived%3Fq%3D%2523foo",
"/bookmarks/archived/action?q=%23foo",
)
def test_encode_search_params(self):
@@ -557,3 +491,15 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
url = reverse("bookmarks:archived") + "?page=alert(%27xss%27)"
response = self.client.get(url)
self.assertNotContains(response, "alert('xss')")
def test_turbo_frame_details_modal_renders_details_modal_update(self):
bookmark = self.setup_bookmark()
url = reverse("bookmarks:archived") + f"?bookmark_id={bookmark.id}"
response = self.client.get(url, headers={"Turbo-Frame": "details-modal"})
self.assertEqual(200, response.status_code)
soup = self.make_soup(response.content.decode())
self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
self.assertIsNone(soup.select_one("#bookmark-list-container"))
self.assertIsNone(soup.select_one("#tag-cloud-container"))

View File

@@ -1,14 +1,11 @@
import datetime
import re
from unittest.mock import patch
from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import formats, timezone
from bookmarks.models import BookmarkAsset, UserProfile
from bookmarks.services import bookmarks, tasks
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
@@ -17,23 +14,23 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
user = self.get_or_create_test_user()
self.client.force_login(user)
def get_view_name(self):
return "bookmarks:details_modal"
def get_base_url(self, bookmark):
return reverse(self.get_view_name(), args=[bookmark.id])
def get_details_form(self, soup, bookmark):
expected_url = reverse("bookmarks:details", args=[bookmark.id])
return soup.find("form", {"action": expected_url})
form_url = reverse("bookmarks:index.action") + f"?details={bookmark.id}"
return soup.find("form", {"action": form_url, "enctype": "multipart/form-data"})
def get_details(self, bookmark, return_url=""):
url = self.get_base_url(bookmark)
if return_url:
url += f"?return_url={return_url}"
def get_index_details_modal(self, bookmark):
url = reverse("bookmarks:index") + f"?details={bookmark.id}"
response = self.client.get(url)
soup = self.make_soup(response.content)
return soup
modal = soup.find("turbo-frame", {"id": "details-modal"})
return modal
def get_shared_details_modal(self, bookmark):
url = reverse("bookmarks:shared") + f"?details={bookmark.id}"
response = self.client.get(url)
soup = self.make_soup(response.content)
modal = soup.find("turbo-frame", {"id": "details-modal"})
return modal
def find_section(self, soup, section_name):
dt = soup.find("dt", string=section_name)
@@ -54,35 +51,68 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def find_asset(self, soup, asset):
return soup.find("div", {"data-asset-id": asset.id})
def details_route_access_test(self, view_name: str, shareable: bool):
def details_route_access_test(self):
# own bookmark
bookmark = self.setup_bookmark()
response = self.client.get(reverse(view_name, args=[bookmark.id]))
response = self.client.get(
reverse("bookmarks:index") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 200)
# other user's bookmark
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
response = self.client.get(reverse(view_name, args=[bookmark.id]))
response = self.client.get(
reverse("bookmarks:index") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
# non-existent bookmark
response = self.client.get(reverse(view_name, args=[9999]))
self.assertEqual(response.status_code, 404)
# non-existent bookmark - just returns without modal in response
response = self.client.get(reverse("bookmarks:index") + "?details=9999")
self.assertEqual(response.status_code, 200)
# guest user
self.client.logout()
response = self.client.get(reverse(view_name, args=[bookmark.id]))
self.assertEqual(response.status_code, 404 if shareable else 302)
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
def details_route_sharing_access_test(self, view_name: str, shareable: bool):
def test_access(self):
# own bookmark
bookmark = self.setup_bookmark()
response = self.client.get(
reverse("bookmarks:index") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 200)
# other user's bookmark
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
response = self.client.get(
reverse("bookmarks:index") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
# non-existent bookmark - just returns without modal in response
response = self.client.get(reverse("bookmarks:index") + "?details=9999")
self.assertEqual(response.status_code, 200)
# guest user
self.client.logout()
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
def test_access_with_sharing(self):
# shared bookmark, sharing disabled
other_user = self.setup_user()
bookmark = self.setup_bookmark(shared=True, user=other_user)
response = self.client.get(reverse(view_name, args=[bookmark.id]))
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
# shared bookmark, sharing enabled
@@ -90,37 +120,31 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.enable_sharing = True
profile.save()
response = self.client.get(reverse(view_name, args=[bookmark.id]))
self.assertEqual(response.status_code, 200 if shareable else 404)
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 200)
# shared bookmark, guest user, no public sharing
self.client.logout()
response = self.client.get(reverse(view_name, args=[bookmark.id]))
self.assertEqual(response.status_code, 404 if shareable else 302)
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 404)
# shared bookmark, guest user, public sharing
profile.enable_public_sharing = True
profile.save()
response = self.client.get(reverse(view_name, args=[bookmark.id]))
self.assertEqual(response.status_code, 200 if shareable else 302)
def test_access(self):
self.details_route_access_test(self.get_view_name(), True)
def test_access_with_sharing(self):
self.details_route_sharing_access_test(self.get_view_name(), True)
def test_assets_access(self):
self.details_route_access_test("bookmarks:details_assets", True)
def test_assets_access_with_sharing(self):
self.details_route_sharing_access_test("bookmarks:details_assets", True)
response = self.client.get(
reverse("bookmarks:shared") + f"?details={bookmark.id}"
)
self.assertEqual(response.status_code, 200)
def test_displays_title(self):
# with title
bookmark = self.setup_bookmark(title="Test title")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
title = soup.find("h2")
self.assertIsNotNone(title)
@@ -128,7 +152,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# with website title
bookmark = self.setup_bookmark(title="", website_title="Website title")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
title = soup.find("h2")
self.assertIsNotNone(title)
@@ -136,7 +160,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# with URL only
bookmark = self.setup_bookmark(title="", website_title="")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
title = soup.find("h2")
self.assertIsNotNone(title)
@@ -145,7 +169,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_website_link(self):
# basics
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.url)
self.assertIsNotNone(link)
self.assertEqual(link["href"], bookmark.url)
@@ -153,7 +177,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# favicons disabled
bookmark = self.setup_bookmark(favicon_file="example.png")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.url)
image = link.select_one("img")
self.assertIsNone(image)
@@ -164,14 +188,14 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.save()
bookmark = self.setup_bookmark(favicon_file="")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.url)
image = link.select_one("img")
self.assertIsNone(image)
# favicons enabled, favicon present
bookmark = self.setup_bookmark(favicon_file="example.png")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.url)
image = link.select_one("img")
self.assertIsNotNone(image)
@@ -180,7 +204,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_reader_mode_link(self):
# no latest snapshot
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
self.assertEqual(self.count_weblinks(soup), 2)
# snapshot is not complete
@@ -194,7 +218,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset_type=BookmarkAsset.TYPE_SNAPSHOT,
status=BookmarkAsset.STATUS_FAILURE,
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
self.assertEqual(self.count_weblinks(soup), 2)
# not a snapshot
@@ -203,7 +227,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset_type="upload",
status=BookmarkAsset.STATUS_COMPLETE,
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
self.assertEqual(self.count_weblinks(soup), 2)
# snapshot is complete
@@ -212,7 +236,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset_type=BookmarkAsset.TYPE_SNAPSHOT,
status=BookmarkAsset.STATUS_COMPLETE,
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
self.assertEqual(self.count_weblinks(soup), 3)
reader_mode_url = reverse("bookmarks:assets.read", args=[asset.id])
@@ -221,7 +245,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_internet_archive_link_with_snapshot_url(self):
bookmark = self.setup_bookmark(web_archive_snapshot_url="https://example.com/")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.web_archive_snapshot_url)
self.assertIsNotNone(link)
self.assertEqual(link["href"], bookmark.web_archive_snapshot_url)
@@ -231,7 +255,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark(
web_archive_snapshot_url="https://example.com/", favicon_file="example.png"
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.web_archive_snapshot_url)
image = link.select_one("svg")
self.assertIsNone(image)
@@ -244,7 +268,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark(
web_archive_snapshot_url="https://example.com/", favicon_file=""
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.web_archive_snapshot_url)
image = link.select_one("svg")
self.assertIsNone(image)
@@ -253,7 +277,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark(
web_archive_snapshot_url="https://example.com/", favicon_file="example.png"
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, bookmark.web_archive_snapshot_url)
image = link.select_one("svg")
self.assertIsNotNone(image)
@@ -267,7 +291,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
"https://web.archive.org/web/20230811214511/https://example.com/"
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
link = self.find_weblink(soup, fallback_web_archive_url)
self.assertIsNotNone(link)
self.assertEqual(link["href"], fallback_web_archive_url)
@@ -281,7 +305,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_BLANK
profile.save()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
website_link = self.find_weblink(soup, bookmark.url)
self.assertIsNotNone(website_link)
@@ -297,7 +321,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.bookmark_link_target = UserProfile.BOOKMARK_LINK_TARGET_SELF
profile.save()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
website_link = self.find_weblink(soup, bookmark.url)
self.assertIsNotNone(website_link)
@@ -312,13 +336,13 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_preview_image(self):
# without image
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
image = soup.select_one("div.preview-image img")
self.assertIsNone(image)
# with image
bookmark = self.setup_bookmark(preview_image_file="example.png")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
image = soup.select_one("div.preview-image img")
self.assertIsNone(image)
@@ -328,13 +352,13 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.save()
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
image = soup.select_one("div.preview-image img")
self.assertIsNone(image)
# preview images enabled, image present
bookmark = self.setup_bookmark(preview_image_file="example.png")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
image = soup.select_one("div.preview-image img")
self.assertIsNotNone(image)
self.assertEqual(image["src"], "/static/example.png")
@@ -342,18 +366,15 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_status(self):
# renders form
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
form = self.get_details_form(soup, bookmark)
self.assertIsNotNone(form)
self.assertEqual(
form["action"], reverse("bookmarks:details", args=[bookmark.id])
)
self.assertEqual(form["method"], "post")
# sharing disabled
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Status")
archived = section.find("input", {"type": "checkbox", "name": "is_archived"})
@@ -369,7 +390,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.save()
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Status")
archived = section.find("input", {"type": "checkbox", "name": "is_archived"})
@@ -381,7 +402,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# unchecked
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Status")
archived = section.find("input", {"type": "checkbox", "name": "is_archived"})
@@ -393,7 +414,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# checked
bookmark = self.setup_bookmark(is_archived=True, unread=True, shared=True)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Status")
archived = section.find("input", {"type": "checkbox", "name": "is_archived"})
@@ -406,106 +427,29 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_status_visibility(self):
# own bookmark
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Status")
self.assertIsNotNone(section)
# other user's bookmark
other_user = self.setup_user(enable_sharing=True)
bookmark = self.setup_bookmark(user=other_user, shared=True)
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
section = self.find_section(soup, "Status")
self.assertIsNone(section)
# guest user
self.client.logout()
other_user.profile.enable_public_sharing = True
other_user.profile.save()
bookmark = self.setup_bookmark(user=other_user, shared=True)
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
section = self.find_section(soup, "Status")
self.assertIsNone(section)
def test_status_update(self):
bookmark = self.setup_bookmark()
# update status
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 302)
bookmark.refresh_from_db()
self.assertTrue(bookmark.is_archived)
self.assertTrue(bookmark.unread)
self.assertTrue(bookmark.shared)
# update individual status
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "", "unread": "on", "shared": ""},
)
self.assertEqual(response.status_code, 302)
bookmark.refresh_from_db()
self.assertFalse(bookmark.is_archived)
self.assertTrue(bookmark.unread)
self.assertFalse(bookmark.shared)
def test_status_update_access(self):
# no sharing
other_user = self.setup_user()
bookmark = self.setup_bookmark(user=other_user)
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 404)
# shared, sharing disabled
bookmark = self.setup_bookmark(user=other_user, shared=True)
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 404)
# shared, sharing enabled
bookmark = self.setup_bookmark(user=other_user, shared=True)
profile = other_user.profile
profile.enable_sharing = True
profile.save()
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 404)
# shared, public sharing enabled
bookmark = self.setup_bookmark(user=other_user, shared=True)
profile = other_user.profile
profile.enable_public_sharing = True
profile.save()
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 404)
# guest user
self.client.logout()
bookmark = self.setup_bookmark(user=other_user, shared=True)
response = self.client.post(
self.get_base_url(bookmark),
{"is_archived": "on", "unread": "on", "shared": "on"},
)
self.assertEqual(response.status_code, 404)
def test_date_added(self):
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Date added")
expected_date = formats.date_format(bookmark.date_added, "DATETIME_FORMAT")
@@ -515,7 +459,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_tags(self):
# without tags
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Tags")
self.assertIsNone(section)
@@ -523,7 +467,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# with tags
bookmark = self.setup_bookmark(tags=[self.setup_tag(), self.setup_tag()])
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Tags")
for tag in bookmark.tags.all():
@@ -535,14 +479,14 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_description(self):
# without description
bookmark = self.setup_bookmark(description="", website_description="")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Description")
self.assertIsNone(section)
# with description
bookmark = self.setup_bookmark(description="Test description")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Description")
self.assertEqual(section.text.strip(), bookmark.description)
@@ -551,7 +495,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark(
description="", website_description="Website description"
)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Description")
self.assertEqual(section.text.strip(), bookmark.website_description)
@@ -559,14 +503,14 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_notes(self):
# without notes
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Notes")
self.assertIsNone(section)
# with notes
bookmark = self.setup_bookmark(notes="Test notes")
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Notes")
self.assertEqual(section.decode_contents(), "<p>Test notes</p>")
@@ -575,52 +519,42 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark()
# with default return URL
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
edit_link = soup.find("a", string="Edit")
self.assertIsNotNone(edit_link)
details_url = reverse("bookmarks:details", args=[bookmark.id])
expected_url = (
reverse("bookmarks:edit", args=[bookmark.id]) + "?return_url=" + details_url
)
self.assertEqual(edit_link["href"], expected_url)
# with custom return URL
soup = self.get_details(bookmark, return_url="/custom")
edit_link = soup.find("a", string="Edit")
self.assertIsNotNone(edit_link)
expected_url = (
reverse("bookmarks:edit", args=[bookmark.id]) + "?return_url=/custom"
)
self.assertEqual(edit_link["href"], expected_url)
details_url = reverse("bookmarks:index") + f"?details={bookmark.id}"
expected_url = "/bookmarks/1/edit?return_url=/bookmarks%3Fdetails%3D1"
self.assertEqual(expected_url, edit_link["href"])
def test_delete_button(self):
bookmark = self.setup_bookmark()
# basics
soup = self.get_details(bookmark)
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
modal = self.get_index_details_modal(bookmark)
delete_button = modal.find("button", {"type": "submit", "name": "remove"})
self.assertIsNotNone(delete_button)
self.assertEqual(delete_button.text.strip(), "Delete...")
self.assertEqual(delete_button["value"], str(bookmark.id))
self.assertEqual("Delete...", delete_button.text.strip())
self.assertEqual(str(bookmark.id), delete_button["value"])
form = delete_button.find_parent("form")
self.assertIsNotNone(form)
expected_url = reverse("bookmarks:index.action") + f"?return_url=/bookmarks"
self.assertEqual(form["action"], expected_url)
# with custom return URL
soup = self.get_details(bookmark, return_url="/custom")
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
form = delete_button.find_parent("form")
expected_url = reverse("bookmarks:index.action") + f"?return_url=/custom"
self.assertEqual(form["action"], expected_url)
expected_url = reverse("bookmarks:index.action")
self.assertEqual(expected_url, form["action"])
def test_actions_visibility(self):
# own bookmark
bookmark = self.setup_bookmark()
soup = self.get_index_details_modal(bookmark)
edit_link = soup.find("a", string="Edit")
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
self.assertIsNotNone(edit_link)
self.assertIsNotNone(delete_button)
# with sharing
other_user = self.setup_user(enable_sharing=True)
bookmark = self.setup_bookmark(user=other_user, shared=True)
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
edit_link = soup.find("a", string="Edit")
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
self.assertIsNone(edit_link)
@@ -632,7 +566,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
profile.save()
bookmark = self.setup_bookmark(user=other_user, shared=True)
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
edit_link = soup.find("a", string="Edit")
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
self.assertIsNone(edit_link)
@@ -642,7 +576,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.client.logout()
bookmark = self.setup_bookmark(user=other_user, shared=True)
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
edit_link = soup.find("a", string="Edit")
delete_button = soup.find("button", {"type": "submit", "name": "remove"})
self.assertIsNone(edit_link)
@@ -651,7 +585,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_assets_visibility_no_snapshot_support(self):
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Files")
self.assertIsNone(section)
@@ -659,7 +593,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
def test_assets_visibility_with_snapshot_support(self):
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.find_section(soup, "Files")
self.assertIsNotNone(section)
@@ -668,7 +602,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# no assets
bookmark = self.setup_bookmark()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Files")
asset_list = section.find("div", {"class": "assets"})
self.assertIsNone(asset_list)
@@ -677,7 +611,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
bookmark = self.setup_bookmark()
self.setup_asset(bookmark)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Files")
asset_list = section.find("div", {"class": "assets"})
self.assertIsNotNone(asset_list)
@@ -691,7 +625,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.setup_asset(bookmark),
]
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
section = self.get_section(soup, "Files")
asset_list = section.find("div", {"class": "assets"})
@@ -717,7 +651,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset.file = ""
asset.save()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, asset)
view_url = reverse("bookmarks:assets.view", args=[asset.id])
view_link = asset_item.find("a", {"href": view_url})
@@ -729,7 +663,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
pending_asset = self.setup_asset(bookmark, status=BookmarkAsset.STATUS_PENDING)
failed_asset = self.setup_asset(bookmark, status=BookmarkAsset.STATUS_FAILURE)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, pending_asset)
asset_text = asset_item.select_one(".asset-text span")
@@ -746,7 +680,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset2 = self.setup_asset(bookmark, file_size=54639)
asset3 = self.setup_asset(bookmark, file_size=11492020)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, asset1)
asset_text = asset_item.select_one(".asset-text")
@@ -766,7 +700,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# with file
asset = self.setup_asset(bookmark)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, asset)
view_link = asset_item.find("a", string="View")
@@ -779,7 +713,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# without file
asset.file = ""
asset.save()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, asset)
view_link = asset_item.find("a", string="View")
@@ -793,7 +727,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
other_user = self.setup_user(enable_sharing=True, enable_public_sharing=True)
bookmark = self.setup_bookmark(shared=True, user=other_user)
asset = self.setup_asset(bookmark)
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
asset_item = self.find_asset(soup, asset)
view_link = asset_item.find("a", string="View")
@@ -805,7 +739,7 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
# shared bookmark, guest user
self.client.logout()
soup = self.get_details(bookmark)
soup = self.get_shared_details_modal(bookmark)
asset_item = self.find_asset(soup, asset)
view_link = asset_item.find("a", string="View")
@@ -815,77 +749,13 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
self.assertIsNotNone(view_link)
self.assertIsNone(delete_button)
def test_remove_asset(self):
# remove asset
bookmark = self.setup_bookmark()
asset = self.setup_asset(bookmark)
response = self.client.post(
self.get_base_url(bookmark), {"remove_asset": asset.id}
)
self.assertEqual(response.status_code, 302)
self.assertFalse(BookmarkAsset.objects.filter(id=asset.id).exists())
# non-existent asset
response = self.client.post(self.get_base_url(bookmark), {"remove_asset": 9999})
self.assertEqual(response.status_code, 404)
# post without asset ID does not remove
asset = self.setup_asset(bookmark)
response = self.client.post(self.get_base_url(bookmark))
self.assertEqual(response.status_code, 302)
self.assertTrue(BookmarkAsset.objects.filter(id=asset.id).exists())
# guest user
asset = self.setup_asset(bookmark)
self.client.logout()
response = self.client.post(
self.get_base_url(bookmark), {"remove_asset": asset.id}
)
self.assertEqual(response.status_code, 404)
self.assertTrue(BookmarkAsset.objects.filter(id=asset.id).exists())
@override_settings(LD_ENABLE_SNAPSHOTS=True)
def test_assets_refresh_when_having_pending_asset(self):
bookmark = self.setup_bookmark()
asset = self.setup_asset(bookmark, status=BookmarkAsset.STATUS_COMPLETE)
fetch_url = reverse("bookmarks:details_assets", args=[bookmark.id])
# no pending asset
soup = self.get_details(bookmark)
files_section = self.find_section(soup, "Files")
assets_wrapper = files_section.find("div", {"ld-fetch": fetch_url})
self.assertIsNone(assets_wrapper)
# with pending asset
asset.status = BookmarkAsset.STATUS_PENDING
asset.save()
soup = self.get_details(bookmark)
files_section = self.find_section(soup, "Files")
assets_wrapper = files_section.find("div", {"ld-fetch": fetch_url})
self.assertIsNotNone(assets_wrapper)
@override_settings(LD_ENABLE_SNAPSHOTS=True)
def test_create_snapshot(self):
with patch.object(
tasks, "_create_html_snapshot_task"
) as mock_create_html_snapshot_task:
bookmark = self.setup_bookmark()
response = self.client.post(
self.get_base_url(bookmark), {"create_snapshot": ""}
)
self.assertEqual(response.status_code, 302)
self.assertEqual(bookmark.bookmarkasset_set.count(), 1)
@override_settings(LD_ENABLE_SNAPSHOTS=True)
def test_create_snapshot_is_disabled_when_having_pending_asset(self):
bookmark = self.setup_bookmark()
asset = self.setup_asset(bookmark, status=BookmarkAsset.STATUS_COMPLETE)
# no pending asset
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
files_section = self.find_section(soup, "Files")
create_button = files_section.find(
"button", string=re.compile("Create HTML snapshot")
@@ -896,40 +766,9 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
asset.status = BookmarkAsset.STATUS_PENDING
asset.save()
soup = self.get_details(bookmark)
soup = self.get_index_details_modal(bookmark)
files_section = self.find_section(soup, "Files")
create_button = files_section.find(
"button", string=re.compile("Create HTML snapshot")
)
self.assertTrue(create_button.has_attr("disabled"))
def test_upload_file(self):
bookmark = self.setup_bookmark()
file_content = b"file content"
upload_file = SimpleUploadedFile("test.txt", file_content)
with patch.object(bookmarks, "upload_asset") as mock_upload_asset:
response = self.client.post(
self.get_base_url(bookmark),
{"upload_asset": "", "upload_asset_file": upload_file},
)
self.assertEqual(response.status_code, 302)
mock_upload_asset.assert_called_once()
args, kwargs = mock_upload_asset.call_args
self.assertEqual(args[0], bookmark)
upload_file = args[1]
self.assertEqual(upload_file.name, "test.txt")
def test_upload_file_without_file(self):
bookmark = self.setup_bookmark()
with patch.object(bookmarks, "upload_asset") as mock_upload_asset:
response = self.client.post(
self.get_base_url(bookmark),
{"upload_asset": ""},
)
self.assertEqual(response.status_code, 400)
mock_upload_asset.assert_not_called()

View File

@@ -1,6 +0,0 @@
from bookmarks.tests.test_bookmark_details_modal import BookmarkDetailsModalTestCase
class BookmarkDetailsViewTestCase(BookmarkDetailsModalTestCase):
def get_view_name(self):
return "bookmarks:details"

View File

@@ -1,85 +1,24 @@
import urllib.parse
from typing import List
from django.contrib.auth.models import User
from django.test import TestCase
from django.urls import reverse
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.models import BookmarkSearch, UserProfile
from bookmarks.tests.helpers import (
BookmarkFactoryMixin,
BookmarkListTestMixin,
TagCloudTestMixin,
)
class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
class BookmarkIndexViewTestCase(
TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin
):
def setUp(self) -> None:
user = self.get_or_create_test_user()
self.client.force_login(user)
def assertVisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
bookmark_list = soup.select_one(
f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]'
)
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select("li[ld-bookmark-item]")
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks:
bookmark_item = bookmark_list.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNotNone(bookmark_item)
def assertInvisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
for bookmark in bookmarks:
bookmark_item = soup.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNone(bookmark_item)
def assertVisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one("div.tag-cloud")
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select("a[data-is-tag-item]")
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select("a[data-is-tag-item]")
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertFalse(tag.name in tag_item_names)
def assertSelectedTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
selected_tags = soup.select_one("p.selected-tags")
self.assertIsNotNone(selected_tags)
tag_list = selected_tags.select("a")
self.assertEqual(len(tag_list), len(tags))
for tag in tags:
self.assertTrue(
tag.name in selected_tags.text,
msg=f"Selected tags do not contain: {tag.name}",
)
def assertEditLink(self, response, url):
html = response.content.decode()
self.assertInHTML(
@@ -285,24 +224,21 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
base_url = reverse("bookmarks:index")
# without params
return_url = urllib.parse.quote_plus(base_url)
url = f"{action_url}?return_url={return_url}"
url = f"{action_url}"
response = self.client.get(base_url)
self.assertBulkActionForm(response, url)
# with query
url_params = "?q=foo"
return_url = urllib.parse.quote_plus(base_url + url_params)
url = f"{action_url}?q=foo&return_url={return_url}"
url = f"{action_url}?q=foo"
response = self.client.get(base_url + url_params)
self.assertBulkActionForm(response, url)
# with query and sort
url_params = "?q=foo&sort=title_asc"
return_url = urllib.parse.quote_plus(base_url + url_params)
url = f"{action_url}?q=foo&sort=title_asc&return_url={return_url}"
url = f"{action_url}?q=foo&sort=title_asc"
response = self.client.get(base_url + url_params)
self.assertBulkActionForm(response, url)
@@ -503,7 +439,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.assertEqual(
actions_form.attrs["action"],
"/bookmarks/action?q=%23foo&return_url=%2Fbookmarks%3Fq%3D%2523foo",
"/bookmarks/action?q=%23foo",
)
def test_encode_search_params(self):
@@ -533,3 +469,15 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
url = reverse("bookmarks:index") + "?page=alert(%27xss%27)"
response = self.client.get(url)
self.assertNotContains(response, "alert('xss')")
def test_turbo_frame_details_modal_renders_details_modal_update(self):
bookmark = self.setup_bookmark()
url = reverse("bookmarks:index") + f"?bookmark_id={bookmark.id}"
response = self.client.get(url, headers={"Turbo-Frame": "details-modal"})
self.assertEqual(200, response.status_code)
soup = self.make_soup(response.content.decode())
self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
self.assertIsNone(soup.select_one("#bookmark-list-container"))
self.assertIsNone(soup.select_one("#tag-cloud-container"))

View File

@@ -1,16 +1,13 @@
from bs4 import BeautifulSoup
from django.db.models import QuerySet
from django.template import Template, RequestContext
from django.test import TestCase, RequestFactory
from bookmarks.models import BookmarkSearch, Tag
from bookmarks.models import BookmarkSearch
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
def render_template(
self, url: str, tags: QuerySet[Tag] = Tag.objects.all(), mode: str = ""
):
def render_template(self, url: str, mode: str = ""):
rf = RequestFactory()
request = rf.get(url)
request.user = self.get_or_create_test_user()
@@ -21,32 +18,31 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
{
"request": request,
"search": search,
"tags": tags,
"mode": mode,
},
)
template_to_render = Template(
"{% load bookmarks %}" "{% bookmark_search search tags mode %}"
"{% load bookmarks %} {% bookmark_search search mode %}"
)
return template_to_render.render(context)
def assertHiddenInput(self, form: BeautifulSoup, name: str, value: str = None):
input = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNotNone(input)
element = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNotNone(element)
if value is not None:
self.assertEqual(input["value"], value)
self.assertEqual(element["value"], value)
def assertNoHiddenInput(self, form: BeautifulSoup, name: str):
input = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNone(input)
element = form.select_one(f'input[name="{name}"][type="hidden"]')
self.assertIsNone(element)
def assertSearchInput(self, form: BeautifulSoup, name: str, value: str = None):
input = form.select_one(f'input[name="{name}"][type="search"]')
self.assertIsNotNone(input)
element = form.select_one(f'input[name="{name}"][type="search"]')
self.assertIsNotNone(element)
if value is not None:
self.assertEqual(input["value"], value)
self.assertEqual(element["value"], value)
def assertSelect(self, form: BeautifulSoup, name: str, value: str = None):
select = form.select_one(f'select[name="{name}"]')

View File

@@ -6,11 +6,16 @@ from django.test import TestCase
from django.urls import reverse
from bookmarks.models import Bookmark, BookmarkSearch, Tag, UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.tests.helpers import (
BookmarkFactoryMixin,
BookmarkListTestMixin,
TagCloudTestMixin,
)
class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
class BookmarkSharedViewTestCase(
TestCase, BookmarkFactoryMixin, BookmarkListTestMixin, TagCloudTestMixin
):
def authenticate(self) -> None:
user = self.get_or_create_test_user()
self.client.force_login(user)
@@ -24,57 +29,6 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
count=count,
)
def assertVisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
bookmark_list = soup.select_one(
f'ul.bookmark-list[data-bookmarks-total="{len(bookmarks)}"]'
)
self.assertIsNotNone(bookmark_list)
bookmark_items = bookmark_list.select("li[ld-bookmark-item]")
self.assertEqual(len(bookmark_items), len(bookmarks))
for bookmark in bookmarks:
bookmark_item = bookmark_list.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNotNone(bookmark_item)
def assertInvisibleBookmarks(
self, response, bookmarks: List[Bookmark], link_target: str = "_blank"
):
soup = self.make_soup(response.content.decode())
for bookmark in bookmarks:
bookmark_item = soup.select_one(
f'li[ld-bookmark-item] a[href="{bookmark.url}"][target="{link_target}"]'
)
self.assertIsNone(bookmark_item)
def assertVisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_cloud = soup.select_one("div.tag-cloud")
self.assertIsNotNone(tag_cloud)
tag_items = tag_cloud.select("a[data-is-tag-item]")
self.assertEqual(len(tag_items), len(tags))
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertTrue(tag.name in tag_item_names)
def assertInvisibleTags(self, response, tags: List[Tag]):
soup = self.make_soup(response.content.decode())
tag_items = soup.select("a[data-is-tag-item]")
tag_item_names = [tag_item.text.strip() for tag_item in tag_items]
for tag in tags:
self.assertFalse(tag.name in tag_item_names)
def assertVisibleUserOptions(self, response, users: List[User]):
html = response.content.decode()
@@ -84,7 +38,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
f'<option value="{user.username}">{user.username}</option>'
)
user_select_html = f"""
<select name="user" class="form-select" required="" id="id_user">
<select name="user" class="form-select" id="id_user" ld-auto-submit>
{''.join(user_options)}
</select>
"""
@@ -593,7 +547,7 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.assertEqual(
actions_form.attrs["action"],
"/bookmarks/shared/action?q=%23foo&return_url=%2Fbookmarks%2Fshared%3Fq%3D%2523foo",
"/bookmarks/shared/action?q=%23foo",
)
def test_encode_search_params(self):
@@ -627,3 +581,15 @@ class BookmarkSharedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
url = reverse("bookmarks:shared") + "?page=alert(%27xss%27)"
response = self.client.get(url)
self.assertNotContains(response, "alert('xss')")
def test_turbo_frame_details_modal_renders_details_modal_update(self):
bookmark = self.setup_bookmark()
url = reverse("bookmarks:shared") + f"?bookmark_id={bookmark.id}"
response = self.client.get(url, headers={"Turbo-Frame": "details-modal"})
self.assertEqual(200, response.status_code)
soup = self.make_soup(response.content.decode())
self.assertIsNotNone(soup.select_one("turbo-frame#details-modal"))
self.assertIsNone(soup.select_one("#bookmark-list-container"))
self.assertIsNone(soup.select_one("#tag-cloud-container"))

View File

@@ -12,7 +12,7 @@ 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
from bookmarks.views import contexts
class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
@@ -51,31 +51,25 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
html,
)
def assertViewLink(
self, html: str, bookmark: Bookmark, return_url=reverse("bookmarks:index")
):
self.assertViewLinkCount(html, bookmark, return_url=return_url)
def assertViewLink(self, html: str, bookmark: Bookmark, base_url=None):
self.assertViewLinkCount(html, bookmark, base_url)
def assertNoViewLink(
self, html: str, bookmark: Bookmark, return_url=reverse("bookmarks:index")
):
self.assertViewLinkCount(html, bookmark, count=0, return_url=return_url)
def assertNoViewLink(self, html: str, bookmark: Bookmark, base_url=None):
self.assertViewLinkCount(html, bookmark, base_url, count=0)
def assertViewLinkCount(
self,
html: str,
bookmark: Bookmark,
base_url: str = None,
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])
if base_url is None:
base_url = reverse("bookmarks:index")
details_url = base_url + f"?details={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>
<a href="{details_url}" data-turbo-action="replace" data-turbo-frame="details-modal">View</a>
""",
html,
count=count,
@@ -652,7 +646,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
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.assertViewLink(html, bookmark, base_url=reverse("bookmarks:shared"))
self.assertNoBookmarkActions(html, bookmark)
self.assertShareInfo(html, bookmark)
@@ -944,7 +938,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.assertWebArchiveLink(
html, "1 week ago", bookmark.web_archive_snapshot_url, link_target="_blank"
)
self.assertViewLink(html, bookmark, return_url=reverse("bookmarks:shared"))
self.assertViewLink(html, bookmark, base_url=reverse("bookmarks:shared"))
self.assertNoBookmarkActions(html, bookmark)
self.assertShareInfo(html, bookmark)
self.assertMarkAsReadButton(html, bookmark, count=0)

View File

@@ -172,3 +172,12 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
rendered_template, 2, True, href="?q=cake&sort=title_asc&page=2"
)
self.assertNextLink(rendered_template, 3, href="?q=cake&sort=title_asc&page=3")
def test_removes_details_parameter(self):
rendered_template = self.render_template(
100, 10, 2, url="/test?details=1&page=2"
)
self.assertPrevLink(rendered_template, 1, href="?page=1")
self.assertPageLink(rendered_template, 1, False, href="?page=1")
self.assertPageLink(rendered_template, 2, True, href="?page=2")
self.assertNextLink(rendered_template, 3, href="?page=3")

View File

@@ -8,7 +8,7 @@ from django.test import TestCase, RequestFactory
from bookmarks.middlewares import LinkdingMiddleware
from bookmarks.models import UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.views.partials import contexts
from bookmarks.views import contexts
class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
@@ -203,13 +203,28 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
tag = self.setup_tag(name="tag1")
self.setup_bookmark(tags=[tag], title="term1")
rendered_template = self.render_template(url="/test?q=term1&sort=title_asc")
self.assertInHTML(
"""
<a href="?q=term1+%23tag1&sort=title_asc" class="mr-2" data-is-tag-item>
<span class="highlight-char">t</span><span>ag1</span>
</a>
""",
rendered_template,
)
def test_tag_url_removes_page_number_and_details_id(self):
tag = self.setup_tag(name="tag1")
self.setup_bookmark(tags=[tag], title="term1")
rendered_template = self.render_template(
url="/test?q=term1&sort=title_asc&page=2"
url="/test?q=term1&sort=title_asc&page=2&details=5"
)
self.assertInHTML(
"""
<a href="?q=term1+%23tag1&sort=title_asc&page=2" class="mr-2" data-is-tag-item>
<a href="?q=term1+%23tag1&sort=title_asc" class="mr-2" data-is-tag-item>
<span class="highlight-char">t</span><span>ag1</span>
</a>
""",
@@ -347,12 +362,30 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
self.setup_bookmark(tags=[tag], title="term1", description="term2")
rendered_template = self.render_template(
url="/test?q=term1 %23tag1 term2&sort=title_asc&page=2"
url="/test?q=term1 %23tag1 term2&sort=title_asc"
)
self.assertInHTML(
"""
<a href="?q=term1+term2&sort=title_asc&page=2"
<a href="?q=term1+term2&sort=title_asc"
class="text-bold mr-2">
<span>-tag1</span>
</a>
""",
rendered_template,
)
def test_selected_tag_url_removes_page_number_and_details_id(self):
tag = self.setup_tag(name="tag1")
self.setup_bookmark(tags=[tag], title="term1", description="term2")
rendered_template = self.render_template(
url="/test?q=term1 %23tag1 term2&sort=title_asc&page=2&details=5"
)
self.assertInHTML(
"""
<a href="?q=term1+term2&sort=title_asc"
class="text-bold mr-2">
<span>-tag1</span>
</a>