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
# Runtime Settings and Variables
self.hasError = False # True if the config class encountered an error
self.errData = [] # List of error messages
self._hasError = False # True if the config class encountered an error
self._errData = [] # List of error messages
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
@@ -92,6 +92,8 @@ class Config:
# User Settings
# =============
self._recentProj = RecentProjects(self._dataPath)
# General GUI Settings
self.guiLang = self._qLocal.name()
self.guiTheme = "" # GUI theme
@@ -245,6 +247,18 @@ class Config:
return
##
# Properties
##
@property
def hasError(self):
return self._hasError
@property
def recentProjects(self):
return self._recentProj
##
# Methods
##
@@ -282,6 +296,15 @@ class Config:
return self._lastPath
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
##
@@ -321,12 +344,12 @@ class Config:
else:
self.saveConfig()
self.loadRecentCache()
self._recentProj.loadCache()
self._checkOptionalPackages()
logger.debug("Config initialisation complete")
return True
return
def initLocalisation(self, nwApp):
"""Initialise the localisation of the GUI.
@@ -345,7 +368,7 @@ class Config:
qTrans = QTranslator()
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
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))
nwApp.installTranslator(qTrans)
self._qtTrans[lngFile] = qTrans
@@ -367,12 +390,12 @@ class Config:
else:
return []
for qmFile in os.listdir(self._nwLangPath):
if not os.path.isfile(os.path.join(self._nwLangPath, qmFile)):
for qmFile in Path(self._nwLangPath).iterdir():
qmName = qmFile.name
if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)):
continue
if not qmFile.startswith(fPre) or not qmFile.endswith(fExt):
continue
qmLang = qmFile[len(fPre):-len(fExt)]
qmLang = qmName[len(fPre):-len(fExt)]
qmName = QLocale(qmLang).nativeLanguageName().title()
if qmLang and qmName and qmLang != "en_GB":
langList[qmLang] = qmName
@@ -392,9 +415,9 @@ class Config:
except Exception as exc:
logger.error("Could not load config file")
logException()
self.hasError = True
self.errData.append("Could not load config file")
self.errData.append(formatException(exc))
self._hasError = True
self._errData.append("Could not load config file")
self._errData.append(formatException(exc))
return False
# Main
@@ -606,80 +629,13 @@ class Config:
except Exception as exc:
logger.error("Could not save config file")
logException()
self.hasError = True
self.errData.append("Could not save config file")
self.errData.append(formatException(exc))
self._hasError = True
self._errData.append("Could not save config file")
self._errData.append(formatException(exc))
return False
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
##
@@ -828,15 +784,6 @@ class Config:
def getTabWidth(self):
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
##
@@ -872,3 +819,78 @@ class Config:
return
# 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()
# Update recent projects
self.mainConf.updateRecentCache(
self.mainConf.recentProjects.update(
self._storage.storagePath, self._data.name, sum(self._data.initCounts), time()
)
self.mainConf.saveRecentCache()
# Check the project tree consistency
for tItem in self._tree:
@@ -424,10 +423,9 @@ class NWProject(QObject):
self._storage.runPostSaveTasks(autoSave=autoSave)
# Update recent projects
self.mainConf.updateRecentCache(
self.mainConf.recentProjects.update(
self._storage.storagePath, self._data.name, sum(self._data.currCounts), saveTime
)
self.mainConf.saveRecentCache()
self._storage.writeLockFile()
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)
)
if msgYes:
self.mainConf.removeFromRecentCache(
self.mainConf.recentProjects.remove(
selList[0].data(self.C_NAME, Qt.UserRole)
)
self._populateList()
@@ -264,23 +264,17 @@ class GuiProjectLoad(QDialog):
def _populateList(self):
"""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()
sortList = sorted(dataList, key=lambda x: x[1], reverse=True)
for theTitle, theTime, theWords, projPath in sortList:
dataList = self.mainConf.recentProjects.listEntries()
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.setIcon(self.C_NAME, self.mainGui.mainTheme.getIcon("proj_nwx"))
newItem.setText(self.C_NAME, theTitle)
newItem.setData(self.C_NAME, Qt.UserRole, projPath)
newItem.setText(self.C_COUNT, formatInt(theWords))
newItem.setText(self.C_TIME, datetime.fromtimestamp(theTime).strftime("%x %X"))
newItem.setIcon(self.C_NAME, nwxIcon)
newItem.setText(self.C_NAME, title)
newItem.setData(self.C_NAME, Qt.UserRole, path)
newItem.setText(self.C_COUNT, formatInt(words))
newItem.setText(self.C_TIME, datetime.fromtimestamp(time).strftime("%x %X"))
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setTextAlignment(self.C_COUNT, 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.
"""
if self.mainConf.hasError:
self.makeAlert(self.mainConf.getErrData(), nwAlert.ERROR)
self.makeAlert(self.mainConf.errorText(), nwAlert.ERROR)
return True
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/>.
"""
import os
import sys
import pytest
from shutil import copyfile
from pathlib import Path
from mock import causeOSError, MockApp
from tools import cmpFiles, writeFile
from novelwriter.config import Config
from novelwriter.config import Config, RecentProjects
from novelwriter.constants import nwFiles
@@ -37,173 +37,140 @@ def testBaseConfig_Constructor(monkeypatch):
"""Test config contructor.
"""
# Linux
monkeypatch.setattr("sys.platform", "linux")
tstConf = Config()
assert tstConf.osLinux is True
assert tstConf.osDarwin is False
assert tstConf.osWindows is False
assert tstConf.osUnknown is False
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "linux")
tstConf = Config()
assert tstConf.osLinux is True
assert tstConf.osDarwin is False
assert tstConf.osWindows is False
assert tstConf.osUnknown is False
# macOS
monkeypatch.setattr("sys.platform", "darwin")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is True
assert tstConf.osWindows is False
assert tstConf.osUnknown is False
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "darwin")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is True
assert tstConf.osWindows is False
assert tstConf.osUnknown is False
# Windows
monkeypatch.setattr("sys.platform", "win32")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
assert tstConf.osWindows is True
assert tstConf.osUnknown is False
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "win32")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
assert tstConf.osWindows is True
assert tstConf.osUnknown is False
# Cygwin
monkeypatch.setattr("sys.platform", "cygwin")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
assert tstConf.osWindows is True
assert tstConf.osUnknown is False
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "cygwin")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
assert tstConf.osWindows is True
assert tstConf.osUnknown is False
# Other
monkeypatch.setattr("sys.platform", "some_other_os")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
assert tstConf.osWindows is False
assert tstConf.osUnknown is True
with monkeypatch.context() as mp:
mp.setattr("sys.platform", "some_other_os")
tstConf = Config()
assert tstConf.osLinux is False
assert tstConf.osDarwin is False
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
@pytest.mark.base
@pytest.mark.skip
def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
"""Test config intialisation.
"""
tstConf = Config()
confFile = os.path.join(tmpDir, "novelwriter.conf")
testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")
confFile = fncPath / nwFiles.CONF_FILE
testFile = tstPaths.outDir / "baseConfig_novelwriter.conf"
compFile = tstPaths.refDir / "baseConfig_novelwriter.conf"
# Make sure we don't have any old conf file
if os.path.isfile(confFile):
os.unlink(confFile)
if confFile.is_file():
confFile.unlink()
# Let the config class figure out the path
with monkeypatch.context() as mp:
mp.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *a: fncDir)
tstConf.initConfig()
assert tstConf._confPath == os.path.join(fncDir, tstConf.appHandle)
assert tstConf._dataPath == os.path.join(fncDir, tstConf.appHandle)
assert not os.path.isfile(confFile)
# Running init against a new oath should write a new config file
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
assert tstConf._confPath == fncPath
assert tstConf._dataPath == fncPath
assert confFile.exists()
# Fail to make folders
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
# Check that we have a default file
copyfile(confFile, testFile)
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")
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
# Block saving the file
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert not tstConf.loadConfig()
assert tstConf.saveConfig() is False
assert tstConf.hasError is True
assert tstConf.errData != []
assert tstConf.getErrData().startswith("Could not")
assert tstConf.hasError is False
assert tstConf.errData == []
assert tstConf.errorText().startswith("Could not save config file")
assert not tstConf.saveConfig()
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
# Block loading the file
with monkeypatch.context() as mp:
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
assert tstConf._confPath == tmpDir
assert tstConf._dataPath == tmpDir
appRoot = tstConf._appRoot
mp.setattr("builtins.open", causeOSError)
assert tstConf.loadConfig() is False
assert tstConf.hasError is True
assert tstConf.errorText().startswith("Could not load config file")
mp.setattr("os.path.isfile", lambda *a: True)
tstConf.initConfig(confPath=tmpDir, dataPath=tmpDir)
assert tstConf._confPath == tmpDir
assert tstConf._dataPath == tmpDir
assert tstConf._appRoot == os.path.dirname(appRoot)
assert tstConf._appPath == os.path.dirname(appRoot)
assert tstConf.loadConfig() is True
# Change a few settings, save, reset, and reload
tstConf.guiTheme = "foo"
tstConf.guiSyntax = "bar"
assert tstConf.saveConfig() is True
# Test Correcting Quote Settings
origDbl = tstConf.fmtDoubleQuotes
origSng = tstConf.fmtSingleQuotes
orDoDbl = tstConf.doReplaceDQuote
orDoSng = tstConf.doReplaceSQuote
newConf = Config()
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
assert newConf.guiTheme == "foo"
assert newConf.guiSyntax == "bar"
# Test Correcting Quote Settings
tstConf.fmtDoubleQuotes = ["\"", "\""]
tstConf.fmtSingleQuotes = ["'", "'"]
tstConf.doReplaceDQuote = True
tstConf.doReplaceSQuote = True
assert tstConf.saveConfig() is True
assert tstConf.loadConfig() is True
assert tstConf.doReplaceDQuote is False
assert tstConf.doReplaceSQuote is False
assert newConf.loadConfig() is True
assert newConf.doReplaceDQuote is False
assert newConf.doReplaceSQuote is False
tstConf.fmtDoubleQuotes = origDbl
tstConf.fmtSingleQuotes = origSng
tstConf.doReplaceDQuote = orDoDbl
tstConf.doReplaceSQuote = orDoSng
assert tstConf.saveConfig() is True
# END Test testBaseConfig_InitLoadSave
@pytest.mark.base
def testBaseConfig_Localisation(fncPath, tstPaths):
"""Test localisation.
"""
tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
# Localisation
# ============
i18nDir = os.path.join(fncDir, "i18n")
os.mkdir(i18nDir)
os.mkdir(os.path.join(i18nDir, "stuff"))
i18nDir = fncPath / "i18n"
i18nDir.mkdir()
tstConf._nwLangPath = i18nDir
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_en_GB.qm"))
writeFile(os.path.join(i18nDir, "nw_en_GB.ts"), "")
writeFile(os.path.join(i18nDir, "nw_abcd.qm"), "")
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_en_GB.qm")
writeFile(i18nDir / "nw_en_GB.ts", "")
writeFile(i18nDir / "nw_abcd.qm", "")
tstApp = MockApp()
tstConf.initLocalisation(tstApp)
@@ -217,82 +184,55 @@ def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir, filesDir):
assert theList == []
# Add Language
copyfile(os.path.join(filesDir, "nw_en_GB.qm"), os.path.join(i18nDir, "nw_fr.qm"))
writeFile(os.path.join(i18nDir, "nw_fr.ts"), "")
copyfile(tstPaths.filesDir / "nw_en_GB.qm", i18nDir / "nw_fr.qm")
writeFile(i18nDir / "nw_fr.ts", "")
theList = tstConf.listLanguages(tstConf.LANG_NW)
assert theList == [("en_GB", "British English"), ("fr", "Français")]
copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
# END Test testBaseConfig_Init
# END Test testBaseConfig_Localisation
@pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
"""Test recent cache file.
def testBaseConfig_Methods(tmpConf, tmpPath):
"""Check class methods.
"""
# Add a couple of values
pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE)
pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE)
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},
}
# Data Path
assert tmpConf.dataPath() == tmpPath
assert tmpConf.dataPath("stuff") == tmpPath / "stuff"
# Fail to Save
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
assert not tmpConf.saveRecentCache()
# Assets Path
appPath = tmpConf._appPath
assert tmpConf.assetPath() == appPath / "assets"
assert tmpConf.assetPath("stuff") == appPath / "assets" / "stuff"
# Save Proper
cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE)
assert tmpConf.saveRecentCache()
assert tmpConf.saveRecentCache()
assert os.path.isfile(cacheFile)
# Last Path
assert tmpConf.lastPath() == tmpPath
# Fail to Load
with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError)
tmpConf.recentProj = {}
assert not tmpConf.loadRecentCache()
assert tmpConf.recentProj == {}
tmpStuff = tmpPath / "stuff"
tmpStuff.mkdir()
tmpConf.setLastPath(tmpStuff)
assert tmpConf.lastPath() == tmpStuff
# Load Proper
tmpConf.recentProj = {}
assert tmpConf.loadRecentCache()
assert tmpConf.recentProj == {
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
}
fileStuff = tmpStuff / "more_stuff.txt"
fileStuff.write_text("Stuff")
tmpConf.setLastPath(fileStuff)
assert tmpConf.lastPath() == tmpStuff
# Remove Non-Existent Entry
assert not tmpConf.removeFromRecentCache("stuff")
assert tmpConf.recentProj == {
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
}
fileStuff.unlink()
tmpStuff.rmdir()
assert tmpConf.lastPath() == Path.home().absolute()
# Remove Second Entry
assert tmpConf.removeFromRecentCache(pathTwo)
assert tmpConf.recentProj == {
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
}
# Recent Projects
assert isinstance(tmpConf.recentProjects, RecentProjects)
# END Test testBaseConfig_RecentCache
# END Test testBaseConfig_Methods
@pytest.mark.base
def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
def testBaseConfig_SettersGetters(tmpConf):
"""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
# ===========
@@ -439,17 +379,6 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
tmpConf.setViewSynopsis(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
@@ -479,3 +408,68 @@ def testBaseConfig_Internal(monkeypatch, tmpConf):
assert tmpConf.hasEnchant is False
# 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