fix: restore page navigation state (#933)

* refactor: store browsing history for navigation purposes

* refactor: remove page_size from FilterState

* refactor: move on from the term "filter" in favor of "BrowsingState"

* fix: refactors didn't propagate to the tests

* fix: ruff complaints

* fix: remaing refactoring errors

* fix: navigation works again

* fix: also store and restore query
This commit is contained in:
Jann Stute
2025-06-04 09:29:07 +02:00
committed by GitHub
parent 1e783a5e3c
commit cf6c56c9d2
12 changed files with 196 additions and 155 deletions

View File

@@ -3,7 +3,7 @@ from tempfile import TemporaryDirectory
import pytest
from tagstudio.core.library.alchemy.enums import FilterState
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.utils.missing_files import MissingRegistry
@@ -28,5 +28,5 @@ def test_refresh_missing_files(library: Library):
assert list(registry.fix_unlinked_entries()) == [0, 1]
# `bar.md` should be relinked to new correct path
results = library.search_library(FilterState.from_path("bar.md", page_size=500))
results = library.search_library(BrowsingState.from_path("bar.md"), page_size=500)
assert results[0].path == Path("bar.md")

View File

@@ -135,7 +135,7 @@ def test_title_update(
qt_driver.folders_to_tags_action = QAction(menu_bar)
# Trigger the update
qt_driver.init_library(library_dir, open_status)
qt_driver._init_library(library_dir, open_status)
# Assert the title is updated correctly
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(library_dir, base_title))

View File

@@ -1,6 +1,6 @@
from typing import TYPE_CHECKING
from tagstudio.core.library.alchemy.enums import FilterState
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.json.library import ItemType
from tagstudio.qt.widgets.item_thumb import ItemThumb
@@ -66,7 +66,7 @@ if TYPE_CHECKING:
# assert qt_driver.selected == [0, 1, 2]
def test_library_state_update(qt_driver: "QtDriver"):
def test_browsing_state_update(qt_driver: "QtDriver"):
# Given
for entry in qt_driver.lib.get_entries(with_joins=True):
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))
@@ -74,27 +74,25 @@ def test_library_state_update(qt_driver: "QtDriver"):
qt_driver.frame_content.append(entry)
# no filter, both items are returned
qt_driver.filter_items()
qt_driver.update_browsing_state()
assert len(qt_driver.frame_content) == 2
# filter by tag
state = FilterState.from_tag_name("foo", page_size=10)
qt_driver.filter_items(state)
assert qt_driver.filter.page_size == 10
state = BrowsingState.from_tag_name("foo")
qt_driver.update_browsing_state(state)
assert len(qt_driver.frame_content) == 1
entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0])
assert list(entry.tags)[0].name == "foo"
# When state is not changed, previous one is still applied
qt_driver.filter_items()
assert qt_driver.filter.page_size == 10
qt_driver.update_browsing_state()
assert len(qt_driver.frame_content) == 1
entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0])
assert list(entry.tags)[0].name == "foo"
# When state property is changed, previous one is overwritten
state = FilterState.from_path("*bar.md", page_size=qt_driver.settings.page_size)
qt_driver.filter_items(state)
state = BrowsingState.from_path("*bar.md")
qt_driver.update_browsing_state(state)
assert len(qt_driver.frame_content) == 1
entry = qt_driver.lib.get_entry_full(qt_driver.frame_content[0])
assert list(entry.tags)[0].name == "bar"

View File

