refactor!: uncouple fields from hardcoded values

This commit is contained in:
Travis Abendshien
2026-05-08 12:49:17 -07:00
parent 47d4de5825
commit 39c5c0a90f
23 changed files with 640 additions and 751 deletions

View File

@@ -12,6 +12,8 @@ from unittest.mock import Mock, patch
import pytest
from PySide6.QtWidgets import QScrollArea
from tagstudio.core.library.alchemy.fields import TextField
CWD = Path(__file__).parent
# this needs to be above `src` imports
sys.path.insert(0, str(CWD.parent))
@@ -40,19 +42,19 @@ def file_mediatypes_library():
entry1 = Entry(
folder=folder,
path=Path("foo.png"),
fields=lib.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
entry2 = Entry(
folder=folder,
path=Path("bar.png"),
fields=lib.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
entry3 = Entry(
folder=folder,
path=Path("baz.apng"),
fields=lib.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
assert lib.add_entries([entry1, entry2, entry3])
@@ -117,7 +119,7 @@ def library(request, library_dir: Path): # pyright: ignore
id=1,
folder=folder,
path=Path("foo.txt"),
fields=lib.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
assert lib.add_tags_to_entries(entry.id, tag.id)
@@ -125,7 +127,7 @@ def library(request, library_dir: Path): # pyright: ignore
id=2,
folder=folder,
path=Path("one/two/bar.md"),
fields=lib.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
assert lib.add_tags_to_entries(entry2.id, tag2.id)

View File

@@ -4,6 +4,7 @@
from pathlib import Path
from tagstudio.core.library.alchemy.fields import TextField
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.library.alchemy.registries.dupe_files_registry import DupeFilesRegistry
@@ -19,13 +20,13 @@ def test_refresh_dupe_files(library: Library):
entry = Entry(
folder=folder,
path=Path("bar/foo.txt"),
fields=library.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
entry2 = Entry(
folder=folder,
path=Path("foo/foo.txt"),
fields=library.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
library.add_entries([entry, entry2])

View File

@@ -27,6 +27,10 @@ EMPTY_LIBRARIES = "empty_libraries"
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_8")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_9")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_100")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_101")),
# str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_102")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_103")),
str(Path(CWD.parent / FIXTURES / EMPTY_LIBRARIES / "DB_VERSION_200")),
],
)
def test_library_migrations(path: str):

View File

