diff --git a/core/editor.py b/core/editor.py index 1054ccd..898aef4 100644 --- a/core/editor.py +++ b/core/editor.py @@ -109,11 +109,17 @@ def __init__(self, editor: "CodeEditor", parent=None): editor.textChanged.connect(self._invalidate_cache) editor.cursorPositionChanged.connect(self.update) - editor.blockCountChanged.connect(lambda _count: self._invalidate_cache()) + editor.blockCountChanged.connect(self._block_count_changed) editor.updateRequest.connect(self._editor_update_requested) - editor.verticalScrollBar().valueChanged.connect(lambda _value: self.update()) + editor.verticalScrollBar().valueChanged.connect(self._scroll_value_changed) editor.document().contentsChanged.connect(self._invalidate_cache) + def _block_count_changed(self, _count): + self._invalidate_cache() + + def _scroll_value_changed(self, _value): + self.update() + def _editor_update_requested(self, _rect, _dy): """Zeichnet die Vorschau nach Scroll- und Viewport-Updates neu.""" self.update() diff --git a/core/todo_scanner.py b/core/todo_scanner.py index 962d124..cead2ff 100644 --- a/core/todo_scanner.py +++ b/core/todo_scanner.py @@ -102,8 +102,8 @@ def scan_file_for_todos( if allowed_tags and tag not in allowed_tags: continue - # Schließende Kommentarzeichen (*/ oder -->) am Zeilenende entfernen - text = re.sub(r'(\*/|-->)\s*$', '', raw_text).strip() + # Kommentar-Endzeichen für die Anzeige entfernen, kein HTML-Sanitizer. + text = re.sub(r'(\*/|--!?>)\s*$', '', raw_text).strip() items.append( TodoItem( diff --git a/features/project_view.py b/features/project_view.py index a2590d3..311f843 100644 --- a/features/project_view.py +++ b/features/project_view.py @@ -222,7 +222,7 @@ def _setup_ui(self): "Öffnet den Dialog zum Bereitstellen und Committen von Git-Änderungen." ) self.btn_commit.setEnabled(False) - self.btn_commit.clicked.connect(lambda: self.open_commit_dialog()) + self.btn_commit.clicked.connect(self._on_commit_clicked) header.addWidget(self.btn_commit) layout.addLayout(header) @@ -338,6 +338,9 @@ def _setup_ui(self): layout.addWidget(self.tree) + def _on_commit_clicked(self, _checked=False): + self.open_commit_dialog() + def set_workspace(self, workspace: Optional[WorkspaceManager]): """Verbindet die ProjectView mit dem WorkspaceManager.""" if self._workspace is not None: diff --git a/tests/test_minimap.py b/tests/test_minimap.py index 7b33753..b3fa374 100644 --- a/tests/test_minimap.py +++ b/tests/test_minimap.py @@ -2,6 +2,9 @@ # -*- coding: utf-8 -*- """Tests für CodeBox-Minimap und ihre Editor-Einstellung.""" +import gc +import weakref + import pytest from PySide6.QtWidgets import QApplication @@ -47,6 +50,23 @@ def test_minimap_navigation_and_visibility(qapp): editor.close() +def test_minimap_signal_connections_release_closed_editor(qapp): + references = [] + for _ in range(3): + editor = CodeEditor() + editor.setPlainText("first\nsecond") + assert editor.minimap._document_lines() == ["first", "second"] + editor.appendPlainText("third") + assert editor.minimap._cached_lines is None + assert editor.minimap._document_lines() == ["first", "second", "third"] + references.append(weakref.ref(editor)) + editor.close() + del editor + gc.collect() + qapp.processEvents() + assert all(reference() is None for reference in references) + + def test_main_window_applies_minimap_setting(qapp, tmp_path, monkeypatch): settings_file = tmp_path / "settings.json" monkeypatch.setattr("config._SETTINGS_FILE", settings_file) diff --git a/tests/test_project_view.py b/tests/test_project_view.py index a3a94b4..9f3f084 100644 --- a/tests/test_project_view.py +++ b/tests/test_project_view.py @@ -1,4 +1,6 @@ """Regressionstests für ProjectView und Pfadsync im Hauptfenster.""" +import gc +import weakref from unittest.mock import patch from PySide6.QtWidgets import QApplication @@ -11,6 +13,23 @@ def _ensure_app(): return QApplication.instance() or QApplication([]) +def test_commit_button_dispatch_and_closed_view_collection(): + app = _ensure_app() + references = [] + for _ in range(3): + view = ProjectView() + with patch.object(view, "open_commit_dialog") as open_dialog: + view.btn_commit.setEnabled(True) + view.btn_commit.click() + open_dialog.assert_called_once_with() + references.append(weakref.ref(view)) + view.close() + del view + gc.collect() + app.processEvents() + assert all(reference() is None for reference in references) + + def test_new_file_in_shows_error_on_write_failure(tmp_path): """Regression (B-008): _new_file_in() muss OSError abfangen und eine Fehlermeldung zeigen, statt die Ausnahme unkontrolliert zu propagieren.""" diff --git a/tests/test_todo_scanner.py b/tests/test_todo_scanner.py index 1d95d5c..dfc70fa 100644 --- a/tests/test_todo_scanner.py +++ b/tests/test_todo_scanner.py @@ -90,6 +90,32 @@ def test_scan_file_for_todos(tmp_path): assert items[2].line_number == 5 +def test_scan_file_comment_terminators_preserve_task_text(tmp_path): + test_file = tmp_path / "comments.html" + test_file.write_text( + "\n" + " \n" + "/* BUG: C ending */\n" + "\n" + " inside text\n", + encoding="utf-8", + ) + + items = scan_file_for_todos(test_file) + assert [item.text for item in items] == [ + "Standard ending", + "Bang ending", + "C ending", + "Keep literal markup", + "Keep unsupported ending --!!>", + "Keep --> inside text", + ] + assert items[1].author == "anna" + assert items[1].line_number == 2 + assert items[1].line_text == " " + + def test_scan_file_binary_and_size_limits(tmp_path): """Prüft, dass Binärdateien und übergroße Dateien übersprungen werden.""" bin_file = tmp_path / "binary.dat"