From 52dddb6e2be6e5168539af918656ae67b62cd134 Mon Sep 17 00:00:00 2001 From: shdra06 Date: Thu, 29 Oct 2026 10:12:11 +0530 Subject: [PATCH] fix(ui): refresh tag categories after editing a parent tag Editing a parent tag from the tag editor only redrew the parent list, so categories inherited through that parent's own parents stayed stale until the original tag was changed. Refresh the category section as well. Fixes #1551. Co-Authored-By: Claude Opus 5.5 --- .../qt/controllers/edit_tag_panel.py | 7 +- tests/qt/test_edit_tag_panel.py | 66 +++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/src/tagstudio/qt/controllers/edit_tag_panel.py b/src/tagstudio/qt/controllers/edit_tag_panel.py index 0fdd9fbc5..51248f7b0 100644 --- a/src/tagstudio/qt/controllers/edit_tag_panel.py +++ b/src/tagstudio/qt/controllers/edit_tag_panel.py @@ -330,7 +330,7 @@ def _build_parent_row_widget(self, tag: Tag, parent_id: int, is_disambiguation: capsule.set_text(self._lib.tag_display_name(tag)) capsule.set_color_group(tag.color) capsule.on_remove.connect(lambda t=parent_id: self._remove_parent_tag_callback(t)) - capsule.on_edit.connect(partial(self._edit_tag, tag, self._set_parent_tags)) + capsule.on_edit.connect(partial(self._edit_tag, tag, self._on_parent_tag_edited)) row.addWidget(capsule) @@ -350,6 +350,11 @@ def _build_parent_row_widget(self, tag: Tag, parent_id: int, is_disambiguation: return capsule.layout().button, disam_button, container + def _on_parent_tag_edited(self) -> None: + # A parent's own parents determine which categories this tag inherits. + self._set_parent_tags() + self._set_categories() + def _toggle_disam_id(self, disambiguation_id: int | None): if self._disambiguation_id == disambiguation_id: self._disambiguation_id = None diff --git a/tests/qt/test_edit_tag_panel.py b/tests/qt/test_edit_tag_panel.py index 5501b504d..e517378a3 100644 --- a/tests/qt/test_edit_tag_panel.py +++ b/tests/qt/test_edit_tag_panel.py @@ -7,6 +7,7 @@ from collections.abc import Callable from typing import cast +import pytest from PySide6.QtWidgets import QCheckBox from pytestqt.qtbot import QtBot @@ -16,6 +17,7 @@ from tagstudio.i18n.translations import Translations from tagstudio.qt.controllers.capsule import Capsule from tagstudio.qt.controllers.edit_tag_panel import CustomTableItem, EditTagPanel +from tagstudio.qt.controllers.modal import Modal from tagstudio.qt.qt_driver import QtDriver @@ -573,6 +575,70 @@ def test_edit_tag_panel_remove_category_inherited_directly_and_indirectly( assert capsule is None +def test_edit_tag_panel_add_category_by_editing_parent( + qtbot: QtBot, + library: Library, + generate_tag: Callable[..., Tag], + monkeypatch: pytest.MonkeyPatch, +): + category = unwrap(library.add_tag(generate_tag("category", id=122, is_category=True))) + parent = unwrap(library.add_tag(generate_tag("parent", id=123))) + child = unwrap(library.add_tag(generate_tag("child", id=124, parent_tags={parent}))) + + panel: EditTagPanel = EditTagPanel(library, child) + qtbot.addWidget(panel) + + assert _find_category_capsule(panel) is None + + modal = _open_parent_edit_modal(panel, monkeypatch) + nested_panel = cast(EditTagPanel, modal.layout().content_widget) + nested_panel._add_parent_tag_callback(category.id) + modal.saved.emit() + + capsule = _find_category_capsule(panel) + assert capsule is not None + assert capsule.layout().button.text() == category.name + + +def test_edit_tag_panel_remove_category_by_editing_parent( + qtbot: QtBot, + library: Library, + generate_tag: Callable[..., Tag], + monkeypatch: pytest.MonkeyPatch, +): + category = unwrap(library.add_tag(generate_tag("category", id=122, is_category=True))) + parent = unwrap(library.add_tag(generate_tag("parent", id=123, parent_tags={category}))) + child = unwrap(library.add_tag(generate_tag("child", id=124, parent_tags={parent}))) + + panel: EditTagPanel = EditTagPanel(library, child) + qtbot.addWidget(panel) + + capsule = _find_category_capsule(panel) + assert capsule is not None + assert capsule.layout().button.text() == category.name + + modal = _open_parent_edit_modal(panel, monkeypatch) + nested_panel = cast(EditTagPanel, modal.layout().content_widget) + nested_panel._remove_parent_tag_callback(category.id) + modal.saved.emit() + + assert _find_category_capsule(panel) is None + + +def _open_parent_edit_modal(panel: EditTagPanel, monkeypatch: pytest.MonkeyPatch) -> Modal: + """Click "Edit" on the first parent tag and return the nested modal it opens.""" + shown: list[Modal] = [] + monkeypatch.setattr(Modal, "show", lambda self: shown.append(self)) # pyright: ignore + + parent_rows = panel.layout().parent_tags_scroll_layout.itemAt(0).widget().layout() + capsule = parent_rows.itemAt(0).widget().layout().itemAt(0).widget() + assert isinstance(capsule, Capsule) + capsule.on_edit.emit() + + assert len(shown) == 1 + return shown[0] + + def _find_category_capsule(panel: EditTagPanel, index: int = 0) -> Capsule | None: item = panel.layout().category_scroll_layout.itemAt(0).widget().layout().itemAt(index) while item is not None: