from typing import List from django.urls import reverse from playwright.sync_api import sync_playwright, expect from bookmarks.e2e.helpers import LinkdingE2ETestCase class BookmarkPagePartialUpdatesE2ETestCase(LinkdingE2ETestCase): def setup_fixture(self): profile = self.get_or_create_test_user().profile profile.enable_sharing = True profile.save() # create a number of bookmarks with different states / visibility to # verify correct data is loaded on update self.setup_numbered_bookmarks(3, with_tags=True) self.setup_numbered_bookmarks(3, with_tags=True, archived=True) self.setup_numbered_bookmarks(3, shared=True, prefix="Joe's Bookmark", user=self.setup_user(enable_sharing=True)) def assertVisibleBookmarks(self, titles: List[str]): bookmark_tags = self.page.locator('li[ld-bookmark-item]') expect(bookmark_tags).to_have_count(len(titles)) for title in titles: matching_tag = bookmark_tags.filter(has_text=title) expect(matching_tag).to_be_visible() def assertVisibleTags(self, titles: List[str]): tag_tags = self.page.locator('.tag-cloud .unselected-tags a') expect(tag_tags).to_have_count(len(titles)) for title in titles: matching_tag = tag_tags.filter(has_text=title) expect(matching_tag).to_be_visible() def test_partial_update_respects_query(self): self.setup_numbered_bookmarks(5, prefix='foo') self.setup_numbered_bookmarks(5, prefix='bar') with sync_playwright() as p: url = reverse('bookmarks:index') + '?q=foo' self.open(url, p) self.assertVisibleBookmarks(['foo 1', 'foo 2', 'foo 3', 'foo 4', 'foo 5']) self.locate_bookmark('foo 2').get_by_text('Archive').click() self.assertVisibleBookmarks(['foo 1', 'foo 3', 'foo 4', 'foo 5']) def test_partial_update_respects_sort(self): self.setup_numbered_bookmarks(5, prefix='foo') with sync_playwright() as p: url = reverse('bookmarks:index') + '?sort=title_asc' page = self.open(url, p) first_item = page.locator('li[ld-bookmark-item]').first expect(first_item).to_contain_text('foo 1') first_item.get_by_text('Archive').click() first_item = page.locator('li[ld-bookmark-item]').first expect(first_item).to_contain_text('foo 2') def test_partial_update_respects_page(self): # add a suffix, otherwise 'foo 1' also matches 'foo 10' self.setup_numbered_bookmarks(50, prefix='foo', suffix='-') with sync_playwright() as p: url = reverse('bookmarks:index') + '?q=foo&page=2' self.open(url, p) # with descending sort, page two has 'foo 1' to 'foo 20' expected_titles = [f'foo {i}-' for i in range(1, 21)] self.assertVisibleBookmarks(expected_titles) self.locate_bookmark('foo 20-').get_by_text('Archive').click() expected_titles = [f'foo {i}-' for i in range(1, 20)] self.assertVisibleBookmarks(expected_titles) def test_multiple_partial_updates(self): self.setup_numbered_bookmarks(5) with sync_playwright() as p: url = reverse('bookmarks:index') self.open(url, p) self.locate_bookmark('Bookmark 1').get_by_text('Archive').click() self.assertVisibleBookmarks(['Bookmark 2', 'Bookmark 3', 'Bookmark 4', 'Bookmark 5']) self.locate_bookmark('Bookmark 2').get_by_text('Archive').click() self.assertVisibleBookmarks(['Bookmark 3', 'Bookmark 4', 'Bookmark 5']) self.locate_bookmark('Bookmark 3').get_by_text('Archive').click() self.assertVisibleBookmarks(['Bookmark 4', 'Bookmark 5']) self.assertReloads(0) def test_active_bookmarks_partial_update_on_archive(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) self.locate_bookmark('Bookmark 2').get_by_text('Archive').click() self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) self.assertVisibleTags(['Tag 1', 'Tag 3']) self.assertReloads(0) def test_active_bookmarks_partial_update_on_delete(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) self.locate_bookmark('Bookmark 2').get_by_text('Remove').click() self.locate_bookmark('Bookmark 2').get_by_text('Confirm').click() self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) self.assertVisibleTags(['Tag 1', 'Tag 3']) self.assertReloads(0) def test_active_bookmarks_partial_update_on_mark_as_read(self): self.setup_fixture() bookmark2 = self.get_numbered_bookmark('Bookmark 2') bookmark2.unread = True bookmark2.save() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) expect(self.locate_bookmark('Bookmark 2')).to_have_class('unread') self.locate_bookmark('Bookmark 2').get_by_text('Unread').click() self.locate_bookmark('Bookmark 2').get_by_text('Yes').click() expect(self.locate_bookmark('Bookmark 2')).not_to_have_class('unread') self.assertReloads(0) def test_active_bookmarks_partial_update_on_unshare(self): self.setup_fixture() bookmark2 = self.get_numbered_bookmark('Bookmark 2') bookmark2.shared = True bookmark2.save() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) expect(self.locate_bookmark('Bookmark 2')).to_have_class('shared') self.locate_bookmark('Bookmark 2').get_by_text('Shared').click() self.locate_bookmark('Bookmark 2').get_by_text('Yes').click() expect(self.locate_bookmark('Bookmark 2')).not_to_have_class('shared') self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_archive(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) self.locate_bulk_edit_toggle().click() self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() self.select_bulk_action('Archive') self.locate_bulk_edit_bar().get_by_text('Execute').click() self.locate_bulk_edit_bar().get_by_text('Confirm').click() self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) self.assertVisibleTags(['Tag 1', 'Tag 3']) self.assertReloads(0) def test_active_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:index'), p) self.locate_bulk_edit_toggle().click() self.locate_bookmark('Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() self.select_bulk_action('Delete') self.locate_bulk_edit_bar().get_by_text('Execute').click() self.locate_bulk_edit_bar().get_by_text('Confirm').click() self.assertVisibleBookmarks(['Bookmark 1', 'Bookmark 3']) self.assertVisibleTags(['Tag 1', 'Tag 3']) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:archived'), p) self.locate_bookmark('Archived Bookmark 2').get_by_text('Unarchive').click() self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_delete(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:archived'), p) self.locate_bookmark('Archived Bookmark 2').get_by_text('Remove').click() self.locate_bookmark('Archived Bookmark 2').get_by_text('Confirm').click() self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_unarchive(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:archived'), p) self.locate_bulk_edit_toggle().click() self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() self.select_bulk_action('Unarchive') self.locate_bulk_edit_bar().get_by_text('Execute').click() self.locate_bulk_edit_bar().get_by_text('Confirm').click() self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) self.assertReloads(0) def test_archived_bookmarks_partial_update_on_bulk_delete(self): self.setup_fixture() with sync_playwright() as p: self.open(reverse('bookmarks:archived'), p) self.locate_bulk_edit_toggle().click() self.locate_bookmark('Archived Bookmark 2').locator('label[ld-bulk-edit-checkbox]').click() self.select_bulk_action('Delete') self.locate_bulk_edit_bar().get_by_text('Execute').click() self.locate_bulk_edit_bar().get_by_text('Confirm').click() self.assertVisibleBookmarks(['Archived Bookmark 1', 'Archived Bookmark 3']) self.assertVisibleTags(['Archived Tag 1', 'Archived Tag 3']) self.assertReloads(0) def test_shared_bookmarks_partial_update_on_unarchive(self): self.setup_fixture() self.setup_numbered_bookmarks(3, shared=True, prefix="My Bookmark", with_tags=True) with sync_playwright() as p: self.open(reverse('bookmarks:shared'), p) self.locate_bookmark('My Bookmark 2').get_by_text('Archive').click() # Shared bookmarks page also shows archived bookmarks, though it probably shouldn't self.assertVisibleBookmarks([ 'My Bookmark 1', 'My Bookmark 2', 'My Bookmark 3', "Joe's Bookmark 1", "Joe's Bookmark 2", "Joe's Bookmark 3", ]) self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 2', 'Shared Tag 3']) self.assertReloads(0) def test_shared_bookmarks_partial_update_on_delete(self): self.setup_fixture() self.setup_numbered_bookmarks(3, shared=True, prefix="My Bookmark", with_tags=True) with sync_playwright() as p: self.open(reverse('bookmarks:shared'), p) self.locate_bookmark('My Bookmark 2').get_by_text('Remove').click() self.locate_bookmark('My Bookmark 2').get_by_text('Confirm').click() self.assertVisibleBookmarks([ 'My Bookmark 1', 'My Bookmark 3', "Joe's Bookmark 1", "Joe's Bookmark 2", "Joe's Bookmark 3", ]) self.assertVisibleTags(['Shared Tag 1', 'Shared Tag 3']) self.assertReloads(0)