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:
Sascha Ißbrücker
2022-08-04 20:31:24 +02:00
committed by GitHub
parent fec966f687
commit dd5e65ecd7
14 changed files with 271 additions and 66 deletions

View File

@@ -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)

View File

@@ -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),

View File

@@ -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(),

View File

@@ -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']
])