mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-13 13:39:27 +02:00
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:
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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"))
|
||||
|
@@ -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()
|
||||
|
@@ -1,6 +0,0 @@
|
||||
from bookmarks.tests.test_bookmark_details_modal import BookmarkDetailsModalTestCase
|
||||
|
||||
|
||||
class BookmarkDetailsViewTestCase(BookmarkDetailsModalTestCase):
|
||||
def get_view_name(self):
|
||||
return "bookmarks:details"
|
@@ -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"))
|
||||
|
@@ -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}"]')
|
||||
|
@@ -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"))
|
||||
|
@@ -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)
|
||||
|
@@ -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")
|
||||
|
@@ -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>
|
||||
|
Reference in New Issue
Block a user