Move recent cache out of config, and update all tests

This commit is contained in:
Veronica Berglyd Olsen
2022-11-09 20:54:13 +01:00
parent 8721d53ab2
commit cb755ba820
5 changed files with 313 additions and 305 deletions
+114 -92
View File
@@ -74,8 +74,8 @@ class Config:
self._appPath = self._appRoot self._appPath = self._appRoot
# Runtime Settings and Variables # Runtime Settings and Variables
self.hasError = False # True if the config class encountered an error self._hasError = False # True if the config class encountered an error
self.errData = [] # List of error messages self._errData = [] # List of error messages
self.confChanged = False # True whenever the config has chenged, false after save self.confChanged = False # True whenever the config has chenged, false after save
self.cmdOpen = None # Path from command line for project to be opened on launch self.cmdOpen = None # Path from command line for project to be opened on launch
@@ -92,6 +92,8 @@ class Config:
# User Settings # User Settings
# ============= # =============
self._recentProj = RecentProjects(self._dataPath)
# General GUI Settings # General GUI Settings
self.guiLang = self._qLocal.name() self.guiLang = self._qLocal.name()
self.guiTheme = "" # GUI theme self.guiTheme = "" # GUI theme
@@ -245,6 +247,18 @@ class Config:
return return
##
# Properties
##
@property
def hasError(self):
return self._hasError
@property
def recentProjects(self):
return self._recentProj
## ##
# Methods # Methods
## ##
@@ -282,6 +296,15 @@ class Config:
return self._lastPath return self._lastPath
return Path.home().absolute() return Path.home().absolute()
def errorText(self):
"""Compile and return error messages from the initialisation of
the Config class, and clear the error buffer.
"""
errMessage = "<br>".join(self._errData)
self._hasError = False
self._errData = []
return errMessage
## ##
# Config Actions # Config Actions
## ##
@@ -321,12 +344,12 @@ class Config:
else: else:
self.saveConfig() self.saveConfig()
self.loadRecentCache() self._recentProj.loadCache()
self._checkOptionalPackages() self._checkOptionalPackages()
logger.debug("Config initialisation complete") logger.debug("Config initialisation complete")
return True return
def initLocalisation(self, nwApp): def initLocalisation(self, nwApp):
"""Initialise the localisation of the GUI. """Initialise the localisation of the GUI.
@@ -345,7 +368,7 @@ class Config:
qTrans = QTranslator() qTrans = QTranslator()
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_")) lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
if lngFile not in self._qtTrans: if lngFile not in self._qtTrans:
if qTrans.load(lngFile, lngPath): if qTrans.load(lngFile, str(lngPath)):
logger.debug("Loaded: %s", os.path.join(lngPath, lngFile)) logger.debug("Loaded: %s", os.path.join(lngPath, lngFile))
nwApp.installTranslator(qTrans) nwApp.installTranslator(qTrans)
self._qtTrans[lngFile] = qTrans self._qtTrans[lngFile] = qTrans
@@ -367,12 +390,12 @@ class Config:
else: else:
return [] return []
for qmFile in os.listdir(self._nwLangPath): for qmFile in Path(self._nwLangPath).iterdir():
if not os.path.isfile(os.path.join(self._nwLangPath, qmFile)): qmName = qmFile.name
if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)):
continue continue
if not qmFile.startswith(fPre) or not qmFile.endswith(fExt):
continue qmLang = qmName[len(fPre):-len(fExt)]
qmLang = qmFile[len(fPre):-len(fExt)]
qmName = QLocale(qmLang).nativeLanguageName().title() qmName = QLocale(qmLang).nativeLanguageName().title()
if qmLang and qmName and qmLang != "en_GB": if qmLang and qmName and qmLang != "en_GB":
langList[qmLang] = qmName langList[qmLang] = qmName
@@ -392,9 +415,9 @@ class Config:
except Exception as exc: except Exception as exc:
logger.error("Could not load config file") logger.error("Could not load config file")
logException() logException()
self.hasError = True self._hasError = True
self.errData.append("Could not load config file") self._errData.append("Could not load config file")
self.errData.append(formatException(exc)) self._errData.append(formatException(exc))
return False return False
# Main # Main
@@ -606,80 +629,13 @@ class Config:
except Exception as exc: except Exception as exc:
logger.error("Could not save config file") logger.error("Could not save config file")
logException() logException()
self.hasError = True self._hasError = True
self.errData.append("Could not save config file") self._errData.append("Could not save config file")
self.errData.append(formatException(exc)) self._errData.append(formatException(exc))
return False return False
return True return True
def loadRecentCache(self):
"""Load the cache file for recent projects.
"""
self.recentProj = {}
cacheFile = self._dataPath / nwFiles.RECENT_FILE
if not os.path.isfile(cacheFile):
return True
try:
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
theData = json.load(inFile)
for projPath, theEntry in theData.items():
self.recentProj[projPath] = {
"title": theEntry.get("title", ""),
"time": theEntry.get("time", 0),
"words": theEntry.get("words", 0),
}
except Exception as exc:
self.hasError = True
self.errData.append("Could not load recent project cache")
self.errData.append(formatException(exc))
return False
return True
def saveRecentCache(self):
"""Save the cache dictionary of recent projects.
"""
cacheFile = self._dataPath / nwFiles.RECENT_FILE
cacheTemp = cacheFile.with_suffix(".tmp")
try:
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
json.dump(self.recentProj, outFile, indent=2)
cacheTemp.replace(cacheFile)
except Exception as exc:
self.hasError = True
self.errData.append("Could not save recent project cache")
self.errData.append(formatException(exc))
return False
return True
def updateRecentCache(self, projPath, projTitle, wordCount, saveTime):
"""Add or update recent cache information on a given project.
"""
self.recentProj[os.path.abspath(projPath)] = {
"title": projTitle,
"time": int(saveTime),
"words": int(wordCount),
}
return True
def removeFromRecentCache(self, thePath):
"""Trying to remove a path from the recent projects cache.
"""
if thePath in self.recentProj:
del self.recentProj[thePath]
logger.debug("Removed recent: %s", thePath)
self.saveRecentCache()
else:
logger.error("Unknown recent: %s", thePath)
return False
return True
## ##
# Setters # Setters
## ##
@@ -828,15 +784,6 @@ class Config:
def getTabWidth(self): def getTabWidth(self):
return self.pxInt(max(self.tabWidth, 0)) return self.pxInt(max(self.tabWidth, 0))
def getErrData(self):
"""Compile and return error messages from the initialisation of
the Config class, and clear the error buffer.
"""
errMessage = "<br>".join(self.errData)
self.hasError = False
self.errData = []
return errMessage
## ##
# Internal Functions # Internal Functions
## ##
@@ -872,3 +819,78 @@ class Config:
return return
# END Class Config # END Class Config
class RecentProjects:
def __init__(self, dataPath):
self._dataPath = dataPath
self._data = {}
return
def loadCache(self):
"""Load the cache file for recent projects.
"""
self._data = {}
cacheFile = self._dataPath / nwFiles.RECENT_FILE
if not cacheFile.is_file():
return True
try:
with open(cacheFile, mode="r", encoding="utf-8") as inFile:
theData = json.load(inFile)
for projPath, theEntry in theData.items():
self._data[projPath] = {
"title": theEntry.get("title", ""),
"words": theEntry.get("words", 0),
"time": theEntry.get("time", 0),
}
except Exception:
logger.error("Could not load recent project cache")
logException()
return False
return True
def saveCache(self):
"""Save the cache dictionary of recent projects.
"""
cacheFile = self._dataPath / nwFiles.RECENT_FILE
cacheTemp = cacheFile.with_suffix(".tmp")
try:
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
json.dump(self._data, outFile, indent=2)
cacheTemp.replace(cacheFile)
except Exception:
logger.error("Could not save recent project cache")
logException()
return False
return True
def listEntries(self):
"""List all items in the cache.
"""
return [(k, e["title"], e["words"], e["time"]) for k, e in self._data.items()]
def update(self, projPath, projTitle, wordCount, saveTime):
"""Add or update recent cache information on a given project.
"""
self._data[str(projPath)] = {
"title": projTitle,
"words": int(wordCount),
"time": int(saveTime),
}
self.saveCache()
return
def remove(self, projPath):
"""Try to remove a path from the recent projects cache.
"""
if self._data.pop(str(projPath), None) is not None:
logger.debug("Removed recent: %s", projPath)
self.saveCache()
return
# END Class RecentProjects
+2 -4
View File
@@ -354,10 +354,9 @@ class NWProject(QObject):
self._loadProjectLocalisation() self._loadProjectLocalisation()
# Update recent projects # Update recent projects
self.mainConf.updateRecentCache( self.mainConf.recentProjects.update(
self._storage.storagePath, self._data.name, sum(self._data.initCounts), time() self._storage.storagePath, self._data.name, sum(self._data.initCounts), time()
) )
self.mainConf.saveRecentCache()
# Check the project tree consistency # Check the project tree consistency
for tItem in self._tree: for tItem in self._tree:
@@ -424,10 +423,9 @@ class NWProject(QObject):
self._storage.runPostSaveTasks(autoSave=autoSave) self._storage.runPostSaveTasks(autoSave=autoSave)
# Update recent projects # Update recent projects
self.mainConf.updateRecentCache( self.mainConf.recentProjects.update(
self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime
) )
self.mainConf.saveRecentCache()
self._storage.writeLockFile() self._storage.writeLockFile()
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name)) self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self._data.name))
+10 -16
View File
@@ -229,7 +229,7 @@ class GuiProjectLoad(QDialog):
).format(projName) ).format(projName)
) )
if msgYes: if msgYes:
self.mainConf.removeFromRecentCache( self.mainConf.recentProjects.remove(
selList[0].data(self.C_NAME, Qt.UserRole) selList[0].data(self.C_NAME, Qt.UserRole)
) )
self._populateList() self._populateList()
@@ -264,23 +264,17 @@ class GuiProjectLoad(QDialog):
def _populateList(self): def _populateList(self):
"""Populate the list box with recent project data. """Populate the list box with recent project data.
""" """
dataList = []
for projPath in self.mainConf.recentProj:
theEntry = self.mainConf.recentProj[projPath]
theTitle = theEntry.get("title", "")
theTime = theEntry.get("time", 0)
theWords = theEntry.get("words", 0)
dataList.append([theTitle, theTime, theWords, projPath])
self.listBox.clear() self.listBox.clear()
sortList = sorted(dataList, key=lambda x: x[1], reverse=True) dataList = self.mainConf.recentProjects.listEntries()
for theTitle, theTime, theWords, projPath in sortList: sortList = sorted(dataList, key=lambda x: x[3], reverse=True)
nwxIcon = self.mainGui.mainTheme.getIcon("proj_nwx")
for path, title, words, time in sortList:
newItem = QTreeWidgetItem([""]*4) newItem = QTreeWidgetItem([""]*4)
newItem.setIcon(self.C_NAME, self.mainGui.mainTheme.getIcon("proj_nwx")) newItem.setIcon(self.C_NAME, nwxIcon)
newItem.setText(self.C_NAME, theTitle) newItem.setText(self.C_NAME, title)
newItem.setData(self.C_NAME, Qt.UserRole, projPath) newItem.setData(self.C_NAME, Qt.UserRole, path)
newItem.setText(self.C_COUNT, formatInt(theWords)) newItem.setText(self.C_COUNT, formatInt(words))
newItem.setText(self.C_TIME, datetime.fromtimestamp(theTime).strftime("%x %X")) newItem.setText(self.C_TIME, datetime.fromtimestamp(time).strftime("%x %X"))
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter) newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter) newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter) newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
+1 -1
View File
@@ -1144,7 +1144,7 @@ class GuiMain(QMainWindow):
errors since it is initialised before the GUI itself. errors since it is initialised before the GUI itself.
""" """
if self.mainConf.hasError: if self.mainConf.hasError:
self.makeAlert(self.mainConf.getErrData(), nwAlert.ERROR) self.makeAlert(self.mainConf.errorText(), nwAlert.ERROR)
return True return True
return False return False
+186 -192
View File
@@ -19,16 +19,16 @@ 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 os
import sys import sys
import pytest import pytest
from shutil import copyfile from shutil import copyfile
from pathlib import Path
from mock import causeOSError, MockApp from mock import causeOSError, MockApp
from tools import cmpFiles, writeFile from tools import cmpFiles, writeFile
from novelwriter.config import Config from novelwriter.config import Config, RecentProjects
from novelwriter.constants import nwFiles from novelwriter.constants import nwFiles
@@ -37,173 +37,140 @@ def testBaseConfig_Constructor(monkeypatch):
"""Test config contructor. """Test config contructor.
""" """
# Linux # Linux
monkeypatch.setattr("sys.platform", "linux") with monkeypatch.context() as mp:
tstConf = Config() mp.setattr("sys.platform", "linux")
assert tstConf.osLinux is True tstConf = Config()
assert tstConf.osDarwin is False assert tstConf.osLinux is True
assert tstConf.osWindows is False assert tstConf.osDarwin is False
assert tstConf.osUnknown is False assert tstConf.osWindows is False
assert tstConf.osUnknown is False
# macOS # macOS
monkeypatch.setattr("sys.platform", "darwin") with monkeypatch.context() as mp:
tstConf = Config() mp.setattr("sys.platform", "darwin")
assert tstConf.osLinux is False tstConf = Config()
assert tstConf.osDarwin is True assert tstConf.osLinux is False
assert tstConf.osWindows is False assert tstConf.osDarwin is True
assert tstConf.osUnknown is False assert tstConf.osWindows is False
assert tstConf.osUnknown is False
# Windows # Windows
monkeypatch.setattr("sys.platform", "win32") with monkeypatch.context() as mp:
tstConf = Config() mp.setattr("sys.platform", "win32")
assert tstConf.osLinux is False tstConf = Config()
assert tstConf.osDarwin is False assert tstConf.osLinux is False
assert tstConf.osWindows is True assert tstConf.osDarwin is False
assert tstConf.osUnknown is False assert tstConf.osWindows is True
assert tstConf.osUnknown is False
# Cygwin # Cygwin
monkeypatch.setattr("sys.platform", "cygwin") with monkeypatch.context() as mp:
tstConf = Config() mp.setattr("sys.platform", "cygwin")
assert tstConf.osLinux is False tstConf = Config()
assert tstConf.osDarwin is False assert tstConf.osLinux is False
assert tstConf.osWindows is True assert tstConf.osDarwin is False
assert tstConf.osUnknown is False assert tstConf.osWindows is True
assert tstConf.osUnknown is False
# Other # Other
monkeypatch.setattr("sys.platform", "some_other_os") with monkeypatch.context() as mp:
tstConf = Config() mp.setattr("sys.platform", "some_other_os")
assert tstConf.osLinux is False tstConf = Config()
assert tstConf.osDarwin is False assert tstConf.osLinux is False
assert tstConf.osWindows is False assert tstConf.osDarwin is False
assert tstConf.osUnknown is True assert tstConf.osWindows is False
assert tstConf.osUnknown is True
# App is single file
with monkeypatch.context() as mp:
mp.setattr("pathlib.Path.is_file", lambda *a: True)
tstConf = Config()
assert tstConf._appPath == tstConf._appRoot
# END Test testBaseConfig_Constructor # END Test testBaseConfig_Constructor
@pytest.mark.base @pytest.mark.base
@pytest.mark.skip def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
"""Test config intialisation. """Test config intialisation.
""" """
tstConf = Config() tstConf = Config()
confFile = os.path.join(tmpDir, "novelwriter.conf") confFile = fncPath / nwFiles.CONF_FILE
testFile = os.path.join(outDir, "baseConfig_novelwriter.conf") testFile = tstPaths.outDir / "baseConfig_novelwriter.conf"
compFile = os.path.join(refDir, "baseConfig_novelwriter.conf") compFile = tstPaths.refDir / "baseConfig_novelwriter.conf"
# Make sure we don't have any old conf file # Make sure we don't have any old conf file
if os.path.isfile(confFile): if confFile.is_file():
os.unlink(confFile) confFile.unlink()
# Let the config class figure out the path # Running init against a new oath should write a new config file
with monkeypatch.context() as mp: tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *a: fncDir) assert tstConf._confPath == fncPath
tstConf.initConfig() assert tstConf._dataPath == fncPath
assert tstConf._confPath == os.path.join(fncDir, tstConf.appHandle) assert confFile.exists()
assert tstConf._dataPath == os.path.join(fncDir, tstConf.appHandle)
assert not os.path.isfile(confFile)
# Fail to make folders # Check that we have a default file
with monkeypatch.context() as mp: copyfile(confFile, testFile)
mp.setattr("os.mkdir", causeOSError) ignore = ("timestamp", "lastnotes", "guilang", "lastpath")
assert cmpFiles(testFile, compFile, ignoreStart=ignore)
tstConf.errorText() # This clears the error cache
tstConfDir = os.path.join(fncDir, "test_conf") # Block saving the file
tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
assert tstConf._confPath is None
assert tstConf._dataPath == tmpDir
assert not os.path.isfile(confFile)
tstDataDir = os.path.join(fncDir, "test_data")
tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
assert tstConf._confPath == tmpDir
assert tstConf._dataPath is None
assert os.path.isfile(confFile)
os.unlink(confFile)
# Test load/save with no path
tstConf._confPath = None
assert tstConf.loadConfig() is False
assert tstConf.saveConfig() is False
# Run again and set the paths directly and correctly
# This should create a config file as well
with monkeypatch.context() as mp:
mp.setattr("os.path.expanduser", lambda *a: "")
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
assert tstConf._confPath == tmpDir
assert tstConf._dataPath == tmpDir
assert os.path.isfile(confFile)
copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# Load and save with OSError
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("builtins.open", causeOSError)
assert tstConf.saveConfig() is False
assert not tstConf.loadConfig()
assert tstConf.hasError is True assert tstConf.hasError is True
assert tstConf.errData != [] assert tstConf.errorText().startswith("Could not save config file")
assert tstConf.getErrData().startswith("Could not")
assert tstConf.hasError is False
assert tstConf.errData == []
assert not tstConf.saveConfig() # Block loading the file
assert tstConf.hasError is True
assert tstConf.errData != []
assert tstConf.getErrData().startswith("Could not")
assert tstConf.hasError is False
assert tstConf.errData == []
# Check handling of novelWriter as a package
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) mp.setattr("builtins.open", causeOSError)
assert tstConf._confPath == tmpDir assert tstConf.loadConfig() is False
assert tstConf._dataPath == tmpDir assert tstConf.hasError is True
appRoot = tstConf._appRoot assert tstConf.errorText().startswith("Could not load config file")
mp.setattr("os.path.isfile", lambda *a: True) # Change a few settings, save, reset, and reload
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir) tstConf.guiTheme = "foo"
assert tstConf._confPath == tmpDir tstConf.guiSyntax = "bar"
assert tstConf._dataPath == tmpDir
assert tstConf._appRoot == os.path.dirname(appRoot)
assert tstConf._appPath == os.path.dirname(appRoot)
assert tstConf.loadConfig() is True
assert tstConf.saveConfig() is True assert tstConf.saveConfig() is True
# Test Correcting Quote Settings newConf = Config()
origDbl = tstConf.fmtDoubleQuotes newConf.initConfig(confPath=fncPath, dataPath=fncPath)
origSng = tstConf.fmtSingleQuotes assert newConf.guiTheme == "foo"
orDoDbl = tstConf.doReplaceDQuote assert newConf.guiSyntax == "bar"
orDoSng = tstConf.doReplaceSQuote
# Test Correcting Quote Settings
tstConf.fmtDoubleQuotes = ["\"", "\""] tstConf.fmtDoubleQuotes = ["\"", "\""]
tstConf.fmtSingleQuotes = ["'", "'"] tstConf.fmtSingleQuotes = ["'", "'"]
tstConf.doReplaceDQuote = True tstConf.doReplaceDQuote = True
tstConf.doReplaceSQuote = True tstConf.doReplaceSQuote = True
assert tstConf.saveConfig() is True assert tstConf.saveConfig() is True
assert tstConf.loadConfig() is True assert newConf.loadConfig() is True
assert tstConf.doReplaceDQuote is False assert newConf.doReplaceDQuote is False
assert tstConf.doReplaceSQuote is False assert newConf.doReplaceSQuote is False
tstConf.fmtDoubleQuotes = origDbl # END Test testBaseConfig_InitLoadSave
tstConf.fmtSingleQuotes = origSng
tstConf.doReplaceDQuote = orDoDbl
tstConf.doReplaceSQuote = orDoSng @pytest.mark.base
assert tstConf.saveConfig() is True def testBaseConfig_Localisation(fncPath, tstPaths):
"""Test localisation.
"""
tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
# Localisation # Localisation
# ============ # ============
i18nDir = os.path.join(fncDir, "i18n") i18nDir = fncPath / "i18n"
os.mkdir(i18nDir) i18nDir.mkdir()
os.mkdir(os.path.join(i18nDir, "stuff"))
tstConf._nwLangPath = i18nDir tstConf._nwLangPath = i18nDir
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm")) copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm")
writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "") writeFile(i18nDir / "nw_en_GB.ts", "")
writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "") writeFile(i18nDir / "nw_abcd.qm", "")
tstApp = MockApp() tstApp = MockApp()
tstConf.initLocalisation(tstApp) tstConf.initLocalisation(tstApp)
@@ -217,82 +184,55 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert theList == [] assert theList == []
# Add Language # Add Language
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_fr.qm")) copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_fr.qm")
writeFile(os.path.join(i18nDir, "nw_fr.ts"), "") writeFile(i18nDir / "nw_fr.ts", "")
theList = tstConf.listLanguages(tstConf.LANG_NW) theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English"), ("fr", "Français")] assert theList == [("en_GB", "British English"), ("fr", "Français")]
copyfile(confFile, testFile) # END Test testBaseConfig_Localisation
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# END Test testBaseConfig_Init
@pytest.mark.base @pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir): def testBaseConfig_Methods(tmpConf, tmpPath):
"""Test recent cache file. """Check class methods.
""" """
# Add a couple of values # Data Path
pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE) assert tmpConf.dataPath() == tmpPath
pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE) assert tmpConf.dataPath("stuff") == tmpPath / "stuff"
assert tmpConf.updateRecentCache(pathOne, "Proj One", 100, 1600002000)
assert tmpConf.updateRecentCache(pathTwo, "Proj Two", 200, 1600005600)
assert tmpConf.recentProj == {
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
}
# Fail to Save # Assets Path
with monkeypatch.context() as mp: appPath = tmpConf._appPath
mp.setattr("builtins.open", causeOSError) assert tmpConf.assetPath() == appPath / "assets"
assert not tmpConf.saveRecentCache() assert tmpConf.assetPath("stuff") == appPath / "assets" / "stuff"
# Save Proper # Last Path
cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE) assert tmpConf.lastPath() == tmpPath
assert tmpConf.saveRecentCache()
assert tmpConf.saveRecentCache()
assert os.path.isfile(cacheFile)
# Fail to Load tmpStuff = tmpPath / "stuff"
with monkeypatch.context() as mp: tmpStuff.mkdir()
mp.setattr("builtins.open", causeOSError) tmpConf.setLastPath(tmpStuff)
tmpConf.recentProj = {} assert tmpConf.lastPath() == tmpStuff
assert not tmpConf.loadRecentCache()
assert tmpConf.recentProj == {}
# Load Proper fileStuff = tmpStuff / "more_stuff.txt"
tmpConf.recentProj = {} fileStuff.write_text("Stuff")
assert tmpConf.loadRecentCache() tmpConf.setLastPath(fileStuff)
assert tmpConf.recentProj == { assert tmpConf.lastPath() == tmpStuff
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
}
# Remove Non-Existent Entry fileStuff.unlink()
assert not tmpConf.removeFromRecentCache("stuff") tmpStuff.rmdir()
assert tmpConf.recentProj == { assert tmpConf.lastPath() == Path.home().absolute()
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
}
# Remove Second Entry # Recent Projects
assert tmpConf.removeFromRecentCache(pathTwo) assert isinstance(tmpConf.recentProjects, RecentProjects)
assert tmpConf.recentProj == {
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
}
# END Test testBaseConfig_RecentCache # END Test testBaseConfig_Methods
@pytest.mark.base @pytest.mark.base
def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir): def testBaseConfig_SettersGetters(tmpConf):
"""Set various sizes and positions """Set various sizes and positions
""" """
confFile = os.path.join(tmpDir, "novelwriter.conf")
testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")
# GUI Scaling # GUI Scaling
# =========== # ===========
@@ -439,17 +379,6 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
tmpConf.setViewSynopsis(True) tmpConf.setViewSynopsis(True)
assert tmpConf.viewSynopsis is True assert tmpConf.viewSynopsis is True
# Check Final File
# ================
assert tmpConf.confChanged is True
assert tmpConf.saveConfig() is True
assert tmpConf.confChanged is False
copyfile(confFile, testFile)
ignore = ("timestamp", "lastnotes", "guilang", "lastpath")
assert cmpFiles(testFile, compFile, ignoreStart=ignore)
# END Test testBaseConfig_SettersGetters # END Test testBaseConfig_SettersGetters
@@ -479,3 +408,68 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
assert tmpConf.hasEnchant is False assert tmpConf.hasEnchant is False
# END Test testBaseConfig_Internal # END Test testBaseConfig_Internal
@pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, fncPath):
"""Test recent cache file.
"""
cacheFile = fncPath / nwFiles.RECENT_FILE
recent = RecentProjects(fncPath)
# Load when there is no file should pass, but load nothing
assert not cacheFile.exists()
assert recent.loadCache() is True
assert recent.listEntries() == []
# Add a couple of values
pathOne = fncPath / "projPathOne" / nwFiles.PROJ_FILE
pathTwo = fncPath / "projPathTwo" / nwFiles.PROJ_FILE
recent.update(pathOne, "Proj One", 100, 1600002000)
recent.update(pathTwo, "Proj Two", 200, 1600005600)
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
assert cacheFile.exists()
cacheFile.unlink()
assert not cacheFile.exists()
# Fail to Save
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert recent.saveCache() is False
assert not cacheFile.exists()
# Save Proper
assert recent.saveCache() is True
assert cacheFile.exists()
# Fail to Load
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert recent.loadCache() is False
assert recent.listEntries() == []
# Load Proper
assert recent.loadCache() is True
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
# Remove Non-Existent Entry
recent.remove("stuff")
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
(str(pathTwo), "Proj Two", 200, 1600005600),
]
# Remove Second Entry
recent.remove(pathTwo)
assert recent.listEntries() == [
(str(pathOne), "Proj One", 100, 1600002000),
]
# END Test testBaseConfig_RecentCache