Update tests and test coverage

This commit is contained in:
Veronica Berglyd Olsen
2025-10-25 22:37:19 +02:00
parent 1938969a63
commit 2d67cb7dcc
12 changed files with 105 additions and 37 deletions
-6
View File
@@ -217,12 +217,6 @@ class NPushButton(QPushButton):
if icon:
self.refreshIcon()
def setThemeIcon(self, icon: str, color: str | None = None) -> None:
"""Set an icon from the current theme."""
self._icon = icon
self._color = color
self.refreshIcon()
def refreshIcon(self) -> None:
"""Reload the theme icon."""
if self._icon:
+2 -2
View File
@@ -34,7 +34,7 @@ from typing import TYPE_CHECKING, TypeVar
from PyQt6.QtCore import QObject, QRunnable, QThreadPool, QTimer, QUrl, pyqtSignal, pyqtSlot
from PyQt6.QtGui import QDesktopServices, QFont, QScreen
from PyQt6.QtWidgets import QApplication, QDialog, QFileDialog, QFontDialog, QMessageBox, QWidget
from PyQt6.QtWidgets import QApplication, QFileDialog, QFontDialog, QMessageBox, QWidget
from novelwriter.common import formatFileFilter
from novelwriter.constants import nwFiles
@@ -536,4 +536,4 @@ class _GuiAlert(QMessageBox):
def _onReject(self) -> None:
"""Process rejected state."""
self._state = False
self.setResult(QDialog.DialogCode.Rejected)
self.close()
+4 -1
View File
@@ -25,7 +25,10 @@ if __name__ == "__main__":
env["QT_SCALE_FACTOR"] = "1.0"
if args.r or args.t or args.u:
cmd = ["coverage", "run", "-m"]
cmd = ["coverage", "run"]
if args.lf or args.sw:
cmd += ["--append"]
cmd += ["-m"]
else:
cmd = [sys.executable, "-m"]
+6 -2
View File
@@ -157,9 +157,11 @@ def projPath(fncPath):
def mockGUI(qtbot, monkeypatch):
"""Create a mock instance of novelWriter's main GUI class."""
from novelwriter.gui.theme import GuiTheme
from novelwriter.shared import _GuiAlert
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
monkeypatch.setattr(_GuiAlert, "finalState", True)
gui = MockGuiMain()
theme = GuiTheme()
monkeypatch.setattr(SHARED, "_gui", gui)
@@ -182,9 +184,11 @@ def nwGUI(qtbot, monkeypatch, functionFixture):
"""Create an instance of the novelWriter GUI."""
from novelwriter.gui.theme import GuiTheme
from novelwriter.guimain import GuiMain
from novelwriter.shared import _GuiAlert
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
monkeypatch.setattr(_GuiAlert, "finalState", True)
CONFIG.loadConfig()
SHARED.initTheme(GuiTheme())
+6
View File
@@ -22,9 +22,12 @@ from __future__ import annotations
from unittest.mock import MagicMock
from PyQt6.QtCore import QSize
from PyQt6.QtGui import QFont, QIcon, QPixmap
from PyQt6.QtWidgets import QWidget
from novelwriter.extensions.modified import NPushButton
class MockGuiMain(QWidget):
@@ -72,6 +75,9 @@ class MockTheme:
def getHeaderDecoration(self, *a) -> QPixmap:
return QPixmap()
def getStandardButton(self, *a) -> NPushButton:
return NPushButton(None, "", QSize(1, 1)) # type: ignore
def getIcon(self, *a) -> QIcon:
return QIcon()
+65 -5
View File
@@ -26,10 +26,10 @@ import pytest
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtWidgets import QFileDialog, QMessageBox, QWidget
from PyQt6.QtWidgets import QFileDialog, QWidget
from novelwriter.core.project import NWProject
from novelwriter.shared import SharedData
from novelwriter.shared import SharedData, _GuiAlert
from tests.mocked import MockGuiMain, MockTheme
from tests.tools import buildTestProject
@@ -143,10 +143,10 @@ def testBaseSharedData_Projects(monkeypatch, caplog, fncPath):
@pytest.mark.base
def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog, mockGUI):
"""Test SharedData class alert helper functions."""
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
monkeypatch.setattr(_GuiAlert, "exec", lambda *a: None)
monkeypatch.setattr(_GuiAlert, "finalState", True)
shared = SharedData()
@@ -188,3 +188,63 @@ def testBaseSharedData_Alerts(qtbot, monkeypatch, caplog):
# Question box
assert shared.question("Why?") is True
assert shared.lastAlert == "Why?"
@pytest.mark.base
def testBaseSharedData_GuiAlert():
"""Test the _GuiAlert class."""
alert = _GuiAlert(None, MockTheme()) # type: ignore
# Default states
assert alert.logMessage == ""
assert alert.finalState is False
# Populate message
text = "one"
info = "two"
details = "three"
alert.setMessage(text, info, details)
assert alert.logMessage == f"{text} {info} {details}"
assert alert.text() == text
assert alert.informativeText() == info
assert alert.detailedText() == details
# Populate exception
exc = Exception("oops")
alert.setException(exc)
assert alert.logMessage == f"{text} {info} {details}"
assert alert.informativeText() == f"{info}<br><b>Exception</b>: {exc!s}"
# Alert: Info
alert.setAlertType(_GuiAlert.INFO, False)
assert hasattr(alert, "_btnOk")
assert alert.windowTitle() == "Information"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
# Alert: Warning
alert.setAlertType(_GuiAlert.WARN, False)
assert hasattr(alert, "_btnOk")
assert alert.windowTitle() == "Warning"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
# Alert: Error
alert.setAlertType(_GuiAlert.ERROR, False)
assert hasattr(alert, "_btnOk")
assert alert.windowTitle() == "Error"
alert._btnOk.click()
assert alert.finalState is True
alert._state = False
# Alert: Question
alert.setAlertType(_GuiAlert.ASK, True)
assert hasattr(alert, "_btnYes")
assert hasattr(alert, "_btnNo")
assert alert.windowTitle() == "Question"
alert._btnYes.click()
assert alert.finalState is True
alert._btnNo.click()
assert alert.finalState is False
+3 -4
View File
@@ -25,13 +25,12 @@ from zipfile import ZipFile
import pytest
from PyQt6.QtWidgets import QMessageBox
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwFiles
from novelwriter.core.project import NWProject, NWProjectState
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.enum import nwItemClass
from novelwriter.shared import _GuiAlert
from tests.mocked import causeOSError
from tests.tools import XML_IGNORE, C, buildTestProject, cmpFiles
@@ -274,14 +273,14 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
# Won't convert legacy file
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert project.openProject(fncPath, clearLock=True) is False
assert "The file format of your project is about to be" in SHARED.lastAlert
# Won't open project from newer version
with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert project.openProject(fncPath, clearLock=True) is False
assert "This project was saved by a newer version" in SHARED.lastAlert
+5 -4
View File
@@ -30,7 +30,7 @@ import pytest
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QPalette
from PyQt6.QtWidgets import QInputDialog, QMessageBox
from PyQt6.QtWidgets import QInputDialog
from novelwriter import CONFIG, SHARED, __hexversion__
from novelwriter.common import jsonEncode
@@ -42,6 +42,7 @@ from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.gui.noveltree import GuiNovelView
from novelwriter.gui.outline import GuiOutlineView
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.shared import _GuiAlert
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.types import QtModCtrl, QtModShift
@@ -104,7 +105,7 @@ def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, projPath):
# Check that closes can be blocked
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is True
assert nwGUI.closeMain() is False
nwGUI.closeProject()
@@ -841,7 +842,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd)
# Block closing
assert SHARED.hasProject is True
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is False
assert SHARED.hasProject is True
@@ -854,7 +855,7 @@ def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd)
shutil.copyfile(lockBack, lockPath)
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert nwGUI.openProject(projPath) is False
assert nwGUI.openProject(projPath) is True
+1 -3
View File
@@ -24,7 +24,7 @@ import sys
import pytest
from PyQt6.QtWidgets import QApplication, QDialog, QMessageBox
from PyQt6.QtWidgets import QApplication, QDialog
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.about import GuiAbout
@@ -49,8 +49,6 @@ LANG_DATA = CONFIG.listLanguages(CONFIG.LANG_NW)
def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath):
"""Test loading the gui with a specific language."""
monkeypatch.setattr(QDialog, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
# Set the test language
CONFIG.guiLocale = language
+3 -2
View File
@@ -25,12 +25,13 @@ from unittest.mock import MagicMock
import pytest
from PyQt6.QtGui import QAction, QDesktopServices, QTextBlock
from PyQt6.QtWidgets import QFileDialog, QMessageBox
from PyQt6.QtWidgets import QFileDialog
from novelwriter import CONFIG, SHARED
from novelwriter.constants import nwKeyWords, nwShortcode, nwStats, nwUnicode
from novelwriter.enum import nwDocAction, nwDocInsert
from novelwriter.gui.doceditor import GuiDocEditor
from novelwriter.shared import _GuiAlert
from novelwriter.types import QtKeepAnchor, QtMoveRight, QtSelectWord
from tests.tools import C, buildTestProject, writeFile
@@ -575,7 +576,7 @@ def testGuiMainMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd
# The document isn't empty, so the message box should pop
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a, **k: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert not nwGUI.importDocument()
assert docEditor.getText() == "Bar"
+7 -6
View File
@@ -26,7 +26,7 @@ import pytest
from PyQt6.QtCore import QEvent, QItemSelectionModel, QModelIndex, QPointF
from PyQt6.QtGui import QMouseEvent
from PyQt6.QtWidgets import QMenu, QMessageBox
from PyQt6.QtWidgets import QMenu
from novelwriter import CONFIG, SHARED
from novelwriter.dialogs.docmerge import GuiDocMerge
@@ -34,6 +34,7 @@ from novelwriter.dialogs.docsplit import GuiDocSplit
from novelwriter.dialogs.editlabel import GuiEditLabel
from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType
from novelwriter.gui.projtree import _TreeContextMenu
from novelwriter.shared import _GuiAlert
from novelwriter.types import (
QtAccepted, QtModNone, QtMouseLeft, QtMouseMiddle, QtRejected,
QtScrollAlwaysOff, QtScrollAsNeeded
@@ -627,7 +628,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# User can cancel move to trash
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
projTree.processDeleteRequest(hScenes, askFirst=True)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -645,7 +646,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# User can block permanent deletion
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
projTree.processDeleteRequest(hScenes[0:2], askFirst=True)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -677,7 +678,7 @@ def testGuiProjTree_DeleteRequest(qtbot, caplog, monkeypatch, nwGUI, projPath, m
# Trash can be completely emptied, but user can block it
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
projTree.emptyTrash()
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "Chapter Folder", "Plot", "Characters", "Trash",
@@ -995,7 +996,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Duplicate title page, but select no
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QtRejected)
mp.setattr(_GuiAlert, "finalState", False)
projTree.duplicateFromHandle(C.hTitlePage)
assert [n.item.itemName for n in tree.model.root.allChildren()] == [
"Novel", "Title Page", "New Folder", "New Chapter", "New Scene",
@@ -1302,7 +1303,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# Click no on the dialog
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QtRejected)
mp.setattr(_GuiAlert, "finalState", False)
ctxMenu._convertFolderToFile(nwItemLayout.DOCUMENT)
assert nodeOne.item.isFolderType()
+3 -2
View File
@@ -26,13 +26,14 @@ import pytest
from PyQt6.QtCore import QUrl
from PyQt6.QtGui import QDesktopServices
from PyQt6.QtWidgets import QFileDialog, QListWidgetItem, QMessageBox
from PyQt6.QtWidgets import QFileDialog, QListWidgetItem
from pytestqt.qtbot import QtBot
from novelwriter.constants import nwLabels
from novelwriter.core.buildsettings import BuildSettings
from novelwriter.enum import nwBuildFmt
from novelwriter.guimain import GuiMain
from novelwriter.shared import _GuiAlert
from novelwriter.tools.manusbuild import GuiManuscriptBuild
from novelwriter.types import QtDialogClose
@@ -134,7 +135,7 @@ def testToolManuscriptBuild_Main(
manus.buildPath.setText(str(fncPath))
manus.buildName.setText("TestBuild")
with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No)
mp.setattr(_GuiAlert, "finalState", False)
assert manus._runBuild() is False
# Test that the open button works