Merge pull request #682 from vkbo/project_language

Project Language
This commit is contained in:
Veronica Berglyd Olsen
2021-02-18 21:26:43 +01:00
committed by GitHub
39 changed files with 877 additions and 820 deletions
+257 -241
View File
File diff suppressed because it is too large Load Diff
+255 -240
View File
File diff suppressed because it is too large Load Diff
+18 -7
View File
@@ -53,6 +53,9 @@ class Config:
CNF_S_LST = 3 CNF_S_LST = 3
CNF_I_LST = 4 CNF_I_LST = 4
LANG_NW = 1
LANG_PROJ = 2
def __init__(self): def __init__(self):
# Set Application Variables # Set Application Variables
@@ -391,21 +394,29 @@ class Config:
return return
def listLanguages(self): def listLanguages(self, lngSet):
"""List localisation files in the i18n folder. The default GUI """List localisation files in the i18n folder. The default GUI
language 'en_GB' is British English. language 'en_GB' is British English.
""" """
langList = { if lngSet == self.LANG_NW:
"en_GB": QLocale("en_GB").nativeLanguageName().title() fPre = "nw_"
} fExt = ".qm"
langList = {"en_GB": QLocale("en_GB").nativeLanguageName().title()}
elif lngSet == self.LANG_PROJ:
fPre = "project_"
fExt = ".json"
langList = {"en": "English"}
else:
return []
for qmFile in os.listdir(self.nwLangPath): for qmFile in os.listdir(self.nwLangPath):
if not os.path.isfile(os.path.join(self.nwLangPath, qmFile)): if not os.path.isfile(os.path.join(self.nwLangPath, qmFile)):
continue continue
if not qmFile.startswith("nw_") or not qmFile.endswith(".qm"): if not qmFile.startswith(fPre) or not qmFile.endswith(fExt):
continue continue
qmLang = qmFile[3:-3] qmLang = qmFile[len(fPre):-len(fExt)]
qmName = QLocale(qmLang).nativeLanguageName().title() qmName = QLocale(qmLang).nativeLanguageName().title()
if qmLang and qmName: if qmLang and qmName and qmLang != "en":
langList[qmLang] = qmName langList[qmLang] = qmName
return sorted(langList.items(), key=lambda x: x[0]) return sorted(langList.items(), key=lambda x: x[0])
+30 -17
View File
@@ -79,7 +79,8 @@ class NWProject():
self.projCache = None # The full path to the project's cache folder self.projCache = None # The full path to the project's cache folder
self.projContent = None # The full path to the project's content folder self.projContent = None # The full path to the project's content folder
self.projDict = None # The spell check dictionary self.projDict = None # The spell check dictionary
self.projLang = None # The spell check language, if different than default self.projSpell = None # The spell check language, if different than default
self.projLang = None # The project language, used for builds
self.projFile = None # The file name of the project main XML file self.projFile = None # The file name of the project main XML file
# Project Meta # Project Meta
@@ -195,6 +196,7 @@ class NWProject():
self.projCache = None self.projCache = None
self.projContent = None self.projContent = None
self.projDict = None self.projDict = None
self.projSpell = None
self.projLang = None self.projLang = None
self.projFile = nwFiles.PROJ_FILE self.projFile = nwFiles.PROJ_FILE
self.projName = "" self.projName = ""
@@ -533,10 +535,12 @@ class NWProject():
continue continue
if xItem.tag == "doBackup": if xItem.tag == "doBackup":
self.doBackup = checkBool(xItem.text, False) self.doBackup = checkBool(xItem.text, False)
elif xItem.tag == "language":
self.projLang = checkString(xItem.text, None, True)
elif xItem.tag == "spellCheck": elif xItem.tag == "spellCheck":
self.spellCheck = checkBool(xItem.text, False) self.spellCheck = checkBool(xItem.text, False)
elif xItem.tag == "spellLang": elif xItem.tag == "spellLang":
self.projLang = checkString(xItem.text, None, True) self.projSpell = checkString(xItem.text, None, True)
elif xItem.tag == "autoOutline": elif xItem.tag == "autoOutline":
self.autoOutline = checkBool(xItem.text, True) self.autoOutline = checkBool(xItem.text, True)
elif xItem.tag == "lastEdited": elif xItem.tag == "lastEdited":
@@ -589,7 +593,7 @@ class NWProject():
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName)) self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
self._scanProjectFolder() self._scanProjectFolder()
self.loadProjectLocalisation(self.projLang) self._loadProjectLocalisation()
self.currWCount = self.lastWCount self.currWCount = self.lastWCount
self.projOpened = time() self.projOpened = time()
@@ -650,8 +654,9 @@ class NWProject():
# Save Project Settings # Save Project Settings
xSettings = etree.SubElement(nwXML, "settings") xSettings = etree.SubElement(nwXML, "settings")
self._packProjectValue(xSettings, "doBackup", self.doBackup) self._packProjectValue(xSettings, "doBackup", self.doBackup)
self._packProjectValue(xSettings, "language", self.projLang)
self._packProjectValue(xSettings, "spellCheck", self.spellCheck) self._packProjectValue(xSettings, "spellCheck", self.spellCheck)
self._packProjectValue(xSettings, "spellLang", self.projLang) self._packProjectValue(xSettings, "spellLang", self.projSpell)
self._packProjectValue(xSettings, "autoOutline", self.autoOutline) self._packProjectValue(xSettings, "autoOutline", self.autoOutline)
self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
@@ -1012,9 +1017,18 @@ class NWProject():
"""Set the project-specific spell check language. """Set the project-specific spell check language.
""" """
theLang = checkString(theLang, None, True) theLang = checkString(theLang, None, True)
if self.projSpell != theLang:
self.projSpell = theLang
self.setProjectChanged(True)
return True
def setProjectLang(self, theLang):
"""Set the project-specific language.
"""
theLang = checkString(theLang, None, True)
if self.projLang != theLang: if self.projLang != theLang:
self.projLang = theLang self.projLang = theLang
self.loadProjectLocalisation(theLang) self._loadProjectLocalisation()
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
@@ -1210,17 +1224,20 @@ class NWProject():
theValue = str(theWord) theValue = str(theWord)
return self.langData.get(theValue, theValue) return self.langData.get(theValue, theValue)
def loadProjectLocalisation(self, theLang): ##
# Internal Functions
##
def _loadProjectLocalisation(self):
"""Load the language data for the current project language. """Load the language data for the current project language.
""" """
if theLang is None: if self.projLang is None:
theLang = self.mainConf.spellLanguage self.langData = {}
if theLang is None: return False
theLang = "en"
lngShort = theLang.split("_")[0] lngShort = self.projLang.split("_")[0]
loadFile = os.path.join(self.mainConf.nwLangPath, "project_en.json") loadFile = os.path.join(self.mainConf.nwLangPath, "project_en.json")
chkFile1 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % theLang) chkFile1 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % self.projLang)
chkFile2 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % lngShort) chkFile2 = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % lngShort)
if os.path.isfile(chkFile1): if os.path.isfile(chkFile1):
@@ -1234,16 +1251,12 @@ class NWProject():
logger.debug("Loaded project language file: %s" % os.path.basename(loadFile)) logger.debug("Loaded project language file: %s" % os.path.basename(loadFile))
except Exception: except Exception:
logger.error("Failed to load index file") logger.error("Failed to project language file")
nw.logException() nw.logException()
return False return False
return True return True
##
# Internal Functions
##
def _readLockFile(self): def _readLockFile(self):
"""Reads the lock file in the project folder. """Reads the lock file in the project folder.
""" """
+2 -1
View File
@@ -405,10 +405,11 @@ class ToHtml(Tokenizer):
def _formatSynopsis(self, tText): def _formatSynopsis(self, tText):
"""Apply HTML formatting to synopsis. """Apply HTML formatting to synopsis.
""" """
sSynop = self._localLookup("Synopsis")
if self.genMode == self.M_PREVIEW: if self.genMode == self.M_PREVIEW:
sSynop = self._trSynopsis
return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {tText}</p>\n" return f"<p class='comment'><span class='synopsis'>{sSynop}:</span> {tText}</p>\n"
else: else:
sSynop = self._localLookup("Synopsis")
return f"<p class='synopsis'><strong>{sSynop}:</strong> {tText}</p>\n" return f"<p class='synopsis'><strong>{sSynop}:</strong> {tText}</p>\n"
def _formatComments(self, tText): def _formatComments(self, tText):
+3
View File
@@ -147,6 +147,9 @@ class Tokenizer():
self._localLookup = self.theProject.localLookup self._localLookup = self.theProject.localLookup
self.tr = partial(QCoreApplication.translate, "Tokenizer") self.tr = partial(QCoreApplication.translate, "Tokenizer")
# Cached Translations
self._trSynopsis = self.tr("Synopsis")
return return
## ##
+21 -1
View File
@@ -41,7 +41,7 @@ from PyQt5.QtWidgets import (
qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel, qApp, QDialog, QVBoxLayout, QHBoxLayout, QTextBrowser, QPushButton, QLabel,
QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction, QLineEdit, QGroupBox, QGridLayout, QProgressBar, QMenu, QAction,
QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget, QFileDialog, QFontDialog, QSpinBox, QScrollArea, QSplitter, QWidget,
QSizePolicy, QDoubleSpinBox QSizePolicy, QDoubleSpinBox, QComboBox
) )
from nw.common import fuzzyTime, makeFileNameSafe from nw.common import fuzzyTime, makeFileNameSafe
@@ -163,6 +163,17 @@ class GuiBuildNovel(QDialog):
self._reFmtCodes(self.theProject.titleFormat["section"]) self._reFmtCodes(self.theProject.titleFormat["section"])
) )
self.buildLang = QComboBox()
self.buildLang.setMinimumWidth(xFmt)
theLangs = self.mainConf.listLanguages(self.mainConf.LANG_PROJ)
self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
for langID, langName in theLangs:
self.buildLang.addItem(langName, langID)
langIdx = self.buildLang.findData(self.theProject.projLang)
if langIdx != -1:
self.buildLang.setCurrentIndex(langIdx)
# Dummy boxes due to QGridView and QLineEdit expand bug # Dummy boxes due to QGridView and QLineEdit expand bug
self.boxTitle = QHBoxLayout() self.boxTitle = QHBoxLayout()
self.boxTitle.addWidget(self.fmtTitle) self.boxTitle.addWidget(self.fmtTitle)
@@ -180,6 +191,7 @@ class GuiBuildNovel(QDialog):
unnumbLabel = QLabel(self.tr("Unnumbered")) unnumbLabel = QLabel(self.tr("Unnumbered"))
sceneLabel = QLabel(self.tr("Scene")) sceneLabel = QLabel(self.tr("Scene"))
sectionLabel = QLabel(self.tr("Section")) sectionLabel = QLabel(self.tr("Section"))
langLabel = QLabel(self.tr("Language"))
self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft) self.titleForm.addWidget(titleLabel, 0, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight) self.titleForm.addLayout(self.boxTitle, 0, 1, 1, 1, Qt.AlignRight)
@@ -191,6 +203,8 @@ class GuiBuildNovel(QDialog):
self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight) self.titleForm.addLayout(self.boxScene, 3, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(sectionLabel, 4, 0, 1, 1, Qt.AlignLeft) self.titleForm.addWidget(sectionLabel, 4, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight) self.titleForm.addLayout(self.boxSection, 4, 1, 1, 1, Qt.AlignRight)
self.titleForm.addWidget(langLabel, 5, 0, 1, 1, Qt.AlignLeft)
self.titleForm.addWidget(self.buildLang, 5, 1, 1, 1, Qt.AlignRight)
self.titleForm.setColumnStretch(0, 0) self.titleForm.setColumnStretch(0, 0)
self.titleForm.setColumnStretch(1, 1) self.titleForm.setColumnStretch(1, 1)
@@ -651,6 +665,9 @@ class GuiBuildNovel(QDialog):
includeBody = self.includeBody.isChecked() includeBody = self.includeBody.isChecked()
replaceUCode = self.replaceUCode.isChecked() replaceUCode = self.replaceUCode.isChecked()
# The language lookup dict is reloaded if needed
self.theProject.setProjectLang(self.buildLang.currentData())
# Get font information # Get font information
fontInfo = QFontInfo(QFont(textFont, textSize)) fontInfo = QFontInfo(QFont(textFont, textSize))
textFixed = fontInfo.fixedPitch() textFixed = fontInfo.fixedPitch()
@@ -1115,6 +1132,7 @@ class GuiBuildNovel(QDialog):
"section" : self.fmtSection.text().strip(), "section" : self.fmtSection.text().strip(),
}) })
buildLang = self.buildLang.currentData()
winWidth = self.mainConf.rpxInt(self.width()) winWidth = self.mainConf.rpxInt(self.width())
winHeight = self.mainConf.rpxInt(self.height()) winHeight = self.mainConf.rpxInt(self.height())
justifyText = self.justifyText.isChecked() justifyText = self.justifyText.isChecked()
@@ -1136,6 +1154,8 @@ class GuiBuildNovel(QDialog):
boxWidth = self.mainConf.rpxInt(mainSplit[0]) boxWidth = self.mainConf.rpxInt(mainSplit[0])
docWidth = self.mainConf.rpxInt(mainSplit[1]) docWidth = self.mainConf.rpxInt(mainSplit[1])
self.theProject.setProjectLang(buildLang)
# GUI Settings # GUI Settings
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth) self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight) self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
+2 -2
View File
@@ -599,10 +599,10 @@ class GuiDocEditor(QTextEdit):
status bar to show the one actually loaded by the spell checker status bar to show the one actually loaded by the spell checker
class. class.
""" """
if self.theProject.projLang is None: if self.theProject.projSpell is None:
theLang = self.mainConf.spellLanguage theLang = self.mainConf.spellLanguage
else: else:
theLang = self.theProject.projLang theLang = self.theProject.projSpell
self.theDict.setLanguage(theLang, self.theProject.projDict) self.theDict.setLanguage(theLang, self.theProject.projDict)
theTag, theProvider = self.theDict.describeDict() theTag, theProvider = self.theDict.describeDict()
+2 -6
View File
@@ -91,7 +91,6 @@ class GuiPreferences(PagedDialog):
logger.debug("Saving new preferences") logger.debug("Saving new preferences")
needsRestart = self.tabGeneral.saveValues() needsRestart = self.tabGeneral.saveValues()
prevSpell = self.mainConf.spellLanguage
self.tabProjects.saveValues() self.tabProjects.saveValues()
self.tabDocs.saveValues() self.tabDocs.saveValues()
@@ -99,9 +98,6 @@ class GuiPreferences(PagedDialog):
self.tabSyntax.saveValues() self.tabSyntax.saveValues()
self.tabAuto.saveValues() self.tabAuto.saveValues()
if prevSpell != self.mainConf.spellLanguage:
self.theProject.loadProjectLocalisation(self.mainConf.spellLanguage)
if needsRestart: if needsRestart:
self.theParent.makeAlert( self.theParent.makeAlert(
self.tr("Some changes will not be applied until novelWriter has been restarted."), self.tr("Some changes will not be applied until novelWriter has been restarted."),
@@ -142,8 +138,8 @@ class GuiPreferencesGeneral(QWidget):
## Select Locale ## Select Locale
self.guiLang = QComboBox() self.guiLang = QComboBox()
self.guiLang.setMinimumWidth(minWidth) self.guiLang.setMinimumWidth(minWidth)
self.theLangs = self.mainConf.listLanguages() theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW)
for lang, langName in self.theLangs: for lang, langName in theLangs:
self.guiLang.addItem(langName, lang) self.guiLang.addItem(langName, lang)
langIdx = self.guiLang.findData(self.mainConf.guiLang) langIdx = self.guiLang.findData(self.mainConf.guiLang)
if langIdx != -1: if langIdx != -1:
+2 -2
View File
@@ -223,8 +223,8 @@ class GuiProjectEditMain(QWidget):
) )
spellIdx = 0 spellIdx = 0
if self.theProject.projLang is not None: if self.theProject.projSpell is not None:
spellIdx = self.spellLang.findData(self.theProject.projLang) spellIdx = self.spellLang.findData(self.theProject.projSpell)
if spellIdx != -1: if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx) self.spellLang.setCurrentIndex(spellIdx)
+7 -6
View File
@@ -1,16 +1,17 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.3a0" hexVersion="0x010300a0" fileVersion="1.2" timeStamp="2021-02-15 17:51:17"> <novelWriterXML appVersion="1.3a0" hexVersion="0x010300a0" fileVersion="1.2" timeStamp="2021-02-18 20:34:04">
<project> <project>
<name>Sample Project</name> <name>Sample Project</name>
<title>Sample Project</title> <title>Sample Project</title>
<author>Jane Smith</author> <author>Jane Smith</author>
<author>Jay Doh</author> <author>Jay Doh</author>
<saveCount>1021</saveCount> <saveCount>1054</saveCount>
<autoCount>161</autoCount> <autoCount>166</autoCount>
<editTime>48283</editTime> <editTime>49435</editTime>
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<language>en</language>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -26,7 +27,7 @@
</autoReplace> </autoReplace>
<titleFormat> <titleFormat>
<title>%title%</title> <title>%title%</title>
<chapter>Chapter %ch%: %title%</chapter> <chapter>Chapter %chw%: %title%</chapter>
<unnumbered>%title%</unnumbered> <unnumbered>%title%</unnumbered>
<scene>Scene %ch%.%sc%: %title%</scene> <scene>Scene %ch%.%sc%: %title%</scene>
<section></section> <section></section>
@@ -120,7 +121,7 @@
<charCount>1810</charCount> <charCount>1810</charCount>
<wordCount>318</wordCount> <wordCount>318</wordCount>
<paraCount>8</paraCount> <paraCount>8</paraCount>
<cursorPos>1112</cursorPos> <cursorPos>1505</cursorPos>
</item> </item>
<item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a"> <item handle="bc0cbd2a407f3" order="2" parent="e7ded148d6e4a">
<name>Another Scene</name> <name>Another Scene</name>
+18 -2
View File
@@ -118,20 +118,36 @@ def tmpConf(tmpDir):
theConf = Config() theConf = Config()
theConf.initConfig(tmpDir, tmpDir) theConf.initConfig(tmpDir, tmpDir)
theConf.setLastPath("") theConf.setLastPath("")
theConf.guiLang = "en_GB"
return theConf return theConf
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def dummyGUI(tmpConf): def fncConf(fncDir):
"""Create a temporary novelWriter configuration object.
"""
confFile = os.path.join(fncDir, "novelwriter.conf")
if os.path.isfile(confFile):
os.unlink(confFile)
theConf = Config()
theConf.initConfig(fncDir, fncDir)
theConf.setLastPath("")
theConf.guiLang = "en_GB"
return theConf
@pytest.fixture(scope="function")
def dummyGUI(monkeypatch, tmpConf):
"""Create a dummy instance of novelWriter's main GUI class. """Create a dummy instance of novelWriter's main GUI class.
""" """
monkeypatch.setattr("nw.CONFIG", tmpConf)
theDummy = DummyMain() theDummy = DummyMain()
theDummy.mainConf = tmpConf theDummy.mainConf = tmpConf
return theDummy return theDummy
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def nwGUI(qtbot, fncDir): def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
"""Create an instance of the novelWriter GUI. """Create an instance of the novelWriter GUI.
""" """
monkeypatch.setattr("nw.CONFIG", fncConf)
nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir]) nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
qtbot.addWidget(nwGUI) qtbot.addWidget(nwGUI)
nwGUI.show() nwGUI.show()
+1
View File
@@ -10,6 +10,7 @@
</project> </project>
<settings> <settings>
<doBackup>False</doBackup> <doBackup>False</doBackup>
<language>en</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
+1
View File
@@ -11,6 +11,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>en</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
-15
View File
@@ -1,15 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import pstats
import os
profDir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, "prof"))
print("")
print("Profiles directory: %s" % profDir)
print("")
profMainWindows = pstats.Stats(os.path.join(profDir, "testMainWindows.prof"))
profMainWindows.sort_stats("cumtime")
profMainWindows.print_stats("nw/")
@@ -11,6 +11,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -11,6 +11,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>True</spellCheck> <spellCheck>True</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -9,6 +9,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>None</spellLang> <spellLang>None</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
@@ -11,6 +11,7 @@
</project> </project>
<settings> <settings>
<doBackup>True</doBackup> <doBackup>True</doBackup>
<language>None</language>
<spellCheck>False</spellCheck> <spellCheck>False</spellCheck>
<spellLang>en</spellLang> <spellLang>en</spellLang>
<autoOutline>True</autoOutline> <autoOutline>True</autoOutline>
+69 -76
View File
@@ -44,7 +44,6 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osDarwin is False assert tstConf.osDarwin is False
assert tstConf.osWindows is False assert tstConf.osWindows is False
assert tstConf.osUnknown is False assert tstConf.osUnknown is False
monkeypatch.undo()
# macOS # macOS
monkeypatch.setattr("sys.platform", "darwin") monkeypatch.setattr("sys.platform", "darwin")
@@ -53,7 +52,6 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osDarwin is True assert tstConf.osDarwin is True
assert tstConf.osWindows is False assert tstConf.osWindows is False
assert tstConf.osUnknown is False assert tstConf.osUnknown is False
monkeypatch.undo()
# Windows # Windows
monkeypatch.setattr("sys.platform", "win32") monkeypatch.setattr("sys.platform", "win32")
@@ -62,7 +60,6 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osDarwin is False assert tstConf.osDarwin is False
assert tstConf.osWindows is True assert tstConf.osWindows is True
assert tstConf.osUnknown is False assert tstConf.osUnknown is False
monkeypatch.undo()
# Cygwin # Cygwin
monkeypatch.setattr("sys.platform", "cygwin") monkeypatch.setattr("sys.platform", "cygwin")
@@ -71,7 +68,6 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osDarwin is False assert tstConf.osDarwin is False
assert tstConf.osWindows is True assert tstConf.osWindows is True
assert tstConf.osUnknown is False assert tstConf.osUnknown is False
monkeypatch.undo()
# Other # Other
monkeypatch.setattr("sys.platform", "some_other_os") monkeypatch.setattr("sys.platform", "some_other_os")
@@ -80,7 +76,6 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osDarwin is False assert tstConf.osDarwin is False
assert tstConf.osWindows is False assert tstConf.osWindows is False
assert tstConf.osUnknown is True assert tstConf.osUnknown is True
monkeypatch.undo()
# END Test testBaseConfig_Constructor # END Test testBaseConfig_Constructor
@@ -99,36 +94,35 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
os.unlink(confFile) os.unlink(confFile)
# Let the config class figure out the path # Let the config class figure out the path
monkeypatch.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir) with monkeypatch.context() as mp:
tstConf.verQtValue = 50600 mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir)
tstConf.initConfig() tstConf.verQtValue = 50600
assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) tstConf.initConfig()
assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
assert not os.path.isfile(confFile) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
tstConf.verQtValue = 50000 assert not os.path.isfile(confFile)
tstConf.initConfig() tstConf.verQtValue = 50000
assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle) tstConf.initConfig()
assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle) assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
assert not os.path.isfile(confFile) assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
monkeypatch.undo() assert not os.path.isfile(confFile)
# Fail to make folders # Fail to make folders
monkeypatch.setattr("os.mkdir", causeOSError) with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
tstConfDir = os.path.join(fncDir, "test_conf") tstConfDir = os.path.join(fncDir, "test_conf")
tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir) tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
assert tstConf.confPath is None assert tstConf.confPath is None
assert tstConf.dataPath == tmpDir assert tstConf.dataPath == tmpDir
assert not os.path.isfile(confFile) assert not os.path.isfile(confFile)
tstDataDir = os.path.join(fncDir, "test_data") tstDataDir = os.path.join(fncDir, "test_data")
tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir) tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
assert tstConf.confPath == tmpDir assert tstConf.confPath == tmpDir
assert tstConf.dataPath is None assert tstConf.dataPath is None
assert os.path.isfile(confFile) assert os.path.isfile(confFile)
os.unlink(confFile) os.unlink(confFile)
monkeypatch.undo()
# Test load/save with no path # Test load/save with no path
tstConf.confPath = None tstConf.confPath = None
@@ -137,35 +131,34 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
# Run again and set the paths directly and correctly # Run again and set the paths directly and correctly
# This should create a config file as well # This should create a config file as well
monkeypatch.setattr("os.path.expanduser", lambda *args: "") with monkeypatch.context() as mp:
tstConf.spellTool = nwConst.SP_INTERNAL mp.setattr("os.path.expanduser", lambda *args: "")
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) tstConf.spellTool = nwConst.SP_INTERNAL
assert tstConf.confPath == tmpDir tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
assert tstConf.dataPath == tmpDir assert tstConf.confPath == tmpDir
assert os.path.isfile(confFile) assert tstConf.dataPath == tmpDir
assert os.path.isfile(confFile)
copyfile(confFile, testFile) copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, [2, 9, 10]) assert cmpFiles(testFile, compFile, [2, 9, 10])
monkeypatch.undo()
# Load and save with OSError # Load and save with OSError
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert not tstConf.loadConfig() assert not tstConf.loadConfig()
assert tstConf.hasError is True assert tstConf.hasError is True
assert tstConf.errData != [] assert tstConf.errData != []
assert tstConf.getErrData().startswith("Could not") assert tstConf.getErrData().startswith("Could not")
assert tstConf.hasError is False assert tstConf.hasError is False
assert tstConf.errData == [] assert tstConf.errData == []
assert not tstConf.saveConfig() assert not tstConf.saveConfig()
assert tstConf.hasError is True assert tstConf.hasError is True
assert tstConf.errData != [] assert tstConf.errData != []
assert tstConf.getErrData().startswith("Could not") assert tstConf.getErrData().startswith("Could not")
assert tstConf.hasError is False assert tstConf.hasError is False
assert tstConf.errData == [] assert tstConf.errData == []
monkeypatch.undo()
assert tstConf.loadConfig() assert tstConf.loadConfig()
assert tstConf.saveConfig() assert tstConf.saveConfig()
@@ -204,7 +197,7 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
tstApp = DummyApp() tstApp = DummyApp()
tstConf.initLocalisation(tstApp) tstConf.initLocalisation(tstApp)
theList = tstConf.listLanguages() theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English")] assert theList == [("en_GB", "British English")]
copyfile(confFile, testFile) copyfile(confFile, testFile)
@@ -233,9 +226,9 @@ def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
} }
# Fail to Save # Fail to Save
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert not tmpConf.saveRecentCache() mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert not tmpConf.saveRecentCache()
# Save Proper # Save Proper
cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE) cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE)
@@ -244,11 +237,11 @@ def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
assert os.path.isfile(cacheFile) assert os.path.isfile(cacheFile)
# Fail to Load # Fail to Load
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
tmpConf.recentProj = {} mp.setattr("builtins.open", causeOSError)
assert not tmpConf.loadRecentCache() tmpConf.recentProj = {}
assert tmpConf.recentProj == {} assert not tmpConf.loadRecentCache()
monkeypatch.undo() assert tmpConf.recentProj == {}
# Load Proper # Load Proper
tmpConf.recentProj = {} tmpConf.recentProj = {}
@@ -555,19 +548,19 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
tmpConf._checkOptionalPackages() tmpConf._checkOptionalPackages()
assert tmpConf.hasEnchant is True assert tmpConf.hasEnchant is True
monkeypatch.setitem(sys.modules, "enchant", None) with monkeypatch.context() as mp:
tmpConf._checkOptionalPackages() mp.setitem(sys.modules, "enchant", None)
assert tmpConf.hasEnchant is False tmpConf._checkOptionalPackages()
monkeypatch.undo() assert tmpConf.hasEnchant is False
monkeypatch.setattr("shutil.which", lambda *args: "dummy") with monkeypatch.context() as mp:
tmpConf._checkOptionalPackages() mp.setattr("shutil.which", lambda *args: "dummy")
assert tmpConf.hasAssistant is True tmpConf._checkOptionalPackages()
monkeypatch.undo() assert tmpConf.hasAssistant is True
monkeypatch.setattr("shutil.which", lambda *args: None) with monkeypatch.context() as mp:
tmpConf._checkOptionalPackages() mp.setattr("shutil.which", lambda *args: None)
assert tmpConf.hasAssistant is False tmpConf._checkOptionalPackages()
monkeypatch.undo() assert tmpConf.hasAssistant is False
# END Test testBaseConfig_Internal # END Test testBaseConfig_Internal
+33 -33
View File
@@ -48,22 +48,22 @@ def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..." assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..."
# Valid Error Message # Valid Error Message
monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3") with monkeypatch.context() as mp:
nwErr.setMessage(Exception, "Fine Error", None) mp.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3")
theMessage = nwErr.msgBody.toPlainText() nwErr.setMessage(Exception, "Fine Error", None)
assert theMessage theMessage = nwErr.msgBody.toPlainText()
assert "Fine Error" in theMessage assert theMessage
assert "Exception" in theMessage assert "Fine Error" in theMessage
assert "(1.2.3)" in theMessage assert "Exception" in theMessage
monkeypatch.undo() assert "(1.2.3)" in theMessage
# No kernel version retrieved # No kernel version retrieved
monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException) with monkeypatch.context() as mp:
nwErr.setMessage(Exception, "Almost Fine Error", None) mp.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException)
theMessage = nwErr.msgBody.toPlainText() nwErr.setMessage(Exception, "Almost Fine Error", None)
assert theMessage theMessage = nwErr.msgBody.toPlainText()
assert "(Unknown)" in theMessage assert theMessage
monkeypatch.undo() assert "(Unknown)" in theMessage
nwErr._doClose() nwErr._doClose()
nwErr.close() nwErr.close()
@@ -84,31 +84,31 @@ def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir):
qtbot.waitForWindowShown(nwGUI) qtbot.waitForWindowShown(nwGUI)
# Normal shutdown # Normal shutdown
monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) with monkeypatch.context() as mp:
monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
exceptionHandler(Exception, "Error Message", None) mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
monkeypatch.undo() exceptionHandler(Exception, "Error Message", None)
# Should not crash when no GUI is found # Should not crash when no GUI is found
monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) with monkeypatch.context() as mp:
monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: []) mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
exceptionHandler(Exception, "Error Message", None) mp.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: [])
monkeypatch.undo() exceptionHandler(Exception, "Error Message", None)
# Should handle qApp failing # Should handle qApp failing
monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) with monkeypatch.context() as mp:
monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException) mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
exceptionHandler(Exception, "Error Message", None) mp.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException)
monkeypatch.undo() exceptionHandler(Exception, "Error Message", None)
# Should handle failing to close main GUI # Should handle failing to close main GUI
monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None) with monkeypatch.context() as mp:
monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None) mp.setattr(NWErrorMessage, "exec_", lambda *args: None)
monkeypatch.setattr(nwGUI, "closeMain", causeException) mp.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
exceptionHandler(Exception, "Error Message", None) mp.setattr(nwGUI, "closeMain", causeException)
monkeypatch.undo() exceptionHandler(Exception, "Error Message", None)
nwGUI.closeMain() nwGUI.closeMain()
-6
View File
@@ -62,8 +62,6 @@ def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
assert ex.value.code == 0 assert ex.value.code == 0
monkeypatch.undo()
# END Test testBaseInit_Launch # END Test testBaseInit_Launch
@pytest.mark.base @pytest.mark.base
@@ -136,8 +134,6 @@ def testBaseInit_Options(monkeypatch, tmpDir):
assert nw.CONFIG.cmdOpen == "sample/" assert nw.CONFIG.cmdOpen == "sample/"
assert nwGUI.closeMain() == "closeMain" assert nwGUI.closeMain() == "closeMain"
monkeypatch.undo()
# END Test testBaseInit_Options # END Test testBaseInit_Options
@pytest.mark.base @pytest.mark.base
@@ -170,6 +166,4 @@ def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
assert "At least PyQt5" in caplog.messages[2] assert "At least PyQt5" in caplog.messages[2]
assert "lxml" in caplog.messages[3] assert "lxml" in caplog.messages[3]
monkeypatch.undo()
# END Test testBaseInit_Imports # END Test testBaseInit_Imports
+9 -9
View File
@@ -50,9 +50,9 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
def dummyOpen(*args, **kwargs): def dummyOpen(*args, **kwargs):
raise OSError raise OSError
monkeypatch.setattr("builtins.open", dummyOpen) with monkeypatch.context() as mp:
assert theDoc.openDocument(sHandle) is None mp.setattr("builtins.open", dummyOpen)
monkeypatch.undo() assert theDoc.openDocument(sHandle) is None
# Load the text # Load the text
assert theDoc.openDocument(sHandle) == "### New Scene\n\n" assert theDoc.openDocument(sHandle) == "### New Scene\n\n"
@@ -95,9 +95,9 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
assert inFile.read() == theText assert inFile.read() == theText
# Cause open() to fail while saving # Cause open() to fail while saving
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert not theDoc.saveDocument(theText) mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert not theDoc.saveDocument(theText)
# Saving with no handle # Saving with no handle
theDoc.clearDocument() theDoc.clearDocument()
@@ -108,9 +108,9 @@ def testCoreDocument_LoadSave(monkeypatch, dummyGUI, nwMinimal):
assert os.path.isfile(docPath) assert os.path.isfile(docPath)
# Cause the delete to fail # Cause the delete to fail
monkeypatch.setattr("os.unlink", causeOSError) with monkeypatch.context() as mp:
assert not theDoc.deleteDocument(xHandle) mp.setattr("os.unlink", causeOSError)
monkeypatch.undo() assert not theDoc.deleteDocument(xHandle)
# Make the delete pass # Make the delete pass
assert theDoc.deleteDocument(xHandle) assert theDoc.deleteDocument(xHandle)
+7 -10
View File
@@ -26,6 +26,7 @@ import json
from shutil import copyfile from shutil import copyfile
from dummy import causeException
from tools import cmpFiles from tools import cmpFiles
from nw.core.project import NWProject from nw.core.project import NWProject
@@ -61,16 +62,12 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
assert not theIndex.reIndexHandle(None) assert not theIndex.reIndexHandle(None)
# Dummy exception function
def doPanic(*arg, **kwargs):
raise Exception
# Make the save fail # Make the save fail
monkeypatch.setattr(json, "dump", doPanic) with monkeypatch.context() as mp:
assert not theIndex.saveIndex() mp.setattr(json, "dump", causeException)
assert not theIndex.saveIndex()
# Make the save pass # Make the save pass
monkeypatch.undo()
assert theIndex.saveIndex() assert theIndex.saveIndex()
# Take a copy of the index # Take a copy of the index
@@ -100,11 +97,11 @@ def testCoreIndex_LoadSave(monkeypatch, nwLipsum, dummyGUI, outDir, refDir):
assert not theIndex._textCounts assert not theIndex._textCounts
# Make the load fail # Make the load fail
monkeypatch.setattr(json, "load", doPanic) with monkeypatch.context() as mp:
assert not theIndex.loadIndex() mp.setattr(json, "load", causeException)
assert not theIndex.loadIndex()
# Make the load pass # Make the load pass
monkeypatch.undo()
assert theIndex.loadIndex() assert theIndex.loadIndex()
assert str(theIndex._tagIndex) == tagIndex assert str(theIndex._tagIndex) == tagIndex
+4 -4
View File
@@ -64,10 +64,10 @@ def testCoreOptions_LoadSave(monkeypatch, dummyGUI, tmpDir):
assert theProject.projMeta == tmpDir assert theProject.projMeta == tmpDir
# Cause open() to fail # Cause open() to fail
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert not theOpts.loadSettings() mp.setattr("builtins.open", causeOSError)
assert not theOpts.saveSettings() assert not theOpts.loadSettings()
monkeypatch.undo() assert not theOpts.saveSettings()
# Load proper # Load proper
assert theOpts.loadSettings() assert theOpts.loadSettings()
+75 -77
View File
@@ -180,7 +180,6 @@ def testCoreProject_NewSampleA(fncDir, tmpConf, dummyGUI, tmpDir):
} }
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
theProject.mainConf = tmpConf
# Sample set, but no path # Sample set, but no path
assert not theProject.newProject({"popSample": True}) assert not theProject.newProject({"popSample": True})
@@ -229,7 +228,6 @@ def testCoreProject_NewSampleB(monkeypatch, fncDir, tmpConf, dummyGUI, tmpDir):
} }
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
theProject.mainConf = tmpConf
# Make sure we do not pick up the nw/assets/sample.zip file # Make sure we do not pick up the nw/assets/sample.zip file
tmpConf.assetPath = tmpDir tmpConf.assetPath = tmpDir
@@ -330,9 +328,9 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
os.rename(wName, rName) os.rename(wName, rName)
# Fail on folder structure check # Fail on folder structure check
monkeypatch.setattr("os.mkdir", causeOSError) with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is False mp.setattr("os.mkdir", causeOSError)
monkeypatch.undo() assert theProject.openProject(nwMinimal) is False
# Fail on lock file # Fail on lock file
theProject.setProjectPath(nwMinimal) theProject.setProjectPath(nwMinimal)
@@ -340,9 +338,9 @@ def testCoreProject_Open(monkeypatch, nwMinimal, dummyGUI):
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(nwMinimal) is False
# Fail to read lockfile (which still opens the project) # Fail to read lockfile (which still opens the project)
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is True mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert theProject.openProject(nwMinimal) is True
assert theProject.closeProject() assert theProject.closeProject()
# Force open with lockfile # Force open with lockfile
@@ -452,14 +450,14 @@ def testCoreProject_Save(monkeypatch, nwMinimal, dummyGUI, refDir):
assert theProject.openProject(nwMinimal) assert theProject.openProject(nwMinimal)
# Fail on folder structure check # Fail on folder structure check
monkeypatch.setattr("os.path.isdir", lambda *args: False) with monkeypatch.context() as mp:
assert theProject.saveProject() is False mp.setattr("os.path.isdir", lambda *args: False)
monkeypatch.undo() assert theProject.saveProject() is False
# Fail on open file # Fail on open file
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert theProject.saveProject() is False mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert theProject.saveProject() is False
# Successful save # Successful save
saveCount = theProject.saveCount saveCount = theProject.saveCount
@@ -501,30 +499,30 @@ def testCoreProject_LockFile(monkeypatch, fncDir, dummyGUI):
theProject.mainConf.kernelVer = "1.0" theProject.mainConf.kernelVer = "1.0"
# Block open # Block open
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert theProject._writeLockFile() is False mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert theProject._writeLockFile() is False
# Write lock file # Write lock file
monkeypatch.setattr("nw.core.project.time", lambda: 123.4) with monkeypatch.context() as mp:
assert theProject._writeLockFile() is True mp.setattr("nw.core.project.time", lambda: 123.4)
monkeypatch.undo() assert theProject._writeLockFile() is True
assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n" assert readFile(lockFile) == "TestHost\nTestOS\n1.0\n123\n"
# Block open # Block open
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert theProject._readLockFile() == ["ERROR"] mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert theProject._readLockFile() == ["ERROR"]
# Read lock file # Read lock file
assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"] assert theProject._readLockFile() == ["TestHost", "TestOS", "1.0", "123"]
# Block unlink # Block unlink
monkeypatch.setattr("os.unlink", causeOSError) with monkeypatch.context() as mp:
assert os.path.isfile(lockFile) mp.setattr("os.unlink", causeOSError)
assert theProject._clearLockFile() is False assert os.path.isfile(lockFile)
assert os.path.isfile(lockFile) assert theProject._clearLockFile() is False
monkeypatch.undo() assert os.path.isfile(lockFile)
# Clear file # Clear file
assert os.path.isfile(lockFile) assert os.path.isfile(lockFile)
@@ -554,9 +552,9 @@ def testCoreProject_Helpers(monkeypatch, fncDir, dummyGUI):
theProject.projPath = fncDir theProject.projPath = fncDir
# Block user's home folder # Block user's home folder
monkeypatch.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir) with monkeypatch.context() as mp:
assert theProject.ensureFolderStructure() is False mp.setattr("os.path.expanduser", lambda *args, **kwargs: fncDir)
monkeypatch.undo() assert theProject.ensureFolderStructure() is False
# Create a file to block meta folder # Create a file to block meta folder
metaDir = os.path.join(fncDir, "meta") metaDir = os.path.join(fncDir, "meta")
@@ -702,9 +700,9 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
# Edit Time # Edit Time
theProject.editTime = 1234 theProject.editTime = 1234
theProject.projOpened = 1600000000 theProject.projOpened = 1600000000
monkeypatch.setattr("nw.core.project.time", lambda: 1600005600) with monkeypatch.context() as mp:
assert theProject.getCurrentEditTime() == 6834 mp.setattr("nw.core.project.time", lambda: 1600005600)
monkeypatch.undo() assert theProject.getCurrentEditTime() == 6834
# Trash folder # Trash folder
# Should create on first call, and just returned on later calls # Should create on first call, and just returned on later calls
@@ -735,11 +733,11 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
# Spell language # Spell language
theProject.projChanged = False theProject.projChanged = False
assert theProject.setSpellLang(None) assert theProject.setSpellLang(None)
assert theProject.projLang is None assert theProject.projSpell is None
assert theProject.setSpellLang("None") assert theProject.setSpellLang("None")
assert theProject.projLang is None assert theProject.projSpell is None
assert theProject.setSpellLang("en_GB") assert theProject.setSpellLang("en_GB")
assert theProject.projLang == "en_GB" assert theProject.projSpell == "en_GB"
assert theProject.projChanged assert theProject.projChanged
# Automatic outline update # Automatic outline update
@@ -839,14 +837,14 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
assert theProject.getSessionWordCount() == 100 assert theProject.getSessionWordCount() == 100
# Session stats # Session stats
monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False) with monkeypatch.context() as mp:
assert not theProject._appendSessionStats(idleTime=0) mp.setattr("os.path.isdir", lambda *args, **kwargs: False)
monkeypatch.undo() assert not theProject._appendSessionStats(idleTime=0)
# Block open # Block open
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert not theProject._appendSessionStats(idleTime=0) mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert not theProject._appendSessionStats(idleTime=0)
# Write entry # Write entry
assert theProject.projMeta == os.path.join(nwMinimal, "meta") assert theProject.projMeta == os.path.join(nwMinimal, "meta")
@@ -856,9 +854,9 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
theProject.novelWCount = 200 theProject.novelWCount = 200
theProject.notesWCount = 100 theProject.notesWCount = 100
monkeypatch.setattr("nw.core.project.time", lambda: 1600005600) with monkeypatch.context() as mp:
assert theProject._appendSessionStats(idleTime=99) mp.setattr("nw.core.project.time", lambda: 1600005600)
monkeypatch.undo() assert theProject._appendSessionStats(idleTime=99)
assert readFile(statsFile) == ( assert readFile(statsFile) == (
"# Offset 100\n" "# Offset 100\n"
@@ -1076,9 +1074,9 @@ def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
writeFile(tstFile, "dummy") writeFile(tstFile, "dummy")
assert os.path.isfile(tstFile) assert os.path.isfile(tstFile)
monkeypatch.setattr("os.unlink", causeOSError) with monkeypatch.context() as mp:
assert not theProject._deprecatedFiles() mp.setattr("os.unlink", causeOSError)
monkeypatch.undo() assert not theProject._deprecatedFiles()
assert theProject._deprecatedFiles() assert theProject._deprecatedFiles()
assert not os.path.isfile(tstFile) assert not os.path.isfile(tstFile)
@@ -1101,18 +1099,18 @@ def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
assert os.path.isdir(errItem) assert os.path.isdir(errItem)
# This causes a failure to create the 'junk' folder # This causes a failure to create the 'junk' folder
monkeypatch.setattr("os.mkdir", causeOSError) with monkeypatch.context() as mp:
errList = [] mp.setattr("os.mkdir", causeOSError)
errList = theProject._legacyDataFolder(tstData, errList) errList = []
assert len(errList) > 0 errList = theProject._legacyDataFolder(tstData, errList)
monkeypatch.undo() assert len(errList) > 0
# This causes a failure to move 'stuff' to 'junk' # This causes a failure to move 'stuff' to 'junk'
monkeypatch.setattr("os.rename", causeOSError) with monkeypatch.context() as mp:
errList = [] mp.setattr("os.rename", causeOSError)
errList = theProject._legacyDataFolder(tstData, errList) errList = []
assert len(errList) > 0 errList = theProject._legacyDataFolder(tstData, errList)
monkeypatch.undo() assert len(errList) > 0
# This should be successful # This should be successful
errList = [] errList = []
@@ -1138,18 +1136,18 @@ def testCoreProject_LegacyData(monkeypatch, dummyGUI, fncDir):
writeFile(tstDoc3b, "dummy") writeFile(tstDoc3b, "dummy")
# Make the above fail # Make the above fail
monkeypatch.setattr("os.rename", causeOSError) with monkeypatch.context() as mp:
monkeypatch.setattr("os.unlink", causeOSError) mp.setattr("os.rename", causeOSError)
errList = [] mp.setattr("os.unlink", causeOSError)
errList = theProject._legacyDataFolder(tstData, errList) errList = []
assert len(errList) > 0 errList = theProject._legacyDataFolder(tstData, errList)
assert os.path.isfile(tstDoc1m) assert len(errList) > 0
assert os.path.isfile(tstDoc1b) assert os.path.isfile(tstDoc1m)
assert os.path.isfile(tstDoc2m) assert os.path.isfile(tstDoc1b)
assert os.path.isfile(tstDoc2b) assert os.path.isfile(tstDoc2m)
assert os.path.isfile(tstDoc3m) assert os.path.isfile(tstDoc2b)
assert os.path.isfile(tstDoc3b) assert os.path.isfile(tstDoc3m)
monkeypatch.undo() assert os.path.isfile(tstDoc3b)
# And succeed ... # And succeed ...
errList = [] errList = []
@@ -1203,14 +1201,14 @@ def testCoreProject_Backup(monkeypatch, dummyGUI, nwMinimal, tmpDir):
theProject.mainConf.backupPath = tmpDir theProject.mainConf.backupPath = tmpDir
# Can't make folder # Can't make folder
monkeypatch.setattr("os.mkdir", causeOSError) with monkeypatch.context() as mp:
assert not theProject.zipIt(doNotify=False) mp.setattr("os.mkdir", causeOSError)
monkeypatch.undo() assert not theProject.zipIt(doNotify=False)
# Can't write archive # Can't write archive
monkeypatch.setattr("shutil.make_archive", causeOSError) with monkeypatch.context() as mp:
assert not theProject.zipIt(doNotify=False) mp.setattr("shutil.make_archive", causeOSError)
monkeypatch.undo() assert not theProject.zipIt(doNotify=False)
# Test correct settings # Test correct settings
assert theProject.zipIt(doNotify=True) assert theProject.zipIt(doNotify=True)
+26 -30
View File
@@ -30,14 +30,13 @@ from tools import readFile, writeFile
from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple from nw.core.spellcheck import NWSpellCheck, NWSpellEnchant, NWSpellSimple
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf): def testCoreSpell_Super(monkeypatch, tmpDir):
"""Test the spell checker super class """Test the spell checker super class
""" """
wList = os.path.join(tmpDir, "wordlist.txt") wList = os.path.join(tmpDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n") writeFile(wList, "a_word\nb_word\nc_word\n")
spChk = NWSpellCheck() spChk = NWSpellCheck()
spChk.mainConf = tmpConf
# Check that dummy functions return results that reflects that spell # Check that dummy functions return results that reflects that spell
# checking is effectively disabled # checking is effectively disabled
@@ -49,16 +48,16 @@ def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf):
# Add a word to the user's dictionary # Add a word to the user's dictionary
assert spChk._readProjectDictionary("dummy") is False assert spChk._readProjectDictionary("dummy") is False
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert spChk._readProjectDictionary(wList) is False mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert spChk._readProjectDictionary(wList) is False
assert spChk._readProjectDictionary(wList) is True assert spChk._readProjectDictionary(wList) is True
assert spChk.projectDict == wList assert spChk.projectDict == wList
# Cannot write to file # Cannot write to file
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
assert spChk.addWord("d_word") is False mp.setattr("builtins.open", causeOSError)
monkeypatch.undo() assert spChk.addWord("d_word") is False
assert readFile(wList) == "a_word\nb_word\nc_word\n" assert readFile(wList) == "a_word\nb_word\nc_word\n"
# First time, OK # First time, OK
@@ -72,28 +71,26 @@ def testCoreSpell_Super(monkeypatch, tmpDir, tmpConf):
# END Test testCoreSpell_Super # END Test testCoreSpell_Super
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf): def testCoreSpell_Enchant(monkeypatch, tmpDir):
"""Test the pyenchant spell checker """Test the pyenchant spell checker
""" """
wList = os.path.join(tmpDir, "wordlist.txt") wList = os.path.join(tmpDir, "wordlist.txt")
writeFile(wList, "a_word\nb_word\nc_word\n") writeFile(wList, "a_word\nb_word\nc_word\n")
# Block the enchant package (and trigger the dummy class) # Block the enchant package (and trigger the dummy class)
monkeypatch.setitem(sys.modules, "enchant", None) with monkeypatch.context() as mp:
spChk = NWSpellEnchant() mp.setitem(sys.modules, "enchant", None)
spChk = NWSpellEnchant()
spChk.setLanguage("en", wList) spChk.setLanguage("en", wList)
assert spChk.setLanguage("", "") is None assert spChk.setLanguage("", "") is None
assert spChk.checkWord("") assert spChk.checkWord("")
assert spChk.suggestWords("") == [] assert spChk.suggestWords("") == []
assert spChk.listDictionaries() == [] assert spChk.listDictionaries() == []
assert spChk.describeDict() == ("", "") assert spChk.describeDict() == ("", "")
monkeypatch.undo()
# Load the proper enchant package # Load the proper enchant package
spChk = NWSpellEnchant() spChk = NWSpellEnchant()
spChk.mainConf = tmpConf
spChk.setLanguage("en", wList) spChk.setLanguage("en", wList)
assert spChk.checkWord("a_word") assert spChk.checkWord("a_word")
@@ -118,7 +115,7 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir, tmpConf):
# END Test testCoreSpell_Enchant # END Test testCoreSpell_Enchant
@pytest.mark.core @pytest.mark.core
def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf): def testCoreSpell_Simple(monkeypatch, tmpDir):
"""Test the fallback simple spell checker """Test the fallback simple spell checker
""" """
wList = os.path.join(tmpDir, "wordlist.txt") wList = os.path.join(tmpDir, "wordlist.txt")
@@ -127,15 +124,14 @@ def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf):
writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n") writeFile(wDict, "# Comment\ne_word\nf_word\ng_word\n")
spChk = NWSpellSimple() spChk = NWSpellSimple()
spChk.mainConf = tmpConf
spChk.mainConf.dictPath = tmpDir spChk.mainConf.dictPath = tmpDir
# Load dictionary, but fail # Load dictionary, but fail
monkeypatch.setattr("builtins.open", causeOSError) with monkeypatch.context() as mp:
spChk.setLanguage("en", wList) mp.setattr("builtins.open", causeOSError)
assert spChk.spellLanguage is None spChk.setLanguage("en", wList)
assert spChk.theWords == set(spChk.projDict) assert spChk.spellLanguage is None
monkeypatch.undo() assert spChk.theWords == set(spChk.projDict)
# Load dictionary properly # Load dictionary properly
spChk.setLanguage("en", wList) spChk.setLanguage("en", wList)
@@ -163,9 +159,9 @@ def testCoreSpell_Simple(monkeypatch, tmpDir, tmpConf):
assert "d_word" in wSuggest assert "d_word" in wSuggest
# Break the matching # Break the matching
monkeypatch.setattr("difflib.get_close_matches", lambda *args, **kwargs: [""]) with monkeypatch.context() as mp:
assert spChk.suggestWords("word") == [] mp.setattr("difflib.get_close_matches", lambda *args, **kwargs: [""])
monkeypatch.undo() assert spChk.suggestWords("word") == []
# Capitalisation # Capitalisation
wSuggest = spChk.suggestWords("D_wrod") wSuggest = spChk.suggestWords("D_wrod")
+13 -13
View File
@@ -112,13 +112,13 @@ def testCoreToken_Setters(dummyGUI):
# END Test testCoreToken_Setters # END Test testCoreToken_Setters
@pytest.mark.core @pytest.mark.core
def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI, tmpConf): def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI):
"""Test handling files and text in the Tokenizer class. """Test handling files and text in the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
theProject.mainConf = tmpConf
theProject.projTree.setSeed(42) theProject.projTree.setSeed(42)
theProject.loadProjectLocalisation("en") theProject.projLang = "en"
theProject._loadProjectLocalisation()
theToken = Tokenizer(theProject, dummyGUI) theToken = Tokenizer(theProject, dummyGUI)
theToken.setKeepMarkdown(True) theToken.setKeepMarkdown(True)
@@ -157,13 +157,13 @@ def testCoreToken_TextOps(monkeypatch, nwMinimal, dummyGUI, tmpConf):
assert theToken.setText(sHandle) is True assert theToken.setText(sHandle) is True
assert theToken.theText == docText assert theToken.theText == docText
monkeypatch.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100) with monkeypatch.context() as mp:
assert theToken.setText(sHandle, docText) is True mp.setattr("nw.constants.nwConst.MAX_DOCSIZE", 100)
assert theToken.theText == ( assert theToken.setText(sHandle, docText) is True
"# ERROR\n\n" assert theToken.theText == (
"Document 'New Scene' is too big (0.00 MB). Skipping.\n\n" "# ERROR\n\n"
) "Document 'New Scene' is too big (0.00 MB). Skipping.\n\n"
monkeypatch.undo() )
assert theToken.setText(sHandle, docText) is True assert theToken.setText(sHandle, docText) is True
assert theToken.theText == docText assert theToken.theText == docText
@@ -411,12 +411,12 @@ def testCoreToken_Tokenize(dummyGUI):
# END Test testCoreToken_Tokenize # END Test testCoreToken_Tokenize
@pytest.mark.core @pytest.mark.core
def testCoreToken_Headers(dummyGUI, tmpConf): def testCoreToken_Headers(dummyGUI):
"""Test the header and page parser of the Tokenizer class. """Test the header and page parser of the Tokenizer class.
""" """
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
theProject.mainConf = tmpConf theProject.projLang = "en"
theProject.loadProjectLocalisation("en") theProject._loadProjectLocalisation()
theToken = Tokenizer(theProject, dummyGUI) theToken = Tokenizer(theProject, dummyGUI)
# Nothing # Nothing
+1 -4
View File
@@ -20,7 +20,6 @@ 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/>.
""" """
import nw
import pytest import pytest
from lxml import etree from lxml import etree
@@ -45,11 +44,9 @@ def xmlToText(xElem):
return rTxt return rTxt
@pytest.mark.core @pytest.mark.core
def testCoreToOdt_Convert(tmpConf, dummyGUI): def testCoreToOdt_Convert(dummyGUI):
"""Test the converter of the ToHtml class. """Test the converter of the ToHtml class.
""" """
nw.CONFIG = tmpConf
theProject = NWProject(dummyGUI) theProject = NWProject(dummyGUI)
dummyGUI.theIndex = NWIndex(theProject, dummyGUI) dummyGUI.theIndex = NWIndex(theProject, dummyGUI)
theDoc = ToOdt(theProject, dummyGUI, isFlat=True) theDoc = ToOdt(theProject, dummyGUI, isFlat=True)
-2
View File
@@ -410,8 +410,6 @@ def testCoreTree_MakeHandles(monkeypatch, dummyGUI):
theTree._projTree[tHandle] = None theTree._projTree[tHandle] = None
assert tHandle == "a79acf4c634a7" assert tHandle == "a79acf4c634a7"
monkeypatch.undo()
# END Test testCoreTree_MakeHandles # END Test testCoreTree_MakeHandles
@pytest.mark.core @pytest.mark.core
+3 -3
View File
@@ -534,9 +534,9 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
# Faulty Keyword Inserts # Faulty Keyword Inserts
assert not nwGUI.docEditor.insertKeyWord("blabla") assert not nwGUI.docEditor.insertKeyWord("blabla")
monkeypatch.setattr(QTextBlock, "isValid", lambda *args, **kwards: False) with monkeypatch.context() as mp:
assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY) mp.setattr(QTextBlock, "isValid", lambda *args, **kwards: False)
monkeypatch.undo() assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY)
nwGUI.docEditor.clear() nwGUI.docEditor.clear()
-1
View File
@@ -110,6 +110,5 @@ def testGuiProjDetails_Dialog(qtbot, monkeypatch, nwGUI, nwLipsum):
# Clean Up # Clean Up
projDet._doClose() projDet._doClose()
nwGUI.closeMain() nwGUI.closeMain()
monkeypatch.undo()
# END Test testGuiProjDetails_Dialog # END Test testGuiProjDetails_Dialog
+9 -10
View File
@@ -62,18 +62,17 @@ def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
# Close project, but call with invalid path # Close project, but call with invalid path
assert nwGUI.closeProject() assert nwGUI.closeProject()
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None) with monkeypatch.context() as mp:
assert not nwGUI.newProject() mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: None)
assert not nwGUI.newProject()
# Now, with an empty dictionary # Now, with an empty dictionary
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {}) mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: {})
assert not nwGUI.newProject() assert not nwGUI.newProject()
# Now, with a non-empty folder # Now, with a non-empty folder
monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal}) mp.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal})
assert not nwGUI.newProject() assert not nwGUI.newProject()
monkeypatch.undo()
## ##
# Test the Wizard # Test the Wizard
-2
View File
@@ -428,6 +428,4 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert nwGUI.closeProject() assert nwGUI.closeProject()
qtbot.wait(stepDelay) qtbot.wait(stepDelay)
monkeypatch.undo()
# END Test testGuiWritingStats_Dialog # END Test testGuiWritingStats_Dialog