@@ -4,7 +4,7 @@ from tempfile import TemporaryDirectory
import pytest
from tagstudio.core.enums import DefaultEnum, LibraryPrefs
from tagstudio.core.library.alchemy.enums import FilterState
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.fields import TextField, _FieldID
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry, Tag
@@ -123,7 +123,8 @@ def test_library_search(library: Library, generate_tag, entry_full):
tag = list(entry_full.tags)[0]
results = library.search_library(
FilterState.from_tag_name(tag.name, page_size=500),
BrowsingState.from_tag_name(tag.name),
page_size=500,
)
assert results.total_count == 1
@@ -152,7 +153,7 @@ def test_entries_count(library: Library):
new_ids = library.add_entries(entries)
assert len(new_ids) == 10
results = library.search_library(FilterState.show_all(page_size=5))
results = library.search_library(BrowsingState.show_all(), page_size=5)
assert results.total_count == 12
assert len(results) == 5
@@ -199,9 +200,7 @@ def test_search_filter_extensions(library: Library, is_exclude: bool):
library.set_prefs(LibraryPrefs.EXTENSION_LIST, ["md"])
# When
results = library.search_library(
FilterState.show_all(page_size=500),
)
results = library.search_library(BrowsingState.show_all(), page_size=500)
# Then
assert results.total_count == 1
@@ -221,7 +220,8 @@ def test_search_library_case_insensitive(library: Library):
# When
results = library.search_library(
FilterState.from_tag_name(tag.name.upper(), page_size=500),
BrowsingState.from_tag_name(tag.name.upper()),
page_size=500,
)
# Then
@@ -443,100 +443,102 @@ def test_library_prefs_multiple_identical_vals():
def test_path_search_ilike(library: Library):
results = library.search_library(FilterState.from_path("bar.md", page_size=500))
results = library.search_library(BrowsingState.from_path("bar.md"), page_size=500)
assert results.total_count == 1
assert len(results.items) == 1
def test_path_search_like(library: Library):
results = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
results = library.search_library(BrowsingState.from_path("BAR.MD"), page_size=500)
assert results.total_count == 0
assert len(results.items) == 0
def test_path_search_default_with_sep(library: Library):
results = library.search_library(FilterState.from_path("one/two", page_size=500))
results = library.search_library(BrowsingState.from_path("one/two"), page_size=500)
assert results.total_count == 1
assert len(results.items) == 1
def test_path_search_glob_after(library: Library):
results = library.search_library(FilterState.from_path("foo*", page_size=500))
results = library.search_library(BrowsingState.from_path("foo*"), page_size=500)
assert results.total_count == 1
assert len(results.items) == 1
def test_path_search_glob_in_front(library: Library):
results = library.search_library(FilterState.from_path("*bar.md", page_size=500))
results = library.search_library(BrowsingState.from_path("*bar.md"), page_size=500)
assert results.total_count == 1
assert len(results.items) == 1
def test_path_search_glob_both_sides(library: Library):
results = library.search_library(FilterState.from_path("*one/two*", page_size=500))
results = library.search_library(BrowsingState.from_path("*one/two*"), page_size=500)
assert results.total_count == 1
assert len(results.items) == 1
# TODO: deduplicate this code with pytest parametrisation or a for loop
def test_path_search_ilike_glob_equality(library: Library):
results_ilike = library.search_library(FilterState.from_path("one/two", page_size=500))
results_glob = library.search_library(FilterState.from_path("*one/two*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("one/two"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*one/two*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("bar.md"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*bar.md*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("bar", page_size=500))
results_glob = library.search_library(FilterState.from_path("*bar*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("bar"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*bar*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("bar.md"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*bar.md*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
# TODO: isn't this the exact same as the one before?
def test_path_search_like_glob_equality(library: Library):
results_ilike = library.search_library(FilterState.from_path("ONE/two", page_size=500))
results_glob = library.search_library(FilterState.from_path("*ONE/two*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("ONE/two"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*ONE/two*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
results_glob = library.search_library(FilterState.from_path("*BAR.MD*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("BAR.MD"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*BAR.MD*"), page_size=500)
assert [e.id for e in results_ilike.items] == [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("BAR.MD", page_size=500))
results_glob = library.search_library(FilterState.from_path("*bar.md*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("BAR.MD"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*bar.md*"), page_size=500)
assert [e.id for e in results_ilike.items] != [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
results_ilike = library.search_library(FilterState.from_path("bar.md", page_size=500))
results_glob = library.search_library(FilterState.from_path("*BAR.MD*", page_size=500))
results_ilike = library.search_library(BrowsingState.from_path("bar.md"), page_size=500)
results_glob = library.search_library(BrowsingState.from_path("*BAR.MD*"), page_size=500)
assert [e.id for e in results_ilike.items] != [e.id for e in results_glob.items]
results_ilike, results_glob = None, None
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("md", 1), ("txt", 1), ("png", 0)])
def test_filetype_search(library: Library, filetype, num_of_filetype):
results = library.search_library(FilterState.from_filetype(filetype, page_size=500))
results = library.search_library(BrowsingState.from_filetype(filetype), page_size=500)
assert len(results.items) == num_of_filetype
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("png", 2), ("apng", 1), ("ng", 0)])
def test_filetype_return_one_filetype(file_mediatypes_library: Library, filetype, num_of_filetype):
results = file_mediatypes_library.search_library(
FilterState.from_filetype(filetype, page_size=500)
BrowsingState.from_filetype(filetype), page_size=500
)
assert len(results.items) == num_of_filetype
@pytest.mark.parametrize(["mediatype", "num_of_mediatype"], [("plaintext", 2), ("image", 0)])
def test_mediatype_search(library: Library, mediatype, num_of_mediatype):
results = library.search_library(FilterState.from_mediatype(mediatype, page_size=500))
results = library.search_library(BrowsingState.from_mediatype(mediatype), page_size=500)
assert len(results.items) == num_of_mediatype

View File

@@ -1,12 +1,12 @@
import pytest
from tagstudio.core.library.alchemy.enums import FilterState
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.query_lang.util import ParsingError
def verify_count(lib: Library, query: str, count: int):
results = lib.search_library(FilterState.from_search_query(query, page_size=500))
results = lib.search_library(BrowsingState.from_search_query(query), page_size=500)
assert results.total_count == count
assert len(results.items) == count
@@ -136,4 +136,4 @@ def test_parent_tags(search_library: Library, query: str, count: int):
)
def test_syntax(search_library: Library, invalid_query: str):
with pytest.raises(ParsingError) as e_info: # noqa: F841
search_library.search_library(FilterState.from_search_query(invalid_query, page_size=500))
search_library.search_library(BrowsingState.from_search_query(invalid_query), page_size=500)