diff --git a/CHANGELOG.md b/CHANGELOG.md index b02f50b..6c0871b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,14 @@ -# 0.2.1.9 +# 0.3 - * Fix a few small matters identified with tests - * Make locales dynamically detected from the locales dir rather than hardcoded + * Introduce Tags + * Make translations dynamically detected from the locales dir rather than hardcoded + * Add Italian translations (thanks @mdaleo404) * Add version information in the navigation * Increase line spacing between lines (except for code blocks) - * Add Italian translations (thanks @mdaleo404) * Prevent being able to left-click a date and have it load in current tab if it is already open in another tab * Avoid second checkbox/bullet on second newline after first newline * Avoid Home/left arrow jumping to the left side of a list symbol + * Various test additions/fixes # 0.2.1.8 diff --git a/README.md b/README.md index 96bdf58..b904f20 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ There is deliberately no network connectivity or syncing intended. * Tabs are supported - right-click on a date from the calendar to open it in a new tab. * Images are supported * Search all pages, or find text on page (Ctrl+F) + * Add tags to pages, find pages by tag in the Tag Browser, and customise tag names and colours * Automatic periodic saving (or explicitly save) * Transparent integrity checking of the database when it opens * Automatic locking of the app after a period of inactivity (default 15 min) @@ -37,7 +38,8 @@ There is deliberately no network connectivity or syncing intended. * Backup the database to encrypted SQLCipher format (which can then be loaded back in to a Bouquin) * Dark and light themes * Automatically generate checkboxes when typing 'TODO' - * Optionally automatically move unchecked checkboxes from yesterday to today, on startup + * It is possible to automatically move unchecked checkboxes from yesterday to today, on startup + * English, French and Italian locales provided ## How to install diff --git a/bouquin/db.py b/bouquin/db.py index 60a58c4..c0fdee2 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -23,6 +23,20 @@ _TAG_COLORS = [ "#BAFFC9", # soft green "#BAE1FF", # soft blue "#E0BAFF", # soft purple + "#FFC4B3", # soft coral + "#FFD8B1", # soft peach + "#FFF1BA", # soft light yellow + "#E9FFBA", # soft lime + "#CFFFE5", # soft mint + "#BAFFF5", # soft aqua + "#BAF0FF", # soft cyan + "#C7E9FF", # soft sky blue + "#C7CEFF", # soft periwinkle + "#F0BAFF", # soft lavender pink + "#FFBAF2", # soft magenta + "#FFD1F0", # soft pink + "#EBD5C7", # soft beige + "#EAEAEA", # soft gray ] @@ -554,16 +568,22 @@ class DBManager: name = name.strip() color = color.strip() or "#CCCCCC" - with self.conn: - cur = self.conn.cursor() - cur.execute( - """ - UPDATE tags - SET name = ?, color = ? - WHERE id = ?; - """, - (name, color, tag_id), - ) + try: + with self.conn: + cur = self.conn.cursor() + cur.execute( + """ + UPDATE tags + SET name = ?, color = ? + WHERE id = ?; + """, + (name, color, tag_id), + ) + except sqlite.IntegrityError as e: + if "UNIQUE constraint failed: tags.name" in str(e): + raise sqlite.IntegrityError( + strings._("tag_already_exists_with_that_name") + ) from e def delete_tag(self, tag_id: int) -> None: """ diff --git a/bouquin/locales/en.json b/bouquin/locales/en.json index e5957ea..a3c9228 100644 --- a/bouquin/locales/en.json +++ b/bouquin/locales/en.json @@ -112,13 +112,14 @@ "toolbar_heading": "Heading", "toolbar_toggle_checkboxes": "Toggle checkboxes", "tags": "Tags", + "tag": "Tag", "manage_tags": "Manage tags", "add_tag_placeholder": "Add a tag and press Enter", "tag_browser_title": "Tag Browser", "tag_browser_instructions": "Click a tag to expand and see all pages with that tag. Click a date to open it. Select a tag to edit its name, change its color, or delete it globally.", "tag_name": "Tag name", "tag_color_hex": "Hex colour", - "color_hex": "Color", + "color_hex": "Colour", "date": "Date", "pick_color": "Pick colour", "invalid_color_title": "Invalid colour", @@ -130,5 +131,6 @@ "new_tag_name": "New tag name:", "change_color": "Change colour", "delete_tag": "Delete tag", - "delete_tag_confirm": "Are you sure you want to delete the tag '{name}'? This will remove it from all pages." + "delete_tag_confirm": "Are you sure you want to delete the tag '{name}'? This will remove it from all pages.", + "tag_already_exists_with_that_name": "A tag already exists with that name" } diff --git a/bouquin/locales/fr.json b/bouquin/locales/fr.json index 2dbe11a..4944bf5 100644 --- a/bouquin/locales/fr.json +++ b/bouquin/locales/fr.json @@ -111,12 +111,13 @@ "toolbar_code_block": "Bloc de code", "toolbar_heading": "Titre", "toolbar_toggle_checkboxes": "Cocher/Décocher les cases", - "tags": "Tags", - "manage_tags": "Gérer les tags", - "add_tag_placeholder": "Ajouter un tag et appuyez sur Entrée", - "tag_browser_title": "Navigateur de tags", - "tag_browser_instructions": "Cliquez sur un tag pour l'étendre et voir toutes les pages avec ce tag. Cliquez sur une date pour l'ouvrir. Sélectionnez un tag pour modifier son nom, changer sa couleur ou le supprimer globalement.", - "tag_name": "Nom du tag", + "tags": "Étiquettes", + "tag": "Étiquette", + "manage_tags": "Gérer les étiquettes", + "add_tag_placeholder": "Ajouter une étiquette et appuyez sur Entrée", + "tag_browser_title": "Navigateur de étiquettes", + "tag_browser_instructions": "Cliquez sur une étiquette pour l'étendre et voir toutes les pages avec cette étiquette. Cliquez sur une date pour l'ouvrir. Sélectionnez une étiquette pour modifier son nom, changer sa couleur ou la supprimer globalement.", + "tag_name": "Nom de l'étiquette", "tag_color_hex": "Couleur hexadécimale", "color_hex": "Couleur", "date": "Date", @@ -126,9 +127,10 @@ "add": "Ajouter", "remove": "Supprimer", "ok": "OK", - "edit_tag_name": "Modifier le nom du tag", - "new_tag_name": "Nouveau nom du tag :", + "edit_tag_name": "Modifier le nom de l'étiquette", + "new_tag_name": "Nouveau nom de l'étiquette :", "change_color": "Changer la couleur", - "delete_tag": "Supprimer le tag", - "delete_tag_confirm": "Êtes-vous sûr de vouloir supprimer le tag '{name}' ? Cela le supprimera de toutes les pages." + "delete_tag": "Supprimer l'étiquette", + "delete_tag_confirm": "Êtes-vous sûr de vouloir supprimer l'étiquette '{name}' ? Cela la supprimera de toutes les pages.", + "tag_already_exists_with_that_name": "Une étiquette portant ce nom existe déjà" } diff --git a/bouquin/locales/it.json b/bouquin/locales/it.json index d6d2018..5e956f0 100644 --- a/bouquin/locales/it.json +++ b/bouquin/locales/it.json @@ -130,5 +130,6 @@ "new_tag_name": "Nuovo nome tag:", "change_color": "Cambia colore", "delete_tag": "Elimina tag", - "delete_tag_confirm": "Sei sicuro di voler eliminare il tag '{name}'? Questo lo rimuoverà da tutte le pagine." + "delete_tag_confirm": "Sei sicuro di voler eliminare il tag '{name}'? Questo lo rimuoverà da tutte le pagine.", + "tag_already_exists_with_that_name": "Esiste già un tag con questo nome" } diff --git a/bouquin/main_window.py b/bouquin/main_window.py index 8692ea9..ce008fa 100644 --- a/bouquin/main_window.py +++ b/bouquin/main_window.py @@ -1077,8 +1077,17 @@ class MainWindow(QMainWindow): dlg = TagBrowserDialog(self.db, self, focus_tag=tag_name_or_date) dlg.openDateRequested.connect(self._load_selected_date) + dlg.tagsModified.connect(self._refresh_current_page_tags) dlg.exec() + def _refresh_current_page_tags(self): + """Refresh the tag chips for the current page (after tag browser changes)""" + if hasattr(self, "tags") and hasattr(self.editor, "current_date"): + date_iso = self.editor.current_date.toString("yyyy-MM-dd") + self.tags.set_current_date(date_iso) + if self.tags.toggle_btn.isChecked(): + self.tags._reload_tags() + # ----------- Settings handler ------------# def _open_settings(self): dlg = SettingsDialog(self.cfg, self.db, self) diff --git a/bouquin/markdown_highlighter.py b/bouquin/markdown_highlighter.py index e68f03c..3576d1b 100644 --- a/bouquin/markdown_highlighter.py +++ b/bouquin/markdown_highlighter.py @@ -199,7 +199,7 @@ class MarkdownHighlighter(QSyntaxHighlighter): self.setFormat(end - 2, 2, self.syntax_format) self.setFormat(content_start, content_end - content_start, self.bold_format) - # --- Italic (*) or (_): skip if it overlaps any triple, keep your guards + # --- Italic (*) or (_): skip if it overlaps any triple for m in re.finditer( r"(? 0.5 else QColor(255, 255, 255) + root.setForeground(1, text_color) root.setText(1, color) # Also show the hex code root.setTextAlignment(1, Qt.AlignCenter) @@ -112,6 +130,9 @@ class TagBrowserDialog(QDialog): self.tree.expandItem(focus_item) self.tree.setCurrentItem(focus_item) + # Re-enable sorting after population + self.tree.setSortingEnabled(was_sorting) + def _on_item_clicked(self, item: QTreeWidgetItem, column: int): """Enable/disable buttons based on selection""" data = item.data(0, Qt.ItemDataRole.UserRole) @@ -156,8 +177,12 @@ class TagBrowserDialog(QDialog): ) if ok and new_name and new_name != old_name: - self._db.update_tag(tag_id, new_name, color) - self._populate(None) + try: + self._db.update_tag(tag_id, new_name, color) + self._populate(None) + self.tagsModified.emit() + except IntegrityError as e: + QMessageBox.critical(self, strings._("db_database_error"), str(e)) def _change_tag_color(self): """Change the color of the selected tag""" @@ -175,8 +200,12 @@ class TagBrowserDialog(QDialog): color = QColorDialog.getColor(QColor(current_color), self) if color.isValid(): - self._db.update_tag(tag_id, name, color.name()) - self._populate(None) + try: + self._db.update_tag(tag_id, name, color.name()) + self._populate(None) + self.tagsModified.emit() + except IntegrityError as e: + QMessageBox.critical(self, strings._("db_database_error"), str(e)) def _delete_tag(self): """Delete the selected tag""" @@ -203,3 +232,4 @@ class TagBrowserDialog(QDialog): if reply == QMessageBox.Yes: self._db.delete_tag(tag_id) self._populate(None) + self.tagsModified.emit() diff --git a/bouquin/tags_widget.py b/bouquin/tags_widget.py index 0e50446..423bd06 100644 --- a/bouquin/tags_widget.py +++ b/bouquin/tags_widget.py @@ -70,7 +70,10 @@ class TagChip(QFrame): def mouseReleaseEvent(self, ev): if ev.button() == Qt.LeftButton: self.clicked.emit(self._name) - super().mouseReleaseEvent(ev) + try: + super().mouseReleaseEvent(ev) + except RuntimeError: + pass class PageTagsWidget(QFrame): diff --git a/bouquin/theme.py b/bouquin/theme.py index 3846398..305f249 100644 --- a/bouquin/theme.py +++ b/bouquin/theme.py @@ -204,7 +204,7 @@ class ThemeManager(QObject): ) if is_dark: - # Use the link color as the accent (you set this to ORANGE in dark palette) + # Use the link color as the accent accent = pal.color(QPalette.Link) r, g, b = accent.red(), accent.green(), accent.blue() accent_hex = accent.name() diff --git a/pyproject.toml b/pyproject.toml index 1bbde99..6b057a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "bouquin" -version = "0.2.1.8" +version = "0.3" description = "Bouquin is a simple, opinionated notebook application written in Python, PyQt and SQLCipher." authors = ["Miguel Jacq "] readme = "README.md" diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 2962a34..9c83b11 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -435,7 +435,7 @@ def test_init_exits_when_prompt_rejected(app, monkeypatch, tmp_path): # Avoid accidentaly creating DB by short-circuiting the prompt loop class MW(MainWindow): def _prompt_for_key_until_valid(self, first_time: bool) -> bool: # noqa: N802 - assert first_time is True # hit line 73 path + assert first_time is True return False with pytest.raises(SystemExit): @@ -938,7 +938,7 @@ def test_apply_idle_minutes_paths_and_unlock(qtbot, tmp_db_cfg, app, monkeypatch # remove timer to hit early return delattr(w, "_idle_timer") - w._apply_idle_minutes(5) # no crash => line 1176 branch + w._apply_idle_minutes(5) # no crash # re-create a timer and simulate locking then disabling idle w._idle_timer = QTimer(w) @@ -1474,7 +1474,7 @@ def test_closeEvent_swallows_exceptions(qtbot, app, tmp_db_cfg, monkeypatch): # ============================================================================ -# Tag Save Handler Tests (lines 1050-1068) +# Tag Save Handler Tests # ============================================================================ @@ -1525,7 +1525,7 @@ def test_main_window_do_tag_save_no_editor(app, fresh_db, tmp_db_cfg, monkeypatc def test_main_window_on_tag_added_triggers_deferred_save( app, fresh_db, tmp_db_cfg, monkeypatch ): - """Test that _on_tag_added defers the save (lines 1043-1048)""" + """Test that _on_tag_added defers the save""" monkeypatch.setattr( "bouquin.main_window.KeyPrompt", lambda *args, **kwargs: Mock(exec=lambda: True, key=lambda: tmp_db_cfg.key), @@ -1546,7 +1546,7 @@ def test_main_window_on_tag_added_triggers_deferred_save( # ============================================================================ -# Tag Activation Tests (lines 1070-1080) +# Tag Activation Tests # ============================================================================ @@ -1600,7 +1600,7 @@ def test_main_window_on_tag_activated_with_tag_name( # ============================================================================ -# Settings Path Change Tests (lines 1105-1116) +# Settings Path Change Tests # ============================================================================ @@ -1651,7 +1651,7 @@ def test_main_window_settings_path_change_success( def test_main_window_settings_path_change_failure( app, fresh_db, tmp_db_cfg, tmp_path, monkeypatch ): - """Test failed database path change shows warning (lines 1108-1113)""" + """Test failed database path change shows warning""" monkeypatch.setattr( "bouquin.main_window.KeyPrompt", lambda *args, **kwargs: Mock(exec=lambda: True, key=lambda: tmp_db_cfg.key), @@ -1691,7 +1691,7 @@ def test_main_window_settings_path_change_failure( def test_main_window_settings_no_path_change(app, fresh_db, tmp_db_cfg, monkeypatch): - """Test settings change without path change (lines 1105 condition False)""" + """Test settings change without path change""" monkeypatch.setattr( "bouquin.main_window.KeyPrompt", lambda *args, **kwargs: Mock(exec=lambda: True, key=lambda: tmp_db_cfg.key), @@ -1729,7 +1729,7 @@ def test_main_window_settings_no_path_change(app, fresh_db, tmp_db_cfg, monkeypa def test_main_window_settings_cancelled(app, fresh_db, tmp_db_cfg, monkeypatch): - """Test cancelling settings dialog (line 1085-1086)""" + """Test cancelling settings dialog""" monkeypatch.setattr( "bouquin.main_window.KeyPrompt", lambda *args, **kwargs: Mock(exec=lambda: True, key=lambda: tmp_db_cfg.key), @@ -1753,7 +1753,7 @@ def test_main_window_settings_cancelled(app, fresh_db, tmp_db_cfg, monkeypatch): # ============================================================================ -# Update Tag Views Tests (lines 1039-1041) +# Update Tag Views Tests # ============================================================================ diff --git a/tests/test_markdown_editor.py b/tests/test_markdown_editor.py index a8be6a7..13244f6 100644 --- a/tests/test_markdown_editor.py +++ b/tests/test_markdown_editor.py @@ -374,7 +374,7 @@ def test_theme_change_rehighlight(highlighter): @pytest.fixture def hl_light(app): - # Light theme path (covers lines ~74-75 in _on_theme_changed) + # Light theme path tm = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT)) doc = QTextDocument() hl = MarkdownHighlighter(doc, tm) @@ -435,7 +435,7 @@ def test_code_block_light_colors(hl_light): def test_end_guard_skips_italic_followed_by_marker(hl_light): """ - Triggers the end-following guard for italic (line ~208), e.g. '*i**'. + Triggers the end-following guard for italic e.g. '*i**'. """ doc, hl = hl_light doc.setPlainText("*i**") @@ -543,7 +543,7 @@ def test_insert_image_from_path_invalid_returns(editor_hello, tmp_path): # ============================================================================ -# setDocument Tests (lines 75-81) +# setDocument Tests # ============================================================================ @@ -582,7 +582,7 @@ def test_markdown_editor_set_document_with_highlighter(app): # ============================================================================ -# showEvent Tests (lines 83-86) +# showEvent Tests # ============================================================================ @@ -604,7 +604,7 @@ def test_markdown_editor_show_event(app, qtbot): # ============================================================================ -# Checkbox Transformation Tests (lines 100-133) +# Checkbox Transformation Tests # ============================================================================ @@ -645,7 +645,7 @@ def test_markdown_editor_transform_checked_checkbox(app, qtbot): def test_markdown_editor_transform_todo(app, qtbot): - """Test transforming TODO to unchecked checkbox (lines 110-114)""" + """Test transforming TODO to unchecked checkbox""" themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT)) editor = MarkdownEditor(themes) editor.show() @@ -726,7 +726,7 @@ def test_markdown_editor_no_transform_when_updating(app): editor.insertPlainText("- [ ] Task") # Should NOT transform since _updating is True - # This tests the early return in _on_text_changed (lines 90-91) + # This tests the early return in _on_text_changed assert editor._updating @@ -779,7 +779,7 @@ def test_markdown_editor_update_code_block_backgrounds(app): # ============================================================================ -# Image Insertion Tests (lines 336-366) +# Image Insertion Tests # ============================================================================ @@ -811,7 +811,7 @@ def test_markdown_editor_insert_image_from_path(app, tmp_path): # ============================================================================ -# Formatting Tests (missing lines in various formatting methods) +# Formatting Tests # ============================================================================ @@ -886,7 +886,7 @@ def test_markdown_editor_toggle_code_empty_selection(app): # ============================================================================ -# Heading Tests (lines 455-459) +# Heading Tests # ============================================================================ @@ -932,7 +932,7 @@ def test_markdown_editor_set_heading_zero_removes_heading(app): # ============================================================================ -# List Tests (lines 483-519) +# List Tests # ============================================================================ @@ -972,7 +972,7 @@ def test_markdown_editor_toggle_list_ordered(app): # ============================================================================ -# Code Block Tests (lines 540-577) +# Code Block Tests # ============================================================================ @@ -1016,7 +1016,7 @@ def test_markdown_editor_apply_code_remove(app): # ============================================================================ -# Checkbox Tests (lines 596-600) +# Checkbox Tests # ============================================================================ @@ -1032,7 +1032,7 @@ def test_markdown_editor_insert_checkbox_unchecked(app): # ============================================================================ -# Toggle Checkboxes Tests (lines 659-660, 686-691) +# Toggle Checkboxes Tests # ============================================================================ @@ -1071,7 +1071,7 @@ def test_markdown_editor_toggle_checkboxes_mixed(app): # ============================================================================ -# Markdown Conversion Tests (lines 703, 710-714, 731) +# Markdown Conversion Tests # ============================================================================ @@ -1113,7 +1113,7 @@ def test_markdown_editor_from_markdown_with_links(app): # ============================================================================ -# Selection and Cursor Tests (lines 747-752) +# Selection and Cursor Tests # ============================================================================ @@ -1152,7 +1152,7 @@ def test_markdown_editor_get_selected_blocks(app): # ============================================================================ -# Key Event Tests (lines 795, 806-809) +# Key Event Tests # ============================================================================ @@ -1194,7 +1194,7 @@ def test_markdown_editor_key_press_return_in_list(app): # ============================================================================ -# Link Handling Tests (lines 898, 922, 949, 990) +# Link Handling Tests # ============================================================================ @@ -1229,12 +1229,12 @@ def test_markdown_editor_mouse_move_over_link(app): # ============================================================================ -# Theme Mode Tests (lines 72-79) +# Theme Mode Tests # ============================================================================ def test_markdown_highlighter_light_mode(app): - """Test highlighter in light mode (lines 74-77)""" + """Test highlighter in light mode""" doc = QTextDocument() themes = ThemeManager(app, ThemeConfig(theme=Theme.LIGHT)) highlighter = MarkdownHighlighter(doc, themes) @@ -1252,7 +1252,7 @@ def test_markdown_highlighter_light_mode(app): def test_markdown_highlighter_dark_mode(app): - """Test highlighter in dark mode (lines 70-71)""" + """Test highlighter in dark mode""" doc = QTextDocument() themes = ThemeManager(app, ThemeConfig(theme=Theme.DARK)) highlighter = MarkdownHighlighter(doc, themes) @@ -1266,7 +1266,7 @@ def test_markdown_highlighter_dark_mode(app): # ============================================================================ -# Highlighting Pattern Tests (lines 196, 208, 211, 213) +# Highlighting Pattern Tests # ============================================================================ diff --git a/tests/test_tags.py b/tests/test_tags.py index 8022e11..c8bc804 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -786,7 +786,7 @@ def test_tag_page_without_content(fresh_db): # ============================================================================ -# TagChip Mouse Event Tests (tags_widget.py lines 70-73) +# TagChip Mouse Event Tests # ============================================================================ @@ -844,12 +844,12 @@ def test_tag_chip_right_click_no_signal(app, qtbot): # ============================================================================ -# PageTagsWidget Edge Cases (tags_widget.py missing lines) +# PageTagsWidget Edge Cases # ============================================================================ def test_page_tags_widget_add_tag_with_completer_popup_visible(app, fresh_db): - """Test adding tag when completer popup is visible (line 148)""" + """Test adding tag when completer popup is visible""" widget = PageTagsWidget(fresh_db) widget.show() date_iso = "2024-01-15" @@ -906,12 +906,12 @@ def test_page_tags_widget_no_current_date_remove_tag(app, fresh_db): # ============================================================================ -# TagBrowserDialog Interactive Tests (tag_browser.py lines 124-126, 139-205) +# TagBrowserDialog Interactive Tests # ============================================================================ def test_tag_browser_button_states_with_page_item(app, fresh_db): - """Test that buttons are disabled when clicking a page item (lines 124-126)""" + """Test that buttons are disabled when clicking a page item""" fresh_db.save_new_version("2024-01-15", "Content", "note") fresh_db.set_tags_for_page("2024-01-15", ["test"]) @@ -936,7 +936,7 @@ def test_tag_browser_button_states_with_page_item(app, fresh_db): def test_tag_browser_edit_tag_name_no_item(app, fresh_db): - """Test editing tag name when no item is selected (lines 139-141)""" + """Test editing tag name when no item is selected""" dialog = TagBrowserDialog(fresh_db) # Try to edit without selecting anything @@ -947,7 +947,7 @@ def test_tag_browser_edit_tag_name_no_item(app, fresh_db): def test_tag_browser_edit_tag_name_page_item(app, fresh_db): - """Test editing tag name when a page item is selected (lines 143-145)""" + """Test editing tag name when a page item is selected""" fresh_db.save_new_version("2024-01-15", "Content", "note") fresh_db.set_tags_for_page("2024-01-15", ["test"]) @@ -969,7 +969,7 @@ def test_tag_browser_edit_tag_name_page_item(app, fresh_db): def test_tag_browser_change_color_no_item(app, fresh_db): - """Test changing color when no item is selected (lines 164-166)""" + """Test changing color when no item is selected""" dialog = TagBrowserDialog(fresh_db) # Try to change color without selecting anything @@ -980,7 +980,7 @@ def test_tag_browser_change_color_no_item(app, fresh_db): def test_tag_browser_change_color_page_item(app, fresh_db): - """Test changing color when a page item is selected (lines 168-170)""" + """Test changing color when a page item is selected""" fresh_db.save_new_version("2024-01-15", "Content", "note") fresh_db.set_tags_for_page("2024-01-15", ["test"]) @@ -1002,7 +1002,7 @@ def test_tag_browser_change_color_page_item(app, fresh_db): def test_tag_browser_delete_tag_no_item(app, fresh_db): - """Test deleting tag when no item is selected (lines 183-185)""" + """Test deleting tag when no item is selected""" dialog = TagBrowserDialog(fresh_db) # Try to delete without selecting anything @@ -1013,7 +1013,7 @@ def test_tag_browser_delete_tag_no_item(app, fresh_db): def test_tag_browser_delete_tag_page_item(app, fresh_db): - """Test deleting tag when a page item is selected (lines 187-189)""" + """Test deleting tag when a page item is selected""" fresh_db.save_new_version("2024-01-15", "Content", "note") fresh_db.set_tags_for_page("2024-01-15", ["test"]) @@ -1036,12 +1036,12 @@ def test_tag_browser_delete_tag_page_item(app, fresh_db): # ============================================================================ -# FlowLayout Edge Case (flow_layout.py line 28) +# FlowLayout Edge Case # ============================================================================ def test_flow_layout_take_at_out_of_bounds(app): - """Test FlowLayout.takeAt with invalid index (line 28)""" + """Test FlowLayout.takeAt with invalid index""" layout = FlowLayout() # Try to take item at index that doesn't exist @@ -1063,7 +1063,7 @@ def test_flow_layout_take_at_negative(app): # ============================================================================ -# DB Edge Case (db.py line 434) +# DB Edge Case for tags # ============================================================================