mirror of
https://github.com/sissbruecker/linkding.git
synced 2025-08-13 05:29:26 +02:00
Display selected tags in tag cloud (#307)
* Add links to remove tags from current query * Display selected tags in tag cloud * Add tag cloud tests * Fix tag cloud in archive * Add tests for bookmark views * Expose parse query string * Improve tag cloud tests * Cleanup * Fix rebase issues * Ignore casing when removing tags from query Co-authored-by: Jon Hauris <jonp@hauris.org>
This commit is contained in:
@@ -2,6 +2,7 @@ import random
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils import timezone
|
||||
from django.utils.crypto import get_random_string
|
||||
@@ -80,6 +81,11 @@ class BookmarkFactoryMixin:
|
||||
return user
|
||||
|
||||
|
||||
class HtmlTestMixin:
|
||||
def make_soup(self, html: str):
|
||||
return BeautifulSoup(html, features="html.parser")
|
||||
|
||||
|
||||
class LinkdingApiTestCase(APITestCase):
|
||||
def get(self, url, expected_status_code=status.HTTP_200_OK):
|
||||
response = self.client.get(url)
|
||||
|
@@ -1,18 +1,20 @@
|
||||
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, Tag, UserProfile
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
|
||||
|
||||
class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def assertVisibleBookmarks(self, response, bookmarks: [Bookmark], link_target: str = '_blank'):
|
||||
def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
|
||||
html = response.content.decode()
|
||||
self.assertContains(response, 'data-is-bookmark-item', count=len(bookmarks))
|
||||
|
||||
@@ -22,7 +24,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
html
|
||||
)
|
||||
|
||||
def assertInvisibleBookmarks(self, response, bookmarks: [Bookmark], link_target: str = '_blank'):
|
||||
def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
|
||||
html = response.content.decode()
|
||||
|
||||
for bookmark in bookmarks:
|
||||
@@ -32,16 +34,27 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
count=0
|
||||
)
|
||||
|
||||
def assertVisibleTags(self, response, tags: [Tag]):
|
||||
def assertVisibleTags(self, response, tags: List[Tag]):
|
||||
self.assertContains(response, 'data-is-tag-item', count=len(tags))
|
||||
|
||||
for tag in tags:
|
||||
self.assertContains(response, tag.name)
|
||||
|
||||
def assertInvisibleTags(self, response, tags: [Tag]):
|
||||
def assertInvisibleTags(self, response, tags: List[Tag]):
|
||||
for tag in tags:
|
||||
self.assertNotContains(response, tag.name)
|
||||
|
||||
def assertSelectedTags(self, response, tags: List[Tag]):
|
||||
soup = self.make_soup(response.content.decode())
|
||||
selected_tags = soup.select('p.selected-tags')[0]
|
||||
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 test_should_list_archived_and_user_owned_bookmarks(self):
|
||||
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||
visible_bookmarks = [
|
||||
@@ -119,7 +132,7 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.setup_tag(),
|
||||
]
|
||||
|
||||
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]], title='searchvalue'),
|
||||
self.setup_bookmark(is_archived=True, tags=[visible_tags[0]], title='searchvalue')
|
||||
self.setup_bookmark(is_archived=True, tags=[visible_tags[1]], title='searchvalue')
|
||||
self.setup_bookmark(is_archived=True, tags=[visible_tags[2]], title='searchvalue')
|
||||
self.setup_bookmark(is_archived=True, tags=[invisible_tags[0]])
|
||||
@@ -131,6 +144,20 @@ class BookmarkArchivedViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_display_selected_tags_from_query(self):
|
||||
tags = [
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
]
|
||||
self.setup_bookmark(is_archived=True, tags=tags)
|
||||
|
||||
response = self.client.get(reverse('bookmarks:archived') + f'?q=%23{tags[0].name}+%23{tags[1].name}')
|
||||
|
||||
self.assertSelectedTags(response, [tags[0], tags[1]])
|
||||
|
||||
def test_should_open_bookmarks_in_new_page_by_default(self):
|
||||
visible_bookmarks = [
|
||||
self.setup_bookmark(is_archived=True),
|
||||
|
@@ -1,3 +1,4 @@
|
||||
from typing import List
|
||||
import urllib.parse
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
@@ -5,16 +6,16 @@ from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
from bookmarks.models import Bookmark, Tag, UserProfile
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
|
||||
|
||||
class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
|
||||
def setUp(self) -> None:
|
||||
user = self.get_or_create_test_user()
|
||||
self.client.force_login(user)
|
||||
|
||||
def assertVisibleBookmarks(self, response, bookmarks: [Bookmark], link_target: str = '_blank'):
|
||||
def assertVisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
|
||||
html = response.content.decode()
|
||||
self.assertContains(response, 'data-is-bookmark-item', count=len(bookmarks))
|
||||
|
||||
@@ -24,7 +25,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
html
|
||||
)
|
||||
|
||||
def assertInvisibleBookmarks(self, response, bookmarks: [Bookmark], link_target: str = '_blank'):
|
||||
def assertInvisibleBookmarks(self, response, bookmarks: List[Bookmark], link_target: str = '_blank'):
|
||||
html = response.content.decode()
|
||||
|
||||
for bookmark in bookmarks:
|
||||
@@ -34,16 +35,27 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
count=0
|
||||
)
|
||||
|
||||
def assertVisibleTags(self, response, tags: [Tag]):
|
||||
def assertVisibleTags(self, response, tags: List[Tag]):
|
||||
self.assertContains(response, 'data-is-tag-item', count=len(tags))
|
||||
|
||||
for tag in tags:
|
||||
self.assertContains(response, tag.name)
|
||||
|
||||
def assertInvisibleTags(self, response, tags: [Tag]):
|
||||
def assertInvisibleTags(self, response, tags: List[Tag]):
|
||||
for tag in tags:
|
||||
self.assertNotContains(response, tag.name)
|
||||
|
||||
def assertSelectedTags(self, response, tags: List[Tag]):
|
||||
soup = self.make_soup(response.content.decode())
|
||||
selected_tags = soup.select('p.selected-tags')[0]
|
||||
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 test_should_list_unarchived_and_user_owned_bookmarks(self):
|
||||
other_user = User.objects.create_user('otheruser', 'otheruser@example.com', 'password123')
|
||||
visible_bookmarks = [
|
||||
@@ -121,7 +133,7 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.setup_tag(),
|
||||
]
|
||||
|
||||
self.setup_bookmark(tags=[visible_tags[0]], title='searchvalue'),
|
||||
self.setup_bookmark(tags=[visible_tags[0]], title='searchvalue')
|
||||
self.setup_bookmark(tags=[visible_tags[1]], title='searchvalue')
|
||||
self.setup_bookmark(tags=[visible_tags[2]], title='searchvalue')
|
||||
self.setup_bookmark(tags=[invisible_tags[0]])
|
||||
@@ -133,6 +145,20 @@ class BookmarkIndexViewTestCase(TestCase, BookmarkFactoryMixin):
|
||||
self.assertVisibleTags(response, visible_tags)
|
||||
self.assertInvisibleTags(response, invisible_tags)
|
||||
|
||||
def test_should_display_selected_tags_from_query(self):
|
||||
tags = [
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
self.setup_tag(),
|
||||
]
|
||||
self.setup_bookmark(tags=tags)
|
||||
|
||||
response = self.client.get(reverse('bookmarks:index') + f'?q=%23{tags[0].name}+%23{tags[1].name}')
|
||||
|
||||
self.assertSelectedTags(response, [tags[0], tags[1]])
|
||||
|
||||
def test_should_open_bookmarks_in_new_page_by_default(self):
|
||||
visible_bookmarks = [
|
||||
self.setup_bookmark(),
|
||||
|
@@ -1,27 +1,27 @@
|
||||
from typing import List
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from django.template import Template, RequestContext
|
||||
from django.test import TestCase, RequestFactory
|
||||
|
||||
from bookmarks.models import Tag, User
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin
|
||||
from bookmarks.models import Tag
|
||||
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
|
||||
|
||||
|
||||
class TagCloudTagTest(TestCase, BookmarkFactoryMixin):
|
||||
def make_soup(self, html: str):
|
||||
return BeautifulSoup(html, features="html.parser")
|
||||
class TagCloudTagTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
|
||||
def render_template(self, tags: List[Tag], selected_tags: List[Tag] = None, url: str = '/test'):
|
||||
if not selected_tags:
|
||||
selected_tags = []
|
||||
|
||||
def render_template(self, tags: List[Tag], url: str = '/test'):
|
||||
rf = RequestFactory()
|
||||
request = rf.get(url)
|
||||
context = RequestContext(request, {
|
||||
'request': request,
|
||||
'tags': tags,
|
||||
'selected_tags': selected_tags,
|
||||
})
|
||||
template_to_render = Template(
|
||||
'{% load bookmarks %}'
|
||||
'{% tag_cloud tags %}'
|
||||
'{% tag_cloud tags selected_tags %}'
|
||||
)
|
||||
return template_to_render.render(context)
|
||||
|
||||
@@ -41,6 +41,11 @@ class TagCloudTagTest(TestCase, BookmarkFactoryMixin):
|
||||
link_element = link_elements[tag_index]
|
||||
self.assertEqual(link_element.text.strip(), tag)
|
||||
|
||||
def assertNumSelectedTags(self, rendered_template: str, count: int):
|
||||
soup = self.make_soup(rendered_template)
|
||||
link_elements = soup.select('p.selected-tags a')
|
||||
self.assertEqual(len(link_elements), count)
|
||||
|
||||
def test_group_alphabetically(self):
|
||||
tags = [
|
||||
self.setup_tag(name='Cockatoo'),
|
||||
@@ -75,14 +80,10 @@ class TagCloudTagTest(TestCase, BookmarkFactoryMixin):
|
||||
])
|
||||
|
||||
def test_no_duplicate_tag_names(self):
|
||||
user1 = User.objects.create_user('user1', 'user1@example.com', 'password123')
|
||||
user2 = User.objects.create_user('user2', 'user2@example.com', 'password123')
|
||||
user3 = User.objects.create_user('user3', 'user3@example.com', 'password123')
|
||||
|
||||
tags = [
|
||||
self.setup_tag(name='shared', user=user1),
|
||||
self.setup_tag(name='shared', user=user2),
|
||||
self.setup_tag(name='shared', user=user3),
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
]
|
||||
|
||||
rendered_template = self.render_template(tags)
|
||||
@@ -92,3 +93,88 @@ class TagCloudTagTest(TestCase, BookmarkFactoryMixin):
|
||||
'shared',
|
||||
],
|
||||
])
|
||||
|
||||
def test_selected_tags(self):
|
||||
tags = [
|
||||
self.setup_tag(name='tag1'),
|
||||
self.setup_tag(name='tag2'),
|
||||
]
|
||||
|
||||
rendered_template = self.render_template(tags, tags, url='/test?q=%23tag1 %23tag2')
|
||||
|
||||
self.assertNumSelectedTags(rendered_template, 2)
|
||||
|
||||
self.assertInHTML('''
|
||||
<a href="?q=%23tag2"
|
||||
class="text-bold mr-2">
|
||||
<span>-tag1</span>
|
||||
</a>
|
||||
''', rendered_template)
|
||||
|
||||
self.assertInHTML('''
|
||||
<a href="?q=%23tag1"
|
||||
class="text-bold mr-2">
|
||||
<span>-tag2</span>
|
||||
</a>
|
||||
''', rendered_template)
|
||||
|
||||
def test_selected_tags_ignore_casing_when_removing_query_part(self):
|
||||
tags = [
|
||||
self.setup_tag(name='TEST'),
|
||||
]
|
||||
|
||||
rendered_template = self.render_template(tags, tags, url='/test?q=%23test')
|
||||
|
||||
self.assertInHTML('''
|
||||
<a href="?q="
|
||||
class="text-bold mr-2">
|
||||
<span>-TEST</span>
|
||||
</a>
|
||||
''', rendered_template)
|
||||
|
||||
def test_no_duplicate_selected_tags(self):
|
||||
tags = [
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
self.setup_tag(name='shared', user=self.setup_user()),
|
||||
]
|
||||
|
||||
rendered_template = self.render_template(tags, tags, url='/test?q=%23shared')
|
||||
|
||||
self.assertInHTML('''
|
||||
<a href="?q="
|
||||
class="text-bold mr-2">
|
||||
<span>-shared</span>
|
||||
</a>
|
||||
''', rendered_template, count=1)
|
||||
|
||||
def test_selected_tag_url_keeps_other_search_terms(self):
|
||||
tag = self.setup_tag(name='tag1')
|
||||
|
||||
rendered_template = self.render_template([tag], [tag], url='/test?q=term1 %23tag1 term2 %21untagged')
|
||||
|
||||
self.assertInHTML('''
|
||||
<a href="?q=term1+term2+%21untagged"
|
||||
class="text-bold mr-2">
|
||||
<span>-tag1</span>
|
||||
</a>
|
||||
''', rendered_template)
|
||||
|
||||
def test_selected_tags_are_excluded_from_groups(self):
|
||||
tags = [
|
||||
self.setup_tag(name='tag1'),
|
||||
self.setup_tag(name='tag2'),
|
||||
self.setup_tag(name='tag3'),
|
||||
self.setup_tag(name='tag4'),
|
||||
self.setup_tag(name='tag5'),
|
||||
]
|
||||
selected_tags = [
|
||||
tags[0],
|
||||
tags[1],
|
||||
]
|
||||
|
||||
rendered_template = self.render_template(tags, selected_tags)
|
||||
|
||||
self.assertTagGroups(rendered_template, [
|
||||
['tag3', 'tag4', 'tag5']
|
||||
])
|
||||
|
Reference in New Issue
Block a user