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