Add coverage to remaining missed lines

This commit is contained in:
Veronica Berglyd Olsen
2024-11-25 00:07:32 +01:00
parent e30d2cbbf1
commit 4cadd1597e
8 changed files with 199 additions and 44 deletions
+1 -3
View File
@@ -183,7 +183,7 @@ class NScrollableForm(QScrollArea):
def addRow( def addRow(
self, self,
label: str | None, label: str | None,
widget: QWidget | list[QWidget | QPixmap | str | int], widget: QWidget | list[QWidget | QPixmap | int],
helpText: str = "", helpText: str = "",
unit: str | None = None, unit: str | None = None,
button: QWidget | None = None, button: QWidget | None = None,
@@ -204,8 +204,6 @@ class NScrollableForm(QScrollArea):
icon = QLabel(self) icon = QLabel(self)
icon.setPixmap(item) icon.setPixmap(item)
wBox.addWidget(icon) wBox.addWidget(icon)
elif isinstance(item, str):
wBox.addWidget(QLabel(item, self))
elif isinstance(item, int): elif isinstance(item, int):
wBox.addSpacing(CONFIG.pxInt(item)) wBox.addSpacing(CONFIG.pxInt(item))
qWidget = QWidget(self) qWidget = QWidget(self)
+1 -17
View File
@@ -225,12 +225,7 @@ class GuiItemDetails(QWidget):
def updateViewBox(self, tHandle: str | None) -> None: def updateViewBox(self, tHandle: str | None) -> None:
"""Populate the details box from a given handle.""" """Populate the details box from a given handle."""
if tHandle is None: if not (tHandle and (nwItem := SHARED.project.tree[tHandle])):
self.clearDetails()
return
nwItem = SHARED.project.tree[tHandle]
if nwItem is None:
self.clearDetails() self.clearDetails()
return return
@@ -297,14 +292,3 @@ class GuiItemDetails(QWidget):
elif change == nwChange.DELETE: elif change == nwChange.DELETE:
self.updateViewBox(None) self.updateViewBox(None)
return return
@pyqtSlot(str, int, int, int)
def updateCounts(self, tHandle: str, cC: int, wC: int, pC: int) -> None:
"""Update the counts if the handle is the same as the one we're
already showing. Otherwise, do nothing.
"""
if tHandle == self._handle:
self.cCountData.setText(f"{cC:n}")
self.wCountData.setText(f"{wC:n}")
self.pCountData.setText(f"{pC:n}")
return
+8 -9
View File
@@ -213,7 +213,7 @@ class GuiProjectView(QWidget):
@pyqtSlot(str, Enum) @pyqtSlot(str, Enum)
def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None: def onProjectItemChanged(self, tHandle: str, change: nwChange) -> None:
"""Refresh other content when project item changed.""" """Refresh other content when project item changed."""
self.projBar.processTemplateDocuments(tHandle, change) self.projBar.processTemplateDocuments(tHandle)
return return
@pyqtSlot(str) @pyqtSlot(str)
@@ -404,17 +404,16 @@ class GuiProjectToolBar(QWidget):
"""Build the templates menu.""" """Build the templates menu."""
for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE): for tHandle, _ in SHARED.project.tree.iterRoots(nwItemClass.TEMPLATE):
for dHandle in SHARED.project.tree.subTree(tHandle): for dHandle in SHARED.project.tree.subTree(tHandle):
self.processTemplateDocuments(dHandle, nwChange.CREATE) self.processTemplateDocuments(dHandle)
return return
def processTemplateDocuments(self, tHandle: str, change: nwChange) -> None: def processTemplateDocuments(self, tHandle: str) -> None:
"""Process change in tree items to update menu content.""" """Process change in tree items to update menu content."""
if change in (nwChange.CREATE, nwChange.UPDATE): if item := SHARED.project.tree[tHandle]:
if item := SHARED.project.tree[tHandle]: if item.isTemplateFile() and item.isActive:
if item.isTemplateFile() and item.isActive: self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon())
self.mTemplates.addUpdate(tHandle, item.itemName, item.getMainIcon()) elif tHandle in self.mTemplates:
elif tHandle in self.mTemplates: self.mTemplates.remove(tHandle)
self.mTemplates.remove(tHandle)
return return
## ##
+75 -6
View File
@@ -24,22 +24,23 @@ from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QEvent, Qt, QThreadPool, QUrl from PyQt5.QtCore import QEvent, QMimeData, Qt, QThreadPool, QUrl
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QClipboard, QDesktopServices, QFont, QMouseEvent, QTextBlock, QTextCursor, QClipboard, QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent,
QTextOption QFont, QMouseEvent, QTextBlock, QTextCursor, QTextOption
) )
from PyQt5.QtWidgets import QAction, QApplication, QMenu from PyQt5.QtWidgets import QAction, QApplication, QMenu, QPlainTextEdit
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles
from novelwriter.constants import nwKeyWords, nwUnicode from novelwriter.constants import nwKeyWords, nwUnicode
from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary from novelwriter.enum import nwDocAction, nwDocInsert, nwItemClass, nwItemLayout, nwTrinary
from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.text.counting import standardCounter from novelwriter.text.counting import standardCounter
from novelwriter.types import ( from novelwriter.types import (
QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtMouseLeft, QtAlignJustify, QtAlignLeft, QtKeepAnchor, QtModCtrl, QtModNone,
QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded QtMouseLeft, QtMoveAnchor, QtMoveRight, QtScrollAlwaysOff, QtScrollAsNeeded
) )
from tests.mocked import causeOSError from tests.mocked import causeOSError
@@ -209,6 +210,74 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumTex
# qtbot.stop() # qtbot.stop()
@pytest.mark.gui
def testGuiEditor_DragAndDrop(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test drag and drop in the editor."""
docEditor = nwGUI.docEditor
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hTitlePage) is True
assert docEditor.docHandle == C.hTitlePage
middle = docEditor.viewport().rect().center()
action = Qt.DropAction.MoveAction
mouse = Qt.MouseButton.NoButton
model = SHARED.project.tree.model
docMime = model.mimeData([model.indexFromHandle(C.hSceneDoc)])
noneMime = QMimeData()
noneMime.setData("plain/text", b"")
assert decodeMimeHandles(docMime) == [C.hSceneDoc]
# Drag Enter
mockEnter = MagicMock()
docEvent = QDragEnterEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragEnterEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QPlainTextEdit, "dragEnterEvent", mockEnter)
# Document Enter
docEditor.dragEnterEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockEnter.call_count == 0
# Regular Enter
docEditor.dragEnterEvent(noneEvent)
assert mockEnter.call_count == 1
# Drag Move
mockMove = MagicMock()
docEvent = QDragMoveEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragMoveEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QPlainTextEdit, "dragMoveEvent", mockMove)
# Document Move
docEditor.dragMoveEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockMove.call_count == 0
# Regular Move
docEditor.dragMoveEvent(noneEvent)
assert mockMove.call_count == 1
# Drop
mockDrop = MagicMock()
docEvent = QDropEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDropEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QPlainTextEdit, "dropEvent", mockDrop)
# Document Drop
docEditor.dropEvent(docEvent)
assert mockDrop.call_count == 0
assert docEditor.docHandle == C.hSceneDoc
# Regular Move
docEditor.dropEvent(noneEvent)
assert mockDrop.call_count == 1
@pytest.mark.gui @pytest.mark.gui
def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd): def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
"""Test extracting various meta data and other values.""" """Test extracting various meta data and other values."""
+77 -3
View File
@@ -24,16 +24,21 @@ from unittest.mock import MagicMock
import pytest import pytest
from PyQt5.QtCore import QEvent, QPoint, Qt, QUrl from PyQt5.QtCore import QEvent, QMimeData, QPoint, Qt, QUrl
from PyQt5.QtGui import QDesktopServices, QMouseEvent, QTextCursor from PyQt5.QtGui import (
from PyQt5.QtWidgets import QAction, QApplication, QMenu QDesktopServices, QDragEnterEvent, QDragMoveEvent, QDropEvent, QMouseEvent,
QTextCursor
)
from PyQt5.QtWidgets import QAction, QApplication, QMenu, QTextBrowser
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import decodeMimeHandles
from novelwriter.enum import nwChange, nwDocAction from novelwriter.enum import nwChange, nwDocAction
from novelwriter.formats.toqdoc import ToQTextDocument from novelwriter.formats.toqdoc import ToQTextDocument
from novelwriter.types import QtModNone, QtMouseLeft from novelwriter.types import QtModNone, QtMouseLeft
from tests.mocked import causeException from tests.mocked import causeException
from tests.tools import C, buildTestProject
@pytest.mark.gui @pytest.mark.gui
@@ -238,3 +243,72 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
docViewer.updateTheme() docViewer.updateTheme()
# qtbot.stop() # qtbot.stop()
@pytest.mark.gui
def testGuiViewer_DragAndDrop(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test drag and drop in the viewer."""
docViewer = nwGUI.docViewer
buildTestProject(nwGUI, projPath)
assert nwGUI.openDocument(C.hTitlePage) is True
assert nwGUI.viewDocument(C.hTitlePage) is True
assert docViewer.docHandle == C.hTitlePage
middle = docViewer.viewport().rect().center()
action = Qt.DropAction.MoveAction
mouse = Qt.MouseButton.NoButton
model = SHARED.project.tree.model
docMime = model.mimeData([model.indexFromHandle(C.hSceneDoc)])
noneMime = QMimeData()
noneMime.setData("plain/text", b"")
assert decodeMimeHandles(docMime) == [C.hSceneDoc]
# Drag Enter
mockEnter = MagicMock()
docEvent = QDragEnterEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragEnterEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dragEnterEvent", mockEnter)
# Document Enter
docViewer.dragEnterEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockEnter.call_count == 0
# Regular Enter
docViewer.dragEnterEvent(noneEvent)
assert mockEnter.call_count == 1
# Drag Move
mockMove = MagicMock()
docEvent = QDragMoveEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDragMoveEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dragMoveEvent", mockMove)
# Document Move
docViewer.dragMoveEvent(docEvent)
assert docEvent.isAccepted() is True
assert mockMove.call_count == 0
# Regular Move
docViewer.dragMoveEvent(noneEvent)
assert mockMove.call_count == 1
# Drop
mockDrop = MagicMock()
docEvent = QDropEvent(middle, action, docMime, mouse, QtModNone)
noneEvent = QDropEvent(middle, action, noneMime, mouse, QtModNone)
with monkeypatch.context() as mp:
mp.setattr(QTextBrowser, "dropEvent", mockDrop)
# Document Drop
docViewer.dropEvent(docEvent)
assert mockDrop.call_count == 0
assert docViewer.docHandle == C.hSceneDoc
# Regular Move
docViewer.dropEvent(noneEvent)
assert mockDrop.call_count == 1
+11
View File
@@ -702,6 +702,17 @@ def testGuiMain_Viewing(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
nwGUI.viewDocument(C.hSceneDoc) nwGUI.viewDocument(C.hSceneDoc)
assert nwGUI.docViewer.toPlainText() == "New Scene\nWith some stuff in it!" assert nwGUI.docViewer.toPlainText() == "New Scene\nWith some stuff in it!"
# Open with keypress
nwGUI.closeDocViewer()
assert nwGUI.docViewer.docHandle is None
nwGUI.projView.setSelectedHandle(C.hSceneDoc)
with monkeypatch.context() as mp:
mp.setattr(nwGUI.projView.projTree, "hasFocus", lambda *a: True)
qtbot.keyClick(
nwGUI.projView.projTree, Qt.Key.Key_Return, modifier=QtModShift, delay=KEY_DELAY
)
assert nwGUI.docViewer.docHandle == C.hSceneDoc
# qtbot.stop() # qtbot.stop()
+17 -1
View File
@@ -663,12 +663,22 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
"Scene 3", "Scene 4", "Scene 3", "Scene 4",
] ]
# Opening documents before delete, will close them
nwGUI.openDocument(hScenes[2])
nwGUI.viewDocument(hScenes[3])
assert nwGUI.docEditor.docHandle == hScenes[2]
assert nwGUI.docViewer.docHandle == hScenes[3]
# Trash can be completely emptied # Trash can be completely emptied
projTree.emptyTrash() projTree.emptyTrash()
assert [n.item.itemName for n in tree.model.root.allChildren()] == [ assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "Chapter Folder", "Plot", "Characters", "Trash", "Novel", "Title Page", "Chapter Folder", "Plot", "Characters", "Trash",
] ]
assert nwGUI.docEditor.docHandle is None
assert nwGUI.docViewer.docHandle is None
# Emptying empty trash pops an alert # Emptying empty trash pops an alert
projTree.emptyTrash() projTree.emptyTrash()
assert SHARED.lastAlert == "The Trash folder is already empty." assert SHARED.lastAlert == "The Trash folder is already empty."
@@ -1423,7 +1433,13 @@ def testGuiProjTree_Templates(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
assert nwNewCharacter.itemName == "Note" assert nwNewCharacter.itemName == "Note"
assert project.storage.getDocument(hNewCharacter).readDocument() == "# Jane\n\n@tag: Jane\n\n" assert project.storage.getDocument(hNewCharacter).readDocument() == "# Jane\n\n@tag: Jane\n\n"
# Remove the templates # Clearing the menu and rebuilding it should work
projBar.mTemplates.clearMenu()
assert len(projBar.mTemplates.actions()) == 0
projBar.buildTemplatesMenu()
assert len(projBar.mTemplates.actions()) == 2
# Remove the note template
assert projBar.mTemplates.menuAction().isVisible() is True assert projBar.mTemplates.menuAction().isVisible() is True
assert len(projBar.mTemplates.actions()) == 2 assert len(projBar.mTemplates.actions()) == 2
assert trash.childCount() == 0 assert trash.childCount() == 0
+9 -5
View File
@@ -31,7 +31,7 @@ from tests.tools import C, buildTestProject
@pytest.mark.gui @pytest.mark.gui
def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd): def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test the the various features of the status bar.""" """Test the the various features of the status bar."""
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
cHandle = SHARED.project.newFile("A Note", C.hCharRoot) cHandle = SHARED.project.newFile("A Note", C.hCharRoot)
@@ -89,9 +89,13 @@ def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
nwGUI._lastTotalCount = 0 nwGUI._lastTotalCount = 0
nwGUI._updateStatusWordCount() nwGUI._updateStatusWordCount()
assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)" assert nwGUI.mainStatus.statsText.text() == "Words: 9 (+9)"
CONFIG.incNotesWCount = True
nwGUI._lastTotalCount = 0 # Update again, but through time tick
nwGUI._updateStatusWordCount() with monkeypatch.context() as mp:
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)" mp.setattr("novelwriter.guimain.time", lambda *a: 50.0)
CONFIG.incNotesWCount = True
nwGUI._lastTotalCount = 0
nwGUI._timeTick()
assert nwGUI.mainStatus.statsText.text() == "Words: 11 (+11)"
# qtbot.stop() # qtbot.stop()