diff --git a/nw/__init__.py b/nw/__init__.py
index 95f15281..e9432711 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -195,7 +195,7 @@ def main(sysArgs=None):
testMode = True
# Set Config Options
- CONFIG.blockGUI = not testMode
+ CONFIG.showGUI = not testMode
CONFIG.debugInfo = debugLevel < logging.INFO
CONFIG.cmdOpen = cmdOpen
diff --git a/nw/config.py b/nw/config.py
index 93141fa5..db8a6c17 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -56,7 +56,7 @@ class Config:
self.appHandle = self.appName.lower()
# Debug Settings
- self.blockGUI = True # Allow blocking the GUI (disabled for testing)
+ self.showGUI = True # Allow blocking the GUI (disabled for testing)
self.debugInfo = False # True if log level is DEBUG or VERBOSE
# Config Error Handling
diff --git a/nw/core/project.py b/nw/core/project.py
index 5efce6ad..f7c18bdd 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -382,6 +382,7 @@ class NWProject():
# ==========================
if not self.ensureFolderStructure():
+ self.clearProject()
return False
self.projDict = path.join(self.projMeta, nwFiles.PROJ_DICT)
@@ -475,6 +476,7 @@ class NWProject():
"Project file does not appear to be a novelWriterXML file.",
nwAlert.ERROR
)
+ self.clearProject()
return False
# Check Project Storage Version
@@ -489,7 +491,7 @@ class NWProject():
# parser will lose the autoReplace settings if allowed to
# read the file. Introduced in version 0.10.
- if fileVersion == "1.0" and self.mainConf.blockGUI:
+ if fileVersion == "1.0" and self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(self.theParent, "Old Project Version", (
"The project file and data is created by a novelWriter version "
@@ -499,9 +501,10 @@ class NWProject():
"any more, so make sure you have a recent backup."
))
if msgRes != QMessageBox.Yes:
+ self.clearProject()
return False
- elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.blockGUI:
+ elif fileVersion != "1.1" and fileVersion != "1.2" and self.mainConf.showGUI:
self.makeAlert((
"Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. "
@@ -509,12 +512,13 @@ class NWProject():
).format(
vers = appVersion,
), nwAlert.ERROR)
+ self.clearProject()
return False
# Check novelWriter Version
# =========================
- if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.blockGUI:
+ if int(hexVersion, 16) > int(nw.__hexversion__, 16) and self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(self.theParent, "Version Conflict", (
"This project was saved by a newer version of novelWriter, version %s. "
@@ -525,6 +529,7 @@ class NWProject():
appVersion, nw.__version__
))
if msgRes != QMessageBox.Yes:
+ self.clearProject()
return False
# Start Parsing the XML
@@ -927,7 +932,7 @@ class NWProject():
return False
if path.isdir(projPath):
- if self.mainConf.blockGUI and listdir(self.projPath):
+ if self.mainConf.showGUI and listdir(self.projPath):
self.theParent.makeAlert((
"New project folder is not empty. "
"Each project requires a dedicated project folder."
diff --git a/nw/error.py b/nw/error.py
index fee8fab6..a8f3c149 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -160,7 +160,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False):
errMsg = NWErrorMessage(nwGUI)
errMsg.setMessage(exType, exValue, exTrace)
- if nw.CONFIG.blockGUI:
+ if nw.CONFIG.showGUI:
errMsg.exec_()
try:
diff --git a/nw/gui/build.py b/nw/gui/build.py
index 52b32ddb..f2c15221 100644
--- a/nw/gui/build.py
+++ b/nw/gui/build.py
@@ -634,7 +634,7 @@ class GuiBuildNovel(QDialog):
if not path.isdir(saveDir):
saveDir = self.mainConf.homePath
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName(
@@ -746,7 +746,7 @@ class GuiBuildNovel(QDialog):
errMsg = "Unknown format"
# Report to user
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
if wSuccess:
self.theParent.makeAlert(
"%s file successfully written to:
%s" % (
diff --git a/nw/gui/docsplit.py b/nw/gui/docsplit.py
index 766d84f5..786ac8fa 100644
--- a/nw/gui/docsplit.py
+++ b/nw/gui/docsplit.py
@@ -163,7 +163,7 @@ class GuiDocSplit(QDialog):
), nwAlert.ERROR)
return
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Split Document", (
diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py
index 5dbbede3..6e8e8cf7 100644
--- a/nw/gui/mainmenu.py
+++ b/nw/gui/mainmenu.py
@@ -192,7 +192,7 @@ class GuiMainMenu(QMenuBar):
self.aOpenProject = QAction("Open Project", self)
self.aOpenProject.setStatusTip("Open project")
self.aOpenProject.setShortcut("Ctrl+Shift+O")
- self.aOpenProject.triggered.connect(lambda: self.theParent.manageProjects())
+ self.aOpenProject.triggered.connect(lambda: self.theParent.showProjectLoadDialog())
self.projMenu.addAction(self.aOpenProject)
# Project > Save Project
@@ -213,7 +213,7 @@ class GuiMainMenu(QMenuBar):
self.aProjectSettings = QAction("Project Settings", self)
self.aProjectSettings.setStatusTip("Project settings")
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
- self.aProjectSettings.triggered.connect(lambda: self.theParent.editProjectDialog())
+ self.aProjectSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog())
self.projMenu.addAction(self.aProjectSettings)
# Project > Separator
@@ -829,7 +829,7 @@ class GuiMainMenu(QMenuBar):
self.aBuildProject = QAction("Build Novel Project", self)
self.aBuildProject.setStatusTip("Launch the Build novel project tool")
self.aBuildProject.setShortcut("F5")
- self.aBuildProject.triggered.connect(lambda: self.theParent.buildProjectDialog())
+ self.aBuildProject.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
self.toolsMenu.addAction(self.aBuildProject)
# Tools > Writing Stats
@@ -843,7 +843,7 @@ class GuiMainMenu(QMenuBar):
self.aPreferences = QAction("Preferences", self)
self.aPreferences.setStatusTip("Preferences")
self.aPreferences.setShortcut("Ctrl+,")
- self.aPreferences.triggered.connect(lambda: self.theParent.editConfigDialog())
+ self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
self.toolsMenu.addAction(self.aPreferences)
return
diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py
index 121d2fd1..5cbfbc72 100644
--- a/nw/gui/preferences.py
+++ b/nw/gui/preferences.py
@@ -104,7 +104,7 @@ class GuiPreferences(PagedDialog):
validEntries &= retA
needsRestart |= retB
- if needsRestart and self.mainConf.blockGUI:
+ if needsRestart:
msgBox = QMessageBox()
msgBox.information(
self, "Preferences",
@@ -120,7 +120,7 @@ class GuiPreferences(PagedDialog):
"""Close the preferences without saving the changes.
"""
logger.verbose("ConfigEditor close button clicked")
- self.close()
+ self.reject()
return
# END Class GuiPreferences
diff --git a/nw/gui/projload.py b/nw/gui/projload.py
index 54e40a11..804a4c2e 100644
--- a/nw/gui/projload.py
+++ b/nw/gui/projload.py
@@ -178,7 +178,7 @@ class GuiProjectLoad(QDialog):
"""Browse for a folder path.
"""
logger.verbose("GuiProjectLoad browse button clicked")
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
projFile, _ = QFileDialog.getOpenFileName(
@@ -219,7 +219,7 @@ class GuiProjectLoad(QDialog):
selList = self.listBox.selectedItems()
if selList:
doRemove = False
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Remove Entry",
diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py
index f5afa35e..998ec83c 100644
--- a/nw/gui/projsettings.py
+++ b/nw/gui/projsettings.py
@@ -124,13 +124,25 @@ class GuiProjectSettings(PagedDialog):
newList = self.tabReplace.getNewList()
self.theProject.setAutoReplace(newList)
- self._doClose()
+ self._saveGuiSettings()
+ self.accept()
return
def _doClose(self):
"""Save settings and close the dialog.
"""
+ self._saveGuiSettings()
+ self.reject()
+ return
+
+ ##
+ # Internal Functions
+ ##
+
+ def _saveGuiSettings(self):
+ """Save GUI settings.
+ """
winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height())
replaceColW = self.mainConf.rpxInt(self.tabReplace.listBox.columnWidth(0))
@@ -139,8 +151,6 @@ class GuiProjectSettings(PagedDialog):
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
- self.close()
-
return
# END Class GuiProjectSettings
diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py
index f4280f30..b5f8443b 100644
--- a/nw/gui/projtree.py
+++ b/nw/gui/projtree.py
@@ -261,7 +261,7 @@ class GuiProjectTree(QTreeWidget):
"""Move an item up or down in the tree, but only if the treeView
has focus. This also applies when the menu is used.
"""
- hasFocus = qApp.focusWidget() == self or not self.mainConf.blockGUI
+ hasFocus = qApp.focusWidget() == self or not self.mainConf.showGUI
if hasFocus and self.theParent.hasProject:
tHandle = self.getSelectedHandle()
@@ -362,7 +362,7 @@ class GuiProjectTree(QTreeWidget):
self.makeAlert("The Trash folder is already empty.", nwAlert.INFO)
return False
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Empty Trash", "Permanently delete %d file%s from Trash?" % (
@@ -416,7 +416,7 @@ class GuiProjectTree(QTreeWidget):
# If the file is in the trash folder already, as the
# user if they want to permanently delete the file.
doPermanent = False
- if self.mainConf.blockGUI and not alreadyAsked:
+ if self.mainConf.showGUI and not alreadyAsked:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Delete File", "Permanently delete file '%s'?" % nwItemS.itemName
@@ -445,7 +445,7 @@ class GuiProjectTree(QTreeWidget):
# The file is not already in the trash folder, so we
# move it there.
doTrash = False
- if self.mainConf.blockGUI and askForTrash:
+ if self.mainConf.showGUI and askForTrash:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Delete File", "Move file '%s' to Trash?" % nwItemS.itemName
diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py
index c356f7f4..d4a4e5d6 100644
--- a/nw/gui/writingstats.py
+++ b/nw/gui/writingstats.py
@@ -326,7 +326,7 @@ class GuiWritingStats(QDialog):
if not path.isdir(saveDir):
saveDir = self.mainConf.homePath
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgOpt = QFileDialog.Options()
dlgOpt |= QFileDialog.DontUseNativeDialog
saveTo = QFileDialog.getSaveFileName(
@@ -380,7 +380,7 @@ class GuiWritingStats(QDialog):
errMsg = str(e)
# Report to user
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
if wSuccess:
self.theParent.makeAlert(
"%s file successfully written to:
%s" % (
diff --git a/nw/guimain.py b/nw/guimain.py
index 06828f2e..483f095d 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -195,8 +195,8 @@ class GuiMain(QMainWindow):
self.setStatus = self.statusBar.setStatus
self.setProjectStatus = self.statusBar.setProjectStatus
- if self.mainConf.blockGUI:
- self.show()
+ # Force a show of the GUI
+ self.show()
# Check that config loaded fine
self.reportConfErr()
@@ -218,7 +218,8 @@ class GuiMain(QMainWindow):
logger.debug("Opening project from additional command line option")
self.openProject(self.mainConf.cmdOpen)
else:
- self.manageProjects()
+ if self.mainConf.showGUI:
+ self.showProjectLoadDialog()
logger.debug("novelWriter is ready ...")
self.statusBar.setStatus("novelWriter is ready ...")
@@ -245,24 +246,6 @@ class GuiMain(QMainWindow):
# Project Actions
##
- def manageProjects(self):
- """Opens the projects dialog for selecting either existing
- projects from a cache of recently opened projects, or provide a
- browse button for projects not yet cached.
- """
- if not self.mainConf.blockGUI:
- return False
-
- dlgProj = GuiProjectLoad(self)
- dlgProj.exec_()
- if dlgProj.result() == QDialog.Accepted:
- if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
- self.openProject(dlgProj.openPath)
- elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
- self.newProject()
-
- return True
-
def newProject(self, projData=None, forceNew=False):
"""Create new project with a few default files and folders.
The variable forceNew is used for testing.
@@ -275,8 +258,8 @@ class GuiMain(QMainWindow):
)
return False
- if projData is None and self.mainConf.blockGUI:
- projData = self.newProjectDialog()
+ if projData is None and self.mainConf.showGUI:
+ projData = self.showNewProjectDialog()
if projData is None:
return False
@@ -316,7 +299,7 @@ class GuiMain(QMainWindow):
# There is no project loaded, everything OK
return True
- if self.mainConf.blockGUI and not isYes:
+ if self.mainConf.showGUI and not isYes:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Close Project", "Save changes and close current project?"
@@ -332,7 +315,7 @@ class GuiMain(QMainWindow):
doBackup = False
if self.theProject.doBackup and self.mainConf.backupOnClose:
doBackup = True
- if self.mainConf.blockGUI and self.mainConf.askBeforeBackup:
+ if self.mainConf.showGUI and self.mainConf.askBeforeBackup:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Backup Project", "Backup current project?"
@@ -379,7 +362,7 @@ class GuiMain(QMainWindow):
# reason handled by the project class.
return False
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
try:
lockDetails = (
"
The project was locked by the computer "
@@ -449,7 +432,7 @@ class GuiMain(QMainWindow):
# If the project is new, it may not have a path, so we need one
if self.theProject.projPath is None:
- projPath = self.saveProjectDialog()
+ projPath = self.selectProjectPath()
self.theProject.setProjectPath(projPath)
if self.theProject.projPath is None:
return False
@@ -611,7 +594,7 @@ class GuiMain(QMainWindow):
return False
if not self.docEditor.isEmpty():
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
msgBox = QMessageBox()
msgRes = msgBox.question(self, "Import Document", (
"Importing the file will overwrite the current content of the document. "
@@ -629,7 +612,7 @@ class GuiMain(QMainWindow):
def mergeDocuments(self):
"""Merge multiple documents to one single new document.
"""
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgMerge = GuiDocMerge(self, self.theProject)
dlgMerge.exec_()
return True
@@ -637,7 +620,7 @@ class GuiMain(QMainWindow):
def splitDocument(self):
"""Split a single document into multiple documents.
"""
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgSplit = GuiDocSplit(self, self.theProject)
dlgSplit.exec_()
return True
@@ -684,7 +667,7 @@ class GuiMain(QMainWindow):
return
logger.verbose("Requesting change to item %s" % tHandle)
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
dlgProj = GuiItemEditor(self, self.theProject, tHandle)
dlgProj.exec_()
if dlgProj.result() == QDialog.Accepted:
@@ -746,7 +729,7 @@ class GuiMain(QMainWindow):
qApp.restoreOverrideCursor()
- if self.mainConf.blockGUI and not beQuiet:
+ if self.mainConf.showGUI and not beQuiet:
self.makeAlert("The project index has been successfully rebuilt.", nwAlert.INFO)
return True
@@ -763,7 +746,7 @@ class GuiMain(QMainWindow):
# Main Dialogs
##
- def saveProjectDialog(self):
+ def selectProjectPath(self):
"""Select where to save project.
"""
dlgOpt = QFileDialog.Options()
@@ -776,7 +759,22 @@ class GuiMain(QMainWindow):
return projPath
return None
- def newProjectDialog(self):
+ def showProjectLoadDialog(self):
+ """Opens the projects dialog for selecting either existing
+ projects from a cache of recently opened projects, or provide a
+ browse button for projects not yet cached.
+ """
+ dlgProj = GuiProjectLoad(self)
+ dlgProj.exec_()
+ if dlgProj.result() == QDialog.Accepted:
+ if dlgProj.openState == GuiProjectLoad.OPEN_STATE:
+ self.openProject(dlgProj.openPath)
+ elif dlgProj.openState == GuiProjectLoad.NEW_STATE:
+ self.newProject()
+
+ return True
+
+ def showNewProjectDialog(self):
"""Open the wizard and assemble the project options dict.
"""
newProj = GuiProjectWizard(self)
@@ -787,62 +785,76 @@ class GuiMain(QMainWindow):
return None
- def editConfigDialog(self):
+ def showPreferencesDialog(self):
"""Open the preferences dialog.
"""
dlgConf = GuiPreferences(self, self.theProject)
- if dlgConf.exec_() == QDialog.Accepted:
+ dlgConf.exec_()
+
+ if dlgConf.result() == QDialog.Accepted:
logger.debug("Applying new preferences")
self.initMain()
self.theTheme.updateTheme()
self.saveDocument()
self.docEditor.initEditor()
self.docViewer.initViewer()
- return True
- def editProjectDialog(self):
+ return
+
+ def showProjectSettingsDialog(self):
"""Open the project settings dialog.
"""
- if self.hasProject:
- dlgProj = GuiProjectSettings(self, self.theProject)
- dlgProj.exec_()
+ if not self.hasProject:
+ logger.error("No project open")
+ return
+
+ dlgProj = GuiProjectSettings(self, self.theProject)
+ dlgProj.exec_()
+
+ if dlgProj.result() == QDialog.Accepted:
+ logger.debug("Applying new project settings")
self.docEditor.setDictionaries()
self._setWindowTitle(self.theProject.projName)
- return True
- def buildProjectDialog(self):
+ return
+
+ def showBuildProjectDialog(self):
"""Open the build project dialog.
"""
- if self.hasProject:
- dlgBuild = GuiBuildNovel(self, self.theProject)
- dlgBuild.setModal(False)
- dlgBuild.show()
- return True
+ if not self.hasProject:
+ logger.error("No project open")
+ return
+
+ dlgBuild = GuiBuildNovel(self, self.theProject)
+ dlgBuild.setModal(False)
+ dlgBuild.show()
+ return
def showWritingStatsDialog(self):
"""Open the session log dialog.
"""
- if self.hasProject:
- dlgStats = GuiWritingStats(self, self.theProject)
- dlgStats.setModal(False)
- dlgStats.show()
- return True
+ if not self.hasProject:
+ logger.error("No project open")
+ return
+
+ dlgStats = GuiWritingStats(self, self.theProject)
+ dlgStats.setModal(False)
+ dlgStats.show()
+ return
def showAboutNWDialog(self):
"""Show the about dialog for novelWriter.
"""
- if self.mainConf.blockGUI:
- dlgAbout = GuiAbout(self)
- dlgAbout.exec_()
- return True
+ dlgAbout = GuiAbout(self)
+ dlgAbout.exec_()
+ return
def showAboutQtDialog(self):
"""Show the about dialog for Qt.
"""
- if self.mainConf.blockGUI:
- msgBox = QMessageBox()
- msgBox.aboutQt(self, "About Qt")
- return True
+ msgBox = QMessageBox()
+ msgBox.aboutQt(self, "About Qt")
+ return
def makeAlert(self, theMessage, theLevel=nwAlert.INFO):
"""Alert both the user and the logger at the same time. Message
@@ -871,7 +883,7 @@ class GuiMain(QMainWindow):
logger.error(msgLine)
# Popup
- if self.mainConf.blockGUI:
+ if self.mainConf.showGUI:
msgBox = QMessageBox()
if theLevel == nwAlert.INFO:
msgBox.information(self, "Information", popMsg)
@@ -902,7 +914,7 @@ class GuiMain(QMainWindow):
def closeMain(self):
"""Save everything, and close novelWriter.
"""
- if self.mainConf.blockGUI and self.hasProject:
+ if self.mainConf.showGUI and self.hasProject:
msgBox = QMessageBox()
msgRes = msgBox.question(
self, "Exit", "Do you want to save changes and exit?"
diff --git a/tests/conftest.py b/tests/conftest.py
index 14988efa..a8945155 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,12 +9,22 @@ import shutil
from os import path, mkdir
from nwdummy import DummyMain
+from PyQt5.QtWidgets import QFileDialog, QMessageBox
+
sys.path.insert(1, path.abspath(path.join(path.dirname(__file__), path.pardir)))
from nw.config import Config # noqa: E402
+##
+# Core Test Folders
+##
+
@pytest.fixture(scope="session")
def nwTemp():
+ """A temporary folder for the test session. This folder is
+ presistent after the test so that the status of generated files can
+ be checked. The folder is instead cleared before a new test session.
+ """
testDir = path.dirname(__file__)
tempDir = path.join(testDir, "temp")
if path.isdir(tempDir):
@@ -25,31 +35,51 @@ def nwTemp():
@pytest.fixture(scope="session")
def nwRef():
+ """The folder where all the reference files are stored for verifying
+ the results of tests.
+ """
testDir = path.dirname(__file__)
refDir = path.join(testDir, "reference")
return refDir
-@pytest.fixture(scope="session")
-def nwConf(nwRef, nwTemp):
- theConf = Config()
- theConf.initConfig(nwRef, nwTemp)
- return theConf
+##
+# novelWriter Objects
+##
@pytest.fixture(scope="session")
def tmpConf(nwTemp):
+ """Create a temporary novelWriter configuration object.
+ """
theConf = Config()
theConf.initConfig(nwTemp, nwTemp)
theConf.setLastPath("")
return theConf
+@pytest.fixture(scope="session")
+def nwConf(nwRef, nwTemp):
+ """Temporary novelWriter configuration used for the dummy instance
+ of novelWriter's main GUI.
+ """
+ theConf = Config()
+ theConf.initConfig(nwRef, nwTemp)
+ return theConf
+
@pytest.fixture(scope="session")
def nwDummy(nwRef, nwTemp, nwConf):
+ """Create a dummy instance of novelWriter's main GUI class.
+ """
theDummy = DummyMain()
theDummy.mainConf = nwConf
return theDummy
+##
+# Temporary Test Folders
+##
+
@pytest.fixture(scope="session")
def nwTempProj(nwTemp):
+ """A temporary folder for project tests.
+ """
projDir = path.join(nwTemp, "proj")
if not path.isdir(projDir):
mkdir(projDir)
@@ -57,6 +87,8 @@ def nwTempProj(nwTemp):
@pytest.fixture(scope="session")
def nwTempGUI(nwTemp):
+ """A temporary folder for GUI tests.
+ """
guiDir = path.join(nwTemp, "gui")
if not path.isdir(guiDir):
mkdir(guiDir)
@@ -64,6 +96,8 @@ def nwTempGUI(nwTemp):
@pytest.fixture(scope="session")
def nwTempBuild(nwTemp):
+ """A temporary folder for build tests.
+ """
buildDir = path.join(nwTemp, "build")
if not path.isdir(buildDir):
mkdir(buildDir)
@@ -71,6 +105,8 @@ def nwTempBuild(nwTemp):
@pytest.fixture(scope="function")
def nwFuncTemp(nwTemp):
+ """A temporary folder for a single test function.
+ """
funcDir = path.join(nwTemp, "ftemp")
if path.isdir(funcDir):
shutil.rmtree(funcDir)
@@ -81,8 +117,14 @@ def nwFuncTemp(nwTemp):
shutil.rmtree(funcDir)
return
+##
+# Temp Folders for Projects
+##
+
@pytest.fixture(scope="function")
def nwMinimal(nwTemp):
+ """A minimal novelWriter example project.
+ """
testDir = path.dirname(__file__)
minimalStore = path.join(testDir, "minimal")
minimalDir = path.join(nwTemp, "minimal")
@@ -102,6 +144,9 @@ def nwMinimal(nwTemp):
@pytest.fixture(scope="function")
def nwLipsum(nwTemp):
+ """A medium sized novelWriter example project with a lot of Lorem
+ Ipsum dummy text.
+ """
testDir = path.dirname(__file__)
lipsumStore = path.join(testDir, "lipsum")
lipsumDir = path.join(nwTemp, "lipsum")
@@ -121,6 +166,8 @@ def nwLipsum(nwTemp):
@pytest.fixture(scope="function")
def nwOldProj(nwTemp):
+ """A minimal movelWriter project using the old folder structure.
+ """
testDir = path.dirname(__file__)
oldProjStore = path.join(testDir, "oldproj")
oldProjDir = path.join(nwTemp, "oldproj")
@@ -131,3 +178,30 @@ def nwOldProj(nwTemp):
if path.isdir(oldProjDir):
shutil.rmtree(oldProjDir)
return
+
+##
+# Monkey Patch Dialogs
+##
+
+@pytest.fixture(scope="function")
+def mnkQtDialogs(monkeypatch, nwTemp):
+ """Mock Qt dialog functions to prevent GUI blocking while testing.
+ """
+ monkeypatch.setattr(
+ QFileDialog, "getExistingDirectory", lambda *args, **kwargs: nwTemp
+ )
+
+ monkeypatch.setattr(
+ QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes
+ )
+ monkeypatch.setattr(
+ QMessageBox, "information", lambda *args, **kwargs: None
+ )
+ monkeypatch.setattr(
+ QMessageBox, "warning", lambda *args, **kwargs: None
+ )
+ monkeypatch.setattr(
+ QMessageBox, "critical", lambda *args, **kwargs: None
+ )
+
+ return
diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py
index 1fceaca1..13d41afb 100644
--- a/tests/test_dialogs.py
+++ b/tests/test_dialogs.py
@@ -7,13 +7,14 @@ import pytest
import json
from shutil import copyfile
-from nwtools import cmpFiles
+from nwtools import cmpFiles, getGuiItem
from os import path
from PyQt5.QtCore import Qt, QItemSelectionModel
from PyQt5.QtWidgets import (
- QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog
+ QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog, QAction,
+ QMessageBox
)
from nw.gui import (
@@ -28,22 +29,34 @@ keyDelay = 2
stepDelay = 20
@pytest.mark.gui
-def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
+def testProjectSettings(qtbot, monkeypatch, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
- # Create new, save, open project
+ # Check that we cannot open when there is no project
+ nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+ assert getGuiItem("GuiProjectSettings") is None
+
+ # Create new project
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
nwGUI.mainConf.backupPath = nwFuncTemp
- projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
+ # Get the dialog object
+ monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiProjectSettings, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
+
+ projEdit = getGuiItem("GuiProjectSettings")
+ assert isinstance(projEdit, GuiProjectSettings)
projEdit.show()
qtbot.addWidget(projEdit)
+ # Main settings
qtbot.wait(stepDelay)
projEdit.tabMain.editName.setText("")
for c in "Project Name":
@@ -199,6 +212,10 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
assert nwGUI.closeProject()
qtbot.wait(stepDelay)
+ # Check that we cannot open when there is no project
+ nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
+ assert getGuiItem("GuiWritingStats") is None
+
assert nwGUI.openProject(nwFuncTemp)
qtbot.wait(stepDelay)
@@ -239,8 +256,11 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
qtbot.wait(stepDelay)
nwGUI.mainConf.lastPath = nwFuncTemp
- sessLog = GuiWritingStats(nwGUI, nwGUI.theProject)
- sessLog.show()
+ nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
+
+ sessLog = getGuiItem("GuiWritingStats")
+ assert isinstance(sessLog, GuiWritingStats)
qtbot.wait(stepDelay)
assert sessLog._saveData(sessLog.FMT_CSV)
@@ -337,17 +357,29 @@ def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
-def testAboutBox(qtbot, nwFuncTemp, nwTemp):
+def testAboutBox(qtbot, monkeypatch, nwFuncTemp, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
- msgAbout = GuiAbout(nwGUI)
+ # NW About
+ monkeypatch.setattr(GuiAbout, "exec_", lambda *args: None)
+ nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
+
+ msgAbout = getGuiItem("GuiAbout")
+ assert isinstance(msgAbout, GuiAbout)
+ msgAbout.show()
+
assert msgAbout.pageAbout.document().characterCount() > 100
assert msgAbout.pageLicense.document().characterCount() > 100
+ # Qt About
+ monkeypatch.setattr(QMessageBox, "aboutQt", lambda *args, **kwargs: None)
+ nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
+
# qtbot.stopForInteraction()
msgAbout._doClose()
nwGUI.closeMain()
@@ -361,11 +393,20 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
qtbot.waitForWindowShown(nwGUI)
qtbot.wait(stepDelay)
- assert nwGUI.openProject(nwLipsum)
+ # Check that we cannot open when there is no project
+ nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
+ assert getGuiItem("GuiBuildNovel") is None
+ # Open a project
+ assert nwGUI.openProject(nwLipsum)
nwGUI.mainConf.lastPath = nwLipsum
- nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
+ # Open the tool
+ nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
+
+ nwBuild = getGuiItem("GuiBuildNovel")
+ assert isinstance(nwBuild, GuiBuildNovel)
# Default Settings
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
@@ -764,7 +805,7 @@ def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
-def testLoadProject(qtbot, nwMinimal, nwTemp):
+def testLoadProject(qtbot, monkeypatch, nwMinimal, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -774,7 +815,13 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
assert nwGUI.openProject(nwMinimal)
assert nwGUI.closeProject()
- nwLoad = GuiProjectLoad(nwGUI)
+ monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
+
+ nwLoad = getGuiItem("GuiProjectLoad")
+ assert isinstance(nwLoad, GuiProjectLoad)
nwLoad.show()
recentCount = nwLoad.listBox.topLevelItemCount()
@@ -793,6 +840,7 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
assert nwLoad.openPath == selPath
assert nwLoad.openState == nwLoad.OPEN_STATE
+ # Just create a new project load from scratch for the rest of the test
del nwLoad
nwLoad = GuiProjectLoad(nwGUI)
nwLoad.show()
@@ -815,7 +863,7 @@ def testLoadProject(qtbot, nwMinimal, nwTemp):
nwGUI.closeMain()
@pytest.mark.gui
-def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
+def testPreferences(qtbot, monkeypatch, mnkQtDialogs, nwMinimal, nwTemp, nwRef, tmpConf):
nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
nwGUI.show()
@@ -823,11 +871,17 @@ def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
qtbot.wait(stepDelay)
assert nwGUI.openProject(nwMinimal)
- nwPrefs = GuiPreferences(nwGUI, nwGUI.theProject)
+
+ monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
+
+ nwPrefs = getGuiItem("GuiPreferences")
+ assert isinstance(nwPrefs, GuiPreferences)
nwPrefs.show()
# Override Config
- tmpConf.blockGUI = False
tmpConf.confPath = nwMinimal
nwGUI.mainConf = tmpConf
nwPrefs.mainConf = tmpConf
@@ -991,3 +1045,17 @@ def testQuotesDialog(qtbot, nwMinimal, nwTemp):
nwQuot.close()
nwGUI.closeMain()
nwGUI.close()
+
+@pytest.mark.gui
+def testDialogsOpenClose(qtbot, mnkQtDialogs, nwMinimal, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp, nwMinimal])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.selectProjectPath() == nwTemp
+
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+ nwGUI.close()
diff --git a/tests/test_gui.py b/tests/test_gui.py
index ec24d5ec..8cf3a4d4 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -223,6 +223,10 @@ def testDocEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
qtbot.keyClick(nwGUI.docEditor, c, delay=keyDelay)
qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ # Trigger autosaves before making more changes
+ nwGUI._autoSaveDocument()
+ nwGUI._autoSaveProject()
+
# Select the 'New Scene' file
nwGUI.setFocus(1)
nwGUI.treeView.clearSelection()
diff --git a/tests/test_project.py b/tests/test_project.py
index 1ec42348..7e46b8e6 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -580,9 +580,9 @@ def testOrphanedFiles(nwDummy, nwLipsum):
assert theProject.closeProject()
@pytest.mark.project
-def testOldProject(nwDummy, nwOldProj):
+def testOldProject(nwDummy, nwOldProj, mnkQtDialogs):
theProject = NWProject(nwDummy)
- theProject.mainConf.blockGUI = False
+ theProject.mainConf.showGUI = False
# Create dummy files for known legacy files
deleteFiles = [