From 435a72c76bc07cdc658368cf86a6456e78734992 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Sun, 27 Jul 2025 13:19:58 +0200 Subject: [PATCH] Add test coverage --- novelwriter/gui/doceditor.py | 20 ++-- tests/test_gui/test_gui_doceditor.py | 136 ++++++++++++++++++++++++++- 2 files changed, 144 insertions(+), 12 deletions(-) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 884c25e2..b31aaed0 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -2293,16 +2293,16 @@ class TextAutoReplace: """Auto-replace text elements based on main configuration. Returns True if anything was changed. """ - pos = cursor.positionInBlock() - apos = cursor.position() + aPos = cursor.position() + bPos = cursor.positionInBlock() block = cursor.block() length = block.length() - 1 - if length < 1 or pos-1 > length: + if length < 1 or bPos-1 > length: return False - cursor.movePosition(QtMoveLeft, QtKeepAnchor, min(4, pos)) + cursor.movePosition(QtMoveLeft, QtKeepAnchor, min(4, bPos)) last = cursor.selectedText() - delete, insert = self._determine(last, pos) + delete, insert = self._determine(last, bPos) check = insert if self._doPadBefore and check in self._padBefore: @@ -2320,7 +2320,7 @@ class TextAutoReplace: insert = insert + self._padChar if delete > 0: - cursor.setPosition(apos) + cursor.setPosition(aPos) cursor.movePosition(QtMoveLeft, QtKeepAnchor, delete) cursor.insertText(insert) return True @@ -2365,13 +2365,13 @@ class TextAutoReplace: elif pos == 3 and t3 == ">>'": return 1, self._quoteSO elif pos == 2 and t2 == "_'": - return 1, self._quoteDO + return 1, self._quoteSO elif t3 == " _'": - return 1, self._quoteDO + return 1, self._quoteSO elif pos == 3 and t3 in ("**'", "=='", "~~'"): - return 1, self._quoteDO + return 1, self._quoteSO elif t4 in (" **'", " =='", " ~~'"): - return 1, self._quoteDO + return 1, self._quoteSO else: return 1, self._quoteSC diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index 6480df93..b01871b0 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -27,7 +27,8 @@ import pytest from PyQt6.QtCore import QEvent, QMimeData, QPointF, Qt, QThreadPool, QUrl from PyQt6.QtGui import ( QAction, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent, - QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption + QDropEvent, QFont, QMouseEvent, QTextBlock, QTextCursor, QTextDocument, + QTextOption ) from PyQt6.QtWidgets import QApplication, QMenu, QPlainTextEdit @@ -36,7 +37,7 @@ from novelwriter.common import decodeMimeHandles from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout -from novelwriter.gui.doceditor import GuiDocEditor, _TagAction +from novelwriter.gui.doceditor import GuiDocEditor, TextAutoReplace, _TagAction from novelwriter.gui.dochighlight import TextBlockData from novelwriter.text.counting import standardCounter from novelwriter.types import ( @@ -2342,3 +2343,134 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum): assert docEditor.textCursor().selectedText() == "" # qtbot.stop() + + +@pytest.mark.gui +def testGuiEditor_TextAutoReplaceSymbols(): + """Test the editor auto-replace functionality.""" + CONFIG.fmtSQuoteOpen = nwUnicode.U_LSQUO + CONFIG.fmtSQuoteClose = nwUnicode.U_RSQUO + CONFIG.fmtDQuoteOpen = nwUnicode.U_LDQUO + CONFIG.fmtDQuoteClose = nwUnicode.U_RDQUO + + CONFIG.doReplaceSQuote = True + CONFIG.doReplaceDQuote = True + CONFIG.doReplaceDash = True + CONFIG.doReplaceDots = True + + ar = TextAutoReplace() + + def prep(text: str) -> tuple[str, int]: + return text, len(text) + + # Double Quote Open + assert ar._determine(*prep('"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('Stuff "')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('>"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('>>"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('_"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep(' _"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('**"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep(' **"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('=="')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep(' =="')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep('~~"')) == (1, nwUnicode.U_LDQUO) + assert ar._determine(*prep(' ~~"')) == (1, nwUnicode.U_LDQUO) + + # Double Quote Close + assert ar._determine(*prep('Stuff"')) == (1, nwUnicode.U_RDQUO) + + # Single Quote Open + assert ar._determine(*prep("'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep("Stuff '")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(">'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(">>'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep("_'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(" _'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep("**'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(" **'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep("=='")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(" =='")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep("~~'")) == (1, nwUnicode.U_LSQUO) + assert ar._determine(*prep(" ~~'")) == (1, nwUnicode.U_LSQUO) + + # Single Quote Close + assert ar._determine(*prep("Stuff'")) == (1, nwUnicode.U_RSQUO) + + # Dashes + assert ar._determine(*prep("-")) == (0, "-") + assert ar._determine(*prep("--")) == (2, nwUnicode.U_ENDASH) + assert ar._determine(*prep("---")) == (3, nwUnicode.U_EMDASH) + assert ar._determine(*prep("----")) == (4, nwUnicode.U_HBAR) + assert ar._determine(*prep("\u2013-")) == (2, nwUnicode.U_EMDASH) + assert ar._determine(*prep("\u2014-")) == (2, nwUnicode.U_HBAR) + + # Ellipsis + assert ar._determine(*prep(".")) == (0, ".") + assert ar._determine(*prep("..")) == (0, ".") + assert ar._determine(*prep("...")) == (3, nwUnicode.U_HELLIP) + + # Block Typed Line Separator (#1150) + assert ar._determine(*prep("Text\u2028")) == (1, nwUnicode.U_PSEP) + + +@pytest.mark.gui +def testGuiEditor_TextAutoReplaceProcess(): + """Test the editor auto-replace functionality.""" + CONFIG.fmtDQuoteOpen = nwUnicode.U_LAQUO + CONFIG.fmtDQuoteClose = nwUnicode.U_RAQUO + + CONFIG.doReplaceDQuote = True + CONFIG.doReplaceDots = True + + ar = TextAutoReplace() + doc = QTextDocument() + + def prep(text: str) -> tuple[str, QTextCursor]: + doc.setPlainText(text) + cursor = QTextCursor(doc) + cursor.setPosition(len(text)) + return text, cursor + + # Nothing to Process + assert ar.process(*prep("")) is False + + # Standard Auto-Replace + assert ar.process(*prep("Text ...")) is True + assert doc.toRawText() == "Text \u2026" + + # Pad Before, Normal + CONFIG.fmtPadBefore = ":\u00bb" + CONFIG.fmtPadThin = False + ar.initSettings() + assert ar.process(*prep("Text:")) is True + assert doc.toRawText() == "Text\u00a0:" + assert ar.process(*prep("Text :")) is True # See #1061 + assert doc.toRawText() == "Text\u00a0:" + assert ar.process(*prep('Text"')) is True + assert doc.toRawText() == "Text\u00a0»" + assert ar.process(*prep("@Synopsis:")) is False + assert doc.toRawText() == "@Synopsis:" + + # Pad Before, Thin + CONFIG.fmtPadBefore = ":\u00bb" + CONFIG.fmtPadThin = True + ar.initSettings() + assert ar.process(*prep("Text:")) is True + assert doc.toRawText() == "Text\u202f:" + assert ar.process(*prep("Text :")) is True # See #1061 + assert doc.toRawText() == "Text\u202f:" + + # Pad After, Normal + CONFIG.fmtPadAfter = "\u00ab" + CONFIG.fmtPadThin = False + ar.initSettings() + assert ar.process(*prep('Text "')) is True + assert doc.toRawText() == "Text «\u00a0" + + # Pad After, Thin + CONFIG.fmtPadAfter = "\u00ab" + CONFIG.fmtPadThin = True + ar.initSettings() + assert ar.process(*prep('Text "')) is True + assert doc.toRawText() == "Text «\u202f"