mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-14 14:09:26 +02:00
Add bundles for organizing bookmarks (#1097)
* add bundle model and query logic * cleanup tests * add basic form * add success message * Add form tests * Add bundle list view * fix edit view * Add remove button * Add basic preview logic * Make pagination use absolute URLs * Hide bookmark edits when rendering preview * Render bookmark list in preview * Reorder bundles * Show bundles in bookmark view * Make bookmark search respect selected bundle * UI tweaks * Fix bookmark scope * Improve bundle preview * Skip preview if form is submitted * Show correct preview after invalid form submission * Add option to hide bundles * Merge new migrations * Add tests for bundle menu * Improve check for preview being removed
This commit is contained in:
@@ -17,7 +17,7 @@ from rest_framework import status
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from bookmarks.models import Bookmark, BookmarkAsset, Tag, User
|
||||
from bookmarks.models import Bookmark, BookmarkAsset, BookmarkBundle, Tag, User
|
||||
|
||||
|
||||
class BookmarkFactoryMixin:
|
||||
@@ -166,6 +166,33 @@ class BookmarkFactoryMixin:
|
||||
def get_numbered_bookmark(self, title: str):
|
||||
return Bookmark.objects.get(title=title)
|
||||
|
||||
def setup_bundle(
|
||||
self,
|
||||
user: User = None,
|
||||
name: str = None,
|
||||
search: str = "",
|
||||
any_tags: str = "",
|
||||
all_tags: str = "",
|
||||
excluded_tags: str = "",
|
||||
order: int = 0,
|
||||
):
|
||||
if user is None:
|
||||
user = self.get_or_create_test_user()
|
||||
if not name:
|
||||
name = get_random_string(length=32)
|
||||
bundle = BookmarkBundle(
|
||||
name=name,
|
||||
owner=user,
|
||||
date_created=timezone.now(),
|
||||
search=search,
|
||||
any_tags=any_tags,
|
||||
all_tags=all_tags,
|
||||
excluded_tags=excluded_tags,
|
||||
order=order,
|
||||
)
|
||||
bundle.save()
|
||||
return bundle
|
||||
|
||||
def setup_asset(
|
||||
self,
|
||||
bookmark: Bookmark,
|
||||
@@ -239,7 +266,7 @@ class BookmarkFactoryMixin:
|
||||
user.profile.save()
|
||||
return user
|
||||
|
||||
def get_tags_from_bookmarks(self, bookmarks: [Bookmark]):
|
||||
def get_tags_from_bookmarks(self, bookmarks: list[Bookmark]):
|
||||
all_tags = []
|
||||
for bookmark in bookmarks:
|
||||
all_tags = all_tags + list(bookmark.tags.all())
|
||||
|
@@ -844,6 +844,26 @@ class BookmarkActionViewTestCase(
|
||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||
|
||||
def test_index_action_bulk_select_across_respects_bundle(self):
|
||||
self.setup_numbered_bookmarks(3, prefix="foo")
|
||||
self.setup_numbered_bookmarks(3, prefix="bar")
|
||||
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
self.client.post(
|
||||
reverse("linkding:bookmarks.index.action") + f"?bundle={bundle.id}",
|
||||
{
|
||||
"bulk_action": ["bulk_delete"],
|
||||
"bulk_execute": [""],
|
||||
"bulk_select_across": ["on"],
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||
|
||||
def test_archived_action_bulk_select_across_only_affects_archived_bookmarks(self):
|
||||
self.setup_bulk_edit_scope_test_data()
|
||||
|
||||
@@ -889,6 +909,26 @@ class BookmarkActionViewTestCase(
|
||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||
|
||||
def test_archived_action_bulk_select_across_respects_bundle(self):
|
||||
self.setup_numbered_bookmarks(3, prefix="foo", archived=True)
|
||||
self.setup_numbered_bookmarks(3, prefix="bar", archived=True)
|
||||
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
self.client.post(
|
||||
reverse("linkding:bookmarks.archived.action") + f"?bundle={bundle.id}",
|
||||
{
|
||||
"bulk_action": ["bulk_delete"],
|
||||
"bulk_execute": [""],
|
||||
"bulk_select_across": ["on"],
|
||||
},
|
||||
)
|
||||
|
||||
self.assertEqual(0, Bookmark.objects.filter(title__startswith="foo").count())
|
||||
self.assertEqual(3, Bookmark.objects.filter(title__startswith="bar").count())
|
||||
|
||||
def test_shared_action_bulk_select_across_not_supported(self):
|
||||
self.setup_bulk_edit_scope_test_data()
|
||||
|
||||
|
@@ -9,7 +9,6 @@ from bookmarks.tests.helpers import (
|
||||
BookmarkFactoryMixin,
|
||||
BookmarkListTestMixin,
|
||||
TagCloudTestMixin,
|
||||
collapse_whitespace,
|
||||
)
|
||||
|
||||
|
||||
@@ -60,7 +59,23 @@ class BookmarkArchivedViewTestCase(
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("linkding:bookmarks.archived") + "?q=foo")
|
||||
html = collapse_whitespace(response.content.decode())
|
||||
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
|
||||
def test_should_list_bookmarks_matching_bundle(self):
|
||||
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, prefix="foo", archived=True
|
||||
)
|
||||
invisible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, prefix="bar", archived=True
|
||||
)
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.archived") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
@@ -105,6 +120,26 @@ class BookmarkArchivedViewTestCase(
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_tags_for_bookmarks_matching_bundle(self):
|
||||
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, with_tags=True, archived=True, prefix="foo", tag_prefix="foo"
|
||||
)
|
||||
invisible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, with_tags=True, archived=True, prefix="bar", tag_prefix="bar"
|
||||
)
|
||||
|
||||
visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
|
||||
invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks)
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.archived") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
||||
user_profile = self.user.profile
|
||||
user_profile.search_preferences = {
|
||||
@@ -515,3 +550,20 @@ class BookmarkArchivedViewTestCase(
|
||||
|
||||
feed = soup.select_one('head link[type="application/rss+xml"]')
|
||||
self.assertIsNone(feed)
|
||||
|
||||
def test_hide_bundles_when_enabled_in_profile(self):
|
||||
# visible by default
|
||||
response = self.client.get(reverse("linkding:bookmarks.archived"))
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML('<h2 id="bundles-heading">Bundles</h2>', html)
|
||||
|
||||
# hidden when disabled in profile
|
||||
user_profile = self.get_or_create_test_user().profile
|
||||
user_profile.hide_bundles = True
|
||||
user_profile.save()
|
||||
|
||||
response = self.client.get(reverse("linkding:bookmarks.archived"))
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML('<h2 id="bundles-heading">Bundles</h2>', html, count=0)
|
||||
|
@@ -585,10 +585,10 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||
asset_item = self.find_asset(asset_list, asset)
|
||||
self.assertIsNotNone(asset_item)
|
||||
|
||||
asset_icon = asset_item.select_one(".asset-icon svg")
|
||||
asset_icon = asset_item.select_one(".list-item-icon svg")
|
||||
self.assertIsNotNone(asset_icon)
|
||||
|
||||
asset_text = asset_item.select_one(".asset-text span")
|
||||
asset_text = asset_item.select_one(".list-item-text span")
|
||||
self.assertIsNotNone(asset_text)
|
||||
self.assertIn(asset.display_name, asset_text.text)
|
||||
|
||||
@@ -687,11 +687,11 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||
soup = self.get_index_details_modal(bookmark)
|
||||
|
||||
asset_item = self.find_asset(soup, pending_asset)
|
||||
asset_text = asset_item.select_one(".asset-text span")
|
||||
asset_text = asset_item.select_one(".list-item-text span")
|
||||
self.assertIn("(queued)", asset_text.text)
|
||||
|
||||
asset_item = self.find_asset(soup, failed_asset)
|
||||
asset_text = asset_item.select_one(".asset-text span")
|
||||
asset_text = asset_item.select_one(".list-item-text span")
|
||||
self.assertIn("(failed)", asset_text.text)
|
||||
|
||||
def test_asset_file_size(self):
|
||||
@@ -703,15 +703,15 @@ class BookmarkDetailsModalTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin
|
||||
soup = self.get_index_details_modal(bookmark)
|
||||
|
||||
asset_item = self.find_asset(soup, asset1)
|
||||
asset_text = asset_item.select_one(".asset-text")
|
||||
asset_text = asset_item.select_one(".list-item-text")
|
||||
self.assertEqual(asset_text.text.strip(), asset1.display_name)
|
||||
|
||||
asset_item = self.find_asset(soup, asset2)
|
||||
asset_text = asset_item.select_one(".asset-text")
|
||||
asset_text = asset_item.select_one(".list-item-text")
|
||||
self.assertIn("53.4\xa0KB", asset_text.text)
|
||||
|
||||
asset_item = self.find_asset(soup, asset3)
|
||||
asset_text = asset_item.select_one(".asset-text")
|
||||
asset_text = asset_item.select_one(".list-item-text")
|
||||
self.assertIn("11.0\xa0MB", asset_text.text)
|
||||
|
||||
def test_asset_actions_visibility(self):
|
||||
|
@@ -34,6 +34,21 @@ class BookmarkIndexViewTestCase(
|
||||
self.assertIsNotNone(form)
|
||||
self.assertEqual(form.attrs["action"], url)
|
||||
|
||||
def assertVisibleBundles(self, soup, bundles):
|
||||
bundle_list = soup.select_one("ul.bundle-menu")
|
||||
self.assertIsNotNone(bundle_list)
|
||||
|
||||
list_items = bundle_list.select("li.bundle-menu-item")
|
||||
self.assertEqual(len(list_items), len(bundles))
|
||||
|
||||
for index, list_item in enumerate(list_items):
|
||||
bundle = bundles[index]
|
||||
link = list_item.select_one("a")
|
||||
href = link.attrs["href"]
|
||||
|
||||
self.assertEqual(bundle.name, list_item.text.strip())
|
||||
self.assertEqual(f"?bundle={bundle.id}", href)
|
||||
|
||||
def test_should_list_unarchived_and_user_owned_bookmarks(self):
|
||||
other_user = User.objects.create_user(
|
||||
"otheruser", "otheruser@example.com", "password123"
|
||||
@@ -58,6 +73,19 @@ class BookmarkIndexViewTestCase(
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
|
||||
def test_should_list_bookmarks_matching_bundle(self):
|
||||
visible_bookmarks = self.setup_numbered_bookmarks(3, prefix="foo")
|
||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, prefix="bar")
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.index") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
|
||||
def test_should_list_tags_for_unarchived_and_user_owned_bookmarks(self):
|
||||
other_user = User.objects.create_user(
|
||||
"otheruser", "otheruser@example.com", "password123"
|
||||
@@ -96,6 +124,26 @@ class BookmarkIndexViewTestCase(
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_tags_for_bookmarks_matching_bundle(self):
|
||||
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, with_tags=True, prefix="foo", tag_prefix="foo"
|
||||
)
|
||||
invisible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, with_tags=True, prefix="bar", tag_prefix="bar"
|
||||
)
|
||||
|
||||
visible_tags = self.get_tags_from_bookmarks(visible_bookmarks)
|
||||
invisible_tags = self.get_tags_from_bookmarks(invisible_bookmarks)
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.index") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_bookmarks_and_tags_for_search_preferences(self):
|
||||
user_profile = self.user.profile
|
||||
user_profile.search_preferences = {
|
||||
@@ -494,3 +542,43 @@ class BookmarkIndexViewTestCase(
|
||||
|
||||
feed = soup.select_one('head link[type="application/rss+xml"]')
|
||||
self.assertIsNone(feed)
|
||||
|
||||
def test_list_bundles(self):
|
||||
books = self.setup_bundle(name="Books bundle", order=3)
|
||||
music = self.setup_bundle(name="Music bundle", order=1)
|
||||
tools = self.setup_bundle(name="Tools bundle", order=2)
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
html = response.content.decode()
|
||||
soup = self.make_soup(html)
|
||||
|
||||
self.assertVisibleBundles(soup, [music, tools, books])
|
||||
|
||||
def test_list_bundles_only_shows_user_owned_bundles(self):
|
||||
user_bundles = [self.setup_bundle(), self.setup_bundle(), self.setup_bundle()]
|
||||
other_user = self.setup_user()
|
||||
self.setup_bundle(user=other_user)
|
||||
self.setup_bundle(user=other_user)
|
||||
self.setup_bundle(user=other_user)
|
||||
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
html = response.content.decode()
|
||||
soup = self.make_soup(html)
|
||||
|
||||
self.assertVisibleBundles(soup, user_bundles)
|
||||
|
||||
def test_hide_bundles_when_enabled_in_profile(self):
|
||||
# visible by default
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML('<h2 id="bundles-heading">Bundles</h2>', html)
|
||||
|
||||
# hidden when disabled in profile
|
||||
user_profile = self.get_or_create_test_user().profile
|
||||
user_profile.hide_bundles = True
|
||||
user_profile.save()
|
||||
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML('<h2 id="bundles-heading">Bundles</h2>', html, count=0)
|
||||
|
@@ -11,21 +11,25 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
||||
form = BookmarkSearchForm(search)
|
||||
self.assertEqual(form["q"].initial, "")
|
||||
self.assertEqual(form["user"].initial, "")
|
||||
self.assertEqual(form["bundle"].initial, None)
|
||||
self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_DESC)
|
||||
self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_OFF)
|
||||
self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||
|
||||
# with params
|
||||
bundle = self.setup_bundle()
|
||||
search = BookmarkSearch(
|
||||
q="search query",
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user="user123",
|
||||
bundle=bundle,
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||
)
|
||||
form = BookmarkSearchForm(search)
|
||||
self.assertEqual(form["q"].initial, "search query")
|
||||
self.assertEqual(form["user"].initial, "user123")
|
||||
self.assertEqual(form["bundle"].initial, bundle.id)
|
||||
self.assertEqual(form["sort"].initial, BookmarkSearch.SORT_ADDED_ASC)
|
||||
self.assertEqual(form["shared"].initial, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
self.assertEqual(form["unread"].initial, BookmarkSearch.FILTER_UNREAD_YES)
|
||||
@@ -61,17 +65,26 @@ class BookmarkSearchFormTest(TestCase, BookmarkFactoryMixin):
|
||||
self.assertCountEqual(form.hidden_fields(), [form["q"], form["sort"]])
|
||||
|
||||
# all modified params
|
||||
bundle = self.setup_bundle()
|
||||
search = BookmarkSearch(
|
||||
q="search query",
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user="user123",
|
||||
bundle=bundle,
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||
)
|
||||
form = BookmarkSearchForm(search)
|
||||
self.assertCountEqual(
|
||||
form.hidden_fields(),
|
||||
[form["q"], form["sort"], form["user"], form["shared"], form["unread"]],
|
||||
[
|
||||
form["q"],
|
||||
form["sort"],
|
||||
form["user"],
|
||||
form["bundle"],
|
||||
form["shared"],
|
||||
form["unread"],
|
||||
],
|
||||
)
|
||||
|
||||
# some modified params are editable fields
|
||||
|
@@ -2,16 +2,23 @@ from django.http import QueryDict
|
||||
from django.test import TestCase
|
||||
|
||||
from bookmarks.models import BookmarkSearch
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
|
||||
|
||||
class BookmarkSearchModelTest(TestCase):
|
||||
class MockRequest:
|
||||
def __init__(self, user):
|
||||
self.user = user
|
||||
|
||||
|
||||
class BookmarkSearchModelTest(TestCase, BookmarkFactoryMixin):
|
||||
def test_from_request(self):
|
||||
# no params
|
||||
query_dict = QueryDict()
|
||||
|
||||
search = BookmarkSearch.from_request(query_dict)
|
||||
search = BookmarkSearch.from_request(None, query_dict)
|
||||
self.assertEqual(search.q, "")
|
||||
self.assertEqual(search.user, "")
|
||||
self.assertEqual(search.bundle, None)
|
||||
self.assertEqual(search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_OFF)
|
||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||
@@ -19,7 +26,7 @@ class BookmarkSearchModelTest(TestCase):
|
||||
# some params
|
||||
query_dict = QueryDict("q=search query&user=user123")
|
||||
|
||||
bookmark_search = BookmarkSearch.from_request(query_dict)
|
||||
bookmark_search = BookmarkSearch.from_request(None, query_dict)
|
||||
self.assertEqual(bookmark_search.q, "search query")
|
||||
self.assertEqual(bookmark_search.user, "user123")
|
||||
self.assertEqual(bookmark_search.sort, BookmarkSearch.SORT_ADDED_DESC)
|
||||
@@ -27,13 +34,16 @@ class BookmarkSearchModelTest(TestCase):
|
||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||
|
||||
# all params
|
||||
bundle = self.setup_bundle()
|
||||
request = MockRequest(self.get_or_create_test_user())
|
||||
query_dict = QueryDict(
|
||||
"q=search query&sort=title_asc&user=user123&shared=yes&unread=yes"
|
||||
f"q=search query&sort=title_asc&user=user123&bundle={bundle.id}&shared=yes&unread=yes"
|
||||
)
|
||||
|
||||
search = BookmarkSearch.from_request(query_dict)
|
||||
search = BookmarkSearch.from_request(request, query_dict)
|
||||
self.assertEqual(search.q, "search query")
|
||||
self.assertEqual(search.user, "user123")
|
||||
self.assertEqual(search.bundle, bundle)
|
||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_SHARED)
|
||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_YES)
|
||||
@@ -45,7 +55,7 @@ class BookmarkSearchModelTest(TestCase):
|
||||
}
|
||||
query_dict = QueryDict("q=search query")
|
||||
|
||||
search = BookmarkSearch.from_request(query_dict, preferences)
|
||||
search = BookmarkSearch.from_request(None, query_dict, preferences)
|
||||
self.assertEqual(search.q, "search query")
|
||||
self.assertEqual(search.user, "")
|
||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_ASC)
|
||||
@@ -60,13 +70,110 @@ class BookmarkSearchModelTest(TestCase):
|
||||
}
|
||||
query_dict = QueryDict("sort=title_desc&shared=no&unread=off")
|
||||
|
||||
search = BookmarkSearch.from_request(query_dict, preferences)
|
||||
search = BookmarkSearch.from_request(None, query_dict, preferences)
|
||||
self.assertEqual(search.q, "")
|
||||
self.assertEqual(search.user, "")
|
||||
self.assertEqual(search.sort, BookmarkSearch.SORT_TITLE_DESC)
|
||||
self.assertEqual(search.shared, BookmarkSearch.FILTER_SHARED_UNSHARED)
|
||||
self.assertEqual(search.unread, BookmarkSearch.FILTER_UNREAD_OFF)
|
||||
|
||||
def test_from_request_ignores_invalid_bundle_param(self):
|
||||
self.setup_bundle()
|
||||
|
||||
# bundle does not exist
|
||||
request = MockRequest(self.get_or_create_test_user())
|
||||
query_dict = QueryDict("bundle=99999")
|
||||
search = BookmarkSearch.from_request(request, query_dict)
|
||||
self.assertIsNone(search.bundle)
|
||||
|
||||
# bundle belongs to another user
|
||||
other_user = self.setup_user()
|
||||
bundle = self.setup_bundle(user=other_user)
|
||||
query_dict = QueryDict(f"bundle={bundle.id}")
|
||||
search = BookmarkSearch.from_request(request, query_dict)
|
||||
self.assertIsNone(search.bundle)
|
||||
|
||||
def test_query_params(self):
|
||||
# no params
|
||||
search = BookmarkSearch()
|
||||
self.assertEqual(search.query_params, {})
|
||||
|
||||
# params are default values
|
||||
search = BookmarkSearch(
|
||||
q="", sort=BookmarkSearch.SORT_ADDED_DESC, user="", bundle=None, shared=""
|
||||
)
|
||||
self.assertEqual(search.query_params, {})
|
||||
|
||||
# some modified params
|
||||
search = BookmarkSearch(q="search query", sort=BookmarkSearch.SORT_ADDED_ASC)
|
||||
self.assertEqual(
|
||||
search.query_params,
|
||||
{"q": "search query", "sort": BookmarkSearch.SORT_ADDED_ASC},
|
||||
)
|
||||
|
||||
# all modified params
|
||||
bundle = self.setup_bundle()
|
||||
search = BookmarkSearch(
|
||||
q="search query",
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user="user123",
|
||||
bundle=bundle,
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||
)
|
||||
self.assertEqual(
|
||||
search.query_params,
|
||||
{
|
||||
"q": "search query",
|
||||
"sort": BookmarkSearch.SORT_ADDED_ASC,
|
||||
"user": "user123",
|
||||
"bundle": bundle.id,
|
||||
"shared": BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||
},
|
||||
)
|
||||
|
||||
# preferences are not query params if they match default
|
||||
preferences = {
|
||||
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||
}
|
||||
search = BookmarkSearch(preferences=preferences)
|
||||
self.assertEqual(search.query_params, {})
|
||||
|
||||
# param is not a query param if it matches the preference
|
||||
preferences = {
|
||||
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||
}
|
||||
search = BookmarkSearch(
|
||||
sort=BookmarkSearch.SORT_TITLE_ASC,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||
preferences=preferences,
|
||||
)
|
||||
self.assertEqual(search.query_params, {})
|
||||
|
||||
# overriding preferences is a query param
|
||||
preferences = {
|
||||
"sort": BookmarkSearch.SORT_TITLE_ASC,
|
||||
"shared": BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
"unread": BookmarkSearch.FILTER_UNREAD_YES,
|
||||
}
|
||||
search = BookmarkSearch(
|
||||
sort=BookmarkSearch.SORT_TITLE_DESC,
|
||||
shared=BookmarkSearch.FILTER_SHARED_UNSHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_OFF,
|
||||
preferences=preferences,
|
||||
)
|
||||
self.assertEqual(
|
||||
search.query_params,
|
||||
{
|
||||
"sort": BookmarkSearch.SORT_TITLE_DESC,
|
||||
"shared": BookmarkSearch.FILTER_SHARED_UNSHARED,
|
||||
"unread": BookmarkSearch.FILTER_UNREAD_OFF,
|
||||
},
|
||||
)
|
||||
|
||||
def test_modified_params(self):
|
||||
# no params
|
||||
bookmark_search = BookmarkSearch()
|
||||
@@ -88,16 +195,18 @@ class BookmarkSearchModelTest(TestCase):
|
||||
self.assertCountEqual(modified_params, ["q", "sort"])
|
||||
|
||||
# all modified params
|
||||
bundle = self.setup_bundle()
|
||||
bookmark_search = BookmarkSearch(
|
||||
q="search query",
|
||||
sort=BookmarkSearch.SORT_ADDED_ASC,
|
||||
user="user123",
|
||||
bundle=bundle,
|
||||
shared=BookmarkSearch.FILTER_SHARED_SHARED,
|
||||
unread=BookmarkSearch.FILTER_UNREAD_YES,
|
||||
)
|
||||
modified_params = bookmark_search.modified_params
|
||||
self.assertCountEqual(
|
||||
modified_params, ["q", "sort", "user", "shared", "unread"]
|
||||
modified_params, ["q", "sort", "user", "bundle", "shared", "unread"]
|
||||
)
|
||||
|
||||
# preferences are not modified params
|
||||
@@ -180,7 +289,10 @@ class BookmarkSearchModelTest(TestCase):
|
||||
)
|
||||
|
||||
# only returns preferences
|
||||
bookmark_search = BookmarkSearch(q="search query", user="user123")
|
||||
bundle = self.setup_bundle()
|
||||
bookmark_search = BookmarkSearch(
|
||||
q="search query", user="user123", bundle=bundle
|
||||
)
|
||||
self.assertEqual(
|
||||
bookmark_search.preferences_dict,
|
||||
{
|
||||
|
@@ -12,7 +12,7 @@ class BookmarkSearchTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
request = rf.get(url)
|
||||
request.user = self.get_or_create_test_user()
|
||||
request.user_profile = self.get_or_create_test_user().profile
|
||||
search = BookmarkSearch.from_request(request.GET)
|
||||
search = BookmarkSearch.from_request(request, request.GET)
|
||||
context = RequestContext(
|
||||
request,
|
||||
{
|
||||
|
@@ -114,6 +114,24 @@ class BookmarkSharedViewTestCase(
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
|
||||
def test_should_list_bookmarks_matching_bundle(self):
|
||||
self.authenticate()
|
||||
user = self.setup_user(enable_sharing=True)
|
||||
|
||||
visible_bookmarks = self.setup_numbered_bookmarks(
|
||||
3, shared=True, user=user, prefix="foo"
|
||||
)
|
||||
invisible_bookmarks = self.setup_numbered_bookmarks(3, shared=True, user=user)
|
||||
|
||||
bundle = self.setup_bundle(search="foo")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.shared") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleBookmarks(response, visible_bookmarks)
|
||||
self.assertInvisibleBookmarks(response, invisible_bookmarks)
|
||||
|
||||
def test_should_list_only_publicly_shared_bookmarks_without_login(self):
|
||||
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
|
||||
user2 = self.setup_user(enable_sharing=True)
|
||||
@@ -224,6 +242,45 @@ class BookmarkSharedViewTestCase(
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_tags_for_bookmarks_matching_bundle(self):
|
||||
self.authenticate()
|
||||
user1 = self.setup_user(enable_sharing=True)
|
||||
user2 = self.setup_user(enable_sharing=True)
|
||||
user3 = self.setup_user(enable_sharing=True)
|
||||
visible_tags = [
|
||||
self.setup_tag(user=user1),
|
||||
self.setup_tag(user=user2),
|
||||
self.setup_tag(user=user3),
|
||||
]
|
||||
invisible_tags = [
|
||||
self.setup_tag(user=user1),
|
||||
self.setup_tag(user=user2),
|
||||
self.setup_tag(user=user3),
|
||||
]
|
||||
|
||||
self.setup_bookmark(
|
||||
shared=True, user=user1, title="searchvalue", tags=[visible_tags[0]]
|
||||
)
|
||||
self.setup_bookmark(
|
||||
shared=True, user=user2, title="searchvalue", tags=[visible_tags[1]]
|
||||
)
|
||||
self.setup_bookmark(
|
||||
shared=True, user=user3, title="searchvalue", tags=[visible_tags[2]]
|
||||
)
|
||||
|
||||
self.setup_bookmark(shared=True, user=user1, tags=[invisible_tags[0]])
|
||||
self.setup_bookmark(shared=True, user=user2, tags=[invisible_tags[1]])
|
||||
self.setup_bookmark(shared=True, user=user3, tags=[invisible_tags[2]])
|
||||
|
||||
bundle = self.setup_bundle(search="searchvalue")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bookmarks.shared") + f"?bundle={bundle.id}"
|
||||
)
|
||||
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_list_only_tags_for_publicly_shared_bookmarks_without_login(self):
|
||||
user1 = self.setup_user(enable_sharing=True, enable_public_sharing=True)
|
||||
user2 = self.setup_user(enable_sharing=True)
|
||||
|
@@ -143,6 +143,19 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
||||
)
|
||||
self.assertBookmarkListEqual(response.data["results"], bookmarks)
|
||||
|
||||
def test_list_bookmarks_should_filter_by_bundle(self):
|
||||
self.authenticate()
|
||||
search_value = self.get_random_string()
|
||||
bookmarks = self.setup_numbered_bookmarks(5, prefix=search_value)
|
||||
self.setup_numbered_bookmarks(5)
|
||||
bundle = self.setup_bundle(search=search_value)
|
||||
|
||||
response = self.get(
|
||||
reverse("linkding:bookmark-list") + f"?bundle={bundle.id}",
|
||||
expected_status_code=status.HTTP_200_OK,
|
||||
)
|
||||
self.assertBookmarkListEqual(response.data["results"], bookmarks)
|
||||
|
||||
def test_list_bookmarks_filter_unread(self):
|
||||
self.authenticate()
|
||||
unread_bookmarks = self.setup_numbered_bookmarks(5, unread=True)
|
||||
@@ -250,6 +263,21 @@ class BookmarksApiTestCase(LinkdingApiTestCase, BookmarkFactoryMixin):
|
||||
)
|
||||
self.assertBookmarkListEqual(response.data["results"], archived_bookmarks)
|
||||
|
||||
def test_list_archived_bookmarks_should_filter_by_bundle(self):
|
||||
self.authenticate()
|
||||
search_value = self.get_random_string()
|
||||
archived_bookmarks = self.setup_numbered_bookmarks(
|
||||
5, archived=True, prefix=search_value
|
||||
)
|
||||
self.setup_numbered_bookmarks(5, archived=True)
|
||||
bundle = self.setup_bundle(search=search_value)
|
||||
|
||||
response = self.get(
|
||||
reverse("linkding:bookmark-archived") + f"?bundle={bundle.id}",
|
||||
expected_status_code=status.HTTP_200_OK,
|
||||
)
|
||||
self.assertBookmarkListEqual(response.data["results"], archived_bookmarks)
|
||||
|
||||
def test_list_archived_bookmarks_should_respect_sort(self):
|
||||
self.authenticate()
|
||||
bookmarks = self.setup_numbered_bookmarks(5, archived=True)
|
||||
|
@@ -10,7 +10,7 @@ from django.urls import reverse
|
||||
from django.utils import timezone, formats
|
||||
|
||||
from bookmarks.middlewares import LinkdingMiddleware
|
||||
from bookmarks.models import Bookmark, UserProfile, User
|
||||
from bookmarks.models import Bookmark, BookmarkSearch, UserProfile, User
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
from bookmarks.views import contexts
|
||||
|
||||
@@ -46,7 +46,6 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
title="View snapshot on the Internet Archive Wayback Machine" target="{link_target}" rel="noopener">
|
||||
{label_content}
|
||||
</a>
|
||||
<span>|</span>
|
||||
""",
|
||||
html,
|
||||
)
|
||||
@@ -266,6 +265,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
contexts.BookmarkListContext
|
||||
] = contexts.ActiveBookmarkListContext,
|
||||
user: User | AnonymousUser = None,
|
||||
is_preview: bool = False,
|
||||
) -> str:
|
||||
rf = RequestFactory()
|
||||
request = rf.get(url)
|
||||
@@ -273,7 +273,10 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
middleware = LinkdingMiddleware(lambda r: HttpResponse())
|
||||
middleware(request)
|
||||
|
||||
bookmark_list_context = context_type(request)
|
||||
search = BookmarkSearch.from_request(request, request.GET)
|
||||
bookmark_list_context = context_type(request, search)
|
||||
if is_preview:
|
||||
bookmark_list_context.is_preview = True
|
||||
context = RequestContext(request, {"bookmark_list": bookmark_list_context})
|
||||
|
||||
template = Template("{% include 'bookmarks/bookmark_list.html' %}")
|
||||
@@ -1047,3 +1050,21 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
soup = self.make_soup(html)
|
||||
bookmarks = soup.select("li[ld-bookmark-item]")
|
||||
self.assertEqual(10, len(bookmarks))
|
||||
|
||||
def test_no_actions_rendered_when_is_preview(self):
|
||||
bookmark = self.setup_bookmark()
|
||||
bookmark.date_added = timezone.now() - relativedelta(days=8)
|
||||
bookmark.web_archive_snapshot_url = "https://example.com"
|
||||
bookmark.save()
|
||||
|
||||
html = self.render_template(is_preview=True)
|
||||
|
||||
# Verify no actions are rendered
|
||||
self.assertNoViewLink(html, bookmark)
|
||||
self.assertNoBookmarkActions(html, bookmark)
|
||||
self.assertMarkAsReadButton(html, bookmark, count=0)
|
||||
self.assertUnshareButton(html, bookmark, count=0)
|
||||
self.assertNotesToggle(html, count=0)
|
||||
|
||||
# But date should still be rendered
|
||||
self.assertWebArchiveLink(html, "1 week ago", bookmark.web_archive_snapshot_url)
|
||||
|
122
bookmarks/tests/test_bundles_edit_view.py
Normal file
122
bookmarks/tests/test_bundles_edit_view.py
Normal file
@@ -0,0 +1,122 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
|
||||
|
||||
class BundleEditViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def create_form_data(self, overrides=None):
|
||||
if overrides is None:
|
||||
overrides = {}
|
||||
form_data = {
|
||||
"name": "Test Bundle",
|
||||
"search": "test search",
|
||||
"any_tags": "tag1 tag2",
|
||||
"all_tags": "required-tag",
|
||||
"excluded_tags": "excluded-tag",
|
||||
}
|
||||
return {**form_data, **overrides}
|
||||
|
||||
def test_should_edit_bundle(self):
|
||||
bundle = self.setup_bundle()
|
||||
|
||||
updated_data = self.create_form_data()
|
||||
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.edit", args=[bundle.id]), updated_data
|
||||
)
|
||||
|
||||
self.assertRedirects(response, reverse("linkding:bundles.index"))
|
||||
|
||||
bundle.refresh_from_db()
|
||||
self.assertEqual(bundle.name, updated_data["name"])
|
||||
self.assertEqual(bundle.search, updated_data["search"])
|
||||
self.assertEqual(bundle.any_tags, updated_data["any_tags"])
|
||||
self.assertEqual(bundle.all_tags, updated_data["all_tags"])
|
||||
self.assertEqual(bundle.excluded_tags, updated_data["excluded_tags"])
|
||||
|
||||
def test_should_render_edit_form_with_prefilled_fields(self):
|
||||
bundle = self.setup_bundle(
|
||||
name="Test Bundle",
|
||||
search="test search terms",
|
||||
any_tags="tag1 tag2 tag3",
|
||||
all_tags="required-tag all-tag",
|
||||
excluded_tags="excluded-tag banned-tag",
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.edit", args=[bundle.id]))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML(
|
||||
f'<input type="text" name="name" value="{bundle.name}" '
|
||||
'autocomplete="off" placeholder=" " class="form-input" '
|
||||
'maxlength="256" required id="id_name">',
|
||||
html,
|
||||
)
|
||||
|
||||
self.assertInHTML(
|
||||
f'<input type="text" name="search" value="{bundle.search}" '
|
||||
'autocomplete="off" placeholder=" " class="form-input" '
|
||||
'maxlength="256" id="id_search">',
|
||||
html,
|
||||
)
|
||||
|
||||
self.assertInHTML(
|
||||
f'<input type="text" name="any_tags" value="{bundle.any_tags}" '
|
||||
'autocomplete="off" autocapitalize="off" class="form-input" '
|
||||
'maxlength="1024" id="id_any_tags">',
|
||||
html,
|
||||
)
|
||||
|
||||
self.assertInHTML(
|
||||
f'<input type="text" name="all_tags" value="{bundle.all_tags}" '
|
||||
'autocomplete="off" autocapitalize="off" class="form-input" '
|
||||
'maxlength="1024" id="id_all_tags">',
|
||||
html,
|
||||
)
|
||||
|
||||
self.assertInHTML(
|
||||
f'<input type="text" name="excluded_tags" value="{bundle.excluded_tags}" '
|
||||
'autocomplete="off" autocapitalize="off" class="form-input" '
|
||||
'maxlength="1024" id="id_excluded_tags">',
|
||||
html,
|
||||
)
|
||||
|
||||
def test_should_return_422_with_invalid_form(self):
|
||||
bundle = self.setup_bundle(
|
||||
name="Test Bundle",
|
||||
search="test search",
|
||||
any_tags="tag1 tag2",
|
||||
all_tags="required-tag",
|
||||
excluded_tags="excluded-tag",
|
||||
)
|
||||
|
||||
invalid_data = self.create_form_data({"name": ""})
|
||||
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.edit", args=[bundle.id]), invalid_data
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 422)
|
||||
|
||||
def test_should_not_allow_editing_other_users_bundles(self):
|
||||
other_user = self.setup_user(name="otheruser")
|
||||
other_users_bundle = self.setup_bundle(user=other_user)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bundles.edit", args=[other_users_bundle.id])
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
updated_data = self.create_form_data()
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.edit", args=[other_users_bundle.id]), updated_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 404)
|
198
bookmarks/tests/test_bundles_index_view.py
Normal file
198
bookmarks/tests/test_bundles_index_view.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from bookmarks.models import BookmarkBundle
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
|
||||
|
||||
class BundleIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def test_render_bundle_list(self):
|
||||
bundles = [
|
||||
self.setup_bundle(name="Bundle 1"),
|
||||
self.setup_bundle(name="Bundle 2"),
|
||||
self.setup_bundle(name="Bundle 3"),
|
||||
]
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.content.decode()
|
||||
|
||||
for bundle in bundles:
|
||||
expected_list_item = f"""
|
||||
<div class="list-item" data-bundle-id="{bundle.id}" draggable="true">
|
||||
<div class="list-item-icon text-secondary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M9 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M9 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M9 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M15 5m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M15 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
<path d="M15 19m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="list-item-text">
|
||||
<span class="truncate">{bundle.name}</span>
|
||||
</div>
|
||||
<div class="list-item-actions">
|
||||
<a class="btn btn-link" href="{reverse("linkding:bundles.edit", args=[bundle.id])}">Edit</a>
|
||||
<button ld-confirm-button type="submit" name="remove_bundle" value="{bundle.id}" class="btn btn-link">Remove</button>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
|
||||
self.assertInHTML(expected_list_item, html)
|
||||
|
||||
def test_renders_user_owned_bundles_only(self):
|
||||
user_bundle = self.setup_bundle(name="User Bundle")
|
||||
|
||||
other_user = self.setup_user(name="otheruser")
|
||||
other_user_bundle = self.setup_bundle(name="Other User Bundle", user=other_user)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML(f'<span class="truncate">{user_bundle.name}</span>', html)
|
||||
self.assertNotIn(other_user_bundle.name, html)
|
||||
|
||||
def test_empty_state(self):
|
||||
response = self.client.get(reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML('<p class="empty-title h5">You have no bundles yet</p>', html)
|
||||
self.assertInHTML(
|
||||
'<p class="empty-subtitle">Create your first bundle to get started</p>',
|
||||
html,
|
||||
)
|
||||
|
||||
def test_add_new_button(self):
|
||||
response = self.client.get(reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
html = response.content.decode()
|
||||
|
||||
self.assertInHTML(
|
||||
f'<a href="{reverse("linkding:bundles.new")}" class="btn btn-primary">Add new bundle</a>',
|
||||
html,
|
||||
)
|
||||
|
||||
def test_remove_bundle(self):
|
||||
bundle = self.setup_bundle(name="Test Bundle")
|
||||
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.action"),
|
||||
{"remove_bundle": str(bundle.id)},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertFalse(BookmarkBundle.objects.filter(id=bundle.id).exists())
|
||||
|
||||
def test_remove_other_user_bundle(self):
|
||||
other_user = self.setup_user(name="otheruser")
|
||||
other_user_bundle = self.setup_bundle(name="Other User Bundle", user=other_user)
|
||||
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.action"),
|
||||
{"remove_bundle": str(other_user_bundle.id)},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
self.assertTrue(BookmarkBundle.objects.filter(id=other_user_bundle.id).exists())
|
||||
|
||||
def assertBundleOrder(self, expected_bundles, user=None):
|
||||
if user is None:
|
||||
user = self.user
|
||||
actual_bundles = BookmarkBundle.objects.filter(owner=user).order_by("order")
|
||||
self.assertEqual(len(actual_bundles), len(expected_bundles))
|
||||
for i, bundle in enumerate(expected_bundles):
|
||||
self.assertEqual(actual_bundles[i].id, bundle.id)
|
||||
self.assertEqual(actual_bundles[i].order, i)
|
||||
|
||||
def move_bundle(self, bundle: BookmarkBundle, position: int):
|
||||
return self.client.post(
|
||||
reverse("linkding:bundles.action"),
|
||||
{"move_bundle": str(bundle.id), "move_position": position},
|
||||
)
|
||||
|
||||
def test_move_bundle(self):
|
||||
bundle1 = self.setup_bundle(name="Bundle 1", order=0)
|
||||
bundle2 = self.setup_bundle(name="Bundle 2", order=1)
|
||||
bundle3 = self.setup_bundle(name="Bundle 3", order=2)
|
||||
|
||||
self.move_bundle(bundle1, 1)
|
||||
self.assertBundleOrder([bundle2, bundle1, bundle3])
|
||||
|
||||
self.move_bundle(bundle1, 0)
|
||||
self.assertBundleOrder([bundle1, bundle2, bundle3])
|
||||
|
||||
self.move_bundle(bundle1, 2)
|
||||
self.assertBundleOrder([bundle2, bundle3, bundle1])
|
||||
|
||||
self.move_bundle(bundle1, 2)
|
||||
self.assertBundleOrder([bundle2, bundle3, bundle1])
|
||||
|
||||
def test_move_bundle_response(self):
|
||||
bundle1 = self.setup_bundle(name="Bundle 1", order=0)
|
||||
self.setup_bundle(name="Bundle 2", order=1)
|
||||
|
||||
response = self.move_bundle(bundle1, 1)
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, reverse("linkding:bundles.index"))
|
||||
|
||||
def test_can_only_move_user_owned_bundles(self):
|
||||
other_user = self.setup_user()
|
||||
other_user_bundle1 = self.setup_bundle(user=other_user)
|
||||
self.setup_bundle(user=other_user)
|
||||
|
||||
response = self.move_bundle(other_user_bundle1, 1)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_move_bundle_only_affects_own_bundles(self):
|
||||
user_bundle1 = self.setup_bundle(name="User Bundle 1", order=0)
|
||||
user_bundle2 = self.setup_bundle(name="User Bundle 2", order=1)
|
||||
|
||||
other_user = self.setup_user(name="otheruser")
|
||||
other_user_bundle = self.setup_bundle(
|
||||
name="Other User Bundle", user=other_user, order=0
|
||||
)
|
||||
|
||||
# Move user bundle
|
||||
self.move_bundle(user_bundle1, 1)
|
||||
self.assertBundleOrder([user_bundle2, user_bundle1], user=self.user)
|
||||
|
||||
# Check that other user's bundle is unaffected
|
||||
self.assertBundleOrder([other_user_bundle], user=other_user)
|
||||
|
||||
def test_remove_non_existing_bundle(self):
|
||||
non_existent_id = 99999
|
||||
|
||||
response = self.client.post(
|
||||
reverse("linkding:bundles.action"),
|
||||
{"remove_bundle": str(non_existent_id)},
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
def test_post_without_action(self):
|
||||
bundle = self.setup_bundle(name="Test Bundle")
|
||||
|
||||
response = self.client.post(reverse("linkding:bundles.action"), {})
|
||||
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertRedirects(response, reverse("linkding:bundles.index"))
|
||||
|
||||
self.assertTrue(BookmarkBundle.objects.filter(id=bundle.id).exists())
|
77
bookmarks/tests/test_bundles_new_view.py
Normal file
77
bookmarks/tests/test_bundles_new_view.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from bookmarks.models import BookmarkBundle
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
|
||||
|
||||
class BundleNewViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def create_form_data(self, overrides=None):
|
||||
if overrides is None:
|
||||
overrides = {}
|
||||
form_data = {
|
||||
"name": "Test Bundle",
|
||||
"search": "test search",
|
||||
"any_tags": "tag1 tag2",
|
||||
"all_tags": "required-tag",
|
||||
"excluded_tags": "excluded-tag",
|
||||
}
|
||||
return {**form_data, **overrides}
|
||||
|
||||
def test_should_create_new_bundle(self):
|
||||
form_data = self.create_form_data()
|
||||
|
||||
response = self.client.post(reverse("linkding:bundles.new"), form_data)
|
||||
|
||||
self.assertEqual(BookmarkBundle.objects.count(), 1)
|
||||
|
||||
bundle = BookmarkBundle.objects.first()
|
||||
self.assertEqual(bundle.owner, self.user)
|
||||
self.assertEqual(bundle.name, form_data["name"])
|
||||
self.assertEqual(bundle.search, form_data["search"])
|
||||
self.assertEqual(bundle.any_tags, form_data["any_tags"])
|
||||
self.assertEqual(bundle.all_tags, form_data["all_tags"])
|
||||
self.assertEqual(bundle.excluded_tags, form_data["excluded_tags"])
|
||||
|
||||
self.assertRedirects(response, reverse("linkding:bundles.index"))
|
||||
|
||||
def test_should_increment_order_for_subsequent_bundles(self):
|
||||
# Create first bundle
|
||||
form_data_1 = self.create_form_data({"name": "Bundle 1"})
|
||||
self.client.post(reverse("linkding:bundles.new"), form_data_1)
|
||||
bundle1 = BookmarkBundle.objects.get(name="Bundle 1")
|
||||
self.assertEqual(bundle1.order, 0)
|
||||
|
||||
# Create second bundle
|
||||
form_data_2 = self.create_form_data({"name": "Bundle 2"})
|
||||
self.client.post(reverse("linkding:bundles.new"), form_data_2)
|
||||
bundle2 = BookmarkBundle.objects.get(name="Bundle 2")
|
||||
self.assertEqual(bundle2.order, 1)
|
||||
|
||||
# Create another bundle with a higher order
|
||||
self.setup_bundle(order=5)
|
||||
|
||||
# Create third bundle
|
||||
form_data_3 = self.create_form_data({"name": "Bundle 3"})
|
||||
self.client.post(reverse("linkding:bundles.new"), form_data_3)
|
||||
bundle3 = BookmarkBundle.objects.get(name="Bundle 3")
|
||||
self.assertEqual(bundle3.order, 6)
|
||||
|
||||
def test_incrementing_order_ignores_other_user_bookmark(self):
|
||||
other_user = self.setup_user()
|
||||
self.setup_bundle(user=other_user, order=10)
|
||||
|
||||
form_data = self.create_form_data({"name": "Bundle 1"})
|
||||
self.client.post(reverse("linkding:bundles.new"), form_data)
|
||||
bundle1 = BookmarkBundle.objects.get(name="Bundle 1")
|
||||
self.assertEqual(bundle1.order, 0)
|
||||
|
||||
def test_should_return_422_with_invalid_form(self):
|
||||
form_data = self.create_form_data({"name": ""})
|
||||
response = self.client.post(reverse("linkding:bundles.new"), form_data)
|
||||
self.assertEqual(response.status_code, 422)
|
116
bookmarks/tests/test_bundles_preview_view.py
Normal file
116
bookmarks/tests/test_bundles_preview_view.py
Normal file
@@ -0,0 +1,116 @@
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
|
||||
|
||||
class BundlePreviewViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def test_preview_empty_bundle(self):
|
||||
bookmark1 = self.setup_bookmark(title="Test Bookmark 1")
|
||||
bookmark2 = self.setup_bookmark(title="Test Bookmark 2")
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Found 2 bookmarks matching this bundle")
|
||||
self.assertContains(response, bookmark1.title)
|
||||
self.assertContains(response, bookmark2.title)
|
||||
self.assertNotContains(response, "No bookmarks match the current bundle")
|
||||
|
||||
def test_preview_with_search_terms(self):
|
||||
bookmark1 = self.setup_bookmark(title="Python Programming")
|
||||
bookmark2 = self.setup_bookmark(title="JavaScript Tutorial")
|
||||
bookmark3 = self.setup_bookmark(title="Django Framework")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bundles.preview"), {"search": "python"}
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Found 1 bookmarks matching this bundle")
|
||||
self.assertContains(response, bookmark1.title)
|
||||
self.assertNotContains(response, bookmark2.title)
|
||||
self.assertNotContains(response, bookmark3.title)
|
||||
|
||||
def test_preview_no_matching_bookmarks(self):
|
||||
bookmark = self.setup_bookmark(title="Python Guide")
|
||||
|
||||
response = self.client.get(
|
||||
reverse("linkding:bundles.preview"), {"search": "nonexistent"}
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "No bookmarks match the current bundle")
|
||||
self.assertNotContains(response, bookmark.title)
|
||||
|
||||
def test_preview_renders_bookmark(self):
|
||||
tag = self.setup_tag(name="test-tag")
|
||||
bookmark = self.setup_bookmark(
|
||||
title="Test Bookmark",
|
||||
description="Test description",
|
||||
url="https://example.com/test",
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, bookmark.title)
|
||||
self.assertContains(response, bookmark.description)
|
||||
self.assertContains(response, bookmark.url)
|
||||
self.assertContains(response, "#test-tag")
|
||||
|
||||
def test_preview_renders_bookmark_in_preview_mode(self):
|
||||
tag = self.setup_tag(name="test-tag")
|
||||
self.setup_bookmark(
|
||||
title="Test Bookmark",
|
||||
description="Test description",
|
||||
url="https://example.com/test",
|
||||
tags=[tag],
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"))
|
||||
soup = self.make_soup(response.content.decode())
|
||||
|
||||
list_item = soup.select_one("li[ld-bookmark-item]")
|
||||
actions = list_item.select(".actions > *")
|
||||
self.assertEqual(len(actions), 1)
|
||||
|
||||
def test_preview_ignores_archived_bookmarks(self):
|
||||
active_bookmark = self.setup_bookmark(title="Active Bookmark")
|
||||
archived_bookmark = self.setup_bookmark(
|
||||
title="Archived Bookmark", is_archived=True
|
||||
)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Found 1 bookmarks matching this bundle")
|
||||
self.assertContains(response, active_bookmark.title)
|
||||
self.assertNotContains(response, archived_bookmark.title)
|
||||
|
||||
def test_preview_requires_authentication(self):
|
||||
self.client.logout()
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"), follow=True)
|
||||
|
||||
self.assertRedirects(
|
||||
response, f"/login/?next={reverse('linkding:bundles.preview')}"
|
||||
)
|
||||
|
||||
def test_preview_only_shows_user_bookmarks(self):
|
||||
other_user = self.setup_user()
|
||||
own_bookmark = self.setup_bookmark(title="Own Bookmark")
|
||||
other_bookmark = self.setup_bookmark(title="Other Bookmark", user=other_user)
|
||||
|
||||
response = self.client.get(reverse("linkding:bundles.preview"))
|
||||
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, "Found 1 bookmarks matching this bundle")
|
||||
self.assertContains(response, own_bookmark.title)
|
||||
self.assertNotContains(response, other_bookmark.title)
|
@@ -32,7 +32,7 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||
)
|
||||
|
||||
def assertPrevLink(self, html: str, page_number: int, href: str = None):
|
||||
href = href if href else "?page={0}".format(page_number)
|
||||
href = href if href else "http://testserver/test?page={0}".format(page_number)
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<li class="page-item">
|
||||
@@ -55,7 +55,7 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||
)
|
||||
|
||||
def assertNextLink(self, html: str, page_number: int, href: str = None):
|
||||
href = href if href else "?page={0}".format(page_number)
|
||||
href = href if href else "http://testserver/test?page={0}".format(page_number)
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<li class="page-item">
|
||||
@@ -76,7 +76,7 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||
href: str = None,
|
||||
):
|
||||
active_class = "active" if active else ""
|
||||
href = href if href else "?page={0}".format(page_number)
|
||||
href = href if href else "http://testserver/test?page={0}".format(page_number)
|
||||
self.assertInHTML(
|
||||
"""
|
||||
<li class="page-item {1}">
|
||||
@@ -164,20 +164,38 @@ class PaginationTagTest(TestCase, BookmarkFactoryMixin):
|
||||
rendered_template = self.render_template(
|
||||
100, 10, 2, url="/test?q=cake&sort=title_asc&page=2"
|
||||
)
|
||||
self.assertPrevLink(rendered_template, 1, href="?q=cake&sort=title_asc&page=1")
|
||||
self.assertPageLink(
|
||||
rendered_template, 1, False, href="?q=cake&sort=title_asc&page=1"
|
||||
self.assertPrevLink(
|
||||
rendered_template,
|
||||
1,
|
||||
href="http://testserver/test?q=cake&sort=title_asc&page=1",
|
||||
)
|
||||
self.assertPageLink(
|
||||
rendered_template, 2, True, href="?q=cake&sort=title_asc&page=2"
|
||||
rendered_template,
|
||||
1,
|
||||
False,
|
||||
href="http://testserver/test?q=cake&sort=title_asc&page=1",
|
||||
)
|
||||
self.assertPageLink(
|
||||
rendered_template,
|
||||
2,
|
||||
True,
|
||||
href="http://testserver/test?q=cake&sort=title_asc&page=2",
|
||||
)
|
||||
self.assertNextLink(
|
||||
rendered_template,
|
||||
3,
|
||||
href="http://testserver/test?q=cake&sort=title_asc&page=3",
|
||||
)
|
||||
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")
|
||||
self.assertPrevLink(rendered_template, 1, href="http://testserver/test?page=1")
|
||||
self.assertPageLink(
|
||||
rendered_template, 1, False, href="http://testserver/test?page=1"
|
||||
)
|
||||
self.assertPageLink(
|
||||
rendered_template, 2, True, href="http://testserver/test?page=2"
|
||||
)
|
||||
self.assertNextLink(rendered_template, 3, href="http://testserver/test?page=3")
|
||||
|
@@ -153,7 +153,7 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.setup_bookmark(tags=[tag1, tag2, self.setup_tag()]),
|
||||
]
|
||||
|
||||
def assertQueryResult(self, query: QuerySet, item_lists: [[any]]):
|
||||
def assertQueryResult(self, query: QuerySet, item_lists: list[list]):
|
||||
expected_items = []
|
||||
for item_list in item_lists:
|
||||
expected_items = expected_items + item_list
|
||||
@@ -1287,3 +1287,267 @@ class QueriesTestCase(TestCase, BookmarkFactoryMixin):
|
||||
search = BookmarkSearch(added_since="invalid-date")
|
||||
query = queries.query_bookmarks(self.user, self.profile, search)
|
||||
self.assertCountEqual(list(query), [older_bookmark, recent_bookmark])
|
||||
|
||||
def test_query_bookmarks_with_bundle_search_terms(self):
|
||||
bundle = self.setup_bundle(search="search_term_A search_term_B")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(
|
||||
title="search_term_A content", description="search_term_B also here"
|
||||
),
|
||||
self.setup_bookmark(url="http://example.com/search_term_A/search_term_B"),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(title="search_term_A only")
|
||||
self.setup_bookmark(description="search_term_B only")
|
||||
self.setup_bookmark(title="unrelated content")
|
||||
|
||||
query = queries.query_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_search_and_bundle_search_terms(self):
|
||||
bundle = self.setup_bundle(search="bundle_term_B")
|
||||
search = BookmarkSearch(q="search_term_A", bundle=bundle)
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(
|
||||
title="search_term_A content", description="bundle_term_B also here"
|
||||
)
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(title="search_term_A only")
|
||||
self.setup_bookmark(description="bundle_term_B only")
|
||||
self.setup_bookmark(title="unrelated content")
|
||||
|
||||
query = queries.query_bookmarks(self.user, self.profile, search)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_bundle_any_tags(self):
|
||||
bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2")
|
||||
|
||||
tag1 = self.setup_tag(name="bundleTag1")
|
||||
tag2 = self.setup_tag(name="bundleTag2")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(tags=[tag1]),
|
||||
self.setup_bookmark(tags=[tag2]),
|
||||
self.setup_bookmark(tags=[tag1, tag2]),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(tags=[other_tag])
|
||||
self.setup_bookmark()
|
||||
|
||||
query = queries.query_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_search_tags_and_bundle_any_tags(self):
|
||||
bundle = self.setup_bundle(any_tags="bundleTagA bundleTagB")
|
||||
search = BookmarkSearch(q="#searchTag1 #searchTag2", bundle=bundle)
|
||||
|
||||
search_tag1 = self.setup_tag(name="searchTag1")
|
||||
search_tag2 = self.setup_tag(name="searchTag2")
|
||||
bundle_tag_a = self.setup_tag(name="bundleTagA")
|
||||
bundle_tag_b = self.setup_tag(name="bundleTagB")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_a]),
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_b]),
|
||||
self.setup_bookmark(
|
||||
tags=[search_tag1, search_tag2, bundle_tag_a, bundle_tag_b]
|
||||
),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2, other_tag])
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2])
|
||||
self.setup_bookmark(tags=[search_tag1, bundle_tag_a])
|
||||
self.setup_bookmark(tags=[search_tag2, bundle_tag_b])
|
||||
self.setup_bookmark(tags=[bundle_tag_a])
|
||||
self.setup_bookmark(tags=[bundle_tag_b])
|
||||
self.setup_bookmark(tags=[bundle_tag_a, bundle_tag_b])
|
||||
self.setup_bookmark(tags=[other_tag])
|
||||
self.setup_bookmark()
|
||||
|
||||
query = queries.query_bookmarks(self.user, self.profile, search)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_bundle_all_tags(self):
|
||||
bundle = self.setup_bundle(all_tags="bundleTag1 bundleTag2")
|
||||
|
||||
tag1 = self.setup_tag(name="bundleTag1")
|
||||
tag2 = self.setup_tag(name="bundleTag2")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [self.setup_bookmark(tags=[tag1, tag2])]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(tags=[tag1])
|
||||
self.setup_bookmark(tags=[tag2])
|
||||
self.setup_bookmark(tags=[tag1, other_tag])
|
||||
self.setup_bookmark(tags=[other_tag])
|
||||
self.setup_bookmark()
|
||||
|
||||
query = queries.query_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_search_tags_and_bundle_all_tags(self):
|
||||
bundle = self.setup_bundle(all_tags="bundleTagA bundleTagB")
|
||||
search = BookmarkSearch(q="#searchTag1 #searchTag2", bundle=bundle)
|
||||
|
||||
search_tag1 = self.setup_tag(name="searchTag1")
|
||||
search_tag2 = self.setup_tag(name="searchTag2")
|
||||
bundle_tag_a = self.setup_tag(name="bundleTagA")
|
||||
bundle_tag_b = self.setup_tag(name="bundleTagB")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(
|
||||
tags=[search_tag1, search_tag2, bundle_tag_a, bundle_tag_b]
|
||||
)
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2, bundle_tag_a])
|
||||
self.setup_bookmark(tags=[search_tag1, bundle_tag_a, bundle_tag_b])
|
||||
self.setup_bookmark(tags=[search_tag1, search_tag2])
|
||||
self.setup_bookmark(tags=[bundle_tag_a, bundle_tag_b])
|
||||
self.setup_bookmark(tags=[search_tag1, bundle_tag_a])
|
||||
self.setup_bookmark(tags=[other_tag])
|
||||
self.setup_bookmark()
|
||||
|
||||
query = queries.query_bookmarks(self.user, self.profile, search)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_bundle_excluded_tags(self):
|
||||
bundle = self.setup_bundle(excluded_tags="excludeTag1 excludeTag2")
|
||||
|
||||
exclude_tag1 = self.setup_tag(name="excludeTag1")
|
||||
exclude_tag2 = self.setup_tag(name="excludeTag2")
|
||||
keep_tag = self.setup_tag(name="keepTag")
|
||||
keep_other_tag = self.setup_tag(name="keepOtherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(tags=[keep_tag]),
|
||||
self.setup_bookmark(tags=[keep_other_tag]),
|
||||
self.setup_bookmark(tags=[keep_tag, keep_other_tag]),
|
||||
self.setup_bookmark(),
|
||||
]
|
||||
|
||||
# Bookmarks that should not be returned
|
||||
self.setup_bookmark(tags=[exclude_tag1])
|
||||
self.setup_bookmark(tags=[exclude_tag2])
|
||||
self.setup_bookmark(tags=[exclude_tag1, keep_tag])
|
||||
self.setup_bookmark(tags=[exclude_tag2, keep_tag])
|
||||
self.setup_bookmark(tags=[exclude_tag1, exclude_tag2])
|
||||
self.setup_bookmark(tags=[exclude_tag1, exclude_tag2, keep_tag])
|
||||
|
||||
query = queries.query_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_bookmarks_with_bundle_combined_tags(self):
|
||||
bundle = self.setup_bundle(
|
||||
any_tags="anyTagA anyTagB",
|
||||
all_tags="allTag1 allTag2",
|
||||
excluded_tags="excludedTag",
|
||||
)
|
||||
|
||||
any_tag_a = self.setup_tag(name="anyTagA")
|
||||
any_tag_b = self.setup_tag(name="anyTagB")
|
||||
all_tag_1 = self.setup_tag(name="allTag1")
|
||||
all_tag_2 = self.setup_tag(name="allTag2")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
excluded_tag = self.setup_tag(name="excludedTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2]),
|
||||
self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2]),
|
||||
self.setup_bookmark(tags=[any_tag_a, any_tag_b, all_tag_1, all_tag_2]),
|
||||
self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2, other_tag]),
|
||||
self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2, other_tag]),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(tags=[any_tag_a, all_tag_1])
|
||||
self.setup_bookmark(tags=[any_tag_b, all_tag_2])
|
||||
self.setup_bookmark(tags=[any_tag_a, any_tag_b, all_tag_1])
|
||||
self.setup_bookmark(tags=[all_tag_1, all_tag_2])
|
||||
self.setup_bookmark(tags=[all_tag_1, all_tag_2, other_tag])
|
||||
self.setup_bookmark(tags=[any_tag_a])
|
||||
self.setup_bookmark(tags=[any_tag_b])
|
||||
self.setup_bookmark(tags=[all_tag_1])
|
||||
self.setup_bookmark(tags=[all_tag_2])
|
||||
self.setup_bookmark(tags=[any_tag_a, all_tag_1, all_tag_2, excluded_tag])
|
||||
self.setup_bookmark(tags=[any_tag_b, all_tag_1, all_tag_2, excluded_tag])
|
||||
self.setup_bookmark(tags=[other_tag])
|
||||
self.setup_bookmark()
|
||||
|
||||
query = queries.query_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_archived_bookmarks_with_bundle(self):
|
||||
bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2")
|
||||
|
||||
tag1 = self.setup_tag(name="bundleTag1")
|
||||
tag2 = self.setup_tag(name="bundleTag2")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(is_archived=True, tags=[tag1]),
|
||||
self.setup_bookmark(is_archived=True, tags=[tag2]),
|
||||
self.setup_bookmark(is_archived=True, tags=[tag1, tag2]),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(is_archived=True, tags=[other_tag])
|
||||
self.setup_bookmark(is_archived=True)
|
||||
self.setup_bookmark(tags=[tag1]),
|
||||
self.setup_bookmark(tags=[tag2]),
|
||||
self.setup_bookmark(tags=[tag1, tag2]),
|
||||
|
||||
query = queries.query_archived_bookmarks(
|
||||
self.user, self.profile, BookmarkSearch(q="", bundle=bundle)
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
||||
def test_query_shared_bookmarks_with_bundle(self):
|
||||
user1 = self.setup_user(enable_sharing=True)
|
||||
user2 = self.setup_user(enable_sharing=True)
|
||||
|
||||
bundle = self.setup_bundle(any_tags="bundleTag1 bundleTag2")
|
||||
|
||||
tag1 = self.setup_tag(name="bundleTag1")
|
||||
tag2 = self.setup_tag(name="bundleTag2")
|
||||
other_tag = self.setup_tag(name="otherTag")
|
||||
|
||||
matching_bookmarks = [
|
||||
self.setup_bookmark(user=user1, shared=True, tags=[tag1]),
|
||||
self.setup_bookmark(user=user2, shared=True, tags=[tag2]),
|
||||
self.setup_bookmark(user=user1, shared=True, tags=[tag1, tag2]),
|
||||
]
|
||||
|
||||
# Bookmarks that should not match
|
||||
self.setup_bookmark(user=user1, shared=True, tags=[other_tag])
|
||||
self.setup_bookmark(user=user2, shared=True)
|
||||
self.setup_bookmark(user=user1, shared=False, tags=[tag1]),
|
||||
self.setup_bookmark(user=user2, shared=False, tags=[tag2]),
|
||||
self.setup_bookmark(user=user1, shared=False, tags=[tag1, tag2]),
|
||||
|
||||
query = queries.query_shared_bookmarks(
|
||||
None, self.profile, BookmarkSearch(q="", bundle=bundle), False
|
||||
)
|
||||
self.assertQueryResult(query, [matching_bookmarks])
|
||||
|
@@ -48,6 +48,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
"items_per_page": "30",
|
||||
"sticky_pagination": False,
|
||||
"collapse_side_panel": False,
|
||||
"hide_bundles": False,
|
||||
}
|
||||
|
||||
return {**form_data, **overrides}
|
||||
@@ -119,6 +120,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
"items_per_page": "10",
|
||||
"sticky_pagination": True,
|
||||
"collapse_side_panel": True,
|
||||
"hide_bundles": True,
|
||||
}
|
||||
response = self.client.post(
|
||||
reverse("linkding:settings.update"), form_data, follow=True
|
||||
@@ -199,6 +201,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.assertEqual(
|
||||
self.user.profile.collapse_side_panel, form_data["collapse_side_panel"]
|
||||
)
|
||||
self.assertEqual(self.user.profile.hide_bundles, form_data["hide_bundles"])
|
||||
|
||||
self.assertSuccessMessage(html, "Profile updated")
|
||||
|
||||
|
@@ -6,7 +6,7 @@ from django.template import Template, RequestContext
|
||||
from django.test import TestCase, RequestFactory
|
||||
|
||||
from bookmarks.middlewares import LinkdingMiddleware
|
||||
from bookmarks.models import UserProfile
|
||||
from bookmarks.models import BookmarkSearch, UserProfile
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
from bookmarks.views import contexts
|
||||
|
||||
@@ -24,7 +24,10 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
middleware = LinkdingMiddleware(lambda r: HttpResponse())
|
||||
middleware(request)
|
||||
|
||||
tag_cloud_context = context_type(request)
|
||||
search = BookmarkSearch.from_request(
|
||||
request, request.GET, request.user_profile.search_preferences
|
||||
)
|
||||
tag_cloud_context = context_type(request, search)
|
||||
context = RequestContext(request, {"tag_cloud": tag_cloud_context})
|
||||
template_to_render = Template("{% include 'bookmarks/tag_cloud.html' %}")
|
||||
return template_to_render.render(context)
|
||||
|
@@ -38,7 +38,7 @@ class ToastsViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
|
||||
# Should render toasts container
|
||||
self.assertContains(response, '<div class="toasts">')
|
||||
self.assertContains(response, '<div class="message-list">')
|
||||
# Should render two toasts
|
||||
self.assertContains(response, '<div class="toast d-flex">', count=2)
|
||||
|
||||
@@ -50,7 +50,7 @@ class ToastsViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
|
||||
# Should not render toasts container
|
||||
self.assertContains(response, '<div class="toasts container grid-lg">', count=0)
|
||||
self.assertContains(response, '<div class="message-list">', count=0)
|
||||
# Should not render toasts
|
||||
self.assertContains(response, '<div class="toast">', count=0)
|
||||
|
||||
@@ -66,7 +66,7 @@ class ToastsViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
response = self.client.get(reverse("linkding:bookmarks.index"))
|
||||
|
||||
# Should not render toasts container
|
||||
self.assertContains(response, '<div class="toasts container grid-lg">', count=0)
|
||||
self.assertContains(response, '<div class="message-list">', count=0)
|
||||
# Should not render toasts
|
||||
self.assertContains(response, '<div class="toast">', count=0)
|
||||
|
||||
|
@@ -12,7 +12,7 @@ class UserSelectTagTest(TestCase, BookmarkFactoryMixin):
|
||||
request = rf.get(url)
|
||||
request.user = self.get_or_create_test_user()
|
||||
request.user_profile = self.get_or_create_test_user().profile
|
||||
search = BookmarkSearch.from_request(request.GET)
|
||||
search = BookmarkSearch.from_request(request, request.GET)
|
||||
context = RequestContext(
|
||||
request,
|
||||
{
|
||||
|
Reference in New Issue
Block a user