@@ -13,7 +13,7 @@ import structlog
from tagstudio.core.enums import DefaultEnum, LibraryPrefs
from tagstudio.core.library.alchemy.enums import BrowsingState
from tagstudio.core.library.alchemy.fields import (
FieldID, # pyright: ignore[reportPrivateUsage]
DatetimeField,
TextField,
)
from tagstudio.core.library.alchemy.library import Library
@@ -82,12 +82,12 @@ def test_library_add_file(library: Library):
entry = Entry(
path=Path("bar.txt"),
folder=unwrap(library.folder),
fields=library.default_fields,
fields=[TextField(name="Title", value="I'm a Test Title")],
)
assert not library.has_path_entry(entry.path)
assert not library.has_entry_with_path(entry.path)
assert library.add_entries([entry])
assert library.has_path_entry(entry.path)
assert library.has_entry_with_path(entry.path)
def test_create_tag(library: Library, generate_tag: Callable[..., Tag]):
@@ -213,13 +213,18 @@ def test_remove_entry_field(library: Library, entry_full: Entry):
assert not entry.text_fields
def test_remove_field_entry_with_multiple_field(library: Library, entry_full: Entry):
def test_remove_text_field_entry_with_multiple_field(library: Library, entry_full: Entry):
# Given
title_field = entry_full.text_fields[0]
# When
# add identical field
assert library.add_field_to_entry(entry_full.id, field_id=title_field.type_key)
assert library.add_text_field_to_entry(
entry_full.id,
name=title_field.name,
value=title_field.value,
is_multiline=title_field.is_multiline,
)
# remove entry field
library.remove_entry_field(title_field, [entry_full.id])
@@ -232,30 +237,22 @@ def test_remove_field_entry_with_multiple_field(library: Library, entry_full: En
def test_update_entry_field(library: Library, entry_full: Entry):
title_field = entry_full.text_fields[0]
library.update_entry_field(
entry_full.id,
title_field,
"new value",
)
library.update_text_field(entry_full.id, title_field, "new value", title_field.is_multiline)
entry = next(library.all_entries(with_joins=True))
assert entry.text_fields[0].value == "new value"
def test_update_entry_with_multiple_identical_fields(library: Library, entry_full: Entry):
def test_update_entry_with_multiple_identical_text_fields(library: Library, entry_full: Entry):
# Given
title_field = entry_full.text_fields[0]
# When
# add identical field
library.add_field_to_entry(entry_full.id, field_id=title_field.type_key)
library.add_text_field_to_entry(entry_full.id, name="Title", value="")
# update one of the fields
library.update_entry_field(
entry_full.id,
title_field,
"new value",
)
library.update_text_field(entry_full.id, title_field, "new value", title_field.is_multiline)
# Then only one should be updated
entry = next(library.all_entries(with_joins=True))
@@ -263,37 +260,64 @@ 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: Entry):
# new entry
target_entry = Entry(
def test_mirror_entry_fields(library: Library):
# Create and add entries with fields
entry_a = Entry(
folder=unwrap(library.folder),
path=Path("xxx"),
path=Path("title_and_date.txt"),
fields=[
TextField(
type_key=FieldID.NOTES.name,
value="notes",
position=0,
)
TextField(name="Title", value="I'm a Test Title"),
DatetimeField(name="Date", value="2026-05-07 12:59:24"),
],
)
entry_b = Entry(
folder=unwrap(library.folder),
path=Path("notes.txt"),
fields=[
TextField(name="Notes", value="These are my notes.\nNo peeking!", is_multiline=True)
],
)
entry_c = Entry(
folder=unwrap(library.folder),
path=Path("date_published.txt"),
fields=[
DatetimeField(name="Date Published", value="2000-01-01 12:00:00"),
],
)
entry_a_id, entry_b_id, entry_c_id = library.add_entries([entry_a, entry_b, entry_c])
# insert new entry and get id
entry_id = library.add_entries([target_entry])[0]
# Retrieve from library
entry_a_ = unwrap(library.get_entry_full(entry_a_id))
entry_b_ = unwrap(library.get_entry_full(entry_b_id))
entry_c_ = unwrap(library.get_entry_full(entry_c_id))
# get new entry from library
new_entry = unwrap(library.get_entry_full(entry_id))
# Sanity check for initial fields
assert entry_a_.fields[0].name == "Title"
assert entry_a_.fields[1].name == "Date"
assert entry_b_.fields[0].name == "Notes"
assert entry_c_.fields[0].name == "Date Published"
assert len(entry_a_.fields) == 2
assert len(entry_b_.fields) == 1
assert len(entry_c_.fields) == 1
# mirror fields onto new entry
library.mirror_entry_fields(new_entry, entry_full)
# Mirror fields between entries
library.mirror_entry_fields([entry_b_, entry_a_, entry_c_])
# get new entry from library again
entry = unwrap(library.get_entry_full(entry_id))
# Retrieve from library, again
entry_a_mirrored = unwrap(library.get_entry_full(entry_a_id))
entry_b_mirrored = unwrap(library.get_entry_full(entry_b_id))
entry_c_mirrored = unwrap(library.get_entry_full(entry_c_id))
# make sure fields are there after getting it from the library again
assert len(entry.fields) == 2
assert {x.type_key for x in entry.fields} == {
FieldID.TITLE.name,
FieldID.NOTES.name,
# Assert presence of all fields on all entries
assert len(entry_a_mirrored.fields) == 4
assert len(entry_b_mirrored.fields) == 4
assert len(entry_c_mirrored.fields) == 4
assert {(type(x), x.name) for x in entry_a_mirrored.fields} == {
(TextField, "Title"),
(TextField, "Notes"),
(DatetimeField, "Date"),
(DatetimeField, "Date Published"),
}
@@ -304,32 +328,32 @@ def test_merge_entries(library: Library):
tag_1: Tag = unwrap(library.add_tag(Tag(id=1011, name="tag_1")))
tag_2: Tag = unwrap(library.add_tag(Tag(id=1012, name="tag_2")))
a = Entry(
entry_a = Entry(
folder=folder,
path=Path("a"),
fields=[
TextField(type_key=FieldID.AUTHOR.name, value="Author McAuthorson", position=0),
TextField(type_key=FieldID.DESCRIPTION.name, value="test description", position=2),
TextField(name="Author", value="Author McAuthorson"),
TextField(name="Description", value="test description", is_multiline=True),
],
)
b = Entry(
entry_b = Entry(
folder=folder,
path=Path("b"),
fields=[TextField(type_key=FieldID.NOTES.name, value="test note", position=1)],
fields=[TextField(name="Notes", value="test note", is_multiline=True)],
)
ids = library.add_entries([a, b])
entry_a_id, entry_b_id = library.add_entries([entry_a, entry_b])
library.add_tags_to_entries(ids[0], [tag_0.id, tag_2.id])
library.add_tags_to_entries(ids[1], [tag_1.id])
library.add_tags_to_entries(entry_a_id, [tag_0.id, tag_2.id])
library.add_tags_to_entries(entry_b_id, [tag_1.id])
entry_a: Entry = unwrap(library.get_entry_full(ids[0]))
entry_b: Entry = unwrap(library.get_entry_full(ids[1]))
entry_a_: Entry = unwrap(library.get_entry_full(entry_a_id))
entry_b_: Entry = unwrap(library.get_entry_full(entry_b_id))
assert library.merge_entries(entry_a, entry_b)
assert not library.has_path_entry(Path("a"))
assert library.has_path_entry(Path("b"))
assert library.merge_entries(entry_a_, entry_b_)
assert not library.has_entry_with_path(Path("a"))
assert library.has_entry_with_path(Path("b"))
entry_b_merged = unwrap(library.get_entry_full(ids[1]))
entry_b_merged = unwrap(library.get_entry_full(entry_b_id))
fields = [field.value for field in entry_b_merged.fields]
assert "Author McAuthorson" in fields
@@ -366,33 +390,6 @@ def test_search_entry_id(library: Library, query_name: int, has_result: bool):
assert (result is not None) == has_result
def test_update_field_order(library: Library, entry_full: Entry):
# Given
title_field = entry_full.text_fields[0]
# When add two more fields
library.add_field_to_entry(entry_full.id, field_id=title_field.type_key, value="first")
library.add_field_to_entry(entry_full.id, field_id=title_field.type_key, value="second")
# remove the one on first position
assert title_field.position == 0
library.remove_entry_field(title_field, [entry_full.id])
# recalculate the positions
library.update_field_position(
type(title_field),
title_field.type_key,
entry_full.id,
)
# Then
entry = next(library.all_entries(with_joins=True))
assert entry.text_fields[0].position == 0
assert entry.text_fields[0].value == "first"
assert entry.text_fields[1].position == 1
assert entry.text_fields[1].value == "second"
def test_library_prefs_multiple_identical_vals():
# check the preferences are inherited from DefaultEnum
assert issubclass(LibraryPrefs, DefaultEnum)