mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
ci(tests): fix broken tests and add type hints (#1062)
* ci: expand pyright ignore rules to vendored and tests * tests: comment out unused Mocks for further evaluation * tests: fix broken tests, add type hints * chore: address type feedback * chore: remove unused qtbot parameter
This commit is contained in:
committed by
GitHub
parent
3a0da4699a
commit
4704b92804
@@ -84,12 +84,13 @@ ignore_errors = true
|
||||
qt_api = "pyside6"
|
||||
|
||||
[tool.pyright]
|
||||
ignore = [".venv/**"]
|
||||
ignore = ["src/tagstudio/qt/helpers/vendored/pydub/", ".venv/**"]
|
||||
include = ["src/tagstudio", "tests"]
|
||||
reportAny = false
|
||||
reportIgnoreCommentWithoutRule = false
|
||||
reportImplicitStringConcatenation = false
|
||||
reportMissingTypeArgument = false
|
||||
reportMissingTypeStubs = false
|
||||
# reportOptionalMemberAccess = false
|
||||
reportUnannotatedClassAttribute = false
|
||||
reportUnknownArgumentType = false
|
||||
|
||||
@@ -209,7 +209,7 @@ class Library:
|
||||
"""Class for the Library object, and all CRUD operations made upon it."""
|
||||
|
||||
library_dir: Path | None = None
|
||||
storage_path: Path | None
|
||||
storage_path: Path | str | None
|
||||
engine: Engine | None = None
|
||||
folder: Folder | None
|
||||
included_files: set[Path] = set()
|
||||
@@ -333,7 +333,9 @@ class Library:
|
||||
else:
|
||||
return tag.name
|
||||
|
||||
def open_library(self, library_dir: Path, storage_path: Path | None = None) -> LibraryStatus:
|
||||
def open_library(
|
||||
self, library_dir: Path, storage_path: Path | str | None = None
|
||||
) -> LibraryStatus:
|
||||
is_new: bool = True
|
||||
if storage_path == ":memory:":
|
||||
self.storage_path = storage_path
|
||||
@@ -341,6 +343,7 @@ class Library:
|
||||
return self.open_sqlite_library(library_dir, is_new)
|
||||
else:
|
||||
self.storage_path = library_dir / TS_FOLDER_NAME / self.SQL_FILENAME
|
||||
assert isinstance(self.storage_path, Path)
|
||||
if self.verify_ts_folder(library_dir) and (is_new := not self.storage_path.exists()):
|
||||
json_path = library_dir / TS_FOLDER_NAME / self.JSON_FILENAME
|
||||
if json_path.exists():
|
||||
@@ -1213,7 +1216,7 @@ class Library:
|
||||
value: str | datetime | None = None,
|
||||
) -> bool:
|
||||
logger.info(
|
||||
"add_field_to_entry",
|
||||
"[Library][add_field_to_entry]",
|
||||
entry_id=entry_id,
|
||||
field_type=field,
|
||||
field_id=field_id,
|
||||
@@ -1388,6 +1391,12 @@ class Library:
|
||||
self, entry_ids: int | list[int], tag_ids: int | list[int] | set[int]
|
||||
) -> bool:
|
||||
"""Add one or more tags to one or more entries."""
|
||||
logger.info(
|
||||
"[Library][add_tags_to_entries]",
|
||||
entry_ids=entry_ids,
|
||||
tag_ids=tag_ids,
|
||||
)
|
||||
|
||||
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
|
||||
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
@@ -1732,18 +1741,26 @@ class Library:
|
||||
value=field.value,
|
||||
)
|
||||
|
||||
def merge_entries(self, from_entry: Entry, into_entry: Entry) -> None:
|
||||
def merge_entries(self, from_entry: Entry, into_entry: Entry) -> bool:
|
||||
"""Add fields and tags from the first entry to the second, and then delete the first."""
|
||||
success = True
|
||||
for field in from_entry.fields:
|
||||
self.add_field_to_entry(
|
||||
result = self.add_field_to_entry(
|
||||
entry_id=into_entry.id,
|
||||
field_id=field.type_key,
|
||||
value=field.value,
|
||||
)
|
||||
if not result:
|
||||
success = False
|
||||
tag_ids = [tag.id for tag in from_entry.tags]
|
||||
self.add_tags_to_entries(into_entry.id, tag_ids)
|
||||
add_result = self.add_tags_to_entries(into_entry.id, tag_ids)
|
||||
self.remove_entries([from_entry.id])
|
||||
|
||||
if not add_result:
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
@property
|
||||
def tag_color_groups(self) -> dict[str, list[TagColorGroup]]:
|
||||
"""Return every TagColorGroup in the library."""
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable, Generator
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
from unittest.mock import Mock, patch
|
||||
@@ -26,6 +32,7 @@ def file_mediatypes_library():
|
||||
|
||||
status = lib.open_library(Path(""), ":memory:")
|
||||
assert status.success
|
||||
assert lib.folder
|
||||
|
||||
entry1 = Entry(
|
||||
folder=lib.folder,
|
||||
@@ -64,18 +71,19 @@ def library_dir():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def library(request, library_dir: Path):
|
||||
def library(request, library_dir: Path): # pyright: ignore
|
||||
# when no param is passed, use the default
|
||||
library_path = library_dir
|
||||
if hasattr(request, "param"):
|
||||
if isinstance(request.param, TemporaryDirectory):
|
||||
library_path = Path(request.param.name)
|
||||
library_path = Path(request.param.name) # pyright: ignore[reportArgumentType]
|
||||
else:
|
||||
library_path = Path(request.param)
|
||||
|
||||
lib = Library()
|
||||
status = lib.open_library(library_path, ":memory:")
|
||||
assert status.success
|
||||
assert lib.folder
|
||||
|
||||
tag = Tag(
|
||||
name="foo",
|
||||
@@ -133,7 +141,7 @@ def search_library() -> Library:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def entry_min(library):
|
||||
def entry_min(library: Library):
|
||||
yield next(library.all_entries())
|
||||
|
||||
|
||||
@@ -143,7 +151,7 @@ def entry_full(library: Library):
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def qt_driver(qtbot, library, library_dir: Path):
|
||||
def qt_driver(library: Library, library_dir: Path):
|
||||
class Args:
|
||||
settings_file = library_dir / "settings.toml"
|
||||
cache_file = library_dir / "tagstudio.ini"
|
||||
@@ -151,31 +159,26 @@ def qt_driver(qtbot, library, library_dir: Path):
|
||||
ci = True
|
||||
|
||||
with patch("tagstudio.qt.ts_qt.Consumer"), patch("tagstudio.qt.ts_qt.CustomRunnable"):
|
||||
driver = QtDriver(Args())
|
||||
driver = QtDriver(Args()) # pyright: ignore[reportArgumentType]
|
||||
|
||||
driver.app = Mock()
|
||||
driver.main_window = Mock()
|
||||
driver.main_window.preview_panel = Mock()
|
||||
driver.main_window.thumb_grid = Mock()
|
||||
driver.main_window.thumb_size = 128
|
||||
driver.item_thumbs = []
|
||||
driver.main_window.menu_bar.autofill_action = Mock()
|
||||
|
||||
driver.copy_buffer = {"fields": [], "tags": []}
|
||||
driver.main_window.menu_bar.copy_fields_action = Mock()
|
||||
driver.main_window.menu_bar.paste_fields_action = Mock()
|
||||
|
||||
driver.lib = library
|
||||
# TODO - downsize this method and use it
|
||||
# driver.start()
|
||||
driver.frame_content = list(library.all_entries())
|
||||
driver.frame_content = [e.id for e in library.all_entries()]
|
||||
yield driver
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def generate_tag():
|
||||
def inner(name, **kwargs):
|
||||
def generate_tag() -> Generator[Callable[..., Tag]]:
|
||||
def inner(name: str, **kwargs) -> Tag: # pyright: ignore
|
||||
params = dict(name=name, color_namespace="tagstudio-standard", color_slug="red") | kwargs
|
||||
return Tag(**params)
|
||||
return Tag(**params) # pyright: ignore[reportArgumentType]
|
||||
|
||||
yield inner
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.core.utils.dupe_files import DupeRegistry
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
|
||||
|
||||
def test_refresh_dupe_files(library):
|
||||
def test_refresh_dupe_files(library: Library):
|
||||
library.library_dir = Path("/tmp/")
|
||||
assert library.folder
|
||||
|
||||
entry = Entry(
|
||||
folder=library.folder,
|
||||
path=Path("bar/foo.txt"),
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.modals.folders_to_tags import folders_to_tags
|
||||
|
||||
|
||||
def test_folders_to_tags(library):
|
||||
def test_folders_to_tags(library: Library):
|
||||
folders_to_tags(library)
|
||||
entry = [x for x in library.all_entries(with_joins=True) if "bar.md" in str(x.path)][0]
|
||||
assert {x.name for x in entry.tags} == {"two", "bar"}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
@@ -16,6 +20,7 @@ def test_refresh_missing_files(library: Library):
|
||||
registry = MissingRegistry(library=library)
|
||||
|
||||
# touch the file `one/two/bar.md` but in wrong location to simulate a moved file
|
||||
assert library.library_dir
|
||||
(library.library_dir / "bar.md").touch()
|
||||
|
||||
# no files actually exist, so it should return all entries
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
|
||||
from tagstudio.core.enums import LibraryPrefs
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.utils.refresh_dir import RefreshDirTracker
|
||||
|
||||
CWD = Path(__file__).parent
|
||||
@@ -11,7 +16,8 @@ CWD = Path(__file__).parent
|
||||
|
||||
@pytest.mark.parametrize("exclude_mode", [True, False])
|
||||
@pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_refresh_new_files(library, exclude_mode):
|
||||
def test_refresh_new_files(library: Library, exclude_mode: bool):
|
||||
assert library.library_dir
|
||||
# Given
|
||||
library.set_prefs(LibraryPrefs.IS_EXCLUDE_LIST, exclude_mode)
|
||||
library.set_prefs(LibraryPrefs.EXTENSION_LIST, [".md"])
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
# import shutil
|
||||
# from pathlib import Path
|
||||
# from tempfile import TemporaryDirectory
|
||||
|
||||
# import pytest
|
||||
# from tagstudio.core.enums import MacroID
|
||||
# from tagstudio.core.library.alchemy.fields import _FieldID
|
||||
|
||||
|
||||
# @pytest.mark.parametrize("library", [TemporaryDirectory()], indirect=True)
|
||||
def test_sidecar_macro(qt_driver, library, cwd, entry_full):
|
||||
# TODO: Rework and finalize sidecar loading + macro systems.
|
||||
pass
|
||||
# entry_full.path = Path("newgrounds/foo.txt")
|
||||
|
||||
# fixture = cwd / "fixtures/sidecar_newgrounds.json"
|
||||
# dst = library.library_dir / "newgrounds" / (entry_full.path.name + ".json")
|
||||
# dst.parent.mkdir()
|
||||
# shutil.copy(fixture, dst)
|
||||
|
||||
# qt_driver.frame_content = [entry_full]
|
||||
# qt_driver.run_macro(MacroID.SIDECAR, entry_full.id)
|
||||
|
||||
# entry = library.get_entry_full(entry_full.id)
|
||||
# new_fields = (
|
||||
# (_FieldID.DESCRIPTION.name, "NG description"),
|
||||
# (_FieldID.ARTIST.name, "NG artist"),
|
||||
# (_FieldID.SOURCE.name, "https://ng.com"),
|
||||
# )
|
||||
# found = [(field.type.key, field.value) for field in entry.fields]
|
||||
|
||||
# # `new_fields` should be subset of `found`
|
||||
# for field in new_fields:
|
||||
# assert field in found, f"Field not found: {field} / {found}"
|
||||
|
||||
# expected_tags = {"ng_tag", "ng_tag2"}
|
||||
# assert {x.name in expected_tags for x in entry.tags}
|
||||
@@ -1,22 +1,37 @@
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.qt.modals.build_tag import BuildTagPanel
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from collections.abc import Callable
|
||||
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag, TagAlias
|
||||
from tagstudio.qt.modals.build_tag import BuildTagPanel, CustomTableItem
|
||||
from tagstudio.qt.translations import Translations
|
||||
|
||||
|
||||
def test_build_tag_panel_add_sub_tag_callback(library, generate_tag):
|
||||
def test_build_tag_panel_add_sub_tag_callback(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
parent = library.add_tag(generate_tag("xxx", id=123))
|
||||
child = library.add_tag(generate_tag("xx", id=124))
|
||||
assert child
|
||||
assert parent
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, child)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
panel.add_parent_tag_callback(parent.id)
|
||||
|
||||
assert len(panel.parent_ids) == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_remove_subtag_callback(library, generate_tag):
|
||||
def test_build_tag_panel_remove_subtag_callback(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
parent = library.add_tag(generate_tag("xxx", id=123))
|
||||
child = library.add_tag(generate_tag("xx", id=124))
|
||||
assert child
|
||||
@@ -29,6 +44,7 @@ def test_build_tag_panel_remove_subtag_callback(library, generate_tag):
|
||||
assert child
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, child)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
panel.remove_parent_tag_callback(parent.id)
|
||||
|
||||
@@ -40,31 +56,39 @@ import os
|
||||
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
||||
|
||||
|
||||
def test_build_tag_panel_add_alias_callback(library, generate_tag):
|
||||
def test_build_tag_panel_add_alias_callback(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
panel.add_alias_callback()
|
||||
|
||||
assert panel.aliases_table.rowCount() == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_remove_alias_callback(library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
def test_build_tag_panel_remove_alias_callback(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
tag: Tag | None = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
library.update_tag(tag, [], {"alias", "alias_2"}, {123, 124})
|
||||
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag
|
||||
|
||||
assert "alias" in tag.alias_strings
|
||||
assert "alias_2" in tag.alias_strings
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
alias = library.get_alias(tag.id, tag.alias_ids[0])
|
||||
alias: TagAlias | None = library.get_alias(tag.id, tag.alias_ids[0])
|
||||
assert alias
|
||||
|
||||
panel.remove_alias_callback(alias.name, alias.id)
|
||||
|
||||
@@ -73,7 +97,9 @@ def test_build_tag_panel_remove_alias_callback(library, generate_tag):
|
||||
assert alias.name not in panel.alias_names
|
||||
|
||||
|
||||
def test_build_tag_panel_set_parent_tags(library, generate_tag):
|
||||
def test_build_tag_panel_set_parent_tags(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
parent = library.add_tag(generate_tag("parent", id=123))
|
||||
child = library.add_tag(generate_tag("child", id=124))
|
||||
assert parent
|
||||
@@ -84,30 +110,37 @@ def test_build_tag_panel_set_parent_tags(library, generate_tag):
|
||||
child = library.get_tag(child.id)
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, child)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
assert len(panel.parent_ids) == 1
|
||||
assert panel.parent_tags_scroll_layout.count() == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_add_aliases(library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
def test_build_tag_panel_add_aliases(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
tag: Tag | None = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
library.update_tag(tag, [], {"alias", "alias_2"}, {123, 124})
|
||||
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag
|
||||
|
||||
assert "alias" in tag.alias_strings
|
||||
assert "alias_2" in tag.alias_strings
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
widget = panel.aliases_table.cellWidget(0, 1)
|
||||
assert isinstance(widget, CustomTableItem)
|
||||
|
||||
alias_names: set[str] = set()
|
||||
alias_names.add(widget.text())
|
||||
|
||||
widget = panel.aliases_table.cellWidget(1, 1)
|
||||
assert isinstance(widget, CustomTableItem)
|
||||
alias_names.add(widget.text())
|
||||
|
||||
assert "alias" in alias_names
|
||||
@@ -123,35 +156,41 @@ def test_build_tag_panel_add_aliases(library, generate_tag):
|
||||
assert len(panel.alias_names) == 2
|
||||
|
||||
|
||||
def test_build_tag_panel_set_aliases(library, generate_tag):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
def test_build_tag_panel_set_aliases(
|
||||
qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]
|
||||
):
|
||||
tag: Tag | None = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
library.update_tag(tag, [], {"alias"}, {123})
|
||||
|
||||
tag = library.get_tag(tag.id)
|
||||
assert tag
|
||||
|
||||
assert len(tag.alias_ids) == 1
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
assert panel.aliases_table.rowCount() == 1
|
||||
assert len(panel.alias_names) == 1
|
||||
assert len(panel.alias_ids) == 1
|
||||
|
||||
|
||||
def test_build_tag_panel_set_tag(library, generate_tag):
|
||||
def test_build_tag_panel_set_tag(qtbot: QtBot, library: Library, generate_tag: Callable[..., Tag]):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
panel: BuildTagPanel = BuildTagPanel(library, tag)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
assert panel.tag
|
||||
assert panel.tag.name == "xxx"
|
||||
|
||||
|
||||
def test_build_tag_panel_build_tag(library):
|
||||
def test_build_tag_panel_build_tag(qtbot: QtBot, library: Library):
|
||||
panel: BuildTagPanel = BuildTagPanel(library)
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
tag: Tag = panel.build_tag()
|
||||
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag
|
||||
from tagstudio.qt.controller.widgets.preview_panel_controller import PreviewPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
def test_update_selection_empty(qt_driver, library):
|
||||
def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Clear the library selection (selecting 1 then unselecting 1)
|
||||
@@ -14,7 +22,7 @@ def test_update_selection_empty(qt_driver, library):
|
||||
assert container.isHidden()
|
||||
|
||||
|
||||
def test_update_selection_single(qt_driver, library, entry_full):
|
||||
def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Select the single entry
|
||||
@@ -26,7 +34,7 @@ def test_update_selection_single(qt_driver, library, entry_full):
|
||||
assert not container.isHidden()
|
||||
|
||||
|
||||
def test_update_selection_multiple(qt_driver, library):
|
||||
def test_update_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
# TODO: Implement mixed field editing. Currently these containers will be hidden,
|
||||
# same as the empty selection behavior.
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
@@ -41,7 +49,7 @@ def test_update_selection_multiple(qt_driver, library):
|
||||
assert container.isHidden()
|
||||
|
||||
|
||||
def test_add_tag_to_selection_single(qt_driver, library, entry_full):
|
||||
def test_add_tag_to_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
assert {t.id for t in entry_full.tags} == {1000}
|
||||
@@ -54,11 +62,11 @@ def test_add_tag_to_selection_single(qt_driver, library, entry_full):
|
||||
panel.field_containers_widget.add_tags_to_selected(2000)
|
||||
|
||||
# Then reload entry
|
||||
refreshed_entry = next(library.all_entries(with_joins=True))
|
||||
refreshed_entry: Entry = next(library.all_entries(with_joins=True))
|
||||
assert {t.id for t in refreshed_entry.tags} == {1000, 2000}
|
||||
|
||||
|
||||
def test_add_same_tag_to_selection_single(qt_driver, library, entry_full):
|
||||
def test_add_same_tag_to_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
assert {t.id for t in entry_full.tags} == {1000}
|
||||
@@ -75,7 +83,7 @@ def test_add_same_tag_to_selection_single(qt_driver, library, entry_full):
|
||||
assert {t.id for t in refreshed_entry.tags} == {1000}
|
||||
|
||||
|
||||
def test_add_tag_to_selection_multiple(qt_driver, library):
|
||||
def test_add_tag_to_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
all_entries = library.all_entries(with_joins=True)
|
||||
|
||||
@@ -102,8 +110,8 @@ def test_add_tag_to_selection_multiple(qt_driver, library):
|
||||
|
||||
# Then reload all entries and recheck the presence of tag 1000
|
||||
refreshed_entries = library.all_entries(with_joins=True)
|
||||
tag_present_on_some: bool = False
|
||||
tag_absent_on_some: bool = False
|
||||
tag_present_on_some = False
|
||||
tag_absent_on_some = False
|
||||
|
||||
for e in refreshed_entries:
|
||||
if 1000 in [t.id for t in e.tags]:
|
||||
@@ -115,7 +123,7 @@ def test_add_tag_to_selection_multiple(qt_driver, library):
|
||||
assert not tag_absent_on_some
|
||||
|
||||
|
||||
def test_meta_tag_category(qt_driver, library, entry_full):
|
||||
def test_meta_tag_category(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Ensure the Favorite tag is on entry_full
|
||||
@@ -131,20 +139,25 @@ def test_meta_tag_category(qt_driver, library, entry_full):
|
||||
match i:
|
||||
case 0:
|
||||
# Check if the container is the Meta Tags category
|
||||
assert container.title == f"<h4>{library.get_tag(2).name}</h4>"
|
||||
tag: Tag | None = library.get_tag(2)
|
||||
assert tag
|
||||
assert container.title == f"<h4>{tag.name}</h4>"
|
||||
case 1:
|
||||
# Check if the container is the Tags category
|
||||
assert container.title == "<h4>Tags</h4>"
|
||||
case 2:
|
||||
# Make sure the container isn't a duplicate Tags category
|
||||
assert container.title != "<h4>Tags</h4>"
|
||||
case _:
|
||||
pass
|
||||
|
||||
|
||||
def test_custom_tag_category(qt_driver, library, entry_full):
|
||||
def test_custom_tag_category(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Set tag 1000 (foo) as a category
|
||||
tag = library.get_tag(1000)
|
||||
tag: Tag | None = library.get_tag(1000)
|
||||
assert tag
|
||||
tag.is_category = True
|
||||
library.update_tag(
|
||||
tag,
|
||||
@@ -163,10 +176,14 @@ def test_custom_tag_category(qt_driver, library, entry_full):
|
||||
match i:
|
||||
case 0:
|
||||
# Check if the container is the Meta Tags category
|
||||
assert container.title == f"<h4>{library.get_tag(2).name}</h4>"
|
||||
tag_2: Tag | None = library.get_tag(2)
|
||||
assert tag_2
|
||||
assert container.title == f"<h4>{tag_2.name}</h4>"
|
||||
case 1:
|
||||
# Check if the container is the custom "foo" category
|
||||
assert container.title == f"<h4>{tag.name}</h4>"
|
||||
case 2:
|
||||
# Make sure the container isn't a plain Tags category
|
||||
assert container.title != "<h4>Tags</h4>"
|
||||
case _:
|
||||
pass
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import os
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
@@ -53,7 +59,10 @@ def test_filepath_setting(qtbot: QtBot, qt_driver: QtDriver, filepath_option: Sh
|
||||
],
|
||||
)
|
||||
def test_file_path_display(
|
||||
qt_driver: QtDriver, library: Library, filepath_option: ShowFilepathOption, expected_path
|
||||
qt_driver: QtDriver,
|
||||
library: Library,
|
||||
filepath_option: ShowFilepathOption,
|
||||
expected_path: Callable[[Library], Path],
|
||||
):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
@@ -68,12 +77,12 @@ def test_file_path_display(
|
||||
assert isinstance(entry, Entry)
|
||||
filename = entry.path
|
||||
assert library.library_dir is not None
|
||||
panel._file_attributes_widget.update_stats(filepath=library.library_dir / filename)
|
||||
panel._file_attributes_widget.update_stats(filepath=library.library_dir / filename) # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
# Generate the expected file string.
|
||||
# This is copied directly from the file_attributes.py file
|
||||
# can be imported as a function in the future
|
||||
display_path = expected_path(library)
|
||||
display_path: Path = expected_path(library)
|
||||
file_str: str = ""
|
||||
separator: str = f"<a style='color: #777777'><b>{os.path.sep}</a>" # Gray
|
||||
for i, part in enumerate(display_path.parts):
|
||||
@@ -86,7 +95,7 @@ def test_file_path_display(
|
||||
file_str += f"<b>{'\u200b'.join(part_)}</b>"
|
||||
|
||||
# Assert the file path is displayed correctly
|
||||
assert panel._file_attributes_widget.file_label.text() == file_str
|
||||
assert panel._file_attributes_widget.file_label.text() == file_str # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@@ -107,7 +116,10 @@ def test_file_path_display(
|
||||
],
|
||||
)
|
||||
def test_title_update(
|
||||
qt_driver: QtDriver, filepath_option: ShowFilepathOption, expected_title, library_dir: Path
|
||||
qt_driver: QtDriver,
|
||||
filepath_option: ShowFilepathOption,
|
||||
expected_title: Callable[[Path, str], str],
|
||||
library_dir: Path,
|
||||
):
|
||||
base_title = qt_driver.base_title
|
||||
|
||||
@@ -135,7 +147,7 @@ def test_title_update(
|
||||
qt_driver.main_window.menu_bar.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) # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
# Assert the title is updated correctly
|
||||
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(library_dir, base_title))
|
||||
qt_driver.main_window.setWindowTitle.assert_called_with(expected_title(library_dir, base_title)) # pyright: ignore[reportAttributeAccessIssue]
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from PySide6.QtCore import QRect
|
||||
from PySide6.QtWidgets import QPushButton, QWidget
|
||||
|
||||
from tagstudio.qt.flowlayout import FlowLayout
|
||||
|
||||
|
||||
def test_flow_layout_happy_path(qtbot):
|
||||
def test_flow_layout_happy_path():
|
||||
class Window(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -15,4 +20,4 @@ def test_flow_layout_happy_path(qtbot):
|
||||
|
||||
window = Window()
|
||||
assert window.flow_layout.count()
|
||||
assert window.flow_layout._do_layout(QRect(0, 0, 0, 0), test_only=False)
|
||||
assert window.flow_layout._do_layout(QRect(0, 0, 0, 0), test_only=False) # pyright: ignore[reportPrivateUsage]
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
from tagstudio.qt.modals.folders_to_tags import generate_preview_data
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
def test_generate_preview_data(library, snapshot):
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.modals.folders_to_tags import BranchData, generate_preview_data
|
||||
|
||||
|
||||
def test_generate_preview_data(library: Library, snapshot: BranchData):
|
||||
preview = generate_preview_data(library)
|
||||
|
||||
assert preview == snapshot
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tagstudio.core.global_settings import GlobalSettings, Theme
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import pytest
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import ItemType
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.widgets.item_thumb import BadgeType, ItemThumb
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_value", (True, False))
|
||||
def test_badge_visual_state(library, qt_driver, entry_min, new_value):
|
||||
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), 0)
|
||||
def test_badge_visual_state(qt_driver: QtDriver, entry_min: int, new_value: bool):
|
||||
thumb = ItemThumb(
|
||||
ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100), show_filename_label=False
|
||||
)
|
||||
|
||||
qt_driver.frame_content = [entry_min]
|
||||
qt_driver.selected = [0]
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry
|
||||
from tagstudio.qt.controller.widgets.preview_panel_controller import PreviewPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
def test_update_selection_empty(qt_driver, library):
|
||||
def test_update_selection_empty(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Clear the library selection (selecting 1 then unselecting 1)
|
||||
@@ -13,7 +21,7 @@ def test_update_selection_empty(qt_driver, library):
|
||||
assert not panel.add_buttons_enabled
|
||||
|
||||
|
||||
def test_update_selection_single(qt_driver, library, entry_full):
|
||||
def test_update_selection_single(qt_driver: QtDriver, library: Library, entry_full: Entry):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Select the single entry
|
||||
@@ -24,7 +32,7 @@ def test_update_selection_single(qt_driver, library, entry_full):
|
||||
assert panel.add_buttons_enabled
|
||||
|
||||
|
||||
def test_update_selection_multiple(qt_driver, library):
|
||||
def test_update_selection_multiple(qt_driver: QtDriver, library: Library):
|
||||
panel = PreviewPanel(library, qt_driver)
|
||||
|
||||
# Select the multiple entries
|
||||
|
||||
@@ -1,77 +1,19 @@
|
||||
from typing import TYPE_CHECKING
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import BrowsingState
|
||||
from tagstudio.core.library.json.library import ItemType
|
||||
|
||||
from tagstudio.core.library.alchemy.enums import BrowsingState, ItemType
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
from tagstudio.qt.widgets.item_thumb import ItemThumb
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
# def test_update_thumbs(qt_driver):
|
||||
# qt_driver.frame_content = [
|
||||
# Entry(
|
||||
# folder=qt_driver.lib.folder,
|
||||
# path=Path("/tmp/foo"),
|
||||
# fields=qt_driver.lib.default_fields,
|
||||
# )
|
||||
# ]
|
||||
|
||||
# qt_driver.item_thumbs = []
|
||||
# for _ in range(3):
|
||||
# qt_driver.item_thumbs.append(
|
||||
# ItemThumb(
|
||||
# mode=ItemType.ENTRY,
|
||||
# library=qt_driver.lib,
|
||||
# driver=qt_driver,
|
||||
# thumb_size=(100, 100),
|
||||
# )
|
||||
# )
|
||||
|
||||
# qt_driver.update_thumbs()
|
||||
|
||||
# for idx, thumb in enumerate(qt_driver.item_thumbs):
|
||||
# # only first item is visible
|
||||
# assert thumb.isVisible() == (idx == 0)
|
||||
|
||||
|
||||
# def test_toggle_item_selection_bridge(qt_driver, entry_min):
|
||||
# # mock some props since we're not running `start()`
|
||||
# qt_driver.autofill_action = Mock()
|
||||
# qt_driver.sort_fields_action = Mock()
|
||||
|
||||
# # set the content manually
|
||||
# qt_driver.frame_content = [entry_min] * 3
|
||||
|
||||
# qt_driver.filter.page_size = 3
|
||||
# qt_driver._init_thumb_grid()
|
||||
# assert len(qt_driver.item_thumbs) == 3
|
||||
|
||||
# # select first item
|
||||
# qt_driver.toggle_item_selection(0, append=False, bridge=False)
|
||||
# assert qt_driver.selected == [0]
|
||||
|
||||
# # add second item to selection
|
||||
# qt_driver.toggle_item_selection(1, append=False, bridge=True)
|
||||
# assert qt_driver.selected == [0, 1]
|
||||
|
||||
# # add third item to selection
|
||||
# qt_driver.toggle_item_selection(2, append=False, bridge=True)
|
||||
# assert qt_driver.selected == [0, 1, 2]
|
||||
|
||||
# # select third item only
|
||||
# qt_driver.toggle_item_selection(2, append=False, bridge=False)
|
||||
# assert qt_driver.selected == [2]
|
||||
|
||||
# qt_driver.toggle_item_selection(0, append=False, bridge=True)
|
||||
# assert qt_driver.selected == [0, 1, 2]
|
||||
|
||||
|
||||
def test_browsing_state_update(qt_driver: "QtDriver"):
|
||||
def test_browsing_state_update(qt_driver: QtDriver):
|
||||
# Given
|
||||
for entry in qt_driver.lib.all_entries(with_joins=True):
|
||||
thumb = ItemThumb(ItemType.ENTRY, qt_driver.lib, qt_driver, (100, 100))
|
||||
qt_driver.item_thumbs.append(thumb)
|
||||
qt_driver.frame_content.append(entry)
|
||||
qt_driver.frame_content.append(entry.id)
|
||||
|
||||
# no filter, both items are returned
|
||||
qt_driver.update_browsing_state()
|
||||
@@ -82,12 +24,14 @@ def test_browsing_state_update(qt_driver: "QtDriver"):
|
||||
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 entry
|
||||
assert list(entry.tags)[0].name == "foo"
|
||||
|
||||
# When state is not changed, previous one is still applied
|
||||
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 entry
|
||||
assert list(entry.tags)[0].name == "foo"
|
||||
|
||||
# When state property is changed, previous one is overwritten
|
||||
@@ -95,10 +39,11 @@ def test_browsing_state_update(qt_driver: "QtDriver"):
|
||||
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 entry
|
||||
assert list(entry.tags)[0].name == "bar"
|
||||
|
||||
|
||||
def test_close_library(qt_driver):
|
||||
def test_close_library(qt_driver: QtDriver):
|
||||
# Given
|
||||
qt_driver.close_library()
|
||||
|
||||
|
||||
@@ -1,24 +1,34 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Tag
|
||||
from tagstudio.qt.modals.build_tag import BuildTagPanel
|
||||
from tagstudio.qt.ts_qt import QtDriver
|
||||
|
||||
|
||||
def test_tag_panel(qtbot, library):
|
||||
def test_tag_panel(qtbot: QtBot, library: Library):
|
||||
panel = BuildTagPanel(library)
|
||||
|
||||
qtbot.addWidget(panel)
|
||||
|
||||
|
||||
def test_add_tag_callback(qt_driver):
|
||||
def test_add_tag_callback(qt_driver: QtDriver):
|
||||
# Given
|
||||
assert len(qt_driver.lib.tags) == 6
|
||||
qt_driver.add_tag_action_callback()
|
||||
|
||||
# When
|
||||
assert isinstance(qt_driver.modal.widget, BuildTagPanel)
|
||||
qt_driver.modal.widget.name_field.setText("xxx")
|
||||
# qt_driver.modal.widget.color_field.setCurrentIndex(1)
|
||||
qt_driver.modal.saved.emit()
|
||||
|
||||
# Then
|
||||
tags: set[Tag] = qt_driver.lib.tags
|
||||
tags: list[Tag] = qt_driver.lib.tags
|
||||
assert len(tags) == 7
|
||||
assert "xxx" in {tag.name for tag in tags}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from pytestqt.qtbot import QtBot
|
||||
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.qt.modals.tag_search import TagSearchPanel
|
||||
|
||||
|
||||
def test_update_tags(qtbot, library):
|
||||
def test_update_tags(qtbot: QtBot, library: Library):
|
||||
# Given
|
||||
panel = TagSearchPanel(library)
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PySide6.QtCore import QSettings
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
@@ -48,5 +48,5 @@ def test_json_migration():
|
||||
# List Type
|
||||
assert modal.check_ext_type()
|
||||
# No Leading Dot
|
||||
for ext in modal.sql_lib.prefs(LibraryPrefs.EXTENSION_LIST):
|
||||
for ext in modal.sql_lib.prefs(LibraryPrefs.EXTENSION_LIST): # pyright: ignore[reportUnknownVariableType]
|
||||
assert ext[0] != "."
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
import pytest
|
||||
import structlog
|
||||
|
||||
from tagstudio.core.enums import DefaultEnum, LibraryPrefs
|
||||
from tagstudio.core.library.alchemy.enums import BrowsingState
|
||||
from tagstudio.core.library.alchemy.fields import TextField, _FieldID
|
||||
from tagstudio.core.library.alchemy.fields import (
|
||||
TextField,
|
||||
_FieldID, # pyright: ignore[reportPrivateUsage]
|
||||
)
|
||||
from tagstudio.core.library.alchemy.library import Library
|
||||
from tagstudio.core.library.alchemy.models import Entry, Tag
|
||||
|
||||
logger = structlog.get_logger()
|
||||
|
||||
def test_library_add_alias(library: Library, generate_tag):
|
||||
|
||||
def test_library_add_alias(library: Library, generate_tag: Callable[..., Tag]):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
@@ -26,7 +38,7 @@ def test_library_add_alias(library: Library, generate_tag):
|
||||
assert len(alias_ids) == 1
|
||||
|
||||
|
||||
def test_library_get_alias(library: Library, generate_tag):
|
||||
def test_library_get_alias(library: Library, generate_tag: Callable[..., Tag]):
|
||||
tag = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag
|
||||
|
||||
@@ -44,7 +56,7 @@ def test_library_get_alias(library: Library, generate_tag):
|
||||
assert alias.name == "test_alias"
|
||||
|
||||
|
||||
def test_library_update_alias(library: Library, generate_tag):
|
||||
def test_library_update_alias(library: Library, generate_tag: Callable[..., Tag]):
|
||||
tag: Tag | None = library.add_tag(generate_tag("xxx", id=123))
|
||||
assert tag is not None
|
||||
|
||||
@@ -89,7 +101,7 @@ def test_library_add_file(library: Library):
|
||||
assert library.has_path_entry(entry.path)
|
||||
|
||||
|
||||
def test_create_tag(library: Library, generate_tag):
|
||||
def test_create_tag(library: Library, generate_tag: Callable[..., Tag]):
|
||||
# tag already exists
|
||||
assert not library.add_tag(generate_tag("foo", id=1000))
|
||||
|
||||
@@ -103,7 +115,7 @@ def test_create_tag(library: Library, generate_tag):
|
||||
assert tag_inc.id > 1000
|
||||
|
||||
|
||||
def test_tag_self_parent(library: Library, generate_tag):
|
||||
def test_tag_self_parent(library: Library, generate_tag: Callable[..., Tag]):
|
||||
# tag already exists
|
||||
assert not library.add_tag(generate_tag("foo", id=1000))
|
||||
|
||||
@@ -118,7 +130,7 @@ def test_tag_self_parent(library: Library, generate_tag):
|
||||
assert len(tag.parent_ids) == 0
|
||||
|
||||
|
||||
def test_library_search(library: Library, generate_tag, entry_full):
|
||||
def test_library_search(library: Library, entry_full: Entry):
|
||||
assert library.entries_count == 2
|
||||
tag = list(entry_full.tags)[0]
|
||||
|
||||
@@ -140,7 +152,7 @@ def test_tag_search(library: Library):
|
||||
assert library.search_tags(tag.name * 2) == [set(), set()]
|
||||
|
||||
|
||||
def test_get_entry(library: Library, entry_min):
|
||||
def test_get_entry(library: Library, entry_min: Entry):
|
||||
assert entry_min.id
|
||||
result = library.get_entry_full(entry_min.id)
|
||||
assert result
|
||||
@@ -159,12 +171,13 @@ def test_entries_count(library: Library):
|
||||
assert len(results) == 5
|
||||
|
||||
|
||||
def test_parents_add(library: Library, generate_tag):
|
||||
def test_parents_add(library: Library, generate_tag: Callable[..., Tag]):
|
||||
# Given
|
||||
tag: Tag | None = library.tags[0]
|
||||
assert tag.id is not None
|
||||
|
||||
parent_tag = generate_tag("parent_tag_01")
|
||||
parent_tag: Tag | None = generate_tag("parent_tag_01")
|
||||
assert parent_tag
|
||||
parent_tag = library.add_tag(parent_tag)
|
||||
assert parent_tag is not None
|
||||
assert parent_tag.id is not None
|
||||
@@ -179,7 +192,7 @@ def test_parents_add(library: Library, generate_tag):
|
||||
assert tag.parent_ids
|
||||
|
||||
|
||||
def test_remove_tag(library: Library, generate_tag):
|
||||
def test_remove_tag(library: Library, generate_tag: Callable[..., Tag]):
|
||||
tag = library.add_tag(generate_tag("food", id=123))
|
||||
|
||||
assert tag
|
||||
@@ -237,7 +250,7 @@ def test_preferences(library: Library):
|
||||
assert library.prefs(pref) == pref.default
|
||||
|
||||
|
||||
def test_remove_entry_field(library: Library, entry_full):
|
||||
def test_remove_entry_field(library: Library, entry_full: Entry):
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
library.remove_entry_field(title_field, [entry_full.id])
|
||||
@@ -246,7 +259,7 @@ def test_remove_entry_field(library: Library, entry_full):
|
||||
assert not entry.text_fields
|
||||
|
||||
|
||||
def test_remove_field_entry_with_multiple_field(library: Library, entry_full):
|
||||
def test_remove_field_entry_with_multiple_field(library: Library, entry_full: Entry):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -262,7 +275,7 @@ def test_remove_field_entry_with_multiple_field(library: Library, entry_full):
|
||||
assert len(entry.text_fields) == 1
|
||||
|
||||
|
||||
def test_update_entry_field(library: Library, entry_full):
|
||||
def test_update_entry_field(library: Library, entry_full: Entry):
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
library.update_entry_field(
|
||||
@@ -275,7 +288,7 @@ def test_update_entry_field(library: Library, entry_full):
|
||||
assert entry.text_fields[0].value == "new value"
|
||||
|
||||
|
||||
def test_update_entry_with_multiple_identical_fields(library: Library, entry_full):
|
||||
def test_update_entry_with_multiple_identical_fields(library: Library, entry_full: Entry):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -296,7 +309,7 @@ def test_update_entry_with_multiple_identical_fields(library: Library, entry_ful
|
||||
assert entry.text_fields[1].value == "new value"
|
||||
|
||||
|
||||
def test_mirror_entry_fields(library: Library, entry_full):
|
||||
def test_mirror_entry_fields(library: Library, entry_full: Entry):
|
||||
# new entry
|
||||
assert library.folder is not None
|
||||
target_entry = Entry(
|
||||
@@ -335,6 +348,14 @@ def test_mirror_entry_fields(library: Library, entry_full):
|
||||
|
||||
def test_merge_entries(library: Library):
|
||||
assert library.folder is not None
|
||||
|
||||
tag_0: Tag | None = library.add_tag(Tag(id=1010, name="tag_0"))
|
||||
assert tag_0 is not None
|
||||
tag_1: Tag | None = library.add_tag(Tag(id=1011, name="tag_1"))
|
||||
assert tag_1 is not None
|
||||
tag_2: Tag | None = library.add_tag(Tag(id=1012, name="tag_2"))
|
||||
assert tag_2 is not None
|
||||
|
||||
a = Entry(
|
||||
folder=library.folder,
|
||||
path=Path("a"),
|
||||
@@ -348,32 +369,34 @@ def test_merge_entries(library: Library):
|
||||
path=Path("b"),
|
||||
fields=[TextField(type_key=_FieldID.NOTES.name, value="test note", position=1)],
|
||||
)
|
||||
try:
|
||||
ids = library.add_entries([a, b])
|
||||
entry_a = library.get_entry_full(ids[0])
|
||||
assert entry_a is not None
|
||||
entry_b = library.get_entry_full(ids[1])
|
||||
assert entry_b is not None
|
||||
tag_0 = library.add_tag(Tag(id=1000, name="tag_0"))
|
||||
tag_1 = library.add_tag(Tag(id=1001, name="tag_1"))
|
||||
assert tag_1 is not None
|
||||
tag_2 = library.add_tag(Tag(id=1002, name="tag_2"))
|
||||
assert tag_2 is not None
|
||||
library.add_tags_to_entries(ids[0], [tag_0.id, tag_2.id])
|
||||
library.add_tags_to_entries(ids[1], [tag_1.id])
|
||||
library.merge_entries(entry_a, entry_b)
|
||||
assert library.has_path_entry(Path("b"))
|
||||
assert not library.has_path_entry(Path("a"))
|
||||
fields = [field.value for field in entry_a.fields]
|
||||
assert "Author McAuthorson" in fields
|
||||
assert "test description" in fields
|
||||
assert "test note" in fields
|
||||
assert b.has_tag(tag_0) and b.has_tag(tag_1) and b.has_tag(tag_2)
|
||||
except AttributeError:
|
||||
AssertionError()
|
||||
ids = library.add_entries([a, b])
|
||||
|
||||
library.add_tags_to_entries(ids[0], [tag_0.id, tag_2.id])
|
||||
library.add_tags_to_entries(ids[1], [tag_1.id])
|
||||
|
||||
entry_a: Entry | None = library.get_entry_full(ids[0])
|
||||
assert entry_a is not None
|
||||
entry_b: Entry | None = library.get_entry_full(ids[1])
|
||||
assert entry_b is not None
|
||||
|
||||
assert library.merge_entries(entry_a, entry_b)
|
||||
assert not library.has_path_entry(Path("a"))
|
||||
assert library.has_path_entry(Path("b"))
|
||||
|
||||
entry_b_merged = library.get_entry_full(ids[1])
|
||||
assert entry_b_merged
|
||||
|
||||
fields = [field.value for field in entry_b_merged.fields]
|
||||
assert "Author McAuthorson" in fields
|
||||
assert "test description" in fields
|
||||
assert "test note" in fields
|
||||
b_tags = [t.id for t in entry_b_merged.tags]
|
||||
assert tag_0.id in b_tags
|
||||
assert tag_1.id in b_tags
|
||||
assert tag_2.id in b_tags
|
||||
|
||||
|
||||
def test_remove_tags_from_entries(library: Library, entry_full):
|
||||
def test_remove_tags_from_entries(library: Library, entry_full: Entry):
|
||||
removed_tag_id = -1
|
||||
for tag in entry_full.tags:
|
||||
removed_tag_id = tag.id
|
||||
@@ -392,13 +415,13 @@ def test_remove_tags_from_entries(library: Library, entry_full):
|
||||
(222, 0),
|
||||
],
|
||||
)
|
||||
def test_search_entry_id(library: Library, query_name: int, has_result):
|
||||
def test_search_entry_id(library: Library, query_name: int, has_result: bool):
|
||||
result = library.get_entry(query_name)
|
||||
|
||||
assert (result is not None) == has_result
|
||||
|
||||
|
||||
def test_update_field_order(library: Library, entry_full):
|
||||
def test_update_field_order(library: Library, entry_full: Entry):
|
||||
# Given
|
||||
title_field = entry_full.text_fields[0]
|
||||
|
||||
@@ -526,13 +549,15 @@ def test_path_search_like_glob_equality(library: Library):
|
||||
|
||||
|
||||
@pytest.mark.parametrize(["filetype", "num_of_filetype"], [("md", 1), ("txt", 1), ("png", 0)])
|
||||
def test_filetype_search(library: Library, filetype, num_of_filetype):
|
||||
def test_filetype_search(library: Library, filetype: str, num_of_filetype: int):
|
||||
results = library.search_library(BrowsingState.from_filetype(filetype), page_size=500)
|
||||
assert len(results.ids) == 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):
|
||||
def test_filetype_return_one_filetype(
|
||||
file_mediatypes_library: Library, filetype: str, num_of_filetype: int
|
||||
):
|
||||
results = file_mediatypes_library.search_library(
|
||||
BrowsingState.from_filetype(filetype), page_size=500
|
||||
)
|
||||
@@ -540,6 +565,6 @@ def test_filetype_return_one_filetype(file_mediatypes_library: Library, filetype
|
||||
|
||||
|
||||
@pytest.mark.parametrize(["mediatype", "num_of_mediatype"], [("plaintext", 2), ("image", 0)])
|
||||
def test_mediatype_search(library: Library, mediatype, num_of_mediatype):
|
||||
def test_mediatype_search(library: Library, mediatype: str, num_of_mediatype: int):
|
||||
results = library.search_library(BrowsingState.from_mediatype(mediatype), page_size=500)
|
||||
assert len(results.ids) == num_of_mediatype
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import pytest
|
||||
import structlog
|
||||
|
||||
@@ -139,5 +144,5 @@ def test_parent_tags(search_library: Library, query: str, count: int):
|
||||
"invalid_query", ["asd AND", "asd AND AND", "tag:(", "(asd", "asd[]", "asd]", ":", "tag: :"]
|
||||
)
|
||||
def test_syntax(search_library: Library, invalid_query: str):
|
||||
with pytest.raises(ParsingError) as e_info: # noqa: F841
|
||||
with pytest.raises(ParsingError) as e_info: # noqa: F841 # pyright: ignore[reportUnusedVariable]
|
||||
search_library.search_library(BrowsingState.from_search_query(invalid_query), page_size=500)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# Copyright (C) 2025
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import string
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
Reference in New Issue
Block a user