Move recent cache out of config, and update all tests
This commit is contained in:
+114
-92
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user