Remove dependence of main GUI class in project class

This commit is contained in:
Veronica Berglyd Olsen
2023-08-14 17:08:30 +02:00
parent a3976ab4fb
commit 3441ff38de
21 changed files with 253 additions and 138 deletions
+1 -1
View File
@@ -344,7 +344,7 @@ class ProjectBuilder:
logger.error("No project path set for the new project") logger.error("No project path set for the new project")
return False return False
project = NWProject(self.mainGui) project = NWProject()
if not project.storage.openProjectInPlace(projPath, newProject=True): if not project.storage.openProjectInPlace(projPath, newProject=True):
return False return False
+24 -40
View File
@@ -33,8 +33,8 @@ from functools import partial
from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal from PyQt5.QtCore import QCoreApplication, QObject, pyqtSignal
from novelwriter import CONFIG, __version__, __hexversion__ from novelwriter import CONFIG, SHARED, __version__, __hexversion__
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.constants import trConst, nwLabels from novelwriter.constants import trConst, nwLabels
from novelwriter.core.tree import NWTree from novelwriter.core.tree import NWTree
@@ -49,7 +49,6 @@ from novelwriter.common import (
) )
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
@@ -61,11 +60,8 @@ class NWProject(QObject):
statusChanged = pyqtSignal(bool) statusChanged = pyqtSignal(bool)
statusMessage = pyqtSignal(str) statusMessage = pyqtSignal(str)
def __init__(self, mainGui: GuiMain) -> None: def __init__(self, parent: QObject | None = None) -> None:
super().__init__(parent=mainGui) super().__init__(parent=parent)
# Internal
self.mainGui = mainGui
# Core Elements # Core Elements
self._options = OptionState(self) # Project-specific GUI options self._options = OptionState(self) # Project-specific GUI options
@@ -197,9 +193,9 @@ class NWProject(QObject):
if self._tree.checkType(tHandle, nwItemType.FILE): if self._tree.checkType(tHandle, nwItemType.FILE):
delDoc = self._storage.getDocument(tHandle) delDoc = self._storage.getDocument(tHandle)
if not delDoc.deleteDocument(): if not delDoc.deleteDocument():
self.mainGui.makeAlert( SHARED.error(
self.tr("Could not delete document file."), self.tr("Could not delete document file."),
info=delDoc.getError(), level=nwAlert.ERROR info=delDoc.getError()
) )
return False return False
@@ -228,9 +224,7 @@ class NWProject(QObject):
""" """
logger.info("Opening project: %s", projPath) logger.info("Opening project: %s", projPath)
if not self._storage.openProjectInPlace(projPath): if not self._storage.openProjectInPlace(projPath):
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("Could not open project with path: {0}").format(projPath))
"Could not open project with path: {0}"
).format(projPath), level=nwAlert.ERROR)
return False return False
# Project Lock # Project Lock
@@ -262,26 +256,24 @@ class NWProject(QObject):
if not xmlParsed: if not xmlParsed:
if xmlReader.state == XMLReadState.NOT_NWX_FILE: if xmlReader.state == XMLReadState.NOT_NWX_FILE:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr(
"Project file does not appear to be a novelWriterXML file." "Project file does not appear to be a novelWriterXML file."
), level=nwAlert.ERROR) ))
elif xmlReader.state == XMLReadState.UNKNOWN_VERSION: elif xmlReader.state == XMLReadState.UNKNOWN_VERSION:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr(
"Unknown or unsupported novelWriter project file format. " "Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. " "The project cannot be opened by this version of novelWriter. "
"The file was saved with novelWriter version {0}." "The file was saved with novelWriter version {0}."
).format(appVersion), level=nwAlert.ERROR) ).format(appVersion))
else: else:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("Failed to parse project xml."))
"Failed to parse project xml."
), level=nwAlert.ERROR)
return False return False
# Check Legacy Upgrade # Check Legacy Upgrade
# ==================== # ====================
if xmlReader.state == XMLReadState.WAS_LEGACY: if xmlReader.state == XMLReadState.WAS_LEGACY:
msgYes = self.mainGui.askQuestion(self.tr( msgYes = SHARED.question(self.tr(
"The file format of your project is about to be updated. " "The file format of your project is about to be updated. "
"If you proceed, older versions of novelWriter will no " "If you proceed, older versions of novelWriter will no "
"longer be able to open this project. Continue?" "longer be able to open this project. Continue?"
@@ -293,13 +285,13 @@ class NWProject(QObject):
# ========================= # =========================
if xmlReader.hexVersion > hexToInt(__hexversion__): if xmlReader.hexVersion > hexToInt(__hexversion__):
msgYes = self.mainGui.askQuestion(self.tr( msgYes = SHARED.question(self.tr(
"This project was saved by a newer version of " "This project was saved by a newer version of "
"novelWriter, version {0}. This is version {1}. If you " "novelWriter, version {0}. This is version {1}. If you "
"continue to open the project, some attributes and " "continue to open the project, some attributes and "
"settings may not be preserved, but the overall project " "settings may not be preserved, but the overall project "
"should be fine. Continue opening the project?" "should be fine. Continue opening the project?"
).format(appVersion, __version__)) ).format(appVersion, __version__), warn=True)
if not msgYes: if not msgYes:
return False return False
@@ -321,9 +313,9 @@ class NWProject(QObject):
# This also handles any orphaned files found # This also handles any orphaned files found
orphans, recovered = self._tree.checkConsistency(self.tr("Recovered")) orphans, recovered = self._tree.checkConsistency(self.tr("Recovered"))
if orphans > 0: if orphans > 0:
self.mainGui.makeAlert(self.tr( SHARED.warn(self.tr(
"Found {0} orphaned file(s) in the project. {1} file(s) were recovered." "Found {0} orphaned file(s) in the project. {1} file(s) were recovered."
).format(orphans, recovered), level=nwAlert.WARN) ).format(orphans, recovered))
self._index.loadIndex() self._index.loadIndex()
if xmlReader.state == XMLReadState.WAS_LEGACY: if xmlReader.state == XMLReadState.WAS_LEGACY:
@@ -347,9 +339,7 @@ class NWProject(QObject):
file. file.
""" """
if not self._storage.isOpen(): if not self._storage.isOpen():
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("There is no project open."))
"There is no project open."
), level=nwAlert.ERROR)
return False return False
saveTime = time() saveTime = time()
@@ -372,9 +362,7 @@ class NWProject(QObject):
editTime = self._data.editTime + max(round(saveTime - self._session.start), 0) editTime = self._data.editTime + max(round(saveTime - self._session.start), 0)
content = self._tree.pack() content = self._tree.pack()
if not xmlWriter.write(self._data, content, saveTime, editTime): if not xmlWriter.write(self._data, content, saveTime, editTime):
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("Failed to save project."), exc=xmlWriter.error)
"Failed to save project."
), level=nwAlert.ERROR, exc=xmlWriter.error)
return False return False
# Save other project data # Save other project data
@@ -415,10 +403,10 @@ class NWProject(QObject):
self.statusMessage.emit(self.tr("Backing up project ...")) self.statusMessage.emit(self.tr("Backing up project ..."))
if not self._data.name: if not self._data.name:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr(
"Cannot backup project because no project name is set. " "Cannot backup project because no project name is set. "
"Please set a Project Name in Project Settings." "Please set a Project Name in Project Settings."
), level=nwAlert.ERROR) ))
return False return False
cleanName = makeFileNameSafe(self._data.name) cleanName = makeFileNameSafe(self._data.name)
@@ -427,9 +415,7 @@ class NWProject(QObject):
try: try:
baseDir.mkdir(exist_ok=True, parents=True) baseDir.mkdir(exist_ok=True, parents=True)
except Exception as exc: except Exception as exc:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("Could not create backup folder."), exc=exc)
"Could not create backup folder."
), level=nwAlert.ERROR, exc=exc)
return False return False
timeStamp = formatTimeStamp(time(), fileSafe=True) timeStamp = formatTimeStamp(time(), fileSafe=True)
@@ -437,14 +423,12 @@ class NWProject(QObject):
if self._storage.zipIt(archName, compression=2): if self._storage.zipIt(archName, compression=2):
size = formatInt(archName.stat().st_size) size = formatInt(archName.stat().st_size)
if doNotify: if doNotify:
self.mainGui.makeAlert( SHARED.info(
self.tr("Created a backup of your project of size {0}B.").format(size), self.tr("Created a backup of your project of size {0}B.").format(size),
info=self.tr("Path: {0}").format(str(backupPath)) info=self.tr("Path: {0}").format(str(backupPath))
) )
else: else:
self.mainGui.makeAlert(self.tr( SHARED.error(self.tr("Could not write backup archive."))
"Could not write backup archive."
), level=nwAlert.ERROR)
return False return False
self.statusMessage.emit(self.tr("Project backed up to '{0}'").format(str(archName))) self.statusMessage.emit(self.tr("Project backed up to '{0}'").format(str(archName)))
+110 -1
View File
@@ -29,6 +29,7 @@ from typing import TYPE_CHECKING
from pathlib import Path from pathlib import Path
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMessageBox, QWidget
if TYPE_CHECKING: # pragma: no cover if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain from novelwriter.guimain import GuiMain
@@ -49,6 +50,7 @@ class SharedData(QObject):
self._theme = None self._theme = None
self._project = None self._project = None
self._lockedBy = None self._lockedBy = None
self._alert = None
return return
@property @property
@@ -74,12 +76,19 @@ class SharedData(QObject):
@property @property
def hasProject(self) -> bool: def hasProject(self) -> bool:
"""Return True of the project instance is populated."""
return self.project.isValid return self.project.isValid
@property @property
def projectLock(self) -> list | None: def projectLock(self) -> list | None:
"""Return cached lock information for the last project."""
return self._lockedBy return self._lockedBy
@property
def alert(self) -> _GuiAlert | None:
"""Return a pointer to the last alert box."""
return self._alert
## ##
# Methods # Methods
## ##
@@ -127,6 +136,48 @@ class SharedData(QObject):
"""Remove the project lock.""" """Remove the project lock."""
return self.project.storage.clearLockFile() return self.project.storage.clearLockFile()
##
# Alert Boxes
##
def info(self, text: str, info: str = "", details: str = "") -> None:
"""Open an information alert box."""
self._alert = _GuiAlert(self.mainGui, self.theme)
self._alert.setMessage(text, info, details)
self._alert.setAlertType(_GuiAlert.INFO, False)
logger.info(self._alert.logMessage, stacklevel=2)
self._alert.exec_()
return
def warn(self, text: str, info: str = "", details: str = "") -> None:
"""Open a warning alert box."""
self._alert = _GuiAlert(self.mainGui, self.theme)
self._alert.setMessage(text, info, details)
self._alert.setAlertType(_GuiAlert.WARN, False)
logger.warning(self._alert.logMessage, stacklevel=2)
self._alert.exec_()
return
def error(self, text: str, info: str = "", details: str = "",
exc: Exception | None = None) -> None:
"""Open an error alert box."""
self._alert = _GuiAlert(self.mainGui, self.theme)
self._alert.setMessage(text, info, details)
self._alert.setAlertType(_GuiAlert.ERROR, False)
if exc:
self._alert.setException(exc)
logger.error(self._alert.logMessage, stacklevel=2)
self._alert.exec_()
return
def question(self, text: str, info: str = "", details: str = "", warn: bool = False) -> bool:
"""Open an error alert box."""
self._alert = _GuiAlert(self.mainGui, self.theme)
self._alert.setMessage(text, info, details)
self._alert.setAlertType(_GuiAlert.ERROR, True)
self._alert.exec_()
return self._alert.result() == QMessageBox.Yes
## ##
# Internal Slots # Internal Slots
## ##
@@ -154,9 +205,67 @@ class SharedData(QObject):
self._project.statusChanged.disconnect() self._project.statusChanged.disconnect()
self._project.statusMessage.disconnect() self._project.statusMessage.disconnect()
self._project.deleteLater() self._project.deleteLater()
self._project = NWProject(self.mainGui) self._project = NWProject(self)
self._project.statusChanged.connect(self._emitProjectStatusChange) self._project.statusChanged.connect(self._emitProjectStatusChange)
self._project.statusMessage.connect(self._emitProjectStatusMeesage) self._project.statusMessage.connect(self._emitProjectStatusMeesage)
return return
# END Class SharedData # END Class SharedData
class _GuiAlert(QMessageBox):
INFO = 0
WARN = 1
ERROR = 2
ASK = 3
def __init__(self, parent: QWidget, theme: GuiTheme) -> None:
super().__init__(parent=parent)
self._theme = theme
self._message = ""
return
@property
def logMessage(self) -> str:
return self._message
def setMessage(self, text: str, info: str, details: str) -> None:
"""Set the alert box message."""
self._message = " ".join(filter(None, [text, info, details]))
self.setText(text)
self.setInformativeText(info)
self.setDetailedText(details)
return
def setException(self, exception: Exception) -> None:
"""Add exception details."""
info = self.informativeText()
text = f"<b>{type(exception).__name__}</b>: {str(exception)}"
self.setInformativeText(f"{info}<br>{text}" if info else text)
return
def setAlertType(self, level: int, isYesNo: bool) -> None:
"""Set the type of alert and whether the dialog should have
Yes/No buttons or just an Ok button.
"""
if isYesNo:
self.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
else:
self.setStandardButtons(QMessageBox.Ok)
pSz = 2*self._theme.baseIconSize
if level == self.INFO:
self.setIconPixmap(self._theme.getPixmap("alert_info", (pSz, pSz)))
self.setWindowTitle(self.tr("Information"))
elif level == self.WARN:
self.setIconPixmap(self._theme.getPixmap("alert_warn", (pSz, pSz)))
self.setWindowTitle(self.tr("Warning"))
elif level == self.ERROR:
self.setIconPixmap(self._theme.getPixmap("alert_error", (pSz, pSz)))
self.setWindowTitle(self.tr("Error"))
elif level == self.ASK:
self.setIconPixmap(self._theme.getPixmap("alert_question", (pSz, pSz)))
self.setWindowTitle(self.tr("Question"))
return
# END Class _GuiAlert
+10 -5
View File
@@ -25,14 +25,14 @@ import shutil
from pathlib import Path from pathlib import Path
from mocked import MockGuiMain
from tools import cleanProject from tools import cleanProject
from mocked import MockGuiMain, MockTheme
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
sys.path.insert(1, str(Path(__file__).parent.parent.absolute())) sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
from novelwriter import CONFIG, main # noqa: E402 from novelwriter import CONFIG, SHARED, main # noqa: E402
_TST_ROOT = Path(__file__).parent _TST_ROOT = Path(__file__).parent
_TMP_ROOT = _TST_ROOT / "temp" _TMP_ROOT = _TST_ROOT / "temp"
@@ -136,10 +136,15 @@ def projPath(fncPath):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def mockGUI(): def mockGUI(qtbot, monkeypatch):
"""Create a mock instance of novelWriter's main GUI class.""" """Create a mock instance of novelWriter's main GUI class."""
theGui = MockGuiMain() monkeypatch.setattr(QMessageBox, "exec_", lambda *a: None)
return theGui monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.Yes)
gui = MockGuiMain()
theme = MockTheme()
monkeypatch.setattr(SHARED, "_gui", gui)
monkeypatch.setattr(SHARED, "_theme", theme)
return gui
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
+15 -2
View File
@@ -19,14 +19,15 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from PyQt5.QtCore import QObject from PyQt5.QtGui import QPixmap
from PyQt5.QtWidgets import QWidget
# =========================================================================== # # =========================================================================== #
# Mock GUI # Mock GUI
# =========================================================================== # # =========================================================================== #
class MockGuiMain(QObject): class MockGuiMain(QWidget):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -98,6 +99,18 @@ class MockStatusBar:
# END Class MockStatusBar # END Class MockStatusBar
class MockTheme:
def __init__(self):
self.baseIconSize = 10
return
def getPixmap(self, *a):
return QPixmap()
# END Class MockTheme
class MockApp: class MockApp:
def __init__(self): def __init__(self):
+2 -2
View File
@@ -220,7 +220,7 @@ def testCoreBuildSettings_BuildValues():
@pytest.mark.core @pytest.mark.core
def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd): def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd):
"""Test filters for project items.""" """Test filters for project items."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
build = BuildSettings() build = BuildSettings()
@@ -368,7 +368,7 @@ def testCoreBuildSettings_Filters(mockGUI, fncPath: Path, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRnd): def testCoreBuildSettings_Collection(monkeypatch, mockGUI, fncPath: Path, mockRnd):
"""Test the collections class for builds.""" """Test the collections class for builds."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
buildsFile = project.storage.getMetaFile(nwFiles.BUILDS_FILE) buildsFile = project.storage.getMetaFile(nwFiles.BUILDS_FILE)
assert isinstance(buildsFile, Path) assert isinstance(buildsFile, Path)
+3 -3
View File
@@ -38,7 +38,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter, Pr
@pytest.mark.core @pytest.mark.core
def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ipsumText): def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ipsumText):
"""Test the DocMerger utility.""" """Test the DocMerger utility."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -126,7 +126,7 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ip
@pytest.mark.core @pytest.mark.core
def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText): def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
"""Test the DocSplitter utility.""" """Test the DocSplitter utility."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -265,7 +265,7 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText)
@pytest.mark.core @pytest.mark.core
def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd): def testCoreTools_DocDuplicator(mockGUI, fncPath, tstPaths, mockRnd):
"""Test the DocDuplicator utility.""" """Test the DocDuplicator utility."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
+6 -6
View File
@@ -76,7 +76,7 @@ BUILD_CONF = {
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths): def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
"""Test building an open document manuscript.""" """Test building an open document manuscript."""
project = NWProject(mockGUI) project = NWProject()
project.openProject(prjLipsum) project.openProject(prjLipsum)
build = BuildSettings() build = BuildSettings()
@@ -180,7 +180,7 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths): def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
"""Test building an HTML manuscript.""" """Test building an HTML manuscript."""
project = NWProject(mockGUI) project = NWProject()
project.openProject(prjLipsum) project.openProject(prjLipsum)
build = BuildSettings() build = BuildSettings()
@@ -250,7 +250,7 @@ def testCoreDocBuild_HTML(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths): def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
"""Test building an Markdown manuscript.""" """Test building an Markdown manuscript."""
project = NWProject(mockGUI) project = NWProject()
project.openProject(prjLipsum) project.openProject(prjLipsum)
build = BuildSettings() build = BuildSettings()
@@ -320,7 +320,7 @@ def testCoreDocBuild_Markdown(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths): def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
"""Test building a NWD manuscript.""" """Test building a NWD manuscript."""
project = NWProject(mockGUI) project = NWProject()
project.openProject(prjLipsum) project.openProject(prjLipsum)
build = BuildSettings() build = BuildSettings()
@@ -390,7 +390,7 @@ def testCoreDocBuild_NWD(monkeypatch, mockGUI, prjLipsum, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_Custom(mockGUI, fncPath: Path): def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
"""Test custom builds and some error handling.""" """Test custom builds and some error handling."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
build = BuildSettings() build = BuildSettings()
@@ -455,7 +455,7 @@ def testCoreDocBuild_Custom(mockGUI, fncPath: Path):
@pytest.mark.core @pytest.mark.core
def testCoreDocBuild_IterBuild(mockGUI, fncPath: Path, mockRnd): def testCoreDocBuild_IterBuild(mockGUI, fncPath: Path, mockRnd):
"""Test iter build wrapper.""" """Test iter build wrapper."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
build = BuildSettings() build = BuildSettings()
build.unpack(BUILD_CONF) build.unpack(BUILD_CONF)
+2 -2
View File
@@ -32,7 +32,7 @@ from novelwriter.core.document import NWDocument
@pytest.mark.core @pytest.mark.core
def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd): def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test loading and saving a document with the NWDocument class.""" """Test loading and saving a document with the NWDocument class."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -173,7 +173,7 @@ def testCoreDocument_LoadSave(monkeypatch, mockGUI, fncPath, mockRnd):
def testCoreDocument_Methods(mockGUI, fncPath, mockRnd): def testCoreDocument_Methods(mockGUI, fncPath, mockRnd):
"""Test other methods of the NWDocument class. """Test other methods of the NWDocument class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
+6 -6
View File
@@ -42,7 +42,7 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths):
testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json" testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json" compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
theProject = NWProject(mockGUI) theProject = NWProject()
assert theProject.openProject(prjLipsum) assert theProject.openProject(prjLipsum)
theIndex = NWIndex(theProject) theIndex = NWIndex(theProject)
@@ -155,7 +155,7 @@ def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ScanThis(mockGUI): def testCoreIndex_ScanThis(mockGUI):
"""Test the tag scanner function scanThis.""" """Test the tag scanner function scanThis."""
theProject = NWProject(mockGUI) theProject = NWProject()
theIndex = theProject.index theIndex = theProject.index
isValid, theBits, thePos = theIndex.scanThis("tag: this, and this") isValid, theBits, thePos = theIndex.scanThis("tag: this, and this")
@@ -204,7 +204,7 @@ def testCoreIndex_ScanThis(mockGUI):
def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd): def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
"""Test the tag checker function checkThese. """Test the tag checker function checkThese.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theIndex = theProject.index theIndex = theProject.index
@@ -281,7 +281,7 @@ def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd): def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
"""Check the index text scanner.""" """Check the index text scanner."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theIndex = theProject.index theIndex = theProject.index
@@ -502,7 +502,7 @@ def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd): def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
"""Check the index data extraction functions.""" """Check the index data extraction functions."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -941,7 +941,7 @@ def testCoreIndex_TagsIndex():
@pytest.mark.core @pytest.mark.core
def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd): def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
"""Check the ItemIndex class.""" """Check the ItemIndex class."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theProject.index.clearIndex() theProject.index.clearIndex()
+7 -7
View File
@@ -34,7 +34,7 @@ from novelwriter.core.project import NWProject
@pytest.mark.core @pytest.mark.core
def testCoreItem_Setters(mockGUI, mockRnd, fncPath): def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
"""Test all the simple setters for the NWItem class.""" """Test all the simple setters for the NWItem class."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
@@ -185,7 +185,7 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreItem_Methods(mockGUI, mockRnd, fncPath): def testCoreItem_Methods(mockGUI, mockRnd, fncPath):
"""Test the simple methods of the NWItem class.""" """Test the simple methods of the NWItem class."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
@@ -333,7 +333,7 @@ def testCoreItem_TypeSetter(mockGUI):
"""Test the setter for all the nwItemType values for the NWItem """Test the setter for all the nwItemType values for the NWItem
class. class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
# Type # Type
@@ -362,7 +362,7 @@ def testCoreItem_ClassSetter(mockGUI):
"""Test the setter for all the nwItemClass values for the NWItem """Test the setter for all the nwItemClass values for the NWItem
class. class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
# Class # Class
@@ -449,7 +449,7 @@ def testCoreItem_LayoutSetter(mockGUI):
"""Test the setter for all the nwItemLayout values for the NWItem """Test the setter for all the nwItemLayout values for the NWItem
class. class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
# Faulty Layouts # Faulty Layouts
@@ -477,7 +477,7 @@ def testCoreItem_LayoutSetter(mockGUI):
def testCoreItem_ClassDefaults(mockGUI): def testCoreItem_ClassDefaults(mockGUI):
"""Test the setter for the default values. """Test the setter for the default values.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theItem = NWItem(theProject, "0000000000000") theItem = NWItem(theProject, "0000000000000")
# Root items should not have their class updated # Root items should not have their class updated
@@ -532,7 +532,7 @@ def testCoreItem_ClassDefaults(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd): def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
"""Test packing and unpacking entries for the NWItem class.""" """Test packing and unpacking entries for the NWItem class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theProject.data.itemStatus.write(None, "New", (100, 100, 100)) theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.data.itemImport.write(None, "New", (100, 100, 100)) theProject.data.itemImport.write(None, "New", (100, 100, 100))
+2 -2
View File
@@ -33,7 +33,7 @@ from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.core @pytest.mark.core
def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath): def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
"""Test loading and saving from the OptionState class.""" """Test loading and saving from the OptionState class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
metaDir = fncPath / "meta" metaDir = fncPath / "meta"
@@ -106,7 +106,7 @@ def testCoreOptions_LoadSave(monkeypatch, mockGUI, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreOptions_SetGet(mockGUI): def testCoreOptions_SetGet(mockGUI):
"""Test setting and getting values from the OptionState class.""" """Test setting and getting values from the OptionState class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theOpts = OptionState(theProject) theOpts = OptionState(theProject)
nwColHidden = NovelTreeColumn.HIDDEN nwColHidden = NovelTreeColumn.HIDDEN
+24 -20
View File
@@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from PyQt5.QtWidgets import QMessageBox
import pytest import pytest
from shutil import copyfile from shutil import copyfile
@@ -27,7 +28,7 @@ from zipfile import ZipFile
from mocked import causeOSError from mocked import causeOSError
from tools import C, cmpFiles, buildTestProject, XML_IGNORE from tools import C, cmpFiles, buildTestProject, XML_IGNORE
from novelwriter import CONFIG from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass from novelwriter.enum import nwItemClass
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
from novelwriter.core.tree import NWTree from novelwriter.core.tree import NWTree
@@ -44,7 +45,7 @@ def testCoreProject_NewRoot(fncPath, tstPaths, mockGUI, mockRnd):
testFile = tstPaths.outDir / "coreProject_NewRoot_nwProject.nwx" testFile = tstPaths.outDir / "coreProject_NewRoot_nwProject.nwx"
compFile = tstPaths.refDir / "coreProject_NewRoot_nwProject.nwx" compFile = tstPaths.refDir / "coreProject_NewRoot_nwProject.nwx"
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -94,7 +95,7 @@ def testCoreProject_NewFileFolder(monkeypatch, fncPath, tstPaths, mockGUI, mockR
testFile = tstPaths.outDir / "coreProject_NewFileFolder_nwProject.nwx" testFile = tstPaths.outDir / "coreProject_NewFileFolder_nwProject.nwx"
compFile = tstPaths.refDir / "coreProject_NewFileFolder_nwProject.nwx" compFile = tstPaths.refDir / "coreProject_NewFileFolder_nwProject.nwx"
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -163,7 +164,7 @@ def testCoreProject_NewFileFolder(monkeypatch, fncPath, tstPaths, mockGUI, mockR
@pytest.mark.core @pytest.mark.core
def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd): def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
"""Test opening a project.""" """Test opening a project."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -204,37 +205,40 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
assert theProject.openProject(fncPath) is False assert theProject.openProject(fncPath) is False
assert "Project file does not appear" in mockGUI.lastAlert lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
assert "Project file does not appear" in lastMsg
# Unknown project file version # Unknown project file version
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
assert theProject.openProject(fncPath) is False assert theProject.openProject(fncPath) is False
assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
assert "Unknown or unsupported novelWriter project file" in lastMsg
# Other parse error # Other parse error
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "read", lambda *a: False) mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
assert theProject.openProject(fncPath) is False assert theProject.openProject(fncPath) is False
assert "Failed to parse project xml" in mockGUI.lastAlert lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
assert "Failed to parse project xml" in lastMsg
# Won't convert legacy file # Won't convert legacy file
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mockGUI.askResponse = False mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert theProject.openProject(fncPath) is False assert theProject.openProject(fncPath) is False
assert "The file format of your project is about to be" in mockGUI.lastQuestion lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
mockGUI.askResponse = True assert "The file format of your project is about to be" in lastMsg
# Won't open project from newer version # Won't open project from newer version
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999)) mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: 0x99999999))
mockGUI.askResponse = False mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert theProject.openProject(fncPath) is False assert theProject.openProject(fncPath) is False
assert "This project was saved by a newer version" in mockGUI.lastQuestion lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
mockGUI.askResponse = True assert "This project was saved by a newer version" in lastMsg
# Fail checking items should still pass # Fail checking items should still pass
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -247,10 +251,10 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY)) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
mp.setattr("novelwriter.core.index.NWIndex.loadIndex", lambda *a: True) mp.setattr("novelwriter.core.index.NWIndex.loadIndex", lambda *a: True)
mockGUI.askResponse = True
theProject.index._indexBroken = True theProject.index._indexBroken = True
assert theProject.openProject(fncPath) is True assert theProject.openProject(fncPath) is True
assert "The file format of your project is about to be" in mockGUI.lastQuestion lastMsg = SHARED.alert.logMessage if SHARED.alert else ""
assert "The file format of your project is about to be" in lastMsg
assert theProject.index._indexBroken is False assert theProject.index._indexBroken is False
theProject.closeProject() theProject.closeProject()
@@ -261,7 +265,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath): def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath):
"""Test saving a project.""" """Test saving a project."""
theProject = NWProject(mockGUI) theProject = NWProject()
# Nothing to save # Nothing to save
assert theProject.saveProject() is False assert theProject.saveProject() is False
@@ -290,7 +294,7 @@ def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd): def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
"""Test helper functions for the project folder.""" """Test helper functions for the project folder."""
theProject = NWProject(mockGUI) theProject = NWProject()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
# Storage Objects # Storage Objects
@@ -354,7 +358,7 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd): def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
"""Test the status and importance flag handling.""" """Test the status and importance flag handling."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -463,7 +467,7 @@ def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd): def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
"""Test other project class methods and functions.""" """Test other project class methods and functions."""
theProject = NWProject(mockGUI) theProject = NWProject()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
# Project Name # Project Name
@@ -581,7 +585,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, fncPath, tstPaths):
backup file and checks that the project XML file is identical to backup file and checks that the project XML file is identical to
the original file. the original file.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
# No Project # No Project
assert theProject.backupProject(doNotify=False) is False assert theProject.backupProject(doNotify=False) is False
+1 -1
View File
@@ -35,7 +35,7 @@ from novelwriter.core.sessions import NWSessionLog
@pytest.mark.core @pytest.mark.core
def testCoreSessions_Main(monkeypatch, mockGUI, fncPath): def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
"""Test log file handling of the NWSessionLog class.""" """Test log file handling of the NWSessionLog class."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
logFile = project.storage.getMetaFile(nwFiles.SESS_FILE) logFile = project.storage.getMetaFile(nwFiles.SESS_FILE)
+3 -3
View File
@@ -36,7 +36,7 @@ from novelwriter.core.spellcheck import FakeEnchant, NWSpellEnchant, UserDiction
@pytest.mark.core @pytest.mark.core
def testCoreSpell_UserDictionary(monkeypatch, mockGUI, fncPath): def testCoreSpell_UserDictionary(monkeypatch, mockGUI, fncPath):
"""Test the UserDictionary class.""" """Test the UserDictionary class."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
# Check that there is no file before we start # Check that there is no file before we start
@@ -114,7 +114,7 @@ def testCoreSpell_UserDictionary(monkeypatch, mockGUI, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_FakeEnchant(monkeypatch, mockGUI, fncPath): def testCoreSpell_FakeEnchant(monkeypatch, mockGUI, fncPath):
"""Test the FakeEnchant spell checker fallback.""" """Test the FakeEnchant spell checker fallback."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
# Make package import fail # Make package import fail
@@ -149,7 +149,7 @@ def testCoreSpell_FakeEnchant(monkeypatch, mockGUI, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, mockGUI, fncPath): def testCoreSpell_Enchant(monkeypatch, mockGUI, fncPath):
"""Test the pyenchant spell checker.""" """Test the pyenchant spell checker."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
# Break the enchant package, and check error handling # Break the enchant package, and check error handling
+3 -3
View File
@@ -43,7 +43,7 @@ class MockProject:
@pytest.mark.core @pytest.mark.core
def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd): def testCoreStorage_OpenProjectInPlace(mockGUI, fncPath, mockRnd):
"""Test opening a project in a folder.""" """Test opening a project in a folder."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
theProject.closeProject() theProject.closeProject()
@@ -180,7 +180,7 @@ def testCoreStorage_ZipIt(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd):
"""Test making a zip archive of a project.""" """Test making a zip archive of a project."""
zipFile = tstPaths.tmpDir / "project.zip" zipFile = tstPaths.tmpDir / "project.zip"
theProject = NWProject(mockGUI) theProject = NWProject()
storage = theProject.storage storage = theProject.storage
assert storage.zipIt(zipFile) is False assert storage.zipIt(zipFile) is False
@@ -365,7 +365,7 @@ def testCoreStorage_DeprecatedFiles(monkeypatch, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath): def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
"""Test cleanup of deprecated files that needs to be converted.""" """Test cleanup of deprecated files that needs to be converted."""
project = NWProject(mockGUI) project = NWProject()
buildTestProject(project, fncPath) buildTestProject(project, fncPath)
legacy = _LegacyStorage(project) legacy = _LegacyStorage(project)
+6 -6
View File
@@ -31,7 +31,7 @@ from novelwriter.core.project import NWProject
def testCoreToHtml_ConvertFormat(mockGUI): def testCoreToHtml_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToHtml class. """Test the tokenizer and converter chain using the ToHtml class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
# Novel Files Headers # Novel Files Headers
@@ -233,7 +233,7 @@ def testCoreToHtml_ConvertFormat(mockGUI):
def testCoreToHtml_ConvertDirect(mockGUI): def testCoreToHtml_ConvertDirect(mockGUI):
"""Test the converter directly using the ToHtml class. """Test the converter directly using the ToHtml class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
theHtml._isNovel = True theHtml._isNovel = True
@@ -380,7 +380,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
def testCoreToHtml_SpecialCases(mockGUI): def testCoreToHtml_SpecialCases(mockGUI):
"""Test some special cases that have caused errors in the past. """Test some special cases that have caused errors in the past.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
theHtml._isNovel = True theHtml._isNovel = True
@@ -454,7 +454,7 @@ def testCoreToHtml_SpecialCases(mockGUI):
def testCoreToHtml_Complex(mockGUI, fncPath): def testCoreToHtml_Complex(mockGUI, fncPath):
"""Test the save method of the ToHtml class. """Test the save method of the ToHtml class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
theHtml._isNovel = True theHtml._isNovel = True
@@ -549,7 +549,7 @@ def testCoreToHtml_Complex(mockGUI, fncPath):
def testCoreToHtml_Methods(mockGUI): def testCoreToHtml_Methods(mockGUI):
"""Test all the other methods of the ToHtml class. """Test all the other methods of the ToHtml class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
theHtml.setKeepMarkdown(True) theHtml.setKeepMarkdown(True)
@@ -609,7 +609,7 @@ def testCoreToHtml_Methods(mockGUI):
def testCoreToHtml_Format(mockGUI): def testCoreToHtml_Format(mockGUI):
"""Test all the formatters for the ToHtml class. """Test all the formatters for the ToHtml class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theHtml = ToHtml(theProject) theHtml = ToHtml(theProject)
# Export Mode # Export Mode
+8 -8
View File
@@ -36,7 +36,7 @@ class BareTokenizer(Tokenizer):
@pytest.mark.core @pytest.mark.core
def testCoreToken_Setters(mockGUI): def testCoreToken_Setters(mockGUI):
"""Test all the setters for the Tokenizer class.""" """Test all the setters for the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
# Verify defaults # Verify defaults
@@ -133,7 +133,7 @@ def testCoreToken_Setters(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath): def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
"""Test handling files and text in the Tokenizer class.""" """Test handling files and text in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
mockRnd.reset() mockRnd.reset()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
@@ -231,7 +231,7 @@ def testCoreToken_StripEscape():
@pytest.mark.core @pytest.mark.core
def testCoreToken_HeaderFormat(mockGUI): def testCoreToken_HeaderFormat(mockGUI):
"""Test the tokenization of header formats in the Tokenizer class.""" """Test the tokenization of header formats in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -434,7 +434,7 @@ def testCoreToken_HeaderFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_MetaFormat(mockGUI): def testCoreToken_MetaFormat(mockGUI):
"""Test the tokenization of meta formats in the Tokenizer class.""" """Test the tokenization of meta formats in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -502,7 +502,7 @@ def testCoreToken_MetaFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_MarginFormat(mockGUI): def testCoreToken_MarginFormat(mockGUI):
"""Test the tokenization of margin formats in the Tokenizer class.""" """Test the tokenization of margin formats in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -556,7 +556,7 @@ def testCoreToken_MarginFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_TextFormat(mockGUI): def testCoreToken_TextFormat(mockGUI):
"""Test the tokenization of text formats in the Tokenizer class.""" """Test the tokenization of text formats in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -677,7 +677,7 @@ def testCoreToken_TextFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_SpecialFormat(mockGUI): def testCoreToken_SpecialFormat(mockGUI):
"""Test the tokenization of special formats in the Tokenizer class.""" """Test the tokenization of special formats in the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
theToken._isNovel = True theToken._isNovel = True
@@ -879,7 +879,7 @@ def testCoreToken_SpecialFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToken_ProcessHeaders(mockGUI): def testCoreToken_ProcessHeaders(mockGUI):
"""Test the header and page parser of the Tokenizer class.""" """Test the header and page parser of the Tokenizer class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theProject.data.setLanguage("en") theProject.data.setLanguage("en")
theProject._loadProjectLocalisation() theProject._loadProjectLocalisation()
theToken = BareTokenizer(theProject) theToken = BareTokenizer(theProject)
+4 -4
View File
@@ -32,7 +32,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
"""Test the tokenizer and converter chain using the ToMarkdown """Test the tokenizer and converter chain using the ToMarkdown
class. class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theMD = ToMarkdown(theProject) theMD = ToMarkdown(theProject)
# Headers # Headers
@@ -159,7 +159,7 @@ def testCoreToMarkdown_ConvertFormat(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToMarkdown_ConvertDirect(mockGUI): def testCoreToMarkdown_ConvertDirect(mockGUI):
"""Test the converter directly using the ToMarkdown class.""" """Test the converter directly using the ToMarkdown class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theMD = ToMarkdown(theProject) theMD = ToMarkdown(theProject)
theMD._isNovel = True theMD._isNovel = True
@@ -209,7 +209,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToMarkdown_Complex(mockGUI, fncPath): def testCoreToMarkdown_Complex(mockGUI, fncPath):
"""Test the save method of the ToMarkdown class.""" """Test the save method of the ToMarkdown class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theMD = ToMarkdown(theProject) theMD = ToMarkdown(theProject)
theMD._isNovel = True theMD._isNovel = True
@@ -261,7 +261,7 @@ def testCoreToMarkdown_Complex(mockGUI, fncPath):
@pytest.mark.core @pytest.mark.core
def testCoreToMarkdown_Format(mockGUI): def testCoreToMarkdown_Format(mockGUI):
"""Test all the formatters for the ToMarkdown class.""" """Test all the formatters for the ToMarkdown class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theMD = ToMarkdown(theProject) theMD = ToMarkdown(theProject)
assert theMD._formatKeywords("", theMD.A_NONE) == "" assert theMD._formatKeywords("", theMD.A_NONE) == ""
+7 -7
View File
@@ -53,7 +53,7 @@ def xmlToText(xElem):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Init(mockGUI): def testCoreToOdt_Init(mockGUI):
"""Test initialisation of the ODT document.""" """Test initialisation of the ODT document."""
theProject = NWProject(mockGUI) theProject = NWProject()
# Flat Doc # Flat Doc
# ======== # ========
@@ -108,7 +108,7 @@ def testCoreToOdt_Init(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_TextFormatting(mockGUI): def testCoreToOdt_TextFormatting(mockGUI):
"""Test formatting of paragraphs.""" """Test formatting of paragraphs."""
theProject = NWProject(mockGUI) theProject = NWProject()
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
theDoc.initDocument() theDoc.initDocument()
@@ -242,7 +242,7 @@ def testCoreToOdt_TextFormatting(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Convert(mockGUI): def testCoreToOdt_Convert(mockGUI):
"""Test the converter of the ToOdt class.""" """Test the converter of the ToOdt class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True theDoc._isNovel = True
@@ -573,7 +573,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
"""Test the converter directly using the ToOdt class to reach some """Test the converter directly using the ToOdt class to reach some
otherwise hard to reach conditions. otherwise hard to reach conditions.
""" """
theProject = NWProject(mockGUI) theProject = NWProject()
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
theDoc._isNovel = True theDoc._isNovel = True
@@ -626,7 +626,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths): def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
"""Test the document save functions.""" """Test the document save functions."""
theProject = NWProject(mockGUI) theProject = NWProject()
theProject.data.setAuthor("Jane Smith") theProject.data.setAuthor("Jane Smith")
theProject.data.setName("Test Project") theProject.data.setName("Test Project")
theProject.data.setSaveCount(1234) theProject.data.setSaveCount(1234)
@@ -668,7 +668,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths): def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
"""Test the document save functions.""" """Test the document save functions."""
theProject = NWProject(mockGUI) theProject = NWProject()
theProject.data.setAuthor("Jane Smith") theProject.data.setAuthor("Jane Smith")
theProject.data.setName("Test Project") theProject.data.setName("Test Project")
theProject.data.setSaveCount(1234) theProject.data.setSaveCount(1234)
@@ -745,7 +745,7 @@ def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Format(mockGUI): def testCoreToOdt_Format(mockGUI):
"""Test the formatters for the ToOdt class.""" """Test the formatters for the ToOdt class."""
theProject = NWProject(mockGUI) theProject = NWProject()
theDoc = ToOdt(theProject, isFlat=True) theDoc = ToOdt(theProject, isFlat=True)
assert theDoc._formatSynopsis("synopsis text") == ( assert theDoc._formatSynopsis("synopsis text") == (
+9 -9
View File
@@ -39,7 +39,7 @@ from novelwriter.core.project import NWProject
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def mockItems(mockGUI, mockRnd): def mockItems(mockGUI, mockRnd):
"""Create a list of mock items.""" """Create a list of mock items."""
theProject = NWProject(mockGUI) theProject = NWProject()
itemA = NWItem(theProject, "a000000000001") itemA = NWItem(theProject, "a000000000001")
itemA._name = "Novel" itemA._name = "Novel"
@@ -112,7 +112,7 @@ def mockItems(mockGUI, mockRnd):
@pytest.mark.core @pytest.mark.core
def testCoreTree_BuildTree(mockGUI, mockItems): def testCoreTree_BuildTree(mockGUI, mockItems):
"""Test building a project tree from a list of items.""" """Test building a project tree from a list of items."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
# Check that tree is empty (calls NWTree.__bool__) # Check that tree is empty (calls NWTree.__bool__)
@@ -269,7 +269,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_PackUnpack(mockGUI, mockItems): def testCoreTree_PackUnpack(mockGUI, mockItems):
"""Test packing and unpacking data.""" """Test packing and unpacking data."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
aHandles = [] aHandles = []
@@ -298,7 +298,7 @@ def testCoreTree_PackUnpack(mockGUI, mockItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fncPath, mockRnd): def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fncPath, mockRnd):
"""Check the project consistency.""" """Check the project consistency."""
theProject = NWProject(mockGUI) theProject = NWProject()
buildTestProject(theProject, fncPath) buildTestProject(theProject, fncPath)
# By default, all is well # By default, all is well
@@ -365,7 +365,7 @@ def testCoreTree_CheckConsistency(caplog: pytest.LogCaptureFixture, mockGUI, fnc
@pytest.mark.core @pytest.mark.core
def testCoreTree_Methods(mockGUI, mockItems): def testCoreTree_Methods(mockGUI, mockItems):
"""Test various class methods.""" """Test various class methods."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
for nwItem in mockItems: for nwItem in mockItems:
@@ -446,7 +446,7 @@ def testCoreTree_Methods(mockGUI, mockItems):
def testCoreTree_MakeHandles(mockGUI): def testCoreTree_MakeHandles(mockGUI):
"""Test generating item handles.""" """Test generating item handles."""
random.seed(42) random.seed(42)
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
handles = ["1c803a3b1799d", "bdd6406671ad1", "3eb1346685257", "23b8c392456de"] handles = ["1c803a3b1799d", "bdd6406671ad1", "3eb1346685257", "23b8c392456de"]
@@ -474,7 +474,7 @@ def testCoreTree_MakeHandles(mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreTree_Stats(mockGUI, mockItems): def testCoreTree_Stats(mockGUI, mockItems):
"""Test project stats methods.""" """Test project stats methods."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
for nwItem in mockItems: for nwItem in mockItems:
@@ -494,7 +494,7 @@ def testCoreTree_Stats(mockGUI, mockItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_Reorder(caplog, mockGUI, mockItems): def testCoreTree_Reorder(caplog, mockGUI, mockItems):
"""Test changing tree order.""" """Test changing tree order."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
aHandle = [] aHandle = []
@@ -529,7 +529,7 @@ def testCoreTree_Reorder(caplog, mockGUI, mockItems):
@pytest.mark.core @pytest.mark.core
def testCoreTree_ToCFile(monkeypatch, fncPath, mockGUI, mockItems): def testCoreTree_ToCFile(monkeypatch, fncPath, mockGUI, mockItems):
"""Test writing the ToC.txt file.""" """Test writing the ToC.txt file."""
theProject = NWProject(mockGUI) theProject = NWProject()
theTree = NWTree(theProject) theTree = NWTree(theProject)
for nwItem in mockItems: for nwItem in mockItems: