Refactor config class, and switch to pathlib (#1228)
This commit is contained in:
@@ -27,7 +27,6 @@ import sys
|
||||
import getopt
|
||||
import logging
|
||||
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QApplication, QErrorMessage
|
||||
|
||||
from novelwriter.error import exceptionHandler, logException
|
||||
@@ -249,7 +248,6 @@ def main(sysArgs=None):
|
||||
nwApp = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
|
||||
nwApp.setApplicationName(CONFIG.appName)
|
||||
nwApp.setApplicationVersion(__version__)
|
||||
nwApp.setWindowIcon(QIcon(CONFIG.appIcon))
|
||||
nwApp.setOrganizationDomain(__domain__)
|
||||
|
||||
# Connect the exception handler before making the main GUI
|
||||
|
||||
+5
-28
@@ -23,12 +23,12 @@ 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 json
|
||||
import uuid
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from configparser import ConfigParser
|
||||
|
||||
@@ -36,7 +36,7 @@ from PyQt5.QtCore import QCoreApplication
|
||||
from PyQt5.QtWidgets import qApp
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
|
||||
from novelwriter.error import formatException, logException
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import nwConst, nwUnicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -458,20 +458,16 @@ def jsonEncode(data, n=0, nmax=0):
|
||||
def readTextFile(path):
|
||||
"""Read the content of a text file in a robust manner.
|
||||
"""
|
||||
if not os.path.isfile(path):
|
||||
path = Path(path)
|
||||
if not path.is_file():
|
||||
return ""
|
||||
|
||||
text = ""
|
||||
try:
|
||||
with open(path, mode="r", encoding="utf-8") as inFile:
|
||||
text = inFile.read()
|
||||
return path.read_text(encoding="utf-8")
|
||||
except Exception:
|
||||
logger.error("Could not read file: %s", path)
|
||||
logException()
|
||||
return ""
|
||||
|
||||
return text
|
||||
|
||||
|
||||
def makeFileNameSafe(value):
|
||||
"""Returns a filename safe string of the value.
|
||||
@@ -483,25 +479,6 @@ def makeFileNameSafe(value):
|
||||
return clean
|
||||
|
||||
|
||||
def ensureFolder(path, parent=None, errLog=None):
|
||||
"""Make sure a folder exists, and if it doesn't, create it.
|
||||
"""
|
||||
try:
|
||||
if parent:
|
||||
path = os.path.join(parent, path)
|
||||
if not os.path.isdir(path):
|
||||
os.mkdir(path)
|
||||
except Exception as exc:
|
||||
logger.error("Could not create folder: %s", path)
|
||||
logException()
|
||||
if isinstance(errLog, list):
|
||||
errLog.append(f"Could not create folder: {path}")
|
||||
errLog.append(formatException(exc))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def sha256sum(path):
|
||||
"""Make a shasum of a file using a buffer.
|
||||
Based on: https://stackoverflow.com/a/44873382/5825851
|
||||
|
||||
+256
-233
@@ -23,12 +23,12 @@ 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 json
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.Qt import PYQT_VERSION_STR
|
||||
from PyQt5.QtCore import (
|
||||
@@ -37,7 +37,7 @@ from PyQt5.QtCore import (
|
||||
)
|
||||
|
||||
from novelwriter.error import logException, formatException
|
||||
from novelwriter.common import ensureFolder, splitVersionNumber, formatTimeStamp, NWConfigParser
|
||||
from novelwriter.common import splitVersionNumber, formatTimeStamp, NWConfigParser
|
||||
from novelwriter.constants import nwFiles, nwUnicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -50,27 +50,51 @@ class Config:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
# Initialisation
|
||||
# ==============
|
||||
|
||||
# Set Application Variables
|
||||
self.appName = "novelWriter"
|
||||
self.appHandle = "novelwriter"
|
||||
|
||||
# Set Paths
|
||||
self.cmdOpen = None # Path from command line for project to be opened on launch
|
||||
self.confPath = None # Folder where the config is saved
|
||||
self.dataPath = None # Folder where app data is stored
|
||||
self.lastPath = None # The last user-selected folder (browse dialogs)
|
||||
self.appPath = None # The full path to the novelwriter package folder
|
||||
self.appRoot = None # The full path to the novelwriter root folder
|
||||
self.appIcon = None # The full path to the novelwriter icon file
|
||||
self.assetPath = None # The full path to the novelwriter/assets folder
|
||||
self.pdfDocs = None # The location of the PDF manual, if it exists
|
||||
confRoot = Path(QStandardPaths.writableLocation(QStandardPaths.ConfigLocation))
|
||||
dataRoot = Path(QStandardPaths.writableLocation(QStandardPaths.AppDataLocation))
|
||||
|
||||
self._confPath = confRoot.absolute() / self.appHandle # The user config location
|
||||
self._dataPath = dataRoot.absolute() / self.appHandle # The user data location
|
||||
self._lastPath = Path.home().absolute() # The user's last used path
|
||||
|
||||
self._appPath = Path(__file__).parent.absolute()
|
||||
self._appRoot = self._appPath.parent
|
||||
if self._appRoot.is_file():
|
||||
# novelWriter is packaged as a single file
|
||||
self._appRoot = self._appRoot.parent
|
||||
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
|
||||
|
||||
# General
|
||||
# Localisation Info
|
||||
self._qLocal = QLocale.system()
|
||||
self._qtTrans = {}
|
||||
self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
||||
self._nwLangPath = str(self._appPath / "assets" / "i18n")
|
||||
|
||||
# PDF Manual
|
||||
pdfDocs = self._appPath / "assets" / "manual.pdf"
|
||||
self.pdfDocs = pdfDocs if pdfDocs.is_file() else None
|
||||
|
||||
# User Settings
|
||||
# =============
|
||||
|
||||
self._recentProj = RecentProjects(self._dataPath)
|
||||
|
||||
# General GUI Settings
|
||||
self.guiLang = self._qLocal.name()
|
||||
self.guiTheme = "" # GUI theme
|
||||
self.guiSyntax = "" # Syntax theme
|
||||
self.guiFont = "" # Defaults to system default font
|
||||
@@ -81,14 +105,7 @@ class Config:
|
||||
self.setDefaultGuiTheme()
|
||||
self.setDefaultSyntaxTheme()
|
||||
|
||||
# Localisation
|
||||
self.qLocal = QLocale.system()
|
||||
self.guiLang = self.qLocal.name()
|
||||
self.qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
||||
self.nwLangPath = None
|
||||
self.qtTrans = {}
|
||||
|
||||
# Sizes
|
||||
# Size Settings
|
||||
self.winGeometry = [1200, 650]
|
||||
self.prefGeometry = [700, 615]
|
||||
self.projColWidth = [200, 60, 140]
|
||||
@@ -98,16 +115,16 @@ class Config:
|
||||
self.outlnPanePos = [500, 150]
|
||||
self.isFullScreen = False
|
||||
|
||||
# Features
|
||||
# Feature Settings
|
||||
self.hideVScroll = False # Hide vertical scroll bars on main widgets
|
||||
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
|
||||
self.emphLabels = True # Add emphasis to H1 and H2 item labels
|
||||
|
||||
# Project
|
||||
# Project Settings
|
||||
self.autoSaveProj = 60 # Interval for auto-saving project in seconds
|
||||
self.autoSaveDoc = 30 # Interval for auto-saving document in seconds
|
||||
|
||||
# Text Editor
|
||||
# Text Editor Settings
|
||||
self.textFont = None # Editor font
|
||||
self.textSize = 12 # Editor font size
|
||||
self.textWidth = 700 # Editor text width
|
||||
@@ -146,7 +163,7 @@ class Config:
|
||||
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
|
||||
self.userIdleTime = 300 # Time of inactivity to consider user idle
|
||||
|
||||
# User-Selected Symbols
|
||||
# User-Selected Symbol Settings
|
||||
self.fmtApostrophe = nwUnicode.U_RSQUO
|
||||
self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO]
|
||||
self.fmtDoubleQuotes = [nwUnicode.U_LDQUO, nwUnicode.U_RDQUO]
|
||||
@@ -154,8 +171,8 @@ class Config:
|
||||
self.fmtPadAfter = ""
|
||||
self.fmtPadThin = False
|
||||
|
||||
# Spell Checking
|
||||
self.spellLanguage = None
|
||||
# Spell Checking Settings
|
||||
self.spellLanguage = "en"
|
||||
|
||||
# Search Bar Switches
|
||||
self.searchCase = False
|
||||
@@ -165,8 +182,8 @@ class Config:
|
||||
self.searchNextFile = False
|
||||
self.searchMatchCap = False
|
||||
|
||||
# Backup
|
||||
self.backupPath = ""
|
||||
# Backup Settings
|
||||
self._backupPath = None
|
||||
self.backupOnClose = False
|
||||
self.askBeforeBackup = True
|
||||
|
||||
@@ -175,6 +192,9 @@ class Config:
|
||||
self.viewComments = True # Comments are shown in the viewer
|
||||
self.viewSynopsis = True # Synopsis is shown in the viewer
|
||||
|
||||
# System and App Information
|
||||
# ==========================
|
||||
|
||||
# Check Qt5 Versions
|
||||
verQt = splitVersionNumber(QT_VERSION_STR)
|
||||
self.verQtString = QT_VERSION_STR
|
||||
@@ -226,6 +246,18 @@ class Config:
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def hasError(self):
|
||||
return self._hasError
|
||||
|
||||
@property
|
||||
def recentProjects(self):
|
||||
return self._recentProj
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
@@ -242,6 +274,44 @@ class Config:
|
||||
"""
|
||||
return int(theSize/self.guiScale)
|
||||
|
||||
def dataPath(self, target=None):
|
||||
"""Return a path in the data folder.
|
||||
"""
|
||||
if isinstance(target, str):
|
||||
return self._dataPath / target
|
||||
return self._dataPath
|
||||
|
||||
def assetPath(self, target=None):
|
||||
"""Return a path in the assets folder.
|
||||
"""
|
||||
if isinstance(target, str):
|
||||
return self._appPath / "assets" / target
|
||||
return self._appPath / "assets"
|
||||
|
||||
def lastPath(self):
|
||||
"""Return the last path used by the user, but ensure it exists.
|
||||
"""
|
||||
if self._lastPath.is_dir():
|
||||
return self._lastPath
|
||||
return Path.home().absolute()
|
||||
|
||||
def backupPath(self):
|
||||
"""Return the backup path.
|
||||
"""
|
||||
if isinstance(self._backupPath, Path):
|
||||
if self._backupPath.is_dir():
|
||||
return self._backupPath
|
||||
return None
|
||||
|
||||
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
|
||||
##
|
||||
@@ -251,109 +321,64 @@ class Config:
|
||||
and dataPath is mainly intended for the test suite.
|
||||
"""
|
||||
logger.debug("Initialising Config ...")
|
||||
if confPath is None:
|
||||
confRoot = QStandardPaths.writableLocation(QStandardPaths.ConfigLocation)
|
||||
self.confPath = os.path.join(os.path.abspath(confRoot), self.appHandle)
|
||||
else:
|
||||
if isinstance(confPath, (str, Path)):
|
||||
logger.info("Setting config from alternative path: %s", confPath)
|
||||
self.confPath = confPath
|
||||
|
||||
if dataPath is None:
|
||||
dataRoot = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
|
||||
self.dataPath = os.path.join(os.path.abspath(dataRoot), self.appHandle)
|
||||
else:
|
||||
self._confPath = Path(confPath)
|
||||
if isinstance(dataPath, (str, Path)):
|
||||
logger.info("Setting data path from alternative path: %s", dataPath)
|
||||
self.dataPath = dataPath
|
||||
self._dataPath = Path(dataPath)
|
||||
|
||||
logger.debug("Config path: %s", self.confPath)
|
||||
logger.debug("Data path: %s", self.dataPath)
|
||||
logger.debug("Config Path: %s", self._confPath)
|
||||
logger.debug("Data Path: %s", self._dataPath)
|
||||
logger.debug("App Root: %s", self._appRoot)
|
||||
logger.debug("App Path: %s", self._appPath)
|
||||
logger.debug("Last Path: %s", self._lastPath)
|
||||
logger.debug("PDF Manual: %s", self.pdfDocs)
|
||||
|
||||
self.lastPath = os.path.expanduser("~")
|
||||
self.appPath = getattr(sys, "_MEIPASS", os.path.abspath(os.path.dirname(__file__)))
|
||||
self.appRoot = os.path.abspath(os.path.join(self.appPath, os.path.pardir))
|
||||
|
||||
if os.path.isfile(self.appRoot):
|
||||
# novelWriter is packaged as a single file, so the app and
|
||||
# root paths are the same, and equal to the folder that
|
||||
# contains the single executable.
|
||||
self.appRoot = os.path.dirname(self.appRoot)
|
||||
self.appPath = self.appRoot
|
||||
|
||||
# Assets
|
||||
self.assetPath = os.path.join(self.appPath, "assets")
|
||||
self.appIcon = os.path.join(self.assetPath, "icons", "novelwriter.svg")
|
||||
|
||||
# Internationalisation
|
||||
self.nwLangPath = os.path.join(self.assetPath, "i18n")
|
||||
|
||||
logger.debug("Assets: %s", self.assetPath)
|
||||
logger.debug("App path: %s", self.appPath)
|
||||
logger.debug("Last path: %s", self.lastPath)
|
||||
|
||||
# If the config and data folders don't not exist, create them
|
||||
# If the config and data folders don't exist, create them
|
||||
# This assumes that the os config and data folders exist
|
||||
if not ensureFolder(self.confPath, errLog=self.errData):
|
||||
self.hasError = True
|
||||
self.confPath = None
|
||||
self._confPath.mkdir(exist_ok=True)
|
||||
self._dataPath.mkdir(exist_ok=True)
|
||||
|
||||
if not ensureFolder(self.dataPath, errLog=self.errData):
|
||||
self.hasError = True
|
||||
self.dataPath = None
|
||||
# Also create the syntax and themes folders if possible
|
||||
if self._dataPath.is_dir():
|
||||
(self._dataPath / "syntax").mkdir(exist_ok=True)
|
||||
(self._dataPath / "themes").mkdir(exist_ok=True)
|
||||
|
||||
# We don't error on these failing since they are not essential
|
||||
if self.dataPath is not None:
|
||||
ensureFolder("syntax", parent=self.dataPath)
|
||||
ensureFolder("themes", parent=self.dataPath)
|
||||
# Check if config file exists, and load it. If not, we save defaults
|
||||
if (self._confPath / nwFiles.CONF_FILE).is_file():
|
||||
self.loadConfig()
|
||||
else:
|
||||
self.saveConfig()
|
||||
|
||||
# Check if config file exists
|
||||
if self.confPath is not None:
|
||||
if os.path.isfile(os.path.join(self.confPath, nwFiles.CONF_FILE)):
|
||||
# If it exists, load it
|
||||
self.loadConfig()
|
||||
else:
|
||||
# If it does not exist, save a copy of the default values
|
||||
self.saveConfig()
|
||||
|
||||
# Load recent projects cache
|
||||
self.loadRecentCache()
|
||||
|
||||
# Check the availability of optional packages
|
||||
self._recentProj.loadCache()
|
||||
self._checkOptionalPackages()
|
||||
|
||||
if not self.spellLanguage:
|
||||
self.spellLanguage = "en"
|
||||
|
||||
# Look for a PDF version of the manual
|
||||
pdfDocs = os.path.join(self.assetPath, "manual.pdf")
|
||||
if os.path.isfile(pdfDocs):
|
||||
logger.debug("Found manual: %s", pdfDocs)
|
||||
self.pdfDocs = pdfDocs
|
||||
|
||||
logger.debug("Config initialisation complete")
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
def initLocalisation(self, nwApp):
|
||||
"""Initialise the localisation of the GUI.
|
||||
"""
|
||||
self.qLocal = QLocale(self.guiLang)
|
||||
QLocale.setDefault(self.qLocal)
|
||||
self.qtTrans = {}
|
||||
self._qLocal = QLocale(self.guiLang)
|
||||
QLocale.setDefault(self._qLocal)
|
||||
self._qtTrans = {}
|
||||
|
||||
langList = [
|
||||
(self.qtLangPath, "qtbase"), # Qt 5.x
|
||||
(self.nwLangPath, "qtbase"), # Alternative Qt 5.x
|
||||
(self.nwLangPath, "nw"), # novelWriter
|
||||
(self._qtLangPath, "qtbase"), # Qt 5.x
|
||||
(self._nwLangPath, "qtbase"), # Alternative Qt 5.x
|
||||
(self._nwLangPath, "nw"), # novelWriter
|
||||
]
|
||||
for lngPath, lngBase in langList:
|
||||
for lngCode in self.qLocal.uiLanguages():
|
||||
for lngCode in self._qLocal.uiLanguages():
|
||||
qTrans = QTranslator()
|
||||
lngFile = "%s_%s" % (lngBase, lngCode.replace("-", "_"))
|
||||
if lngFile not in self.qtTrans:
|
||||
if qTrans.load(lngFile, lngPath):
|
||||
logger.debug("Loaded: %s", os.path.join(lngPath, lngFile))
|
||||
if lngFile not in self._qtTrans:
|
||||
if qTrans.load(lngFile, str(lngPath)):
|
||||
logger.debug("Loaded: %s/%s", lngPath, lngFile)
|
||||
nwApp.installTranslator(qTrans)
|
||||
self.qtTrans[lngFile] = qTrans
|
||||
self._qtTrans[lngFile] = qTrans
|
||||
|
||||
return
|
||||
|
||||
@@ -372,12 +397,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
|
||||
@@ -388,20 +413,18 @@ class Config:
|
||||
"""Load preferences from file and replace default settings.
|
||||
"""
|
||||
logger.debug("Loading config file")
|
||||
if self.confPath is None:
|
||||
return False
|
||||
|
||||
theConf = NWConfigParser()
|
||||
cnfPath = os.path.join(self.confPath, nwFiles.CONF_FILE)
|
||||
cnfPath = self._confPath / nwFiles.CONF_FILE
|
||||
try:
|
||||
with open(cnfPath, mode="r", encoding="utf-8") as inFile:
|
||||
theConf.read_file(inFile)
|
||||
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
|
||||
@@ -473,9 +496,10 @@ class Config:
|
||||
|
||||
# Backup
|
||||
cnfSec = "Backup"
|
||||
self.backupPath = theConf.rdStr(cnfSec, "backuppath", self.backupPath)
|
||||
backupPath = theConf.rdStr(cnfSec, "backuppath", None)
|
||||
self.backupOnClose = theConf.rdBool(cnfSec, "backuponclose", self.backupOnClose)
|
||||
self.askBeforeBackup = theConf.rdBool(cnfSec, "askbeforebackup", self.askBeforeBackup)
|
||||
self.setBackupPath(backupPath)
|
||||
|
||||
# State
|
||||
cnfSec = "State"
|
||||
@@ -491,7 +515,7 @@ class Config:
|
||||
|
||||
# Path
|
||||
cnfSec = "Path"
|
||||
self.lastPath = theConf.rdStr(cnfSec, "lastpath", self.lastPath)
|
||||
self._lastPath = Path(theConf.rdStr(cnfSec, "lastpath", self._lastPath))
|
||||
|
||||
# Check Certain Values for None
|
||||
self.spellLanguage = self._checkNone(self.spellLanguage)
|
||||
@@ -511,8 +535,6 @@ class Config:
|
||||
"""Save the current preferences to file.
|
||||
"""
|
||||
logger.debug("Saving config file")
|
||||
if self.confPath is None:
|
||||
return False
|
||||
|
||||
theConf = NWConfigParser()
|
||||
|
||||
@@ -585,7 +607,7 @@ class Config:
|
||||
}
|
||||
|
||||
theConf["Backup"] = {
|
||||
"backuppath": str(self.backupPath),
|
||||
"backuppath": str(self._backupPath or ""),
|
||||
"backuponclose": str(self.backupOnClose),
|
||||
"askbeforebackup": str(self.askBeforeBackup),
|
||||
}
|
||||
@@ -603,11 +625,11 @@ class Config:
|
||||
}
|
||||
|
||||
theConf["Path"] = {
|
||||
"lastpath": str(self.lastPath),
|
||||
"lastpath": str(self._lastPath),
|
||||
}
|
||||
|
||||
# Write config file
|
||||
cnfPath = os.path.join(self.confPath, nwFiles.CONF_FILE)
|
||||
cnfPath = self._confPath / nwFiles.CONF_FILE
|
||||
try:
|
||||
with open(cnfPath, mode="w", encoding="utf-8") as outFile:
|
||||
theConf.write(outFile)
|
||||
@@ -615,102 +637,37 @@ 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.
|
||||
"""
|
||||
if self.dataPath is None:
|
||||
return False
|
||||
|
||||
self.recentProj = {}
|
||||
|
||||
cacheFile = os.path.join(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.
|
||||
"""
|
||||
if self.dataPath is None:
|
||||
return False
|
||||
|
||||
cacheFile = os.path.join(self.dataPath, nwFiles.RECENT_FILE)
|
||||
cacheTemp = os.path.join(self.dataPath, nwFiles.RECENT_FILE+"~")
|
||||
|
||||
try:
|
||||
with open(cacheTemp, mode="w+", encoding="utf-8") as outFile:
|
||||
json.dump(self.recentProj, outFile, indent=2)
|
||||
except Exception as exc:
|
||||
self.hasError = True
|
||||
self.errData.append("Could not save recent project cache")
|
||||
self.errData.append(formatException(exc))
|
||||
return False
|
||||
|
||||
if os.path.isfile(cacheFile):
|
||||
os.unlink(cacheFile)
|
||||
os.rename(cacheTemp, cacheFile)
|
||||
|
||||
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
|
||||
##
|
||||
|
||||
def setLastPath(self, lastPath):
|
||||
"""Set the last used path (by the user).
|
||||
"""Set the last used path. Only the folder is saved, so if the
|
||||
path is not a folder, the parent of the path is used instead.
|
||||
"""
|
||||
if lastPath is None or lastPath == "":
|
||||
self.lastPath = ""
|
||||
else:
|
||||
self.lastPath = os.path.dirname(lastPath)
|
||||
return True
|
||||
if isinstance(lastPath, (str, Path)):
|
||||
lastPath = Path(lastPath)
|
||||
if not lastPath.is_dir():
|
||||
lastPath = lastPath.parent
|
||||
if lastPath.is_dir():
|
||||
self._lastPath = lastPath
|
||||
logger.debug("Last path updated: %s" % self._lastPath)
|
||||
return
|
||||
|
||||
def setBackupPath(self, backupPath):
|
||||
"""Set the current backup path.
|
||||
"""
|
||||
self._backupPath = None
|
||||
if isinstance(backupPath, (str, Path)):
|
||||
self._backupPath = Path(backupPath)
|
||||
return
|
||||
|
||||
def setWinSize(self, newWidth, newHeight):
|
||||
"""Set the size of the main window, but only if the change is
|
||||
@@ -726,7 +683,7 @@ class Config:
|
||||
if abs(self.winGeometry[1] - newHeight) > 5:
|
||||
self.winGeometry[1] = newHeight
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setPreferencesSize(self, newWidth, newHeight):
|
||||
"""Sat the size of the Preferences dialog window.
|
||||
@@ -734,63 +691,63 @@ class Config:
|
||||
self.prefGeometry[0] = int(newWidth/self.guiScale)
|
||||
self.prefGeometry[1] = int(newHeight/self.guiScale)
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setProjColWidths(self, colWidths):
|
||||
"""Set the column widths of the Load Project dialog.
|
||||
"""
|
||||
self.projColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setMainPanePos(self, panePos):
|
||||
"""Set the position of the main GUI splitter.
|
||||
"""
|
||||
self.mainPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setDocPanePos(self, panePos):
|
||||
"""Set the position of the main editor/viewer splitter.
|
||||
"""
|
||||
self.docPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setViewPanePos(self, panePos):
|
||||
"""Set the position of the viewer meta data splitter.
|
||||
"""
|
||||
self.viewPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setOutlinePanePos(self, panePos):
|
||||
"""Set the position of the outline details splitter.
|
||||
"""
|
||||
self.outlnPanePos = [int(x/self.guiScale) for x in panePos]
|
||||
self.confChanged = True
|
||||
return True
|
||||
return
|
||||
|
||||
def setShowRefPanel(self, checkState):
|
||||
"""Set the visibility state of the reference panel.
|
||||
"""
|
||||
self.showRefPanel = checkState
|
||||
self.confChanged = True
|
||||
return self.showRefPanel
|
||||
return
|
||||
|
||||
def setViewComments(self, viewState):
|
||||
"""Set the visibility state of comments in the viewer.
|
||||
"""
|
||||
self.viewComments = viewState
|
||||
self.confChanged = True
|
||||
return self.viewComments
|
||||
return
|
||||
|
||||
def setViewSynopsis(self, viewState):
|
||||
"""Set the visibility state of synopsis comments in the viewer.
|
||||
"""
|
||||
self.viewSynopsis = viewState
|
||||
self.confChanged = True
|
||||
return self.viewSynopsis
|
||||
return
|
||||
|
||||
##
|
||||
# Default Setters
|
||||
@@ -843,15 +800,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
|
||||
##
|
||||
@@ -887,3 +835,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
|
||||
|
||||
@@ -24,7 +24,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import logging
|
||||
import novelwriter
|
||||
@@ -431,8 +430,8 @@ class ProjectBuilder:
|
||||
logger.error("No project path set for the example project")
|
||||
return False
|
||||
|
||||
pkgSample = os.path.join(self.mainConf.assetPath, "sample.zip")
|
||||
if os.path.isfile(pkgSample):
|
||||
pkgSample = self.mainConf.assetPath("sample.zip")
|
||||
if pkgSample.is_file():
|
||||
try:
|
||||
shutil.unpack_archive(pkgSample, projPath)
|
||||
except Exception as exc:
|
||||
|
||||
+39
-34
@@ -23,8 +23,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/>.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import logging
|
||||
import novelwriter
|
||||
@@ -49,7 +47,6 @@ from novelwriter.common import (
|
||||
checkStringNone, formatTimeStamp, hexToInt, isHandle, makeFileNameSafe, minmax
|
||||
)
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -78,10 +75,8 @@ class NWProject(QObject):
|
||||
self._projOpened = 0 # The time stamp of when the project file was opened
|
||||
self._projChanged = False # The project has unsaved changes
|
||||
self._projAltered = False # The project has been altered this session
|
||||
self.lockedBy = None # Data on which computer has the project open
|
||||
|
||||
# Class Settings
|
||||
self.projFiles = [] # A list of all files in the content folder on load
|
||||
self._lockedBy = None # Data on which computer has the project open
|
||||
self._projFiles = [] # A list of all files in the content folder on load
|
||||
|
||||
# Internal Mapping
|
||||
self.tr = partial(QCoreApplication.translate, "NWProject")
|
||||
@@ -127,6 +122,10 @@ class NWProject(QObject):
|
||||
def projAltered(self):
|
||||
return self._projAltered
|
||||
|
||||
@property
|
||||
def projFiles(self):
|
||||
return self._projFiles
|
||||
|
||||
##
|
||||
# Item Methods
|
||||
##
|
||||
@@ -246,7 +245,7 @@ class NWProject(QObject):
|
||||
self._data = NWProjectData(self)
|
||||
|
||||
# Project Settings
|
||||
self.projFiles = []
|
||||
self._projFiles = []
|
||||
|
||||
return
|
||||
|
||||
@@ -274,7 +273,7 @@ class NWProject(QObject):
|
||||
logger.warning("Failed to check lock file")
|
||||
else:
|
||||
logger.error("Project is locked, so not opening")
|
||||
self.lockedBy = lockStatus
|
||||
self._lockedBy = lockStatus
|
||||
self.clearProject()
|
||||
return False
|
||||
else:
|
||||
@@ -355,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:
|
||||
@@ -425,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))
|
||||
@@ -446,22 +443,9 @@ class NWProject(QObject):
|
||||
self._storage.clearLockFile()
|
||||
self._storage.closeSession()
|
||||
self.clearProject()
|
||||
self.lockedBy = None
|
||||
self._lockedBy = None
|
||||
return True
|
||||
|
||||
def setDefaultStatusImport(self):
|
||||
"""Set the default status and importance values.
|
||||
"""
|
||||
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0))
|
||||
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0))
|
||||
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0))
|
||||
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0))
|
||||
return
|
||||
|
||||
def backupProject(self, doNotify):
|
||||
"""Create a zip file of the entire project.
|
||||
"""
|
||||
@@ -472,7 +456,8 @@ class NWProject(QObject):
|
||||
logger.info("Backing up project")
|
||||
self.mainGui.setStatus(self.tr("Backing up project ..."))
|
||||
|
||||
if not self.mainConf.backupPath:
|
||||
backupPath = self.mainConf.backupPath()
|
||||
if not isinstance(backupPath, Path):
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot backup project because no valid backup path is set. "
|
||||
"Please set a valid backup location in Preferences."
|
||||
@@ -487,7 +472,7 @@ class NWProject(QObject):
|
||||
return False
|
||||
|
||||
cleanName = makeFileNameSafe(self._data.name)
|
||||
baseDir = Path(self.mainConf.backupPath) / cleanName
|
||||
baseDir = backupPath / cleanName
|
||||
try:
|
||||
baseDir.mkdir(exist_ok=True)
|
||||
except Exception as exc:
|
||||
@@ -520,6 +505,19 @@ class NWProject(QObject):
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setDefaultStatusImport(self):
|
||||
"""Set the default status and importance values.
|
||||
"""
|
||||
self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0))
|
||||
self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0))
|
||||
self._data.itemImport.write(None, self.tr("New"), (100, 100, 100))
|
||||
self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0))
|
||||
self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0))
|
||||
self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0))
|
||||
return
|
||||
|
||||
def setProjectLang(self, theLang):
|
||||
"""Set the project-specific language.
|
||||
"""
|
||||
@@ -567,6 +565,13 @@ class NWProject(QObject):
|
||||
# Getters
|
||||
##
|
||||
|
||||
def getLockStatus(self):
|
||||
"""Return the project lock information for the project.
|
||||
"""
|
||||
if isinstance(self._lockedBy, list) and len(self._lockedBy) == 4:
|
||||
return self._lockedBy
|
||||
return None
|
||||
|
||||
def getFormattedAuthors(self):
|
||||
"""Return a formatted string of authors.
|
||||
"""
|
||||
@@ -692,13 +697,13 @@ class NWProject(QObject):
|
||||
def _loadProjectLocalisation(self):
|
||||
"""Load the language data for the current project language.
|
||||
"""
|
||||
if self._data.language is None or self.mainConf.nwLangPath is None:
|
||||
if self._data.language is None or self.mainConf._nwLangPath is None:
|
||||
self._langData = {}
|
||||
return False
|
||||
|
||||
langFile = Path(self.mainConf.nwLangPath) / f"project_{self._data.language}.json"
|
||||
langFile = Path(self.mainConf._nwLangPath) / f"project_{self._data.language}.json"
|
||||
if not langFile.is_file():
|
||||
langFile = Path(self.mainConf.nwLangPath) / "project_en_GB.json"
|
||||
langFile = Path(self.mainConf._nwLangPath) / "project_en_GB.json"
|
||||
|
||||
try:
|
||||
with open(langFile, mode="r", encoding="utf-8") as inFile:
|
||||
@@ -725,7 +730,7 @@ class NWProject(QObject):
|
||||
# Then check the files in the data folder
|
||||
logger.debug("Checking files in project content folder")
|
||||
orphanFiles = []
|
||||
self.projFiles = []
|
||||
self._projFiles = []
|
||||
|
||||
for item in contentPath.iterdir():
|
||||
itemName = item.name
|
||||
@@ -742,7 +747,7 @@ class NWProject(QObject):
|
||||
continue
|
||||
|
||||
if fHandle in self._tree:
|
||||
self.projFiles.append(fHandle)
|
||||
self._projFiles.append(fHandle)
|
||||
logger.debug("Checking file %s, handle '%s': OK", itemName, fHandle)
|
||||
else:
|
||||
logger.warning("Checking file %s, handle '%s': Orphaned", itemName, fHandle)
|
||||
|
||||
@@ -23,7 +23,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
@@ -233,7 +232,7 @@ class GuiAbout(QDialog):
|
||||
def _fillNotesPage(self):
|
||||
"""Load the content for the Release Notes page.
|
||||
"""
|
||||
docPath = os.path.join(self.mainConf.assetPath, "text", "release_notes.htm")
|
||||
docPath = self.mainConf.assetPath("text") / "release_notes.htm"
|
||||
docText = readTextFile(docPath)
|
||||
if docText:
|
||||
self.pageNotes.setHtml(docText)
|
||||
@@ -244,7 +243,7 @@ class GuiAbout(QDialog):
|
||||
def _fillLicensePage(self):
|
||||
"""Load the content for the Licence page.
|
||||
"""
|
||||
docPath = os.path.join(self.mainConf.assetPath, "text", "gplv3_en.htm")
|
||||
docPath = self.mainConf.assetPath("text") / "gplv3_en.htm"
|
||||
docText = readTextFile(docPath)
|
||||
if docText:
|
||||
self.pageLicense.setHtml(docText)
|
||||
|
||||
@@ -23,7 +23,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
@@ -384,7 +383,7 @@ class GuiPreferencesProjects(QWidget):
|
||||
self.mainForm.addGroupLabel(self.tr("Project Backup"))
|
||||
|
||||
# Backup Path
|
||||
self.backupPath = self.mainConf.backupPath
|
||||
self.backupPath = self.mainConf.backupPath()
|
||||
self.backupGetPath = QPushButton(self.tr("Browse"))
|
||||
self.backupGetPath.clicked.connect(self._backupFolder)
|
||||
self.backupPathRow = self.mainForm.addRow(
|
||||
@@ -451,7 +450,7 @@ class GuiPreferencesProjects(QWidget):
|
||||
self.mainConf.autoSaveProj = self.autoSaveProj.value()
|
||||
|
||||
# Project Backup
|
||||
self.mainConf.backupPath = self.backupPath
|
||||
self.mainConf.setBackupPath(self.backupPath)
|
||||
self.mainConf.backupOnClose = self.backupOnClose.isChecked()
|
||||
self.mainConf.askBeforeBackup = self.askBeforeBackup.isChecked()
|
||||
|
||||
@@ -470,12 +469,9 @@ class GuiPreferencesProjects(QWidget):
|
||||
def _backupFolder(self):
|
||||
"""Open a dialog to select the backup folder.
|
||||
"""
|
||||
currDir = self.backupPath
|
||||
if not os.path.isdir(currDir):
|
||||
currDir = ""
|
||||
|
||||
currDir = self.backupPath or ""
|
||||
newDir = QFileDialog.getExistingDirectory(
|
||||
self, self.tr("Backup Directory"), currDir, options=QFileDialog.ShowDirsOnly
|
||||
self, self.tr("Backup Directory"), str(currDir), options=QFileDialog.ShowDirsOnly
|
||||
)
|
||||
if newDir:
|
||||
self.backupPath = newDir
|
||||
|
||||
@@ -23,10 +23,10 @@ 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 logging
|
||||
import novelwriter
|
||||
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
@@ -190,8 +190,8 @@ class GuiProjectLoad(QDialog):
|
||||
self, self.tr("Open Project"), "", filter=";;".join(extFilter)
|
||||
)
|
||||
if projFile:
|
||||
thePath = os.path.abspath(os.path.dirname(projFile))
|
||||
self.selPath.setText(thePath)
|
||||
thePath = Path(projFile).absolute()
|
||||
self.selPath.setText(str(thePath))
|
||||
self.openPath = thePath
|
||||
self.openState = self.OPEN_STATE
|
||||
self.accept()
|
||||
@@ -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)
|
||||
|
||||
@@ -26,6 +26,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from pathlib import Path
|
||||
from urllib.parse import urljoin
|
||||
from urllib.request import pathname2url
|
||||
|
||||
@@ -104,10 +105,11 @@ class GuiMainMenu(QMenuBar):
|
||||
def _openUserManualFile(self):
|
||||
"""Open the documentation in PDF format.
|
||||
"""
|
||||
if self.mainConf.pdfDocs is None:
|
||||
return False
|
||||
QDesktopServices.openUrl(QUrl(urljoin("file:", pathname2url(self.mainConf.pdfDocs))))
|
||||
return True
|
||||
if isinstance(self.mainConf.pdfDocs, Path):
|
||||
QDesktopServices.openUrl(
|
||||
QUrl(urljoin("file:", pathname2url(str(self.mainConf.pdfDocs))))
|
||||
)
|
||||
return
|
||||
|
||||
##
|
||||
# Menu Builders
|
||||
@@ -824,7 +826,7 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Tools > Backup
|
||||
self.aBackupProject = QAction(self.tr("Backup Project"), self)
|
||||
self.aBackupProject.triggered.connect(lambda: self.theProject.backupProject(doNoify=True))
|
||||
self.aBackupProject.triggered.connect(lambda: self.theProject.backupProject(True))
|
||||
self.toolsMenu.addAction(self.aBackupProject)
|
||||
|
||||
# Tools > Export Project
|
||||
@@ -881,7 +883,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.helpMenu.addAction(self.aHelpDocs)
|
||||
|
||||
# Help > User Manual (PDF)
|
||||
if self.mainConf.pdfDocs is not None:
|
||||
if isinstance(self.mainConf.pdfDocs, Path):
|
||||
self.aPdfDocs = QAction(self.tr("User Manual (PDF)"), self)
|
||||
self.aPdfDocs.setShortcut("Shift+F1")
|
||||
self.aPdfDocs.triggered.connect(self._openUserManualFile)
|
||||
|
||||
+21
-28
@@ -24,7 +24,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
@@ -119,12 +118,10 @@ class GuiTheme:
|
||||
self._availThemes = {}
|
||||
self._availSyntax = {}
|
||||
|
||||
self._listConf(self._availSyntax, os.path.join(self.mainConf.assetPath, "syntax"))
|
||||
self._listConf(self._availThemes, os.path.join(self.mainConf.assetPath, "themes"))
|
||||
|
||||
if self.mainConf.dataPath: # Not guaranteed to be set
|
||||
self._listConf(self._availSyntax, os.path.join(self.mainConf.dataPath, "syntax"))
|
||||
self._listConf(self._availThemes, os.path.join(self.mainConf.dataPath, "themes"))
|
||||
self._listConf(self._availSyntax, self.mainConf.assetPath("syntax"))
|
||||
self._listConf(self._availThemes, self.mainConf.assetPath("themes"))
|
||||
self._listConf(self._availSyntax, self.mainConf.dataPath("syntax"))
|
||||
self._listConf(self._availThemes, self.mainConf.dataPath("themes"))
|
||||
|
||||
self.loadTheme()
|
||||
self.loadSyntax()
|
||||
@@ -380,13 +377,12 @@ class GuiTheme:
|
||||
def _listConf(self, targetDict, checkDir):
|
||||
"""Scan for theme config files and populate the dictionary.
|
||||
"""
|
||||
if not os.path.isdir(checkDir):
|
||||
if not checkDir.is_dir():
|
||||
return False
|
||||
|
||||
for checkFile in os.listdir(checkDir):
|
||||
confPath = os.path.join(checkDir, checkFile)
|
||||
if os.path.isfile(confPath) and confPath.endswith(".conf"):
|
||||
targetDict[checkFile[:-5]] = confPath
|
||||
for checkFile in checkDir.iterdir():
|
||||
if checkFile.is_file() and checkFile.name.endswith(".conf"):
|
||||
targetDict[checkFile.name[:-5]] = checkFile
|
||||
|
||||
return True
|
||||
|
||||
@@ -476,7 +472,7 @@ class GuiIcons:
|
||||
self._confName = "icons.conf"
|
||||
|
||||
# Icon Theme Path
|
||||
self._iconPath = os.path.join(self.mainConf.assetPath, "icons")
|
||||
self._iconPath = self.mainConf.assetPath("icons")
|
||||
|
||||
# Icon Theme Meta
|
||||
self.themeName = ""
|
||||
@@ -499,12 +495,12 @@ class GuiIcons:
|
||||
update functions for the classes where they're used.
|
||||
"""
|
||||
self._themeMap = {}
|
||||
themePath = os.path.join(self.mainConf.assetPath, "icons", iconTheme)
|
||||
if not os.path.isdir(themePath):
|
||||
themePath = self._iconPath / iconTheme
|
||||
if not themePath.is_dir():
|
||||
logger.warning("No icons loaded for '%s'", iconTheme)
|
||||
return False
|
||||
|
||||
themeConf = os.path.join(themePath, self._confName)
|
||||
themeConf = themePath / self._confName
|
||||
logger.info("Loading icon theme '%s'", iconTheme)
|
||||
|
||||
# Config File
|
||||
@@ -535,8 +531,8 @@ class GuiIcons:
|
||||
if iconName not in self.ICON_KEYS:
|
||||
logger.error("Unknown icon name '%s' in config file", iconName)
|
||||
else:
|
||||
iconPath = os.path.join(themePath, iconFile)
|
||||
if os.path.isfile(iconPath):
|
||||
iconPath = themePath / iconFile
|
||||
if iconPath.is_file():
|
||||
self._themeMap[iconName] = iconPath
|
||||
logger.debug("Icon slot '%s' using file '%s'", iconName, iconFile)
|
||||
else:
|
||||
@@ -572,18 +568,16 @@ class GuiIcons:
|
||||
if decoKey in self._themeMap:
|
||||
imgPath = self._themeMap[decoKey]
|
||||
elif decoKey in self.IMAGE_MAP:
|
||||
imgPath = os.path.join(
|
||||
self.mainConf.assetPath, "images", self.IMAGE_MAP[decoKey]
|
||||
)
|
||||
imgPath = self.mainConf.assetPath("images") / self.IMAGE_MAP[decoKey]
|
||||
else:
|
||||
logger.error("Decoration with name '%s' does not exist", decoKey)
|
||||
return QPixmap()
|
||||
|
||||
if not os.path.isfile(imgPath):
|
||||
if not imgPath.is_file():
|
||||
logger.error("Asset not found: %s", imgPath)
|
||||
return QPixmap()
|
||||
|
||||
theDeco = QPixmap(imgPath)
|
||||
theDeco = QPixmap(str(imgPath))
|
||||
if pxW is not None and pxH is not None:
|
||||
return theDeco.scaled(pxW, pxH, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
|
||||
elif pxW is None and pxH is not None:
|
||||
@@ -667,15 +661,14 @@ class GuiIcons:
|
||||
|
||||
# If we just want the app icons, return right away
|
||||
if iconKey == "novelwriter":
|
||||
return QIcon(os.path.join(self._iconPath, "novelwriter.svg"))
|
||||
return QIcon(str(self._iconPath / "novelwriter.svg"))
|
||||
elif iconKey == "proj_nwx":
|
||||
return QIcon(os.path.join(self._iconPath, "x-novelwriter-project.svg"))
|
||||
return QIcon(str(self._iconPath / "x-novelwriter-project.svg"))
|
||||
|
||||
# Otherwise, we load from the theme folder
|
||||
if iconKey in self._themeMap:
|
||||
relPath = os.path.relpath(self._themeMap[iconKey], self._iconPath)
|
||||
logger.debug("Loading: %s", relPath)
|
||||
return QIcon(self._themeMap[iconKey])
|
||||
logger.debug("Loading: %s", self._themeMap[iconKey].name)
|
||||
return QIcon(str(self._themeMap[iconKey]))
|
||||
|
||||
# If we didn't find one, give up and return an empty icon
|
||||
logger.warning("Did not load an icon for '%s'", iconKey)
|
||||
|
||||
+16
-13
@@ -23,12 +23,12 @@ 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 logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||
@@ -95,7 +95,11 @@ class GuiMain(QMainWindow):
|
||||
# Prepare Main Window
|
||||
self.resize(*self.mainConf.getWinSize())
|
||||
self._updateWindowTitle()
|
||||
self.setWindowIcon(QIcon(self.mainConf.appIcon))
|
||||
|
||||
nwIcon = self.mainConf.assetPath("icons") / "novelwriter.svg"
|
||||
self.nwIcon = QIcon(str(nwIcon)) if nwIcon.is_file() else QIcon()
|
||||
self.setWindowIcon(self.nwIcon)
|
||||
qApp.setWindowIcon(self.nwIcon)
|
||||
|
||||
# Build the GUI
|
||||
# =============
|
||||
@@ -357,7 +361,7 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No projData or projPath set")
|
||||
return False
|
||||
|
||||
if os.path.isfile(os.path.join(projPath, nwFiles.PROJ_FILE)):
|
||||
if (Path(projPath) / nwFiles.PROJ_FILE).is_file():
|
||||
self.makeAlert(self.tr(
|
||||
"A project already exists in that location. "
|
||||
"Please choose another folder."
|
||||
@@ -409,7 +413,7 @@ class GuiMain(QMainWindow):
|
||||
if not msgYes:
|
||||
doBackup = False
|
||||
if doBackup:
|
||||
self.theProject.backupProject(doNotify=False)
|
||||
self.theProject.backupProject(False)
|
||||
else:
|
||||
saveOK = True
|
||||
|
||||
@@ -447,7 +451,8 @@ class GuiMain(QMainWindow):
|
||||
if not self.theProject.openProject(projFile):
|
||||
# The project open failed.
|
||||
|
||||
if self.theProject.lockedBy is None:
|
||||
lockStatus = self.theProject.getLockStatus()
|
||||
if lockStatus is None:
|
||||
# The project is not locked, so failed for some other
|
||||
# reason handled by the project class.
|
||||
return False
|
||||
@@ -459,10 +464,8 @@ class GuiMain(QMainWindow):
|
||||
"'{0}' ({1} {2}), last active on {3}."
|
||||
)
|
||||
).format(
|
||||
self.theProject.lockedBy[0],
|
||||
self.theProject.lockedBy[1],
|
||||
self.theProject.lockedBy[2],
|
||||
datetime.fromtimestamp(int(self.theProject.lockedBy[3])).strftime("%x %X")
|
||||
lockStatus[0], lockStatus[1], lockStatus[2],
|
||||
datetime.fromtimestamp(int(lockStatus[3])).strftime("%x %X")
|
||||
)
|
||||
except Exception:
|
||||
lockDetails = ""
|
||||
@@ -694,7 +697,7 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
lastPath = self.mainConf.lastPath
|
||||
lastPath = self.mainConf.lastPath()
|
||||
extFilter = [
|
||||
self.tr("Text files ({0})").format("*.txt"),
|
||||
self.tr("Markdown files ({0})").format("*.md"),
|
||||
@@ -702,7 +705,7 @@ class GuiMain(QMainWindow):
|
||||
self.tr("All files ({0})").format("*"),
|
||||
]
|
||||
loadFile, _ = QFileDialog.getOpenFileName(
|
||||
self, self.tr("Import File"), lastPath, filter=";;".join(extFilter)
|
||||
self, self.tr("Import File"), str(lastPath), filter=";;".join(extFilter)
|
||||
)
|
||||
if not loadFile:
|
||||
return False
|
||||
@@ -1140,7 +1143,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
|
||||
|
||||
@@ -1363,7 +1366,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Help
|
||||
self.addAction(self.mainMenu.aHelpDocs)
|
||||
if self.mainConf.pdfDocs is not None:
|
||||
if isinstance(self.mainConf.pdfDocs, Path):
|
||||
self.addAction(self.mainMenu.aPdfDocs)
|
||||
|
||||
return True
|
||||
|
||||
@@ -23,7 +23,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import novelwriter
|
||||
@@ -891,13 +890,9 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
cleanName = makeFileNameSafe(self.theProject.data.name)
|
||||
fileName = "%s.%s" % (cleanName, fileExt)
|
||||
saveDir = self.mainConf.lastPath
|
||||
if not os.path.isdir(saveDir):
|
||||
saveDir = os.path.expanduser("~")
|
||||
|
||||
savePath = os.path.join(saveDir, fileName)
|
||||
savePath = self.mainConf.lastPath() / fileName
|
||||
savePath, _ = QFileDialog.getSaveFileName(
|
||||
self, self.tr("Save Document As"), savePath
|
||||
self, self.tr("Save Document As"), str(savePath)
|
||||
)
|
||||
if not savePath:
|
||||
return False
|
||||
|
||||
@@ -23,7 +23,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
import novelwriter
|
||||
@@ -120,7 +119,7 @@ class GuiLipsum(QDialog):
|
||||
def _doInsert(self):
|
||||
"""Load the text and insert it in the open document.
|
||||
"""
|
||||
lipsumFile = os.path.join(self.mainConf.assetPath, "text", "lipsum.txt")
|
||||
lipsumFile = self.mainConf.assetPath("text") / "lipsum.txt"
|
||||
lipsumText = readTextFile(lipsumFile).splitlines()
|
||||
|
||||
if self.randSwitch.isChecked():
|
||||
|
||||
@@ -236,12 +236,9 @@ class ProjWizardFolderPage(QWizardPage):
|
||||
def _doBrowse(self):
|
||||
"""Select a project folder.
|
||||
"""
|
||||
lastPath = self.mainConf.lastPath
|
||||
if not os.path.isdir(lastPath):
|
||||
lastPath = ""
|
||||
|
||||
lastPath = self.mainConf.lastPath()
|
||||
projDir = QFileDialog.getExistingDirectory(
|
||||
self, self.tr("Select Project Folder"), lastPath, options=QFileDialog.ShowDirsOnly
|
||||
self, self.tr("Select Project Folder"), str(lastPath), options=QFileDialog.ShowDirsOnly
|
||||
)
|
||||
if projDir:
|
||||
projName = self.field("projName")
|
||||
|
||||
@@ -23,7 +23,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import logging
|
||||
import novelwriter
|
||||
@@ -363,15 +362,9 @@ class GuiWritingStats(QDialog):
|
||||
return False
|
||||
|
||||
# Generate the file name
|
||||
saveDir = self.mainConf.lastPath
|
||||
if not os.path.isdir(saveDir):
|
||||
saveDir = os.path.expanduser("~")
|
||||
|
||||
fileName = "sessionStats.%s" % fileExt
|
||||
savePath = os.path.join(saveDir, fileName)
|
||||
|
||||
savePath = self.mainConf.lastPath() / f"sessionStats.{fileExt}"
|
||||
savePath, _ = QFileDialog.getSaveFileName(
|
||||
self, self.tr("Save Data As"), savePath, "%s (*.%s)" % (textFmt, fileExt)
|
||||
self, self.tr("Save Data As"), str(savePath), "%s (*.%s)" % (textFmt, fileExt)
|
||||
)
|
||||
if not savePath:
|
||||
return False
|
||||
|
||||
+53
-85
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
import shutil
|
||||
@@ -50,25 +49,14 @@ def initQt(qtbot):
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tmpDir():
|
||||
"""A temporary folder for the test session. This folder is
|
||||
presistent after the test so that the status of generated files can
|
||||
be checked. The folder is instead cleared before a new test session.
|
||||
"""
|
||||
testDir = os.path.dirname(__file__)
|
||||
theDir = os.path.join(testDir, "temp")
|
||||
if os.path.isdir(theDir):
|
||||
shutil.rmtree(theDir)
|
||||
if not os.path.isdir(theDir):
|
||||
os.mkdir(theDir)
|
||||
return theDir
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def tmpPath(tmpDir):
|
||||
def tmpPath():
|
||||
"""A temporary folder for the test session. Path version.
|
||||
"""
|
||||
return Path(tmpDir)
|
||||
theTemp = Path(__file__).parent / "temp"
|
||||
if theTemp.exists():
|
||||
shutil.rmtree(theTemp)
|
||||
theTemp.mkdir(exist_ok=True)
|
||||
return theTemp
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@@ -99,57 +87,15 @@ def fncPath(tmpPath):
|
||||
return fncPath
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def refDir():
|
||||
"""The folder where all the reference files are stored for verifying
|
||||
the results of tests.
|
||||
"""
|
||||
testDir = os.path.dirname(__file__)
|
||||
theDir = os.path.join(testDir, "reference")
|
||||
return theDir
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def filesDir():
|
||||
"""The folder where additional test files are stored.
|
||||
"""
|
||||
testDir = os.path.dirname(__file__)
|
||||
theDir = os.path.join(testDir, "files")
|
||||
return theDir
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def outDir(tmpDir):
|
||||
"""An output folder for test results
|
||||
"""
|
||||
theDir = os.path.join(tmpDir, "results")
|
||||
if not os.path.isdir(theDir):
|
||||
os.mkdir(theDir)
|
||||
return theDir
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def fncDir(tmpDir):
|
||||
"""A temporary folder for a single test function.
|
||||
"""
|
||||
fncDir = os.path.join(tmpDir, "function")
|
||||
if os.path.isdir(fncDir):
|
||||
shutil.rmtree(fncDir)
|
||||
if not os.path.isdir(fncDir):
|
||||
os.mkdir(fncDir)
|
||||
return fncDir
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def fncProj(fncDir):
|
||||
def projPath(fncPath):
|
||||
"""A temporary folder for a single test function,
|
||||
with a project folder.
|
||||
"""
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
if os.path.isdir(prjDir):
|
||||
prjDir = fncPath / "project"
|
||||
if prjDir.exists():
|
||||
shutil.rmtree(prjDir)
|
||||
if not os.path.isdir(prjDir):
|
||||
os.mkdir(prjDir)
|
||||
prjDir.mkdir(exist_ok=True)
|
||||
return prjDir
|
||||
|
||||
|
||||
@@ -158,29 +104,29 @@ def fncProj(fncDir):
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def tmpConf(tmpDir):
|
||||
def tmpConf(tmpPath):
|
||||
"""Create a temporary novelWriter configuration object.
|
||||
"""
|
||||
confFile = os.path.join(tmpDir, "novelwriter.conf")
|
||||
if os.path.isfile(confFile):
|
||||
os.unlink(confFile)
|
||||
confFile = tmpPath / "novelwriter.conf"
|
||||
if confFile.is_file():
|
||||
confFile.unlink()
|
||||
theConf = Config()
|
||||
theConf.initConfig(tmpDir, tmpDir)
|
||||
theConf.setLastPath("")
|
||||
theConf.initConfig(tmpPath, tmpPath)
|
||||
theConf.setLastPath(tmpPath)
|
||||
theConf.guiLang = "en_GB"
|
||||
return theConf
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def fncConf(fncDir):
|
||||
def fncConf(fncPath):
|
||||
"""Create a temporary novelWriter configuration object.
|
||||
"""
|
||||
confFile = os.path.join(fncDir, "novelwriter.conf")
|
||||
if os.path.isfile(confFile):
|
||||
os.unlink(confFile)
|
||||
confFile = fncPath / "novelwriter.conf"
|
||||
if confFile.is_file():
|
||||
confFile.unlink()
|
||||
theConf = Config()
|
||||
theConf.initConfig(fncDir, fncDir)
|
||||
theConf.setLastPath("")
|
||||
theConf.initConfig(fncPath, fncPath)
|
||||
theConf.setLastPath(fncPath)
|
||||
theConf.guiLang = "en_GB"
|
||||
return theConf
|
||||
|
||||
@@ -196,7 +142,7 @@ def mockGUI(monkeypatch, tmpConf):
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
||||
def nwGUI(qtbot, monkeypatch, fncPath, fncConf):
|
||||
"""Create an instance of the novelWriter GUI.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Ok)
|
||||
@@ -205,12 +151,12 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
|
||||
monkeypatch.setattr("novelwriter.CONFIG", fncConf)
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
|
||||
nwGUI = novelwriter.main(["--testmode", f"--config={fncPath}", f"--data={fncPath}"])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(20)
|
||||
|
||||
nwGUI.mainConf.lastPath = fncDir
|
||||
nwGUI.mainConf.setLastPath(fncPath)
|
||||
|
||||
yield nwGUI
|
||||
|
||||
@@ -252,14 +198,36 @@ def mockRnd(monkeypatch):
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwLipsum(tmpDir):
|
||||
def nwLipsum(tmpPath):
|
||||
"""A medium sized novelWriter example project with a lot of Lorem
|
||||
Ipsum text.
|
||||
"""
|
||||
tstDir = os.path.dirname(__file__)
|
||||
srcDir = os.path.join(tstDir, "lipsum")
|
||||
dstDir = os.path.join(tmpDir, "lipsum")
|
||||
if os.path.isdir(dstDir):
|
||||
tstDir = Path(__file__).parent
|
||||
srcDir = tstDir / "lipsum"
|
||||
dstDir = tmpPath / "lipsum"
|
||||
if dstDir.exists():
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
shutil.copytree(srcDir, dstDir)
|
||||
cleanProject(dstDir)
|
||||
|
||||
yield str(dstDir)
|
||||
|
||||
if dstDir.exists():
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def prjLipsum(tmpPath):
|
||||
"""A medium sized novelWriter example project with a lot of Lorem
|
||||
Ipsum text.
|
||||
"""
|
||||
tstDir = Path(__file__).parent
|
||||
srcDir = tstDir / "lipsum"
|
||||
dstDir = tmpPath / "lipsum"
|
||||
if dstDir.exists():
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
shutil.copytree(srcDir, dstDir)
|
||||
@@ -267,7 +235,7 @@ def nwLipsum(tmpDir):
|
||||
|
||||
yield dstDir
|
||||
|
||||
if os.path.isdir(dstDir):
|
||||
if dstDir.exists():
|
||||
shutil.rmtree(dstDir)
|
||||
|
||||
return
|
||||
|
||||
@@ -19,10 +19,9 @@ 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 hashlib
|
||||
import os
|
||||
import time
|
||||
import pytest
|
||||
import hashlib
|
||||
|
||||
from mock import causeOSError
|
||||
from tools import writeFile
|
||||
@@ -33,8 +32,8 @@ from novelwriter.common import (
|
||||
checkUuid, isHandle, isTitleTag, isItemClass, isItemType, isItemLayout,
|
||||
hexToInt, minmax, checkIntTuple, formatInt, formatTimeStamp, formatTime,
|
||||
simplified, yesNo, splitVersionNumber, transferCase, fuzzyTime,
|
||||
numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, ensureFolder,
|
||||
sha256sum, getGuiItem, NWConfigParser
|
||||
numberToRoman, jsonEncode, readTextFile, makeFileNameSafe, sha256sum,
|
||||
getGuiItem, NWConfigParser
|
||||
)
|
||||
|
||||
|
||||
@@ -591,18 +590,18 @@ def testBaseCommon_JsonEncode():
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_ReadTextFile(monkeypatch, fncDir, ipsumText):
|
||||
def testBaseCommon_ReadTextFile(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the readTextFile function.
|
||||
"""
|
||||
testText = "\n\n".join(ipsumText) + "\n"
|
||||
testFile = os.path.join(fncDir, "ipsum.txt")
|
||||
testFile = fncPath / "ipsum.txt"
|
||||
writeFile(testFile, testText)
|
||||
|
||||
assert readTextFile(os.path.join(fncDir, "not_a_file.txt")) == ""
|
||||
assert readTextFile(fncPath / "not_a_file.txt") == ""
|
||||
assert readTextFile(testFile) == testText
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
mp.setattr("pathlib.Path.read_text", causeOSError)
|
||||
assert readTextFile(testFile) == ""
|
||||
|
||||
# END Test testBaseCommon_ReadTextFile
|
||||
@@ -621,33 +620,7 @@ def testBaseCommon_MakeFileNameSafe():
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_EnsureFolder(monkeypatch, fncDir):
|
||||
"""Test the ensureFolder function.
|
||||
"""
|
||||
newDir1 = os.path.join(fncDir, "newDir1")
|
||||
newDir2 = os.path.join(fncDir, "newDir2")
|
||||
newDir3 = os.path.join(fncDir, "newDir3")
|
||||
|
||||
assert ensureFolder(None) is False
|
||||
|
||||
assert ensureFolder(newDir1) is True
|
||||
assert os.path.isdir(newDir1)
|
||||
|
||||
assert ensureFolder("newDir2", parent=fncDir) is True
|
||||
assert os.path.isdir(newDir2)
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.mkdir", causeOSError)
|
||||
errLog = []
|
||||
assert ensureFolder("newDir3", parent=fncDir, errLog=errLog) is False
|
||||
assert errLog[0] == f"Could not create folder: {newDir3}"
|
||||
assert not os.path.isdir(newDir3)
|
||||
|
||||
# END Test testBaseCommon_EnsureFolder
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText):
|
||||
def testBaseCommon_Sha256Sum(monkeypatch, fncPath, ipsumText):
|
||||
"""Test the sha256sum function.
|
||||
"""
|
||||
longText = 50*(" ".join(ipsumText) + " ")
|
||||
@@ -656,9 +629,9 @@ def testBaseCommon_Sha256Sum(monkeypatch, fncDir, ipsumText):
|
||||
|
||||
assert len(longText) == 175650
|
||||
|
||||
longFile = os.path.join(fncDir, "long_file.txt")
|
||||
shortFile = os.path.join(fncDir, "short_file.txt")
|
||||
noneFile = os.path.join(fncDir, "none_file.txt")
|
||||
longFile = fncPath / "long_file.txt"
|
||||
shortFile = fncPath / "short_file.txt"
|
||||
noneFile = fncPath / "none_file.txt"
|
||||
|
||||
writeFile(longFile, longText)
|
||||
writeFile(shortFile, shortText)
|
||||
@@ -697,10 +670,10 @@ def testBaseCommon_GetGuiItem(nwGUI):
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_NWConfigParser(fncDir):
|
||||
def testBaseCommon_NWConfigParser(fncPath):
|
||||
"""Test the NWConfigParser subclass.
|
||||
"""
|
||||
tstConf = os.path.join(fncDir, "test.cfg")
|
||||
tstConf = fncPath / "test.cfg"
|
||||
writeFile(tstConf, (
|
||||
"[main]\n"
|
||||
"stropt = value\n"
|
||||
|
||||
+219
-226
@@ -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,172 +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
|
||||
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"))
|
||||
tstConf.nwLangPath = i18nDir
|
||||
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)
|
||||
@@ -216,88 +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.
|
||||
"""
|
||||
# Check failing
|
||||
tmpConf.dataPath = None
|
||||
assert not tmpConf.loadRecentCache()
|
||||
assert not tmpConf.saveRecentCache()
|
||||
tmpConf.dataPath = tmpDir
|
||||
# Data Path
|
||||
assert tmpConf.dataPath() == tmpPath
|
||||
assert tmpConf.dataPath("stuff") == tmpPath / "stuff"
|
||||
|
||||
# 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},
|
||||
}
|
||||
# Assets Path
|
||||
appPath = tmpConf._appPath
|
||||
assert tmpConf.assetPath() == appPath / "assets"
|
||||
assert tmpConf.assetPath("stuff") == appPath / "assets" / "stuff"
|
||||
|
||||
# Fail to Save
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert not tmpConf.saveRecentCache()
|
||||
# Last Path
|
||||
assert tmpConf.lastPath() == tmpPath
|
||||
|
||||
# Save Proper
|
||||
cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE)
|
||||
assert tmpConf.saveRecentCache()
|
||||
assert tmpConf.saveRecentCache()
|
||||
assert os.path.isfile(cacheFile)
|
||||
tmpStuff = tmpPath / "stuff"
|
||||
tmpStuff.mkdir()
|
||||
tmpConf.setLastPath(tmpStuff)
|
||||
assert tmpConf.lastPath() == tmpStuff
|
||||
|
||||
# Fail to Load
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
tmpConf.recentProj = {}
|
||||
assert not tmpConf.loadRecentCache()
|
||||
assert tmpConf.recentProj == {}
|
||||
fileStuff = tmpStuff / "more_stuff.txt"
|
||||
fileStuff.write_text("Stuff")
|
||||
tmpConf.setLastPath(fileStuff)
|
||||
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.unlink()
|
||||
tmpStuff.rmdir()
|
||||
assert tmpConf.lastPath() == Path.home().absolute()
|
||||
|
||||
# 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},
|
||||
}
|
||||
# Recent Projects
|
||||
assert isinstance(tmpConf.recentProjects, RecentProjects)
|
||||
|
||||
# Remove Second Entry
|
||||
assert tmpConf.removeFromRecentCache(pathTwo)
|
||||
assert tmpConf.recentProj == {
|
||||
pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
|
||||
}
|
||||
|
||||
# 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
|
||||
# ===========
|
||||
|
||||
@@ -318,98 +253,98 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
|
||||
|
||||
# Window Size
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setWinSize(1205, 655)
|
||||
assert not tmpConf.confChanged
|
||||
tmpConf.setWinSize(1205, 655)
|
||||
assert tmpConf.confChanged is False
|
||||
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setWinSize(70, 70)
|
||||
tmpConf.setWinSize(70, 70)
|
||||
assert tmpConf.getWinSize() == [70, 70]
|
||||
assert tmpConf.winGeometry == [35, 35]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setWinSize(70, 70)
|
||||
tmpConf.setWinSize(70, 70)
|
||||
assert tmpConf.getWinSize() == [70, 70]
|
||||
assert tmpConf.winGeometry == [70, 70]
|
||||
|
||||
assert tmpConf.setWinSize(1200, 650)
|
||||
tmpConf.setWinSize(1200, 650)
|
||||
|
||||
# Preferences Size
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setPreferencesSize(70, 70)
|
||||
tmpConf.setPreferencesSize(70, 70)
|
||||
assert tmpConf.getPreferencesSize() == [70, 70]
|
||||
assert tmpConf.prefGeometry == [35, 35]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setPreferencesSize(70, 70)
|
||||
tmpConf.setPreferencesSize(70, 70)
|
||||
assert tmpConf.getPreferencesSize() == [70, 70]
|
||||
assert tmpConf.prefGeometry == [70, 70]
|
||||
|
||||
assert tmpConf.setPreferencesSize(700, 615)
|
||||
tmpConf.setPreferencesSize(700, 615)
|
||||
|
||||
# Project Settings Tree Columns
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setProjColWidths([10, 20, 30])
|
||||
tmpConf.setProjColWidths([10, 20, 30])
|
||||
assert tmpConf.getProjColWidths() == [10, 20, 30]
|
||||
assert tmpConf.projColWidth == [5, 10, 15]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setProjColWidths([10, 20, 30])
|
||||
tmpConf.setProjColWidths([10, 20, 30])
|
||||
assert tmpConf.getProjColWidths() == [10, 20, 30]
|
||||
assert tmpConf.projColWidth == [10, 20, 30]
|
||||
|
||||
assert tmpConf.setProjColWidths([200, 60, 140])
|
||||
tmpConf.setProjColWidths([200, 60, 140])
|
||||
|
||||
# Main Pane Splitter
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setMainPanePos([200, 700])
|
||||
tmpConf.setMainPanePos([200, 700])
|
||||
assert tmpConf.getMainPanePos() == [200, 700]
|
||||
assert tmpConf.mainPanePos == [100, 350]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setMainPanePos([200, 700])
|
||||
tmpConf.setMainPanePos([200, 700])
|
||||
assert tmpConf.getMainPanePos() == [200, 700]
|
||||
assert tmpConf.mainPanePos == [200, 700]
|
||||
|
||||
assert tmpConf.setMainPanePos([300, 800])
|
||||
tmpConf.setMainPanePos([300, 800])
|
||||
|
||||
# Doc Pane Splitter
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setDocPanePos([300, 300])
|
||||
tmpConf.setDocPanePos([300, 300])
|
||||
assert tmpConf.getDocPanePos() == [300, 300]
|
||||
assert tmpConf.docPanePos == [150, 150]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setDocPanePos([300, 300])
|
||||
tmpConf.setDocPanePos([300, 300])
|
||||
assert tmpConf.getDocPanePos() == [300, 300]
|
||||
assert tmpConf.docPanePos == [300, 300]
|
||||
|
||||
assert tmpConf.setDocPanePos([400, 400])
|
||||
tmpConf.setDocPanePos([400, 400])
|
||||
|
||||
# View Pane Splitter
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setViewPanePos([400, 250])
|
||||
tmpConf.setViewPanePos([400, 250])
|
||||
assert tmpConf.getViewPanePos() == [400, 250]
|
||||
assert tmpConf.viewPanePos == [200, 125]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setViewPanePos([400, 250])
|
||||
tmpConf.setViewPanePos([400, 250])
|
||||
assert tmpConf.getViewPanePos() == [400, 250]
|
||||
assert tmpConf.viewPanePos == [400, 250]
|
||||
|
||||
assert tmpConf.setViewPanePos([500, 150])
|
||||
tmpConf.setViewPanePos([500, 150])
|
||||
|
||||
# Outline Pane Splitter
|
||||
tmpConf.guiScale = 2.0
|
||||
assert tmpConf.setOutlinePanePos([400, 250])
|
||||
tmpConf.setOutlinePanePos([400, 250])
|
||||
assert tmpConf.getOutlinePanePos() == [400, 250]
|
||||
assert tmpConf.outlnPanePos == [200, 125]
|
||||
|
||||
tmpConf.guiScale = 1.0
|
||||
assert tmpConf.setOutlinePanePos([400, 250])
|
||||
tmpConf.setOutlinePanePos([400, 250])
|
||||
assert tmpConf.getOutlinePanePos() == [400, 250]
|
||||
assert tmpConf.outlnPanePos == [400, 250]
|
||||
|
||||
assert tmpConf.setOutlinePanePos([500, 150])
|
||||
tmpConf.setOutlinePanePos([500, 150])
|
||||
|
||||
# Getters Only
|
||||
# ============
|
||||
@@ -429,27 +364,20 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
|
||||
# Flag Setters
|
||||
# ============
|
||||
|
||||
assert tmpConf.setShowRefPanel(False) is False
|
||||
tmpConf.setShowRefPanel(False)
|
||||
assert tmpConf.showRefPanel is False
|
||||
assert tmpConf.setShowRefPanel(True) is True
|
||||
tmpConf.setShowRefPanel(True)
|
||||
assert tmpConf.showRefPanel is True
|
||||
|
||||
assert tmpConf.setViewComments(False) is False
|
||||
tmpConf.setViewComments(False)
|
||||
assert tmpConf.viewComments is False
|
||||
assert tmpConf.setViewComments(True) is True
|
||||
tmpConf.setViewComments(True)
|
||||
assert tmpConf.viewComments is True
|
||||
|
||||
assert tmpConf.setViewSynopsis(False) is False
|
||||
tmpConf.setViewSynopsis(False)
|
||||
assert tmpConf.viewSynopsis is False
|
||||
assert tmpConf.setViewSynopsis(True) is True
|
||||
|
||||
# Check Final File
|
||||
# ================
|
||||
|
||||
assert tmpConf.confChanged is True
|
||||
assert tmpConf.saveConfig() is True
|
||||
assert tmpConf.confChanged is False
|
||||
|
||||
copyfile(confFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=("timestamp", "lastnotes", "guilang"))
|
||||
tmpConf.setViewSynopsis(True)
|
||||
assert tmpConf.viewSynopsis is True
|
||||
|
||||
# END Test testBaseConfig_SettersGetters
|
||||
|
||||
@@ -480,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
|
||||
|
||||
@@ -20,9 +20,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtWidgets import QMessageBox, qApp
|
||||
|
||||
from mock import causeException
|
||||
|
||||
@@ -30,18 +27,9 @@ from novelwriter.error import NWErrorMessage, exceptionHandler
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
|
||||
def testBaseError_Dialog(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the error dialog.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
|
||||
qApp.closeAllWindows()
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(20)
|
||||
|
||||
nwErr = NWErrorMessage(nwGUI)
|
||||
qtbot.addWidget(nwErr)
|
||||
nwErr.show()
|
||||
@@ -76,19 +64,11 @@ def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir):
|
||||
def testBaseError_Handler(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the error handler. This test doesn'thave any asserts, but it
|
||||
checks that the error handler handles potential exceptions. The test
|
||||
will fail if excpetions are not handled.
|
||||
"""
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
|
||||
qApp.closeAllWindows()
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
qtbot.wait(20)
|
||||
|
||||
# Normal shutdown
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(NWErrorMessage, "exec_", lambda *a: None)
|
||||
|
||||
@@ -28,13 +28,13 @@ from mock import MockGuiMain
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
|
||||
def testBaseInit_Launch(caplog, monkeypatch, tmpPath):
|
||||
"""Check launching the main GUI.
|
||||
"""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
|
||||
# TestMode Launch
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
nwGUI = novelwriter.main(["--testmode", f"--config={tmpPath}", f"--data={tmpPath}"])
|
||||
assert isinstance(nwGUI, MockGuiMain)
|
||||
|
||||
# Darwin Launch
|
||||
@@ -43,7 +43,7 @@ def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
|
||||
novelwriter.CONFIG.osDarwin = True
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setitem(sys.modules, "Foundation", None)
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
nwGUI = novelwriter.main(["--testmode", f"--config={tmpPath}", f"--data={tmpPath}"])
|
||||
assert isinstance(nwGUI, MockGuiMain)
|
||||
assert "Failed" in caplog.text
|
||||
|
||||
@@ -55,7 +55,7 @@ def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
|
||||
novelwriter.CONFIG.osWindows = True
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setitem(sys.modules, "ctypes", None)
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
nwGUI = novelwriter.main(["--testmode", f"--config={tmpPath}", f"--data={tmpPath}"])
|
||||
assert isinstance(nwGUI, MockGuiMain)
|
||||
if not sys.platform.startswith("darwin"):
|
||||
# For some reason, the test doesn't work on macOS
|
||||
@@ -71,19 +71,19 @@ def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *a: None)
|
||||
monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *a: 0)
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
novelwriter.main(["--config=%s" % tmpDir, "--data=%s" % tmpDir])
|
||||
novelwriter.main([f"--config={tmpPath}", f"--data={tmpPath}"])
|
||||
assert ex.value.code == 0
|
||||
|
||||
# END Test testBaseInit_Launch
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Options(monkeypatch, tmpDir):
|
||||
def testBaseInit_Options(monkeypatch, tmpPath):
|
||||
"""Test command line options for logging level.
|
||||
"""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
monkeypatch.setattr(sys, "argv", [
|
||||
"novelWriter.py", "--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir
|
||||
"novelWriter.py", "--testmode", f"--config={tmpPath}", f"--data={tmpPath}"
|
||||
])
|
||||
|
||||
# Defaults w/None Args
|
||||
@@ -93,20 +93,20 @@ def testBaseInit_Options(monkeypatch, tmpDir):
|
||||
|
||||
# Defaults
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "--style=Fusion"]
|
||||
["--testmode", f"--config={tmpPath}", f"--data={tmpPath}", "--style=Fusion"]
|
||||
)
|
||||
assert novelwriter.logger.getEffectiveLevel() == logging.WARNING
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
|
||||
# Log Levels
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--info", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", "--info", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
assert novelwriter.logger.getEffectiveLevel() == logging.INFO
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--debug", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", "--debug", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
assert novelwriter.logger.getEffectiveLevel() == logging.DEBUG
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
@@ -114,14 +114,14 @@ def testBaseInit_Options(monkeypatch, tmpDir):
|
||||
# Help and Version
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--help", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", "--help", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
assert ex.value.code == 0
|
||||
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--version", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", "--version", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
assert ex.value.code == 0
|
||||
@@ -129,14 +129,14 @@ def testBaseInit_Options(monkeypatch, tmpDir):
|
||||
# Invalid options
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--invalid", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", "--invalid", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
assert ex.value.code == 2
|
||||
|
||||
# Project Path
|
||||
nwGUI = novelwriter.main(
|
||||
["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "sample/"]
|
||||
["--testmode", f"--config={tmpPath}", f"--data={tmpPath}", "sample/"]
|
||||
)
|
||||
assert novelwriter.CONFIG.cmdOpen == "sample/"
|
||||
assert nwGUI.closeMain() == "closeMain"
|
||||
@@ -145,7 +145,7 @@ def testBaseInit_Options(monkeypatch, tmpDir):
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
|
||||
def testBaseInit_Imports(caplog, monkeypatch, tmpPath):
|
||||
"""Check import error handling.
|
||||
"""
|
||||
monkeypatch.setattr("novelwriter.guimain.GuiMain", MockGuiMain)
|
||||
@@ -161,7 +161,7 @@ def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
|
||||
|
||||
with pytest.raises(SystemExit) as ex:
|
||||
_ = novelwriter.main(
|
||||
["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
|
||||
["--testmode", f"--config={tmpPath}", f"--data={tmpPath}"]
|
||||
)
|
||||
|
||||
assert ex.value.code & 4 == 4 # Python version not satisfied
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import uuid
|
||||
import pytest
|
||||
|
||||
@@ -35,12 +34,12 @@ from novelwriter.core.coretools import DocMerger, DocSplitter, ProjectBuilder
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText):
|
||||
def testCoreTools_DocMerger(monkeypatch, mockGUI, fncPath, tstPaths, mockRnd, ipsumText):
|
||||
"""Test the DocMerger utility.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
# Create Files to Merge
|
||||
# =====================
|
||||
@@ -77,9 +76,9 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRn
|
||||
# Merge to New
|
||||
# ============
|
||||
|
||||
saveFile = os.path.join(fncDir, "content", "0000000000014.nwd")
|
||||
testFile = os.path.join(outDir, "coreDocTools_DocMerger_0000000000014.nwd")
|
||||
compFile = os.path.join(refDir, "coreDocTools_DocMerger_0000000000014.nwd")
|
||||
saveFile = fncPath / "content" / "0000000000014.nwd"
|
||||
testFile = tstPaths.outDir / "coreDocTools_DocMerger_0000000000014.nwd"
|
||||
compFile = tstPaths.refDir / "coreDocTools_DocMerger_0000000000014.nwd"
|
||||
|
||||
assert docMerger.newTargetDoc(hChapter1, "All of Chapter 1") == "0000000000014"
|
||||
|
||||
@@ -92,7 +91,7 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRn
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert docMerger.writeTargetDoc() is False
|
||||
assert not os.path.isfile(saveFile)
|
||||
assert not saveFile.exists()
|
||||
assert docMerger.getError() != ""
|
||||
|
||||
# Write properly, and compare
|
||||
@@ -103,9 +102,9 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRn
|
||||
# Merge into Existing
|
||||
# ===================
|
||||
|
||||
saveFile = os.path.join(fncDir, "content", "0000000000010.nwd")
|
||||
testFile = os.path.join(outDir, "coreDocTools_DocMerger_0000000000010.nwd")
|
||||
compFile = os.path.join(refDir, "coreDocTools_DocMerger_0000000000010.nwd")
|
||||
saveFile = fncPath / "content" / "0000000000010.nwd"
|
||||
testFile = tstPaths.outDir / "coreDocTools_DocMerger_0000000000010.nwd"
|
||||
compFile = tstPaths.refDir / "coreDocTools_DocMerger_0000000000010.nwd"
|
||||
|
||||
docMerger.setTargetDoc(hChapter1)
|
||||
|
||||
@@ -124,12 +123,12 @@ def testCoreTools_DocMerger(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRn
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mockRnd, ipsumText):
|
||||
def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncPath, mockRnd, ipsumText):
|
||||
"""Test the DocSplitter utility.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
# Create File to Split
|
||||
# ====================
|
||||
@@ -264,15 +263,15 @@ def testCoreTools_DocSplitter(monkeypatch, mockGUI, fncDir, outDir, refDir, mock
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewMinimal(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
|
||||
def testCoreTools_NewMinimal(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary. With
|
||||
default setting, creating a Minimal project.
|
||||
"""
|
||||
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
|
||||
|
||||
projFile = os.path.join(fncDir, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "coreTools_NewMinimal_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "coreTools_NewMinimal_nwProject.nwx")
|
||||
projFile = fncPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "coreTools_NewMinimal_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreTools_NewMinimal_nwProject.nwx"
|
||||
|
||||
projBuild = ProjectBuilder(mockGUI)
|
||||
|
||||
@@ -283,10 +282,10 @@ def testCoreTools_NewMinimal(monkeypatch, fncDir, outDir, refDir, mockGUI, mockR
|
||||
assert projBuild.buildProject("stuff") is False
|
||||
|
||||
# Try again with a proper path
|
||||
assert projBuild.buildProject({"projPath": fncDir}) is True
|
||||
assert projBuild.buildProject({"projPath": fncPath}) is True
|
||||
|
||||
# Creating the project once more should fail
|
||||
assert projBuild.buildProject({"projPath": fncDir}) is False
|
||||
assert projBuild.buildProject({"projPath": fncPath}) is False
|
||||
|
||||
# Save and close
|
||||
copyfile(projFile, testFile)
|
||||
@@ -296,21 +295,21 @@ def testCoreTools_NewMinimal(monkeypatch, fncDir, outDir, refDir, mockGUI, mockR
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewCustomA(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
|
||||
def testCoreTools_NewCustomA(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary.
|
||||
Custom type with chapters and scenes.
|
||||
"""
|
||||
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
|
||||
|
||||
projFile = os.path.join(fncDir, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "coreTools_NewCustomA_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "coreTools_NewCustomA_nwProject.nwx")
|
||||
projFile = fncPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "coreTools_NewCustomA_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreTools_NewCustomA_nwProject.nwx"
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": fncDir,
|
||||
"projPath": fncPath,
|
||||
"popSample": False,
|
||||
"popMinimal": False,
|
||||
"popCustom": True,
|
||||
@@ -334,21 +333,21 @@ def testCoreTools_NewCustomA(monkeypatch, fncDir, outDir, refDir, mockGUI, mockR
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewCustomB(monkeypatch, fncDir, outDir, refDir, mockGUI, mockRnd):
|
||||
def testCoreTools_NewCustomB(monkeypatch, fncPath, tstPaths, mockGUI, mockRnd):
|
||||
"""Create a new project from a project wizard dictionary.
|
||||
Custom type without chapters, but with scenes.
|
||||
"""
|
||||
monkeypatch.setattr("uuid.uuid4", lambda *a: uuid.UUID("d0f3fe10-c6e6-4310-8bfd-181eb4224eed"))
|
||||
|
||||
projFile = os.path.join(fncDir, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "coreTools_NewCustomB_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "coreTools_NewCustomB_nwProject.nwx")
|
||||
projFile = fncPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "coreTools_NewCustomB_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "coreTools_NewCustomB_nwProject.nwx"
|
||||
|
||||
projData = {
|
||||
"projName": "Test Custom",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": fncDir,
|
||||
"projPath": fncPath,
|
||||
"popSample": False,
|
||||
"popMinimal": False,
|
||||
"popCustom": True,
|
||||
@@ -372,7 +371,7 @@ def testCoreTools_NewCustomB(monkeypatch, fncDir, outDir, refDir, mockGUI, mockR
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir):
|
||||
def testCoreTools_NewSample(monkeypatch, fncPath, tmpConf, tmpPath, mockGUI):
|
||||
"""Check that we can create a new project can be created from the
|
||||
provided sample project via a zip file.
|
||||
"""
|
||||
@@ -380,7 +379,7 @@ def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir):
|
||||
"projName": "Test Sample",
|
||||
"projTitle": "Test Novel",
|
||||
"projAuthors": "Jane Doe\nJohn Doh\n",
|
||||
"projPath": fncDir,
|
||||
"projPath": fncPath,
|
||||
"popSample": True,
|
||||
"popMinimal": False,
|
||||
"popCustom": False,
|
||||
@@ -392,9 +391,11 @@ def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir):
|
||||
assert projBuild.buildProject({"popSample": True}) is False
|
||||
|
||||
# Force the lookup path for assets to our temp folder
|
||||
srcSample = os.path.abspath(os.path.join(tmpConf.appRoot, "sample"))
|
||||
dstSample = os.path.join(tmpDir, "sample.zip")
|
||||
tmpConf.assetPath = tmpDir
|
||||
srcSample = tmpConf._appRoot / "sample"
|
||||
dstSample = tmpPath / "sample.zip"
|
||||
monkeypatch.setattr(
|
||||
"novelwriter.config.Config.assetPath", lambda *a: tmpPath / "sample.zip"
|
||||
)
|
||||
|
||||
# Cannot extract when the zip does not exist
|
||||
assert projBuild.buildProject(projData) is False
|
||||
@@ -404,16 +405,15 @@ def testCoreTools_NewSample(fncDir, tmpConf, mockGUI, tmpDir):
|
||||
outFile.write("foo")
|
||||
|
||||
assert projBuild.buildProject(projData) is False
|
||||
os.unlink(dstSample)
|
||||
dstSample.unlink()
|
||||
|
||||
# Create a real zip file, and unpack it
|
||||
with ZipFile(dstSample, "w") as zipObj:
|
||||
zipObj.write(os.path.join(srcSample, "nwProject.nwx"), "nwProject.nwx")
|
||||
for docFile in os.listdir(os.path.join(srcSample, "content")):
|
||||
srcDoc = os.path.join(srcSample, "content", docFile)
|
||||
zipObj.write(srcDoc, "content/"+docFile)
|
||||
zipObj.write(srcSample / "nwProject.nwx", "nwProject.nwx")
|
||||
for docFile in (srcSample / "content").iterdir():
|
||||
zipObj.write(docFile, f"content/{docFile.name}")
|
||||
|
||||
assert projBuild.buildProject(projData) is True
|
||||
os.unlink(dstSample)
|
||||
dstSample.unlink()
|
||||
|
||||
# END Test testCoreTools_NewSample
|
||||
|
||||
@@ -23,7 +23,6 @@ import json
|
||||
import pytest
|
||||
|
||||
from shutil import copyfile
|
||||
from pathlib import Path
|
||||
|
||||
from mock import causeException
|
||||
from tools import C, buildTestProject, cmpFiles, writeFile
|
||||
@@ -35,16 +34,16 @@ from novelwriter.core.project import NWProject
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_LoadSave(monkeypatch, nwLipsum, mockGUI, tstPaths):
|
||||
def testCoreIndex_LoadSave(monkeypatch, prjLipsum, mockGUI, tstPaths):
|
||||
"""Test core functionality of scaning, saving, loading and checking
|
||||
the index cache file.
|
||||
"""
|
||||
projFile = Path(nwLipsum) / "meta" / nwFiles.INDEX_FILE
|
||||
projFile = prjLipsum / "meta" / nwFiles.INDEX_FILE
|
||||
testFile = tstPaths.outDir / "coreIndex_LoadSave_tagsIndex.json"
|
||||
compFile = tstPaths.refDir / "coreIndex_LoadSave_tagsIndex.json"
|
||||
|
||||
theProject = NWProject(mockGUI)
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theProject.openProject(prjLipsum)
|
||||
|
||||
theIndex = NWIndex(theProject)
|
||||
assert repr(theIndex) == "<NWIndex project='Lorem Ipsum'>"
|
||||
@@ -196,12 +195,12 @@ def testCoreIndex_ScanThis(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
|
||||
def testCoreIndex_CheckThese(mockGUI, fncPath, mockRnd):
|
||||
"""Test the tag checker function checkThese.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
theIndex = theProject.index
|
||||
theIndex.clearIndex()
|
||||
|
||||
@@ -274,12 +273,12 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ScanText(mockGUI, fncDir, mockRnd):
|
||||
def testCoreIndex_ScanText(mockGUI, fncPath, mockRnd):
|
||||
"""Check the index text scanner.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
theIndex = theProject.index
|
||||
|
||||
# Some items for fail to scan tests
|
||||
@@ -486,12 +485,12 @@ def testCoreIndex_ScanText(mockGUI, fncDir, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ExtractData(mockGUI, fncDir, mockRnd):
|
||||
def testCoreIndex_ExtractData(mockGUI, fncPath, mockRnd):
|
||||
"""Check the index data extraction functions.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
theIndex = theProject.index
|
||||
theIndex.reIndexHandle(C.hNovelRoot)
|
||||
@@ -940,12 +939,12 @@ def testCoreIndex_TagsIndex():
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreIndex_ItemIndex(mockGUI, fncDir, mockRnd):
|
||||
def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
||||
"""Check the ItemIndex class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
theProject.index.clearIndex()
|
||||
|
||||
nHandle = C.hTitlePage
|
||||
|
||||
@@ -31,12 +31,12 @@ from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreItem_Setters(mockGUI, mockRnd, fncDir):
|
||||
def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
|
||||
"""Test all the simple setters for the NWItem class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
theItem = NWItem(theProject)
|
||||
|
||||
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
|
||||
@@ -192,12 +192,12 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncDir):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreItem_Methods(mockGUI, mockRnd, fncDir):
|
||||
def testCoreItem_Methods(mockGUI, mockRnd, fncPath):
|
||||
"""Test the simple methods of the NWItem class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
theItem = NWItem(theProject)
|
||||
|
||||
# Describe Me
|
||||
|
||||
@@ -180,6 +180,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
# Fail on lock file
|
||||
assert theProject._storage.writeLockFile()
|
||||
assert theProject.openProject(fncPath) is False
|
||||
assert isinstance(theProject.getLockStatus(), list)
|
||||
|
||||
# Fail to read lockfile (which still opens the project)
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -193,6 +194,7 @@ def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncPath, mockRnd):
|
||||
assert theProject._storage.writeLockFile()
|
||||
assert theProject.openProject(fncPath, overrideLock=True) is True
|
||||
assert theProject.closeProject()
|
||||
assert theProject.getLockStatus() is None
|
||||
|
||||
# Fail getting xml reader
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -625,7 +627,7 @@ def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
def testCoreProject_OrphanedFiles(mockGUI, prjLipsum):
|
||||
"""Check that files in the content folder that are not tracked in
|
||||
the project XML file are handled correctly by the orphaned files
|
||||
function. It should also restore as much meta data as possible from
|
||||
@@ -633,7 +635,7 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
|
||||
assert theProject.openProject(nwLipsum) is True
|
||||
assert theProject.openProject(prjLipsum) is True
|
||||
assert theProject.tree["636b6aa9b697b"] is None
|
||||
|
||||
# Add a file with non-existent parent
|
||||
@@ -646,7 +648,7 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
assert theProject.closeProject() is True
|
||||
|
||||
# First Item with Meta Data
|
||||
orphPath = Path(nwLipsum) / "content" / "636b6aa9b697b.nwd"
|
||||
orphPath = prjLipsum / "content" / "636b6aa9b697b.nwd"
|
||||
writeFile(orphPath, (
|
||||
"%%~name:[Recovered] Mars\n"
|
||||
"%%~path:5eaea4e8cdee8/636b6aa9b697b\n"
|
||||
@@ -656,22 +658,22 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
))
|
||||
|
||||
# Second Item without Meta Data
|
||||
orphPath = Path(nwLipsum) / "content" / "736b6aa9b697b.nwd"
|
||||
orphPath = prjLipsum / "content" / "736b6aa9b697b.nwd"
|
||||
writeFile(orphPath, "\n")
|
||||
|
||||
# Invalid File Name
|
||||
tstPath = Path(nwLipsum) / "content" / "636b6aa9b697b.txt"
|
||||
tstPath = prjLipsum / "content" / "636b6aa9b697b.txt"
|
||||
writeFile(tstPath, "\n")
|
||||
|
||||
# Invalid File Name
|
||||
tstPath = Path(nwLipsum) / "content" / "636b6aa9b697bb.nwd"
|
||||
tstPath = prjLipsum / "content" / "636b6aa9b697bb.nwd"
|
||||
writeFile(tstPath, "\n")
|
||||
|
||||
# Invalid File Name
|
||||
tstPath = Path(nwLipsum) / "content" / "abcdefghijklm.nwd"
|
||||
tstPath = prjLipsum / "content" / "abcdefghijklm.nwd"
|
||||
writeFile(tstPath, "\n")
|
||||
|
||||
assert theProject.openProject(nwLipsum)
|
||||
assert theProject.openProject(prjLipsum)
|
||||
assert theProject.storage.storagePath is not None
|
||||
assert theProject.storage.runtimePath is not None
|
||||
assert theProject.tree["636b6aa9b697bb"] is None
|
||||
@@ -697,7 +699,7 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
|
||||
assert oItem.itemType == nwItemType.FILE
|
||||
assert oItem.itemLayout == nwItemLayout.NOTE
|
||||
|
||||
assert theProject.saveProject(nwLipsum)
|
||||
assert theProject.saveProject(prjLipsum)
|
||||
assert theProject.closeProject()
|
||||
|
||||
# Finally, check that the orphaned files function returns
|
||||
@@ -730,17 +732,17 @@ def testCoreProject_Backup(monkeypatch, mockGUI, fncPath, tmpPath):
|
||||
mockGUI.hasProject = True
|
||||
|
||||
# Invalid path
|
||||
theProject.mainConf.backupPath = None
|
||||
theProject.mainConf._backupPath = None
|
||||
assert theProject.backupProject(doNotify=False) is False
|
||||
|
||||
# Missing project name
|
||||
theProject.mainConf.backupPath = str(tmpPath)
|
||||
theProject.mainConf._backupPath = tmpPath
|
||||
theProject.data.setName("")
|
||||
assert theProject.backupProject(doNotify=False) is False
|
||||
|
||||
# Valid Settings
|
||||
# ==============
|
||||
theProject.mainConf.backupPath = str(tmpPath)
|
||||
theProject.mainConf._backupPath = tmpPath
|
||||
theProject.data.setName("Test Minimal")
|
||||
|
||||
# Can't make folder
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import readFile
|
||||
@@ -441,7 +440,7 @@ def testCoreToHtml_SpecialCases(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToHtml_Complex(mockGUI, fncDir):
|
||||
def testCoreToHtml_Complex(mockGUI, fncPath):
|
||||
"""Test the save method of the ToHtml class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
@@ -529,7 +528,7 @@ def testCoreToHtml_Complex(mockGUI, fncDir):
|
||||
bodyText="".join(resText).rstrip()
|
||||
)
|
||||
|
||||
saveFile = os.path.join(fncDir, "outFile.htm")
|
||||
saveFile = fncPath / "outFile.htm"
|
||||
theHtml.saveHTML5(saveFile)
|
||||
assert readFile(saveFile) == htmlDoc
|
||||
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import C, buildTestProject, readFile
|
||||
@@ -132,12 +131,12 @@ def testCoreToken_Setters(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncDir):
|
||||
def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
|
||||
"""Test handling files and text in the Tokenizer class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
mockRnd.reset()
|
||||
buildTestProject(theProject, fncDir)
|
||||
buildTestProject(theProject, fncPath)
|
||||
|
||||
theProject.data.setLanguage("en")
|
||||
theProject._loadProjectLocalisation()
|
||||
@@ -210,7 +209,7 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncDir):
|
||||
assert theToken.theResult == "This is text with escapes: ** ~~ __"
|
||||
|
||||
# Save File
|
||||
savePath = os.path.join(fncDir, "dump.nwd")
|
||||
savePath = fncPath / "dump.nwd"
|
||||
theToken.saveRawMarkdown(savePath)
|
||||
assert readFile(savePath) == (
|
||||
"# Notes: Plot\n\n"
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import readFile
|
||||
@@ -208,7 +207,7 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToMarkdown_Complex(mockGUI, fncDir):
|
||||
def testCoreToMarkdown_Complex(mockGUI, fncPath):
|
||||
"""Test the save method of the ToMarkdown class.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
@@ -253,7 +252,7 @@ def testCoreToMarkdown_Complex(mockGUI, fncDir):
|
||||
# Check File
|
||||
# ==========
|
||||
|
||||
saveFile = os.path.join(fncDir, "outFile.md")
|
||||
saveFile = fncPath / "outFile.md"
|
||||
theMD.saveMarkdown(saveFile)
|
||||
assert readFile(saveFile) == "".join(resText)
|
||||
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import zipfile
|
||||
|
||||
@@ -612,7 +611,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_SaveFlat(mockGUI, fncDir, outDir, refDir):
|
||||
def testCoreToOdt_SaveFlat(mockGUI, fncPath, tstPaths):
|
||||
"""Test the document save functions.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
@@ -634,12 +633,12 @@ def testCoreToOdt_SaveFlat(mockGUI, fncDir, outDir, refDir):
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
|
||||
flatFile = os.path.join(fncDir, "document.fodt")
|
||||
testFile = os.path.join(outDir, "coreToOdt_SaveFlat_document.fodt")
|
||||
compFile = os.path.join(refDir, "coreToOdt_SaveFlat_document.fodt")
|
||||
flatFile = fncPath / "document.fodt"
|
||||
testFile = tstPaths.outDir / "coreToOdt_SaveFlat_document.fodt"
|
||||
compFile = tstPaths.refDir / "coreToOdt_SaveFlat_document.fodt"
|
||||
|
||||
theDoc.saveFlatXML(flatFile)
|
||||
assert os.path.isfile(flatFile)
|
||||
assert flatFile.exists()
|
||||
|
||||
copyfile(flatFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [4, 5])
|
||||
@@ -648,7 +647,7 @@ def testCoreToOdt_SaveFlat(mockGUI, fncDir, outDir, refDir):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreToOdt_SaveFull(mockGUI, fncDir, outDir, refDir):
|
||||
def testCoreToOdt_SaveFull(mockGUI, fncPath, tstPaths):
|
||||
"""Test the document save functions.
|
||||
"""
|
||||
theProject = NWProject(mockGUI)
|
||||
@@ -667,25 +666,25 @@ def testCoreToOdt_SaveFull(mockGUI, fncDir, outDir, refDir):
|
||||
theDoc.doConvert()
|
||||
theDoc.closeDocument()
|
||||
|
||||
fullFile = os.path.join(fncDir, "document.odt")
|
||||
fullFile = fncPath / "document.odt"
|
||||
|
||||
theDoc.saveOpenDocText(fullFile)
|
||||
assert os.path.isfile(fullFile)
|
||||
assert fullFile.exists()
|
||||
assert zipfile.is_zipfile(fullFile)
|
||||
|
||||
maniFile = os.path.join(outDir, "coreToOdt_SaveFull_manifest.xml")
|
||||
settFile = os.path.join(outDir, "coreToOdt_SaveFull_settings.xml")
|
||||
contFile = os.path.join(outDir, "coreToOdt_SaveFull_content.xml")
|
||||
metaFile = os.path.join(outDir, "coreToOdt_SaveFull_meta.xml")
|
||||
stylFile = os.path.join(outDir, "coreToOdt_SaveFull_styles.xml")
|
||||
maniFile = tstPaths.outDir / "coreToOdt_SaveFull_manifest.xml"
|
||||
settFile = tstPaths.outDir / "coreToOdt_SaveFull_settings.xml"
|
||||
contFile = tstPaths.outDir / "coreToOdt_SaveFull_content.xml"
|
||||
metaFile = tstPaths.outDir / "coreToOdt_SaveFull_meta.xml"
|
||||
stylFile = tstPaths.outDir / "coreToOdt_SaveFull_styles.xml"
|
||||
|
||||
maniComp = os.path.join(refDir, "coreToOdt_SaveFull_manifest.xml")
|
||||
settComp = os.path.join(refDir, "coreToOdt_SaveFull_settings.xml")
|
||||
contComp = os.path.join(refDir, "coreToOdt_SaveFull_content.xml")
|
||||
metaComp = os.path.join(refDir, "coreToOdt_SaveFull_meta.xml")
|
||||
stylComp = os.path.join(refDir, "coreToOdt_SaveFull_styles.xml")
|
||||
maniComp = tstPaths.refDir / "coreToOdt_SaveFull_manifest.xml"
|
||||
settComp = tstPaths.refDir / "coreToOdt_SaveFull_settings.xml"
|
||||
contComp = tstPaths.refDir / "coreToOdt_SaveFull_content.xml"
|
||||
metaComp = tstPaths.refDir / "coreToOdt_SaveFull_meta.xml"
|
||||
stylComp = tstPaths.refDir / "coreToOdt_SaveFull_styles.xml"
|
||||
|
||||
extaxtTo = os.path.join(outDir, "coreToOdt_SaveFull")
|
||||
extaxtTo = tstPaths.outDir / "coreToOdt_SaveFull"
|
||||
|
||||
with zipfile.ZipFile(fullFile, mode="r") as theZip:
|
||||
theZip.extract("META-INF/manifest.xml", extaxtTo)
|
||||
@@ -694,17 +693,17 @@ def testCoreToOdt_SaveFull(mockGUI, fncDir, outDir, refDir):
|
||||
theZip.extract("meta.xml", extaxtTo)
|
||||
theZip.extract("styles.xml", extaxtTo)
|
||||
|
||||
maniOut = os.path.join(outDir, "coreToOdt_SaveFull", "META-INF", "manifest.xml")
|
||||
settOut = os.path.join(outDir, "coreToOdt_SaveFull", "settings.xml")
|
||||
contOut = os.path.join(outDir, "coreToOdt_SaveFull", "content.xml")
|
||||
metaOut = os.path.join(outDir, "coreToOdt_SaveFull", "meta.xml")
|
||||
stylOut = os.path.join(outDir, "coreToOdt_SaveFull", "styles.xml")
|
||||
maniOut = tstPaths.outDir / "coreToOdt_SaveFull" / "META-INF" / "manifest.xml"
|
||||
settOut = tstPaths.outDir / "coreToOdt_SaveFull" / "settings.xml"
|
||||
contOut = tstPaths.outDir / "coreToOdt_SaveFull" / "content.xml"
|
||||
metaOut = tstPaths.outDir / "coreToOdt_SaveFull" / "meta.xml"
|
||||
stylOut = tstPaths.outDir / "coreToOdt_SaveFull" / "styles.xml"
|
||||
|
||||
def prettifyXml(inFile, outFile):
|
||||
with open(outFile, mode="wb") as fileStream:
|
||||
fileStream.write(
|
||||
etree.tostring(
|
||||
etree.parse(inFile),
|
||||
etree.parse(str(inFile)),
|
||||
pretty_print=True,
|
||||
encoding="utf-8",
|
||||
xml_declaration=True
|
||||
|
||||
@@ -21,6 +21,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from tools import getGuiItem
|
||||
|
||||
from PyQt5.QtWidgets import QAction, QMessageBox
|
||||
@@ -29,7 +31,7 @@ from novelwriter.dialogs.about import GuiAbout
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgAbout_NWDialog(qtbot, nwGUI):
|
||||
def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the novelWriter about dialogs.
|
||||
"""
|
||||
# NW About
|
||||
@@ -45,13 +47,12 @@ def testDlgAbout_NWDialog(qtbot, nwGUI):
|
||||
assert msgAbout.pageNotes.document().characterCount() > 100
|
||||
assert msgAbout.pageLicense.document().characterCount() > 100
|
||||
|
||||
msgAbout.mainConf.assetPath = "whatever"
|
||||
|
||||
msgAbout._fillNotesPage()
|
||||
assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..."
|
||||
|
||||
msgAbout._fillLicensePage()
|
||||
assert msgAbout.pageLicense.toPlainText() == "Error loading licence text ..."
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("novelwriter.config.Config.assetPath", lambda *a: Path("whatever"))
|
||||
msgAbout._fillNotesPage()
|
||||
assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..."
|
||||
msgAbout._fillLicensePage()
|
||||
assert msgAbout.pageLicense.toPlainText() == "Error loading licence text ..."
|
||||
|
||||
msgAbout.showReleaseNotes()
|
||||
assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
|
||||
|
||||
@@ -29,11 +29,11 @@ from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgMerge_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
def testDlgMerge_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test the merge documents tool.
|
||||
"""
|
||||
# Create a new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Check that the dialog kan handle invalid items
|
||||
nwMerge = GuiDocMerge(nwGUI, C.hInvalid, [C.hInvalid])
|
||||
|
||||
@@ -28,13 +28,13 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test the split document tool.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
# Create a new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
@@ -19,19 +19,17 @@ 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 pytest
|
||||
import novelwriter
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
from tools import cmpFiles, getGuiItem
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog, QMessageBox
|
||||
QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog
|
||||
)
|
||||
|
||||
from novelwriter.config import Config
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
|
||||
@@ -39,31 +37,11 @@ KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
"""Test the load project wizard.
|
||||
"""
|
||||
# Block message box
|
||||
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
|
||||
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
|
||||
|
||||
# Must create a clean config and GUI object as the test-wide
|
||||
# novelwriter.CONFIG object is created on import an can be tainted by other tests
|
||||
confFile = os.path.join(fncDir, "novelwriter.conf")
|
||||
if os.path.isfile(confFile):
|
||||
os.unlink(confFile)
|
||||
theConf = Config()
|
||||
theConf.initConfig(fncDir, fncDir)
|
||||
theConf.setLastPath("")
|
||||
origConf = novelwriter.CONFIG
|
||||
novelwriter.CONFIG = theConf
|
||||
|
||||
nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
|
||||
qtbot.addWidget(nwGUI)
|
||||
nwGUI.show()
|
||||
|
||||
theConf = nwGUI.mainConf
|
||||
assert theConf.confPath == fncDir
|
||||
assert theConf._confPath == fncPath
|
||||
|
||||
monkeypatch.setattr(GuiPreferences, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiPreferences, "result", lambda *a: QDialog.Accepted)
|
||||
@@ -80,7 +58,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
nwPrefs = getGuiItem("GuiPreferences")
|
||||
assert isinstance(nwPrefs, GuiPreferences)
|
||||
nwPrefs.show()
|
||||
assert nwPrefs.mainConf.confPath == fncDir
|
||||
assert nwPrefs.mainConf._confPath == fncPath
|
||||
|
||||
assert nwPrefs.updateTheme is False
|
||||
assert nwPrefs.updateSyntax is False
|
||||
@@ -238,22 +216,20 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
|
||||
nwPrefs._doClose()
|
||||
|
||||
assert theConf.confChanged
|
||||
theConf.lastPath = ""
|
||||
|
||||
assert nwGUI.mainConf.saveConfig()
|
||||
projFile = os.path.join(fncDir, "novelwriter.conf")
|
||||
testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf")
|
||||
compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
|
||||
projFile = fncPath / "novelwriter.conf"
|
||||
testFile = tstPaths.outDir / "guiPreferences_novelwriter.conf"
|
||||
compFile = tstPaths.refDir / "guiPreferences_novelwriter.conf"
|
||||
copyfile(projFile, testFile)
|
||||
ignTuple = (
|
||||
"timestamp", "guifont", "lastnotes", "guilang", "geometry",
|
||||
"preferences", "projcols", "mainpane", "docpane", "viewpane",
|
||||
"outlinepane", "textfont", "textsize"
|
||||
"outlinepane", "textfont", "textsize", "lastpath", "backuppath"
|
||||
)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=ignTuple)
|
||||
|
||||
# Clean up
|
||||
novelwriter.CONFIG = origConf
|
||||
nwGUI.closeMain()
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
@@ -38,7 +38,6 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum):
|
||||
qtbot.wait(100)
|
||||
|
||||
# Open the Writing Stats dialog
|
||||
nwGUI.mainConf.lastPath = ""
|
||||
nwGUI.mainMenu.aProjectDetails.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiProjectDetails") is not None, timeout=1000)
|
||||
|
||||
|
||||
@@ -20,7 +20,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from tools import buildTestProject, getGuiItem
|
||||
|
||||
@@ -33,10 +32,10 @@ from novelwriter.dialogs.projload import GuiProjectLoad
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, projPath):
|
||||
"""Test the load project wizard.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.closeProject()
|
||||
|
||||
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
|
||||
@@ -87,10 +86,10 @@ def testDlgLoadProject_Main(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
nwLoad._doDeleteRecent()
|
||||
assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
|
||||
|
||||
getFile = os.path.join(fncProj, "nwProject.nwx")
|
||||
getFile = str(projPath / "nwProject.nwx")
|
||||
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (getFile, None))
|
||||
qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
|
||||
assert nwLoad.openPath == fncProj
|
||||
assert nwLoad.openPath == projPath / "nwProject.nwx"
|
||||
assert nwLoad.openState == nwLoad.OPEN_STATE
|
||||
|
||||
nwLoad.close()
|
||||
|
||||
@@ -82,16 +82,16 @@ def testDlgProjSettings_Dialog(qtbot, monkeypatch, nwGUI):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
"""Test the main tab of the project settings dialog.
|
||||
"""
|
||||
# Mock components
|
||||
monkeypatch.setattr(nwGUI.docEditor.spEnchant, "listDictionaries", lambda: [("en", "none")])
|
||||
|
||||
# Create new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
mockRnd.reset()
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
nwGUI.mainConf.backupPath = fncPath
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
@@ -148,7 +148,7 @@ def testDlgProjSettings_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
"""Test the status and importance tabs of the project settings
|
||||
dialog.
|
||||
"""
|
||||
@@ -159,8 +159,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj,
|
||||
|
||||
# Create new project
|
||||
mockRnd.reset()
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
buildTestProject(nwGUI, projPath)
|
||||
nwGUI.mainConf.backupPath = fncPath
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
@@ -350,7 +350,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, fncDir, fncProj,
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
"""Test the auto-replace tab of the project settings dialog.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -360,8 +360,8 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mock
|
||||
|
||||
# Create new project
|
||||
mockRnd.reset()
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
nwGUI.mainConf.backupPath = fncDir
|
||||
buildTestProject(nwGUI, projPath)
|
||||
nwGUI.mainConf.backupPath = fncPath
|
||||
|
||||
# Set some values
|
||||
theProject = nwGUI.theProject
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
@@ -33,18 +32,18 @@ from novelwriter.dialogs.wordlist import GuiWordList
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
def testDlgWordList_Dialog(qtbot, monkeypatch, nwGUI, projPath):
|
||||
"""test the word list editor.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
monkeypatch.setattr(GuiWordList, "exec_", lambda *a: None)
|
||||
monkeypatch.setattr(GuiWordList, "result", lambda *a: QDialog.Accepted)
|
||||
monkeypatch.setattr(GuiWordList, "accept", lambda *a: None)
|
||||
|
||||
# Open project
|
||||
nwGUI.openProject(fncProj)
|
||||
dictFile = os.path.join(fncProj, "meta", nwFiles.PROJ_DICT)
|
||||
nwGUI.openProject(projPath)
|
||||
dictFile = projPath / "meta" / nwFiles.PROJ_DICT
|
||||
|
||||
# Load the dialog
|
||||
nwGUI.mainMenu.aEditWordList.activate(QAction.Trigger)
|
||||
|
||||
@@ -37,11 +37,11 @@ KEY_DELAY = 1
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Init(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_Init(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test initialising the editor.
|
||||
"""
|
||||
# Open project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc)
|
||||
|
||||
nwGUI.docEditor.setText("### Lorem Ipsum\n\n%s" % ipsumText[0])
|
||||
@@ -80,10 +80,10 @@ def testGuiEditor_Init(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test loading text into the editor.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
longText = "### Lorem Ipsum\n\n%s" % "\n\n".join(ipsumText*20)
|
||||
@@ -135,10 +135,10 @@ def testGuiEditor_LoadText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test saving text from the editor.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
# Save Text
|
||||
@@ -179,10 +179,10 @@ def testGuiEditor_SaveText(qtbot, monkeypatch, caplog, nwGUI, fncProj, ipsumText
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_MetaData(qtbot, nwGUI, fncProj, mockRnd):
|
||||
def testGuiEditor_MetaData(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test extracting various meta data and other values.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
# Get Text
|
||||
@@ -226,13 +226,13 @@ def testGuiEditor_MetaData(qtbot, nwGUI, fncProj, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Actions(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the document actions. This is not an extensive test of the
|
||||
action features, just that the actions are actually called. The
|
||||
various action features are tested when their respective functions
|
||||
are tested.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
@@ -459,10 +459,10 @@ def testGuiEditor_Actions(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the document insert functions.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
@@ -542,10 +542,10 @@ def testGuiEditor_Insert(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd)
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the text manipulation functions.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
@@ -749,10 +749,10 @@ def testGuiEditor_TextManipulation(qtbot, monkeypatch, nwGUI, fncProj, ipsumText
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the block formatting function.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
theText = "### A Scene\n\n%s" % "\n\n".join(ipsumText)
|
||||
@@ -1062,10 +1062,10 @@ def testGuiEditor_BlockFormatting(qtbot, monkeypatch, nwGUI, fncProj, ipsumText,
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_Tags(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_Tags(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test the document editor tags functionality.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
|
||||
# Create Scene
|
||||
@@ -1121,7 +1121,7 @@ def testGuiEditor_Tags(qtbot, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mockRnd):
|
||||
def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, mockRnd):
|
||||
"""Test saving text from the editor.
|
||||
"""
|
||||
class MockThreadPool:
|
||||
@@ -1139,7 +1139,7 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, fncProj, ipsumText, mo
|
||||
nwGUI.docEditor.wcTimerDoc.blockSignals(True)
|
||||
nwGUI.docEditor.wcTimerSel.blockSignals(True)
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Run on an empty document
|
||||
nwGUI.docEditor._runDocCounter()
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from tools import C, cmpFiles, buildTestProject, XML_IGNORE, writeFile
|
||||
@@ -67,7 +66,7 @@ def testGuiMain_ProjectBlocker(nwGUI):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
|
||||
def testGuiMain_NewProject(monkeypatch, nwGUI, projPath):
|
||||
"""Test creating a new project.
|
||||
"""
|
||||
# No data
|
||||
@@ -79,34 +78,34 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, fncProj):
|
||||
with monkeypatch.context() as mp:
|
||||
nwGUI.hasProject = True
|
||||
mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
|
||||
assert nwGUI.newProject(projData={"projPath": fncProj}) is False
|
||||
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
||||
|
||||
# No project path
|
||||
assert nwGUI.newProject(projData={}) is False
|
||||
|
||||
# Project file already exists
|
||||
projFile = os.path.join(fncProj, nwFiles.PROJ_FILE)
|
||||
projFile = projPath / nwFiles.PROJ_FILE
|
||||
writeFile(projFile, "Stuff")
|
||||
assert nwGUI.newProject(projData={"projPath": fncProj}) is False
|
||||
os.unlink(projFile)
|
||||
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
||||
projFile.unlink()
|
||||
|
||||
# An unreachable path should also fail
|
||||
projPath = os.path.join(fncProj, "stuff", "stuff", "stuff")
|
||||
assert nwGUI.newProject(projData={"projPath": projPath}) is False
|
||||
stuffPath = projPath / "stuff" / "stuff" / "stuff"
|
||||
assert nwGUI.newProject(projData={"projPath": stuffPath}) is False
|
||||
|
||||
# This one should work just fine
|
||||
assert nwGUI.newProject(projData={"projPath": fncProj}) is True
|
||||
assert os.path.isfile(os.path.join(fncProj, nwFiles.PROJ_FILE))
|
||||
assert os.path.isdir(os.path.join(fncProj, "content"))
|
||||
assert nwGUI.newProject(projData={"projPath": projPath}) is True
|
||||
assert (projPath / nwFiles.PROJ_FILE).is_file()
|
||||
assert (projPath / "content").is_dir()
|
||||
|
||||
# END Test testGuiMain_NewProject
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test handling of project tree items based on GUI focus states.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
sHandle = "000000000000f"
|
||||
assert nwGUI.openSelectedItem() is False
|
||||
@@ -153,7 +152,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mockRnd):
|
||||
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
|
||||
"""Test the document editor.
|
||||
"""
|
||||
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
|
||||
@@ -162,7 +161,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
# Create new, save, close project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.saveProject()
|
||||
assert nwGUI.closeProject()
|
||||
|
||||
@@ -176,14 +175,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
assert nwGUI.theProject.data.spellCheck is False
|
||||
|
||||
# Check the files
|
||||
projFile = os.path.join(fncProj, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Initial_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
|
||||
projFile = projPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Initial_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Initial_nwProject.nwx"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
|
||||
|
||||
# Re-open project
|
||||
assert nwGUI.openProject(fncProj)
|
||||
assert nwGUI.openProject(projPath)
|
||||
|
||||
# Check that we loaded the data
|
||||
assert len(nwGUI.theProject.tree) == 8
|
||||
@@ -494,33 +493,33 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
assert nwGUI.saveProject()
|
||||
|
||||
# Check the files
|
||||
projFile = os.path.join(fncProj, "nwProject.nwx")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||
projFile = projPath / "nwProject.nwx"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Final_nwProject.nwx"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Final_nwProject.nwx"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, ignoreStart=(*XML_IGNORE, "<spellCheck"))
|
||||
|
||||
projFile = os.path.join(fncProj, "content", "000000000000f.nwd")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_000000000000f.nwd")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_000000000000f.nwd")
|
||||
projFile = projPath / "content" / "000000000000f.nwd"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Final_000000000000f.nwd"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Final_000000000000f.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
projFile = os.path.join(fncProj, "content", "0000000000010.nwd")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000010.nwd")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000010.nwd")
|
||||
projFile = projPath / "content" / "0000000000010.nwd"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000010.nwd"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000010.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
projFile = os.path.join(fncProj, "content", "0000000000011.nwd")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000011.nwd")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000011.nwd")
|
||||
projFile = projPath / "content" / "0000000000011.nwd"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000011.nwd"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000011.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
projFile = os.path.join(fncProj, "content", "0000000000012.nwd")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_0000000000012.nwd")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_0000000000012.nwd")
|
||||
projFile = projPath / "content" / "0000000000012.nwd"
|
||||
testFile = tstPaths.outDir / "guiEditor_Main_Final_0000000000012.nwd"
|
||||
compFile = tstPaths.refDir / "guiEditor_Main_Final_0000000000012.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
@@ -530,10 +529,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_FocusFullMode(qtbot, nwGUI, fncProj, mockRnd):
|
||||
def testGuiMain_FocusFullMode(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test toggling focus mode in main window.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.isFocusMode is False
|
||||
|
||||
# Focus Mode
|
||||
|
||||
@@ -20,10 +20,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QTextCursor, QTextBlock
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
|
||||
|
||||
from tools import C, writeFile, buildTestProject
|
||||
@@ -422,10 +421,10 @@ def testGuiMenu_ContextMenus(qtbot, nwGUI, nwLipsum):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncPath, projPath, mockRnd):
|
||||
"""Test the Insert menu.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
assert nwGUI.projView.projTree._getTreeItem(C.hSceneDoc) is not None
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
@@ -626,8 +625,8 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
assert not nwGUI.importDocument()
|
||||
|
||||
# Then a valid path, but bot a file that exists
|
||||
theFile = os.path.join(fncDir, "import.txt")
|
||||
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (theFile, ""))
|
||||
theFile = fncPath / "import.txt"
|
||||
monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *a, **k: (str(theFile), ""))
|
||||
assert not nwGUI.importDocument()
|
||||
|
||||
# Create the file and try again, but with no target document open
|
||||
@@ -666,7 +665,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
|
||||
theBits = theMessage.split("<br>")
|
||||
assert len(theBits) == 2
|
||||
assert theBits[0] == "The currently open file is saved in:"
|
||||
assert theBits[1] == os.path.join(fncProj, "content", "000000000000f.nwd")
|
||||
assert theBits[1] == str(projPath / "content" / "000000000000f.nwd")
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
|
||||
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test navigating the novel tree.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
nwGUI.switchFocus(nwWidget.TREE)
|
||||
nwGUI.projView.projTree.clearSelection()
|
||||
|
||||
@@ -32,12 +32,11 @@ from novelwriter.enum import nwItemClass, nwOutline, nwView
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, projPath):
|
||||
"""Test the outline view.
|
||||
"""
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
nwGUI.rebuildIndex()
|
||||
nwGUI._changeView(nwView.OUTLINE)
|
||||
@@ -156,7 +155,6 @@ def testGuiOutline_Content(qtbot, nwGUI, nwLipsum):
|
||||
"""Test the outline view.
|
||||
"""
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
nwGUI.mainConf.lastPath = nwLipsum
|
||||
|
||||
nwGUI.rebuildIndex()
|
||||
nwGUI._changeView(nwView.OUTLINE)
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from mock import causeOSError
|
||||
@@ -36,7 +35,7 @@ from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test adding and removing items from the project tree.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -49,8 +48,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
|
||||
assert projView.projTree.newTreeItem(nwItemType.FILE) is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# No itemType set
|
||||
projView.projTree.clearSelection()
|
||||
@@ -168,7 +166,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test adding and removing items from the project tree.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -180,8 +178,7 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
assert projView.projTree.moveTreeItem(1) is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Move Documents
|
||||
# ==============
|
||||
@@ -279,7 +276,7 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test external requests for removing items from project tree.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -291,8 +288,7 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir,
|
||||
assert projView.requestDeleteItem() is False
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Try emptying the trash already now, when there is no trash folder
|
||||
assert projView.emptyTrash() is False
|
||||
@@ -363,7 +359,7 @@ def testGuiProjTree_RequestDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir,
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test moving items to Trash.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -372,8 +368,7 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, m
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Invalid item
|
||||
caplog.clear()
|
||||
@@ -417,7 +412,7 @@ def testGuiProjTree_MoveItemToTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, m
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test permanently deleting items.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -426,8 +421,7 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fnc
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Invalid item
|
||||
caplog.clear()
|
||||
@@ -470,7 +464,7 @@ def testGuiProjTree_PermanentlyDeleteItem(qtbot, caplog, monkeypatch, nwGUI, fnc
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test emptying Trash.
|
||||
"""
|
||||
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
|
||||
@@ -484,8 +478,7 @@ def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRn
|
||||
assert "No project open" in caplog.text
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# No Trash folder
|
||||
assert projTree.emptyTrash() is False
|
||||
@@ -524,7 +517,7 @@ def testGuiProjTree_EmptyTrash(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRn
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test the building of the project tree context menu. All this does
|
||||
is test that the menu builds. It doesn't open the actual menu,
|
||||
"""
|
||||
@@ -532,8 +525,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
# Handles for new objects
|
||||
hCharNote = "0000000000011"
|
||||
@@ -643,7 +635,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ipsumText):
|
||||
def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd, ipsumText):
|
||||
"""Test the merge document function.
|
||||
"""
|
||||
mergeData = {}
|
||||
@@ -654,8 +646,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
|
||||
monkeypatch.setattr(GuiDocMerge, "getData", lambda *a: mergeData)
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
projTree = nwGUI.projView.projTree
|
||||
@@ -746,7 +737,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, i
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ipsumText):
|
||||
def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd, ipsumText):
|
||||
"""Test the split document function.
|
||||
"""
|
||||
splitData = {}
|
||||
@@ -758,8 +749,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
monkeypatch.setattr(GuiDocSplit, "getData", lambda *a: (splitData, splitText))
|
||||
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
theProject = nwGUI.theProject
|
||||
projTree = nwGUI.projView.projTree
|
||||
@@ -828,13 +818,13 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
assert projTree._splitDocument(hSplitDoc) is True
|
||||
for tHandle in fstSet:
|
||||
assert tHandle in theProject.tree
|
||||
assert not os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd"))
|
||||
assert not (projPath / "content" / f"{tHandle}.nwd").is_file()
|
||||
|
||||
# Writing succeeds
|
||||
assert projTree._splitDocument(hSplitDoc) is True
|
||||
for tHandle in sndSet:
|
||||
assert tHandle in theProject.tree
|
||||
assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd"))
|
||||
assert (projPath / "content" / f"{tHandle}.nwd").is_file()
|
||||
|
||||
# Add to a folder and move source to trash
|
||||
splitData["intoFolder"] = True
|
||||
@@ -843,7 +833,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
assert "0000000000029" in theProject.tree # The folder
|
||||
for tHandle in trdSet:
|
||||
assert tHandle in theProject.tree
|
||||
assert os.path.isfile(os.path.join(prjDir, "content", f"{tHandle}.nwd"))
|
||||
assert (projPath / "content" / f"{tHandle}.nwd").is_file()
|
||||
|
||||
assert theProject.tree.isTrash(hSplitDoc) is True
|
||||
|
||||
@@ -858,13 +848,12 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, fncDir, mockRnd, ip
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
|
||||
def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test various parts of the project tree class not covered by
|
||||
other tests.
|
||||
"""
|
||||
# Create a project
|
||||
prjDir = os.path.join(fncDir, "project")
|
||||
buildTestProject(nwGUI, prjDir)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
|
||||
projView = nwGUI.projView
|
||||
projTree = nwGUI.projView.projTree
|
||||
|
||||
@@ -28,10 +28,10 @@ from novelwriter.enum import nwState
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiStatusBar_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
def testGuiStatusBar_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test the the various features of the status bar.
|
||||
"""
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
cHandle = nwGUI.theProject.newFile("A Note", C.hCharRoot)
|
||||
newDoc = nwGUI.theProject.storage.getDocument(cHandle)
|
||||
newDoc.writeDocument("# A Note\n\n")
|
||||
|
||||
@@ -19,10 +19,10 @@ 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 shutil
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from configparser import ConfigParser
|
||||
|
||||
from mock import causeOSError
|
||||
@@ -38,7 +38,7 @@ from novelwriter.gui.theme import GuiIcons, GuiTheme
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Main(qtbot, nwGUI, fncDir):
|
||||
def testGuiTheme_Main(qtbot, nwGUI, fncPath):
|
||||
"""Test the theme class init.
|
||||
"""
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
@@ -75,15 +75,15 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir):
|
||||
# Scan for Themes
|
||||
# ===============
|
||||
|
||||
assert mainTheme._listConf({}, "not_a_path") is False
|
||||
assert mainTheme._listConf({}, Path("not_a_path")) is False
|
||||
|
||||
themeOne = os.path.join(fncDir, "themes", "themeone.conf")
|
||||
themeTwo = os.path.join(fncDir, "themes", "themetwo.conf")
|
||||
themeOne = fncPath / "themes" / "themeone.conf"
|
||||
themeTwo = fncPath / "themes" / "themetwo.conf"
|
||||
writeFile(themeOne, "# Stuff")
|
||||
writeFile(themeTwo, "# Stuff")
|
||||
|
||||
result = {}
|
||||
assert mainTheme._listConf(result, os.path.join(fncDir, "themes")) is True
|
||||
assert mainTheme._listConf(result, fncPath / "themes") is True
|
||||
assert result["themeone"] == themeOne
|
||||
assert result["themetwo"] == themeTwo
|
||||
|
||||
@@ -123,7 +123,7 @@ def testGuiTheme_Main(qtbot, nwGUI, fncDir):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncPath):
|
||||
"""Test the theme part of the class.
|
||||
"""
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
@@ -132,15 +132,8 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "themes", "default_dark.conf"),
|
||||
os.path.join(fncDir, "themes")
|
||||
)
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "themes", "default.conf"),
|
||||
os.path.join(fncDir, "themes")
|
||||
)
|
||||
writeFile(os.path.join(fncDir, "themes", "default.qss"), "/* Stuff */")
|
||||
shutil.copy(mainConf.assetPath("themes") / "default_dark.conf", fncPath / "themes")
|
||||
shutil.copy(mainConf.assetPath("themes") / "default.conf", fncPath / "themes")
|
||||
|
||||
# Block the reading of the files
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -197,7 +190,7 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncPath):
|
||||
"""Test the syntax part of the class.
|
||||
"""
|
||||
mainTheme: GuiTheme = nwGUI.mainTheme
|
||||
@@ -206,14 +199,8 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "syntax", "default_dark.conf"),
|
||||
os.path.join(fncDir, "syntax")
|
||||
)
|
||||
shutil.copy(
|
||||
os.path.join(mainConf.assetPath, "syntax", "default_light.conf"),
|
||||
os.path.join(fncDir, "syntax")
|
||||
)
|
||||
shutil.copy(mainConf.assetPath("syntax") / "default_dark.conf", fncPath / "syntax")
|
||||
shutil.copy(mainConf.assetPath("syntax") / "default_light.conf", fncPath / "syntax")
|
||||
|
||||
# Block the reading of the files
|
||||
with monkeypatch.context() as mp:
|
||||
@@ -270,11 +257,10 @@ def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI, fncDir):
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||
def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncPath):
|
||||
"""Test the icon cache class.
|
||||
"""
|
||||
iconCache: GuiIcons = nwGUI.mainTheme.iconCache
|
||||
mainConf: Config = nwGUI.mainConf
|
||||
|
||||
# Load Theme
|
||||
# ==========
|
||||
@@ -288,10 +274,11 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||
assert iconCache.loadTheme("typicons_dark") is False
|
||||
|
||||
# Load a broken theme file
|
||||
iconsDir = os.path.join(fncDir, "icons")
|
||||
os.mkdir(iconsDir)
|
||||
os.mkdir(os.path.join(iconsDir, "testicons"))
|
||||
writeFile(os.path.join(iconsDir, "testicons", "icons.conf"), (
|
||||
iconsDir = fncPath / "icons"
|
||||
testIcons = iconsDir / "testicons"
|
||||
iconsDir.mkdir()
|
||||
testIcons.mkdir()
|
||||
writeFile(testIcons / "icons.conf", (
|
||||
"[Main]\n"
|
||||
"name = Test Icons\n"
|
||||
"\n"
|
||||
@@ -300,15 +287,15 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||
"stuff = stuff.svg\n"
|
||||
))
|
||||
|
||||
assetPath = mainConf.assetPath
|
||||
mainConf.assetPath = fncDir
|
||||
iconPath = iconCache._iconPath
|
||||
iconCache._iconPath = fncPath / "icons"
|
||||
|
||||
caplog.clear()
|
||||
assert iconCache.loadTheme("testicons") is True
|
||||
assert "Unknown icon name 'stuff' in config file" in caplog.text
|
||||
assert "Icon file 'add.svg' not in theme folder" in caplog.text
|
||||
|
||||
mainConf.assetPath = assetPath
|
||||
iconCache._iconPath = iconPath
|
||||
|
||||
# Load working theme file
|
||||
assert iconCache.loadTheme("typicons_dark") is True
|
||||
@@ -327,7 +314,7 @@ def testGuiTheme_Icons(qtbot, caplog, monkeypatch, nwGUI, fncDir):
|
||||
|
||||
# Fail finding the file
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.path.isfile", lambda *a: False)
|
||||
mp.setattr("pathlib.Path.is_file", lambda *a: False)
|
||||
qPix = iconCache.loadDecoration("wiz-back")
|
||||
assert qPix.isNull() is True
|
||||
|
||||
|
||||
@@ -20,9 +20,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import os
|
||||
|
||||
from shutil import copyfile
|
||||
|
||||
from tools import cmpFiles, getGuiItem
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
@@ -32,7 +32,7 @@ from novelwriter.tools import GuiBuildNovel
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
def testToolBuild_Main(qtbot, monkeypatch, nwGUI, prjLipsum, tstPaths):
|
||||
"""Test the build tool.
|
||||
"""
|
||||
# Block message box
|
||||
@@ -43,7 +43,7 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
assert getGuiItem("GuiBuildNovel") is None
|
||||
|
||||
# Open a project
|
||||
assert nwGUI.openProject(nwLipsum)
|
||||
assert nwGUI.openProject(prjLipsum)
|
||||
|
||||
# Open the tool
|
||||
nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
|
||||
@@ -61,55 +61,47 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
# Invalid file format
|
||||
assert not nwBuild._saveDocument(-1)
|
||||
|
||||
# Non-existent path
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("os.path.expanduser", lambda *a, **k: nwLipsum)
|
||||
assert nwGUI.mainConf.lastPath != nwLipsum
|
||||
nwGUI.mainConf.lastPath = "no_such_path"
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
assert nwGUI.mainConf.lastPath == nwLipsum
|
||||
|
||||
# No path selected
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: ("", ""))
|
||||
assert not nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
|
||||
# Default Settings
|
||||
nwGUI.mainConf.lastPath = nwLipsum
|
||||
nwGUI.mainConf._lastPath = prjLipsum
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.nwd")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.nwd")
|
||||
projFile = prjLipsum / "Lorem Ipsum.nwd"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.nwd"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.htm")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.htm")
|
||||
projFile = prjLipsum / "Lorem Ipsum.htm"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.htm"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.htm"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.md")
|
||||
projFile = prjLipsum / "Lorem Ipsum.md"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.md"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.md"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_GH)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1G_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1G_Lorem_Ipsum.md")
|
||||
projFile = prjLipsum / "Lorem Ipsum.md"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step1G_Lorem_Ipsum.md"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step1G_Lorem_Ipsum.md"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_FODT)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.fodt")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.fodt")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.fodt")
|
||||
projFile = prjLipsum / "Lorem Ipsum.fodt"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step1_Lorem_Ipsum.fodt"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step1_Lorem_Ipsum.fodt"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [4, 5])
|
||||
|
||||
@@ -130,30 +122,30 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.nwd")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.nwd")
|
||||
projFile = prjLipsum / "Lorem Ipsum.nwd"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.nwd"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.htm")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.htm")
|
||||
projFile = prjLipsum / "Lorem Ipsum.htm"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.htm"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.htm"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.md")
|
||||
projFile = prjLipsum / "Lorem Ipsum.md"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.md"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.md"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_FODT)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.fodt")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.fodt")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.fodt")
|
||||
projFile = prjLipsum / "Lorem Ipsum.fodt"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step2_Lorem_Ipsum.fodt"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step2_Lorem_Ipsum.fodt"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [4, 5])
|
||||
|
||||
@@ -164,30 +156,30 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
# Save files that can be compared
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.nwd")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.nwd")
|
||||
projFile = prjLipsum / "Lorem Ipsum.nwd"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.nwd"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.htm")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.htm")
|
||||
projFile = prjLipsum / "Lorem Ipsum.htm"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.htm"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.htm"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_MD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.md")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.md")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.md")
|
||||
projFile = prjLipsum / "Lorem Ipsum.md"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.md"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.md"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_FODT)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.fodt")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.fodt")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.fodt")
|
||||
projFile = prjLipsum / "Lorem Ipsum.fodt"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step3_Lorem_Ipsum.fodt"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step3_Lorem_Ipsum.fodt"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [4, 5])
|
||||
|
||||
@@ -205,43 +197,43 @@ def testToolBuild_Main(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
|
||||
|
||||
# Save files that can be compared
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_NWD)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step4_Lorem_Ipsum.nwd")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step4_Lorem_Ipsum.nwd")
|
||||
projFile = prjLipsum / "Lorem Ipsum.nwd"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step4_Lorem_Ipsum.nwd"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step4_Lorem_Ipsum.nwd"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_HTM)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step4_Lorem_Ipsum.htm")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step4_Lorem_Ipsum.htm")
|
||||
projFile = prjLipsum / "Lorem Ipsum.htm"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step4_Lorem_Ipsum.htm"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step4_Lorem_Ipsum.htm"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile)
|
||||
|
||||
# Check the JSON files too at this stage
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_JSON_H)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step4H_Lorem_Ipsum.json")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step4H_Lorem_Ipsum.json")
|
||||
projFile = prjLipsum / "Lorem Ipsum.json"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step4H_Lorem_Ipsum.json"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step4H_Lorem_Ipsum.json"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [8])
|
||||
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_JSON_M)
|
||||
projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
|
||||
testFile = os.path.join(outDir, "guiBuild_Tool_Step4M_Lorem_Ipsum.json")
|
||||
compFile = os.path.join(refDir, "guiBuild_Tool_Step4M_Lorem_Ipsum.json")
|
||||
projFile = prjLipsum / "Lorem Ipsum.json"
|
||||
testFile = tstPaths.outDir / "guiBuild_Tool_Step4M_Lorem_Ipsum.json"
|
||||
compFile = tstPaths.refDir / "guiBuild_Tool_Step4M_Lorem_Ipsum.json"
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [8])
|
||||
|
||||
# Since odt and fodt is built by the same code, we don't check the
|
||||
# output. but just that the different format can be written as well
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_ODT)
|
||||
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.odt"))
|
||||
assert (prjLipsum / "Lorem Ipsum.odt").is_file()
|
||||
|
||||
# Print to PDF
|
||||
if not nwGUI.mainConf.osDarwin:
|
||||
assert nwBuild._saveDocument(nwBuild.FMT_PDF)
|
||||
assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.pdf"))
|
||||
assert (prjLipsum / "Lorem Ipsum.pdf").is_file()
|
||||
|
||||
# Close the build tool
|
||||
htmlText = nwBuild.htmlText
|
||||
|
||||
@@ -29,7 +29,7 @@ from novelwriter.tools import GuiLipsum
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolLipsum_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
def testToolLipsum_Main(qtbot, nwGUI, projPath, mockRnd):
|
||||
"""Test the Lorem Ipsum tool.
|
||||
"""
|
||||
# Check that we cannot open when there is no project
|
||||
@@ -37,7 +37,7 @@ def testToolLipsum_Main(qtbot, nwGUI, fncProj, mockRnd):
|
||||
assert getGuiItem("GuiLipsum") is None
|
||||
|
||||
# Create a new project
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||
assert len(nwGUI.docEditor.getText()) == 15
|
||||
|
||||
|
||||
@@ -19,7 +19,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/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pytest
|
||||
|
||||
@@ -37,7 +36,7 @@ from novelwriter.tools.projwizard import (
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
|
||||
def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, projPath):
|
||||
"""Test the launch of the project wizard.
|
||||
Disabled for macOS because the test segfaults on QWizard.show()
|
||||
"""
|
||||
@@ -45,7 +44,7 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
# ========================
|
||||
|
||||
# New with a project open should cause an error
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(nwGUI, "closeProject", lambda *a: False)
|
||||
assert nwGUI.newProject() is False
|
||||
@@ -61,13 +60,12 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
assert nwGUI.newProject() is False
|
||||
|
||||
# Now, with a non-empty folder
|
||||
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": fncProj})
|
||||
mp.setattr(nwGUI, "showNewProjectDialog", lambda *a: {"projPath": projPath})
|
||||
assert nwGUI.newProject() is False
|
||||
|
||||
# Test the Wizard Launching
|
||||
# =========================
|
||||
|
||||
nwGUI.mainConf.lastPath = " "
|
||||
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
|
||||
result = nwGUI.showNewProjectDialog()
|
||||
@@ -97,12 +95,11 @@ def testToolProjectWizard_Handling(qtbot, monkeypatch, nwGUI, fncProj):
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.parametrize("prjType", ["minimal", "custom1", "custom2", "sample"])
|
||||
@pytest.mark.skipif(sys.platform.startswith("darwin"), reason="Not running on Darwin")
|
||||
def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncPath, prjType):
|
||||
"""Test the new project wizard with a set of selection scenarios.
|
||||
"""
|
||||
monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *a: None)
|
||||
|
||||
nwGUI.mainConf.lastPath = " "
|
||||
nwWiz = GuiProjectWizard(nwGUI)
|
||||
nwWiz.show()
|
||||
qtbot.addWidget(nwWiz)
|
||||
@@ -132,12 +129,12 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
assert storagePage.errLabel.text() == ""
|
||||
|
||||
# Set an invalid path
|
||||
storagePage.projPath.setText(os.path.join(fncDir, "not", "a", "path"))
|
||||
storagePage.projPath.setText(str(fncPath / "not" / "a" / "path"))
|
||||
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
||||
assert storagePage.errLabel.text().startswith("Error")
|
||||
|
||||
# Set an existing path
|
||||
storagePage.projPath.setText(fncDir)
|
||||
storagePage.projPath.setText(str(fncPath))
|
||||
assert not nwWiz.button(QWizard.NextButton).isEnabled()
|
||||
assert storagePage.errLabel.text().startswith("Error")
|
||||
|
||||
@@ -148,12 +145,12 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
assert storagePage.errLabel.text() == ""
|
||||
|
||||
# Let the browse feature handle it
|
||||
projPath = os.path.join(fncDir, "Test Wizard")
|
||||
projPath = fncPath / "Test Wizard"
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: fncDir)
|
||||
mp.setattr(QFileDialog, "getExistingDirectory", lambda *a, **k: str(fncPath))
|
||||
qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
|
||||
|
||||
assert storagePage.projPath.text() == projPath
|
||||
assert storagePage.projPath.text() == str(projPath)
|
||||
assert storagePage.errLabel.text() == ""
|
||||
|
||||
# Setting projPath should activate the button
|
||||
@@ -218,7 +215,7 @@ def testToolProjectWizard_Run(qtbot, monkeypatch, nwGUI, fncDir, prjType):
|
||||
assert projData["projName"] == "Test Wizard"
|
||||
assert projData["projTitle"] == "My Novel"
|
||||
assert projData["projAuthors"] == "Jane Doe"
|
||||
assert projData["projPath"] == projPath
|
||||
assert projData["projPath"] == str(projPath)
|
||||
assert projData["popMinimal"] == prjType.startswith("minimal")
|
||||
assert projData["popCustom"] == prjType.startswith("custom")
|
||||
assert projData["popSample"] == prjType.startswith("sample")
|
||||
|
||||
@@ -19,9 +19,8 @@ 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 pytest
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from mock import causeOSError
|
||||
from tools import getGuiItem, writeFile, buildTestProject
|
||||
@@ -34,17 +33,16 @@ from novelwriter.constants import nwFiles
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncPath, projPath):
|
||||
"""Test the full writing stats tool.
|
||||
"""
|
||||
# Create a project to work on
|
||||
buildTestProject(nwGUI, fncProj)
|
||||
buildTestProject(nwGUI, projPath)
|
||||
qtbot.wait(100)
|
||||
assert nwGUI.saveProject()
|
||||
sessFile = os.path.join(fncProj, "meta", nwFiles.SESS_STATS)
|
||||
sessFile = projPath / "meta" / nwFiles.SESS_STATS
|
||||
|
||||
# Open the Writing Stats dialog
|
||||
nwGUI.mainConf.lastPath = ""
|
||||
nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
|
||||
qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
|
||||
|
||||
@@ -55,7 +53,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
# ============
|
||||
|
||||
# No initial logfile
|
||||
assert not os.path.isfile(sessFile)
|
||||
assert not sessFile.is_file()
|
||||
assert not sessLog._loadLogFile()
|
||||
|
||||
# Make a test log file
|
||||
@@ -67,7 +65,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
"2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
|
||||
"2020-01-06 21:00:00 2020-01-06 21:00:10 125 5\n"
|
||||
))
|
||||
assert os.path.isfile(sessFile)
|
||||
assert sessFile.is_file()
|
||||
assert sessLog._loadLogFile()
|
||||
assert sessLog.wordOffset == 123
|
||||
assert len(sessLog.logData) == 4
|
||||
@@ -111,9 +109,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert not sessLog._saveData(None)
|
||||
|
||||
# Make the save succeed
|
||||
monkeypatch.setattr("os.path.expanduser", lambda *a: fncDir)
|
||||
monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, ""))
|
||||
|
||||
sessLog.listBox.sortByColumn(sessLog.C_TIME, 0)
|
||||
|
||||
assert sessLog.novelWords.text() == "{:n}".format(600)
|
||||
@@ -135,10 +131,8 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
qtbot.wait(100)
|
||||
|
||||
assert nwGUI.mainConf.lastPath == fncDir
|
||||
|
||||
# Check the exported files
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.load(inFile)
|
||||
|
||||
@@ -177,7 +171,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.loads(inFile.read())
|
||||
|
||||
@@ -223,7 +217,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.load(inFile)
|
||||
|
||||
@@ -271,7 +265,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.load(inFile)
|
||||
|
||||
@@ -301,7 +295,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.load(inFile)
|
||||
|
||||
@@ -354,7 +348,7 @@ def testToolWritingStats_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
|
||||
qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
|
||||
assert sessLog._saveData(sessLog.FMT_JSON)
|
||||
|
||||
jsonStats = os.path.join(fncDir, "sessionStats.json")
|
||||
jsonStats = fncPath / "sessionStats.json"
|
||||
with open(jsonStats, mode="r", encoding="utf-8") as inFile:
|
||||
jsonData = json.load(inFile)
|
||||
|
||||
|
||||
+14
-12
@@ -19,10 +19,11 @@ 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 time
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtWidgets import qApp
|
||||
|
||||
XML_IGNORE = ("<novelWriterXML", "<project")
|
||||
@@ -129,24 +130,25 @@ def writeFile(fileName, fileData):
|
||||
outFile.write(fileData)
|
||||
|
||||
|
||||
def cleanProject(projPath):
|
||||
def cleanProject(path):
|
||||
"""Delete all generated files in a project.
|
||||
"""
|
||||
cacheDir = os.path.join(projPath, "cache")
|
||||
if os.path.isdir(cacheDir):
|
||||
path = Path(path)
|
||||
cacheDir = path / "cache"
|
||||
if cacheDir.is_dir():
|
||||
shutil.rmtree(cacheDir)
|
||||
|
||||
metaDir = os.path.join(projPath, "meta")
|
||||
if os.path.isdir(metaDir):
|
||||
metaDir = path / "meta"
|
||||
if metaDir.is_dir():
|
||||
shutil.rmtree(metaDir)
|
||||
|
||||
bakFile = os.path.join(projPath, "nwProject.bak")
|
||||
if os.path.isfile(bakFile):
|
||||
os.unlink(bakFile)
|
||||
bakFile = path / "nwProject.bak"
|
||||
if bakFile.is_file():
|
||||
bakFile.unlink()
|
||||
|
||||
tocFile = os.path.join(projPath, "ToC.txt")
|
||||
if os.path.isfile(tocFile):
|
||||
os.unlink(tocFile)
|
||||
tocFile = path / "ToC.txt"
|
||||
if tocFile.is_file():
|
||||
tocFile.unlink()
|
||||
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user