Speed up navigation (#824)

* use client-side navigation

* update tests

* add setting for enabling link prefetching

* do not prefetch bookmark details

* theme progress bar

* cleanup behaviors

* update test
This commit is contained in:
Sascha Ißbrücker
2024-09-14 11:32:19 +02:00
committed by GitHub
parent 3ae9cf0420
commit c929e8f11c
29 changed files with 283 additions and 144 deletions

View File

@@ -1,10 +1,10 @@
from django.contrib.auth.models import User
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from django.test import TransactionTestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -20,9 +20,12 @@ class BookmarkArchivedViewPerformanceTestCase(
return connections[DEFAULT_DB_ALIAS]
def test_should_not_increase_number_of_queries_per_bookmark(self):
# create global settings
GlobalSettings.get()
# create initial bookmarks
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(user=self.user, is_archived=True)
# capture number of queries
@@ -37,7 +40,7 @@ class BookmarkArchivedViewPerformanceTestCase(
# add more bookmarks
num_additional_bookmarks = 10
for index in range(num_additional_bookmarks):
for _ in range(num_additional_bookmarks):
self.setup_bookmark(user=self.user, is_archived=True)
# assert num queries doesn't increase

View File

@@ -1,10 +1,10 @@
from django.contrib.auth.models import User
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from django.test import TransactionTestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -18,9 +18,12 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM
return connections[DEFAULT_DB_ALIAS]
def test_should_not_increase_number_of_queries_per_bookmark(self):
# create global settings
GlobalSettings.get()
# create initial bookmarks
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(user=self.user)
# capture number of queries
@@ -35,7 +38,7 @@ class BookmarkIndexViewPerformanceTestCase(TransactionTestCase, BookmarkFactoryM
# add more bookmarks
num_additional_bookmarks = 10
for index in range(num_additional_bookmarks):
for _ in range(num_additional_bookmarks):
self.setup_bookmark(user=self.user)
# assert num queries doesn't increase

View File

@@ -1,10 +1,10 @@
from django.contrib.auth.models import User
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from django.test import TransactionTestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS
from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -18,9 +18,12 @@ class BookmarkSharedViewPerformanceTestCase(TransactionTestCase, BookmarkFactory
return connections[DEFAULT_DB_ALIAS]
def test_should_not_increase_number_of_queries_per_bookmark(self):
# create global settings
GlobalSettings.get()
# create initial users and bookmarks
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
user = self.setup_user(enable_sharing=True)
self.setup_bookmark(user=user, shared=True)
@@ -36,7 +39,7 @@ class BookmarkSharedViewPerformanceTestCase(TransactionTestCase, BookmarkFactory
# add more users and bookmarks
num_additional_bookmarks = 10
for index in range(num_additional_bookmarks):
for _ in range(num_additional_bookmarks):
user = self.setup_user(enable_sharing=True)
self.setup_bookmark(user=user, shared=True)

View File

@@ -5,6 +5,7 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.authtoken.models import Token
from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import LinkdingApiTestCase, BookmarkFactoryMixin
@@ -16,13 +17,16 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
)[0]
self.client.credentials(HTTP_AUTHORIZATION="Token " + self.api_token.key)
# create global settings
GlobalSettings.get()
def get_connection(self):
return connections[DEFAULT_DB_ALIAS]
def test_list_bookmarks_max_queries(self):
# set up some bookmarks with associated tags
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(tags=[self.setup_tag()])
# capture number of queries
@@ -40,7 +44,7 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
def test_list_archived_bookmarks_max_queries(self):
# set up some bookmarks with associated tags
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(is_archived=True, tags=[self.setup_tag()])
# capture number of queries
@@ -59,7 +63,7 @@ class BookmarksApiPerformanceTestCase(LinkdingApiTestCase, BookmarkFactoryMixin)
# set up some bookmarks with associated tags
share_user = self.setup_user(enable_sharing=True)
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(user=share_user, shared=True, tags=[self.setup_tag()])
# capture number of queries

View File

@@ -9,7 +9,7 @@ from django.test import TestCase, RequestFactory
from django.urls import reverse
from django.utils import timezone, formats
from bookmarks.middlewares import UserProfileMiddleware
from bookmarks.middlewares import LinkdingMiddleware
from bookmarks.models import Bookmark, UserProfile, User
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.views.partials import contexts
@@ -74,6 +74,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
f"""
<a ld-fetch="{details_modal_url}?return_url={return_url}"
ld-on="click" ld-target="body|append"
data-turbo-prefetch="false"
href="{details_url}">View</a>
""",
html,
@@ -270,7 +271,7 @@ class BookmarkListTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
rf = RequestFactory()
request = rf.get(url)
request.user = user or self.get_or_create_test_user()
middleware = UserProfileMiddleware(lambda r: HttpResponse())
middleware = LinkdingMiddleware(lambda r: HttpResponse())
middleware(request)
bookmark_list_context = context_type(request)

View File

@@ -4,7 +4,7 @@ from django.test import TestCase
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from bookmarks.models import FeedToken
from bookmarks.models import FeedToken, GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin
@@ -15,13 +15,16 @@ class FeedsPerformanceTestCase(TestCase, BookmarkFactoryMixin):
self.client.force_login(user)
self.token = FeedToken.objects.get_or_create(user=user)[0]
# create global settings
GlobalSettings.get()
def get_connection(self):
return connections[DEFAULT_DB_ALIAS]
def test_all_max_queries(self):
# set up some bookmarks with associated tags
num_initial_bookmarks = 10
for index in range(num_initial_bookmarks):
for _ in range(num_initial_bookmarks):
self.setup_bookmark(tags=[self.setup_tag()])
# capture number of queries

View File

@@ -1,16 +1,17 @@
from django.test import TestCase
from django.urls import reverse
from bookmarks.models import GlobalSettings
from bookmarks.tests.helpers import BookmarkFactoryMixin
class NavMenuTestCase(TestCase, BookmarkFactoryMixin):
class LayoutTestCase(TestCase, BookmarkFactoryMixin):
def setUp(self) -> None:
user = self.get_or_create_test_user()
self.client.force_login(user)
def test_should_respect_share_profile_setting(self):
def test_nav_menu_should_respect_share_profile_setting(self):
self.user.profile.enable_sharing = False
self.user.profile.save()
response = self.client.get(reverse("bookmarks:index"))
@@ -36,3 +37,29 @@ class NavMenuTestCase(TestCase, BookmarkFactoryMixin):
html,
count=2,
)
def test_metadata_should_respect_prefetch_links_setting(self):
settings = GlobalSettings.get()
settings.enable_link_prefetch = False
settings.save()
response = self.client.get(reverse("bookmarks:index"))
html = response.content.decode()
self.assertInHTML(
'<meta name="turbo-prefetch" content="false">',
html,
count=1,
)
settings.enable_link_prefetch = True
settings.save()
response = self.client.get(reverse("bookmarks:index"))
html = response.content.decode()
self.assertInHTML(
'<meta name="turbo-prefetch" content="false">',
html,
count=0,
)

View File

@@ -6,7 +6,7 @@ from bookmarks.tests.helpers import BookmarkFactoryMixin
from bookmarks.middlewares import standard_profile
class UserProfileMiddlewareTestCase(TestCase, BookmarkFactoryMixin):
class LinkdingMiddlewareTestCase(TestCase, BookmarkFactoryMixin):
def test_unauthenticated_user_should_use_standard_profile_by_default(self):
response = self.client.get(reverse("login"))

View File

@@ -79,6 +79,13 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
reverse("login") + "?next=" + reverse("bookmarks:settings.general"),
)
response = self.client.get(reverse("bookmarks:settings.update"), follow=True)
self.assertRedirects(
response,
reverse("login") + "?next=" + reverse("bookmarks:settings.update"),
)
def test_update_profile(self):
form_data = {
"update_profile": "",
@@ -105,7 +112,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"custom_css": "body { background-color: #000; }",
"auto_tagging_rules": "example.com tag",
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
self.user.profile.refresh_from_db()
@@ -179,7 +188,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
form_data = {
"theme": UserProfile.THEME_DARK,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
self.user.profile.refresh_from_db()
@@ -199,14 +210,14 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"enable_favicons": True,
}
)
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_favicons.assert_called_once_with(self.user)
# No update scheduled if favicons are already enabled
mock_schedule_bookmarks_without_favicons.reset_mock()
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_favicons.assert_not_called()
@@ -217,7 +228,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
}
)
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_favicons.assert_not_called()
@@ -229,7 +240,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"refresh_favicons": "",
}
response = self.client.post(
reverse("bookmarks:settings.general"), form_data
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
@@ -243,9 +254,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
tasks, "schedule_refresh_favicons"
) as mock_schedule_refresh_favicons:
form_data = {}
response = self.client.post(
reverse("bookmarks:settings.general"), form_data
)
response = self.client.post(reverse("bookmarks:settings.update"), form_data)
html = response.content.decode()
mock_schedule_refresh_favicons.assert_not_called()
@@ -315,14 +324,14 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"enable_preview_images": True,
}
)
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_previews.assert_called_once_with(self.user)
# No update scheduled if favicons are already enabled
mock_schedule_bookmarks_without_previews.reset_mock()
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_previews.assert_not_called()
@@ -333,7 +342,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
}
)
self.client.post(reverse("bookmarks:settings.general"), form_data)
self.client.post(reverse("bookmarks:settings.update"), form_data)
mock_schedule_bookmarks_without_previews.assert_not_called()
@@ -422,10 +431,11 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"create_missing_html_snapshots": "",
}
response = self.client.post(
reverse("bookmarks:settings.general"), form_data
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
self.assertEqual(response.status_code, 200)
mock_create_missing_html_snapshots.assert_called_once()
self.assertSuccessMessage(
html, "Queued 5 missing snapshots. This may take a while..."
@@ -441,10 +451,11 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"create_missing_html_snapshots": "",
}
response = self.client.post(
reverse("bookmarks:settings.general"), form_data
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
self.assertEqual(response.status_code, 200)
mock_create_missing_html_snapshots.assert_called_once()
self.assertSuccessMessage(html, "No missing snapshots found.")
@@ -457,10 +468,11 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
mock_create_missing_html_snapshots.return_value = 5
form_data = {}
response = self.client.post(
reverse("bookmarks:settings.general"), form_data
reverse("bookmarks:settings.update"), form_data, follow=True
)
html = response.content.decode()
self.assertEqual(response.status_code, 200)
mock_create_missing_html_snapshots.assert_not_called()
self.assertSuccessMessage(
html, "Queued 5 missing snapshots. This may take a while...", count=0
@@ -477,7 +489,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
"guest_profile_user": selectable_user.id,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
)
self.assertEqual(response.status_code, 200)
self.assertSuccessMessage(response.content.decode(), "Global settings updated")
@@ -491,7 +505,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"landing_page": GlobalSettings.LANDING_PAGE_LOGIN,
"guest_profile_user": "",
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
)
self.assertEqual(response.status_code, 200)
self.assertSuccessMessage(response.content.decode(), "Global settings updated")
@@ -509,7 +525,9 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
form_data = {
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(
reverse("bookmarks:settings.update"), form_data, follow=True
)
self.assertEqual(response.status_code, 200)
self.assertSuccessMessage(
response.content.decode(), "Global settings updated", count=0
@@ -520,7 +538,7 @@ class SettingsGeneralViewTestCase(TestCase, BookmarkFactoryMixin):
"update_global_settings": "",
"landing_page": GlobalSettings.LANDING_PAGE_SHARED_BOOKMARKS,
}
response = self.client.post(reverse("bookmarks:settings.general"), form_data)
response = self.client.post(reverse("bookmarks:settings.update"), form_data)
self.assertEqual(response.status_code, 403)
def test_global_settings_only_visible_for_superuser(self):

View File

@@ -68,17 +68,18 @@ class SettingsIntegrationsViewTestCase(TestCase, BookmarkFactoryMixin):
token = FeedToken.objects.first()
self.assertInHTML(
f'<a href="http://testserver/feeds/{token.key}/all">All bookmarks</a>', html
)
self.assertInHTML(
f'<a href="http://testserver/feeds/{token.key}/unread">Unread bookmarks</a>',
f'<a target="_blank" href="http://testserver/feeds/{token.key}/all">All bookmarks</a>',
html,
)
self.assertInHTML(
f'<a href="http://testserver/feeds/{token.key}/shared">Shared bookmarks</a>',
f'<a target="_blank" href="http://testserver/feeds/{token.key}/unread">Unread bookmarks</a>',
html,
)
self.assertInHTML(
f'<a href="http://testserver/feeds/shared">Public shared bookmarks</a>',
f'<a target="_blank" href="http://testserver/feeds/{token.key}/shared">Shared bookmarks</a>',
html,
)
self.assertInHTML(
'<a target="_blank" href="http://testserver/feeds/shared">Public shared bookmarks</a>',
html,
)

View File

@@ -5,7 +5,7 @@ from django.http import HttpResponse
from django.template import Template, RequestContext
from django.test import TestCase, RequestFactory
from bookmarks.middlewares import UserProfileMiddleware
from bookmarks.middlewares import LinkdingMiddleware
from bookmarks.models import UserProfile
from bookmarks.tests.helpers import BookmarkFactoryMixin, HtmlTestMixin
from bookmarks.views.partials import contexts
@@ -21,7 +21,7 @@ class TagCloudTemplateTest(TestCase, BookmarkFactoryMixin, HtmlTestMixin):
rf = RequestFactory()
request = rf.get(url)
request.user = user or self.get_or_create_test_user()
middleware = UserProfileMiddleware(lambda r: HttpResponse())
middleware = LinkdingMiddleware(lambda r: HttpResponse())
middleware(request)
tag_cloud_context = context_type(request)