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