Add test coverage of editor context menu
This commit is contained in:
@@ -1067,49 +1067,48 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _openContextMenu(self, pos: QPoint) -> None:
|
||||
"""Triggered by right click to open the context menu. Also
|
||||
triggered by the Ctrl+. shortcut.
|
||||
"""
|
||||
"""Open the editor context menu at a given coordinate."""
|
||||
uCursor = self.textCursor()
|
||||
pCursor = self.cursorForPosition(pos)
|
||||
pBlock = pCursor.block()
|
||||
|
||||
ctxMenu = QMenu(self)
|
||||
ctxMenu.setObjectName("ContextMenu")
|
||||
if pBlock.userState() == GuiDocHighlighter.BLOCK_TITLE:
|
||||
aLabel = ctxMenu.addAction(self.tr("Set as Document Name"))
|
||||
aLabel.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
||||
action = ctxMenu.addAction(self.tr("Set as Document Name"))
|
||||
action.triggered.connect(lambda: self._emitRenameItem(pBlock))
|
||||
|
||||
# Follow
|
||||
status = self._processTag(cursor=pCursor, follow=False)
|
||||
if status == nwTrinary.POSITIVE:
|
||||
aTag = ctxMenu.addAction(self.tr("Follow Tag"))
|
||||
aTag.triggered.connect(lambda: self._processTag(cursor=pCursor, follow=True))
|
||||
action = ctxMenu.addAction(self.tr("Follow Tag"))
|
||||
action.triggered.connect(lambda: self._processTag(cursor=pCursor, follow=True))
|
||||
ctxMenu.addSeparator()
|
||||
elif status == nwTrinary.NEGATIVE:
|
||||
aTag = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
||||
aTag.triggered.connect(lambda: self._processTag(cursor=pCursor, create=True))
|
||||
action = ctxMenu.addAction(self.tr("Create Note for Tag"))
|
||||
action.triggered.connect(lambda: self._processTag(cursor=pCursor, create=True))
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
# Cut, Copy and Paste
|
||||
if uCursor.hasSelection():
|
||||
aCut = ctxMenu.addAction(self.tr("Cut"))
|
||||
aCut.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
|
||||
aCopy = ctxMenu.addAction(self.tr("Copy"))
|
||||
aCopy.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
|
||||
action = ctxMenu.addAction(self.tr("Cut"))
|
||||
action.triggered.connect(lambda: self.docAction(nwDocAction.CUT))
|
||||
action = ctxMenu.addAction(self.tr("Copy"))
|
||||
action.triggered.connect(lambda: self.docAction(nwDocAction.COPY))
|
||||
|
||||
aPaste = ctxMenu.addAction(self.tr("Paste"))
|
||||
aPaste.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
|
||||
action = ctxMenu.addAction(self.tr("Paste"))
|
||||
action.triggered.connect(lambda: self.docAction(nwDocAction.PASTE))
|
||||
ctxMenu.addSeparator()
|
||||
|
||||
# Selections
|
||||
aSAll = ctxMenu.addAction(self.tr("Select All"))
|
||||
aSAll.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
|
||||
aSWrd = ctxMenu.addAction(self.tr("Select Word"))
|
||||
aSWrd.triggered.connect(
|
||||
action = ctxMenu.addAction(self.tr("Select All"))
|
||||
action.triggered.connect(lambda: self.docAction(nwDocAction.SEL_ALL))
|
||||
action = ctxMenu.addAction(self.tr("Select Word"))
|
||||
action.triggered.connect(
|
||||
lambda: self._makePosSelection(QTextCursor.SelectionType.WordUnderCursor, pos)
|
||||
)
|
||||
aSPar = ctxMenu.addAction(self.tr("Select Paragraph"))
|
||||
aSPar.triggered.connect(lambda: self._makePosSelection(
|
||||
action = ctxMenu.addAction(self.tr("Select Paragraph"))
|
||||
action.triggered.connect(lambda: self._makePosSelection(
|
||||
QTextCursor.SelectionType.BlockUnderCursor, pos)
|
||||
)
|
||||
|
||||
@@ -1128,16 +1127,16 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
ctxMenu.addSeparator()
|
||||
ctxMenu.addAction(self.tr("Spelling Suggestion(s)"))
|
||||
for option in suggest[:15]:
|
||||
aFix = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
|
||||
aFix.triggered.connect(
|
||||
action = ctxMenu.addAction(f"{nwUnicode.U_ENDASH} {option}")
|
||||
action.triggered.connect(
|
||||
lambda _, option=option: self._correctWord(sCursor, option)
|
||||
)
|
||||
else:
|
||||
ctxMenu.addAction("%s %s" % (nwUnicode.U_ENDASH, self.tr("No Suggestions")))
|
||||
|
||||
ctxMenu.addSeparator()
|
||||
aAdd = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
|
||||
aAdd.triggered.connect(lambda: self._addWord(word, block))
|
||||
action = ctxMenu.addAction(self.tr("Add Word to Dictionary"))
|
||||
action.triggered.connect(lambda: self._addWord(word, block))
|
||||
|
||||
# Execute the context menu
|
||||
ctxMenu.exec_(self.viewport().mapToGlobal(pos))
|
||||
|
||||
@@ -20,13 +20,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
from tools import C, buildTestProject
|
||||
from mocked import causeOSError
|
||||
|
||||
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextOption
|
||||
from PyQt5.QtGui import QClipboard, QTextBlock, QTextCursor, QTextOption
|
||||
from PyQt5.QtCore import QThreadPool, Qt
|
||||
from PyQt5.QtWidgets import QAction, qApp
|
||||
from PyQt5.QtWidgets import QAction, QMenu, qApp
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemLayout, nwTrinary, nwWidget
|
||||
@@ -209,6 +210,148 @@ def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
||||
# END Test testGuiEditor_MetaData
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_ContextMenu(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test the editor context menu."""
|
||||
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
|
||||
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
docEditor = nwGUI.docEditor
|
||||
sceneItem = SHARED.project.tree[C.hSceneDoc]
|
||||
assert sceneItem is not None
|
||||
|
||||
def getMenuForPos(pos: int, select: bool = False) -> QMenu | None:
|
||||
nonlocal docEditor
|
||||
cursor = docEditor.textCursor()
|
||||
cursor.setPosition(pos)
|
||||
if select:
|
||||
cursor.select(QTextCursor.SelectionType.WordUnderCursor)
|
||||
docEditor.setTextCursor(cursor)
|
||||
docEditor._openContextMenu(docEditor.cursorRect().center())
|
||||
for obj in docEditor.children():
|
||||
if isinstance(obj, QMenu) and obj.objectName() == "ContextMenu":
|
||||
return obj
|
||||
return None
|
||||
|
||||
docText = (
|
||||
"### A Scene\n\n"
|
||||
"@pov: Jane\n"
|
||||
"Some text ..."
|
||||
)
|
||||
docEditor.setPlainText(docText)
|
||||
assert docEditor.getText() == docText
|
||||
|
||||
# Rename Item from Heading
|
||||
ctxMenu = getMenuForPos(1)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Set as Document Name", "Paste",
|
||||
"Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(GuiEditLabel, "getLabel", lambda a, text: (text, True))
|
||||
assert sceneItem.itemName == "New Scene"
|
||||
ctxMenu.actions()[0].trigger()
|
||||
assert sceneItem.itemName == "A Scene"
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Create Character
|
||||
ctxMenu = getMenuForPos(21)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Create Note for Tag", "Paste",
|
||||
"Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
ctxMenu.actions()[0].trigger()
|
||||
janeItem = SHARED.project.tree["0000000000010"]
|
||||
assert janeItem is not None
|
||||
assert janeItem.itemName == "Jane"
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Follow Character Tag
|
||||
ctxMenu = getMenuForPos(21)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Follow Tag", "Paste",
|
||||
"Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
ctxMenu.actions()[0].trigger()
|
||||
assert nwGUI.docViewer.docHandle == "0000000000010"
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Select Word
|
||||
ctxMenu = getMenuForPos(31)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Paste", "Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
ctxMenu.actions()[3].trigger()
|
||||
assert docEditor.textCursor().selectedText() == "text"
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Select Paragraph
|
||||
ctxMenu = getMenuForPos(31)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Paste", "Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
ctxMenu.actions()[4].trigger()
|
||||
assert docEditor.textCursor().selectedText() == "Some text ..."
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Select All
|
||||
ctxMenu = getMenuForPos(31)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Paste", "Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
ctxMenu.actions()[2].trigger()
|
||||
assert docEditor.textCursor().selectedText() == docEditor.document().toRawText()
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Copy Text
|
||||
ctxMenu = getMenuForPos(31, True)
|
||||
assert ctxMenu is not None
|
||||
assert docEditor.textCursor().selectedText() == "text"
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert actions == [
|
||||
"Cut", "Copy", "Paste", "Select All", "Select Word", "Select Paragraph"
|
||||
]
|
||||
qApp.clipboard().clear()
|
||||
ctxMenu.actions()[1].trigger()
|
||||
assert qApp.clipboard().text(QClipboard.Mode.Clipboard) == "text"
|
||||
|
||||
# Cut Text
|
||||
qApp.clipboard().clear()
|
||||
ctxMenu.actions()[0].trigger()
|
||||
assert qApp.clipboard().text(QClipboard.Mode.Clipboard) == "text"
|
||||
assert "text" not in docEditor.getText()
|
||||
|
||||
# Paste Text
|
||||
ctxMenu.actions()[2].trigger()
|
||||
assert docEditor.getText() == docText
|
||||
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
# END Test testGuiEditor_ContextMenu
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the document actions. This is not an extensive test of the
|
||||
|
||||
Reference in New Issue
Block a user