diff --git a/nw/__init__.py b/nw/__init__.py
index a5960f30..65ab7f59 100644
--- a/nw/__init__.py
+++ b/nw/__init__.py
@@ -246,19 +246,20 @@ def main(sysArgs=None):
errorCode |= 32
if errorData:
- if not testMode:
- errApp = QApplication([])
- errMsg = QErrorMessage()
- errMsg.resize(500, 300)
- errMsg.showMessage((
- "
A critical error has been encountered
"
- "novelWriter cannot start due to the following issues:
"
- "
- %s
"
- "Shutting down ...
"
- ) % (
- "
- ".join(errorData)
- ))
- errApp.exec_()
+ errApp = QApplication([])
+ errMsg = QErrorMessage()
+ errMsg.resize(500, 300)
+ errMsg.showMessage((
+ "A critical error has been encountered
"
+ "novelWriter cannot start due to the following issues:
"
+ "
- %s
"
+ "Shutting down ...
"
+ ) % (
+ "
- ".join(errorData)
+ ))
+ for errMsg in errorData:
+ logger.critical(errMsg)
+ errApp.exec_()
sys.exit(errorCode)
# Finish initialising config
@@ -293,4 +294,4 @@ def main(sysArgs=None):
nwGUI = GuiMain()
sys.exit(nwApp.exec_())
- return
+# END Function main
diff --git a/nw/config.py b/nw/config.py
index 04c1a4db..9c232109 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -27,12 +27,12 @@
import logging
import configparser
+import shutil
import json
import sys
import os
from time import time
-from shutil import which
from PyQt5.Qt import PYQT_VERSION_STR
from PyQt5.QtCore import QT_VERSION_STR, QStandardPaths, QSysInfo
@@ -44,10 +44,11 @@ logger = logging.getLogger(__name__)
class Config:
- CNF_STR = 0
- CNF_INT = 1
- CNF_BOOL = 2
- CNF_LIST = 3
+ CNF_STR = 0
+ CNF_INT = 1
+ CNF_BOOL = 2
+ CNF_S_LST = 3
+ CNF_I_LST = 4
def __init__(self):
@@ -211,12 +212,8 @@ class Config:
self.osUnknown = True
# Other System Info
- if self.verQtValue >= 50600:
- self.hostName = QSysInfo.machineHostName()
- self.kernelVer = QSysInfo.kernelVersion()
- else:
- self.hostName = "Unknown"
- self.kernelVer = "Unknown"
+ self.hostName = "Unknown"
+ self.kernelVer = "Unknown"
# Packages
self.hasEnchant = False # The pyenchant package
@@ -321,6 +318,11 @@ class Config:
self.errData.append(str(e))
self.dataPath = None
+ # Host and Kernel
+ if self.verQtValue >= 50600:
+ self.hostName = QSysInfo.machineHostName()
+ self.kernelVer = QSysInfo.kernelVersion()
+
# Load recent projects cache
self.loadRecentCache()
@@ -388,25 +390,25 @@ class Config:
## Sizes
cnfSec = "Sizes"
self.winGeometry = self._parseLine(
- cnfParse, cnfSec, "geometry", self.CNF_LIST, self.winGeometry
+ cnfParse, cnfSec, "geometry", self.CNF_I_LST, self.winGeometry
)
self.treeColWidth = self._parseLine(
- cnfParse, cnfSec, "treecols", self.CNF_LIST, self.treeColWidth
+ cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth
)
self.projColWidth = self._parseLine(
- cnfParse, cnfSec, "projcols", self.CNF_LIST, self.projColWidth
+ cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth
)
self.mainPanePos = self._parseLine(
- cnfParse, cnfSec, "mainpane", self.CNF_LIST, self.mainPanePos
+ cnfParse, cnfSec, "mainpane", self.CNF_I_LST, self.mainPanePos
)
self.docPanePos = self._parseLine(
- cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos
+ cnfParse, cnfSec, "docpane", self.CNF_I_LST, self.docPanePos
)
self.viewPanePos = self._parseLine(
- cnfParse, cnfSec, "viewpane", self.CNF_LIST, self.viewPanePos
+ cnfParse, cnfSec, "viewpane", self.CNF_I_LST, self.viewPanePos
)
self.outlnPanePos = self._parseLine(
- cnfParse, cnfSec, "outlinepane", self.CNF_LIST, self.outlnPanePos
+ cnfParse, cnfSec, "outlinepane", self.CNF_I_LST, self.outlnPanePos
)
self.isFullScreen = self._parseLine(
cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen
@@ -484,10 +486,10 @@ class Config:
cnfParse, cnfSec, "autoscrollpos", self.CNF_INT, self.autoScrollPos
)
self.fmtSingleQuotes = self._parseLine(
- cnfParse, cnfSec, "fmtsinglequote", self.CNF_LIST, self.fmtSingleQuotes
+ cnfParse, cnfSec, "fmtsinglequote", self.CNF_S_LST, self.fmtSingleQuotes
)
self.fmtDoubleQuotes = self._parseLine(
- cnfParse, cnfSec, "fmtdoublequote", self.CNF_LIST, self.fmtDoubleQuotes
+ cnfParse, cnfSec, "fmtdoublequote", self.CNF_S_LST, self.fmtDoubleQuotes
)
self.spellTool = self._parseLine(
cnfParse, cnfSec, "spelltool", self.CNF_STR, self.spellTool
@@ -901,23 +903,28 @@ class Config:
# Internal Functions
##
- def _unpackList(self, inStr, listLen, listDefault, castTo=int):
- """Unpack a comma separated string of items into a list.
- """
- inData = inStr.split(",")
- outData = []
- for i in range(listLen):
- try:
- outData.append(castTo(inData[i]))
- except Exception:
- outData.append(listDefault[i])
- return outData
-
def _packList(self, inData):
- """Pack a list of items into a comma separated string.
+ """Pack a list of items into a comma-separated string.
"""
return ", ".join([str(inVal) for inVal in inData])
+ def _unpackList(self, inStr, listDefault, cnfType):
+ """Unpack a comma-separated string of items into a list.
+ """
+ inData = inStr.split(",")
+ outData = listDefault.copy()
+ for i in range(min(len(inData), len(listDefault))):
+ try:
+ if cnfType == self.CNF_S_LST:
+ outData[i] = inData[i].strip()
+ elif cnfType == self.CNF_I_LST:
+ outData[i] = int(inData[i].strip())
+ else:
+ continue
+ except Exception:
+ continue
+ return outData
+
def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault):
"""Parse a line and return the correct datatype.
"""
@@ -930,18 +937,24 @@ class Config:
return cnfParse.getint(cnfSec, cnfName)
elif cnfType == self.CNF_BOOL:
return cnfParse.getboolean(cnfSec, cnfName)
- elif cnfType == self.CNF_LIST:
+ elif cnfType == self.CNF_I_LST:
return self._unpackList(
- cnfParse.get(cnfSec, cnfName), len(cnfDefault), cnfDefault
+ cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_I_LST
+ )
+ elif cnfType == self.CNF_S_LST:
+ return self._unpackList(
+ cnfParse.get(cnfSec, cnfName), cnfDefault, self.CNF_S_LST
)
except ValueError as e:
logger.error("Failed to load value from config file.")
logger.error(str(e))
+ return cnfDefault
return cnfDefault
def _checkNone(self, checkVal):
- """Convert a string to a none type.
+ """Return a NoneType if the value correspomds to None, otherwise
+ return the value unchanged.
"""
if checkVal is None:
return None
@@ -961,10 +974,8 @@ class Config:
self.hasEnchant = False
logger.debug("Checking package 'pyenchant': Missing")
- try:
- self.hasAssistant = which("assistant")
- except Exception:
- self.hasAssistant = False
+ assistPath = shutil.which("assistant")
+ self.hasAssistant = assistPath is not None
if self.hasAssistant:
logger.debug("Checking executable 'assistant': Ok")
else:
diff --git a/nw/error.py b/nw/error.py
index 988b49e6..24a802aa 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -135,10 +135,9 @@ class NWErrorMessage(QDialog):
# END Class NWErrorMessage
-def exceptionHandler(exType, exValue, exTrace, testMode=False):
+def exceptionHandler(exType, exValue, exTrace):
"""Function to catch unhandled global exceptions.
"""
- import nw
import logging
from traceback import print_tb
from PyQt5.QtWidgets import qApp
@@ -160,8 +159,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False):
errMsg = NWErrorMessage(nwGUI)
errMsg.setMessage(exType, exValue, exTrace)
- if nw.CONFIG.showGUI:
- errMsg.exec_()
+ errMsg.exec_()
try:
# Try a controlled shudown
@@ -173,10 +171,7 @@ def exceptionHandler(exType, exValue, exTrace, testMode=False):
logger.critical("Could not close the project before exiting")
logger.critical(str(e))
- if testMode:
- return errMsg.msgBody.toPlainText()
- else:
- qApp.exit(1)
+ qApp.exit(1)
except Exception as e:
logger.critical(str(e))
diff --git a/pytest.ini b/pytest.ini
index b61d59ec..c2278317 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,6 @@
[pytest]
markers =
- error: Test various error handling scenarios
+ base: Base functionality tests
core: Core functionality tests
gui: Qt5 GUI tests
serial
diff --git a/tests/README.md b/tests/README.md
index cd1be55b..7d8e378d 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -61,6 +61,10 @@ The commands for the respective test categories are listed below.
| Type | Test Target | Source File(s) | Marker | Filter |
| :--- | :----------------- | :-------------------- | :-------- | :-------------------- |
+| Unit | Main function | nw/\_\_init\_\_.py | `-m base` | `-k testBaseInit` |
+| Unit | Common functions | nw/common.py | `-m base` | `-k testBaseCommon` |
+| Unit | Config class | nw/config.py | `-m base` | `-k testBaseConfig` |
+| Unit | Error handlers | nw/error.py | `-m base` | `-k testBaseError` |
| Unit | Core functions | nw/core/tools.py | `-m core` | `-k testCoreTools` |
| Unit | NWDoc class | nw/core/document.py | `-m core` | `-k testCoreDocument` |
| Unit | NWIndex class | nw/core/index.py | `-m core` | `-k testCoreIndex` |
diff --git a/tests/conftest.py b/tests/conftest.py
index 0d66d0ea..ea9a92b3 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -73,6 +73,9 @@ def fncDir(tmpDir):
def tmpConf(tmpDir):
"""Create a temporary novelWriter configuration object.
"""
+ confFile = os.path.join(tmpDir, "novelwriter.conf")
+ if os.path.isfile(confFile):
+ os.unlink(confFile)
theConf = Config()
theConf.initConfig(tmpDir, tmpDir)
theConf.setLastPath("")
diff --git a/tests/dummy.py b/tests/dummy.py
index 0f26e0ff..daf0e070 100644
--- a/tests/dummy.py
+++ b/tests/dummy.py
@@ -13,7 +13,7 @@ class DummyMain():
self.hasProject = True
self.theIndex = None
self.theProject = None
- self.statusBar = StatusBar()
+ self.statusBar = DummyStatusBar()
# Test Variables
self.askResponse = True
@@ -42,6 +42,12 @@ class DummyMain():
def rebuildIndex(self):
return
+ def closeMain(self):
+ return "closeMain"
+
+ def close(self):
+ return "close"
+
# Test Functions
def undo(self):
@@ -52,9 +58,9 @@ class DummyMain():
self.lastAlert = ""
return
-# END Class GuiMain
+# END Class DummyMain
-class StatusBar():
+class DummyStatusBar():
def __init__(self):
return
@@ -62,7 +68,7 @@ class StatusBar():
def setStatus(self, theText):
return
-# END Class StatusBar
+# END Class DummyStatusBar
# =========================================================================== #
# Error Functions
@@ -71,3 +77,6 @@ class StatusBar():
def causeOSError(*args, **kwargs):
raise OSError
+
+def causeException(*args, **kwargs):
+ raise Exception
diff --git a/tests/reference/novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf
similarity index 100%
rename from tests/reference/novelwriter.conf
rename to tests/reference/baseConfig_novelwriter.conf
diff --git a/tests/reference/coreProject_2_nwProject.nwx b/tests/reference/coreProject_NewCustomA_nwProject.nwx
similarity index 100%
rename from tests/reference/coreProject_2_nwProject.nwx
rename to tests/reference/coreProject_NewCustomA_nwProject.nwx
diff --git a/tests/reference/coreProject_3_nwProject.nwx b/tests/reference/coreProject_NewCustomB_nwProject.nwx
similarity index 100%
rename from tests/reference/coreProject_3_nwProject.nwx
rename to tests/reference/coreProject_NewCustomB_nwProject.nwx
diff --git a/tests/reference/coreProject_5_nwProject.nwx b/tests/reference/coreProject_NewFile_nwProject.nwx
similarity index 100%
rename from tests/reference/coreProject_5_nwProject.nwx
rename to tests/reference/coreProject_NewFile_nwProject.nwx
diff --git a/tests/reference/coreProject_1_nwProject.nwx b/tests/reference/coreProject_NewMinimal_nwProject.nwx
similarity index 100%
rename from tests/reference/coreProject_1_nwProject.nwx
rename to tests/reference/coreProject_NewMinimal_nwProject.nwx
diff --git a/tests/reference/coreProject_4_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx
similarity index 100%
rename from tests/reference/coreProject_4_nwProject.nwx
rename to tests/reference/coreProject_NewRoot_nwProject.nwx
diff --git a/tests/test_common.py b/tests/test_base_common.py
similarity index 78%
rename from tests/test_common.py
rename to tests/test_base_common.py
index 135627fc..8cdb8a74 100644
--- a/tests/test_common.py
+++ b/tests/test_base_common.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-"""novelWriter Common Class Tester
+"""novelWriter Common Functions Tester
"""
import time
@@ -11,8 +11,10 @@ from nw.common import (
)
from tools import cmpList
-@pytest.mark.core
-def testCheckString():
+@pytest.mark.base
+def testBaseCommon_CheckString():
+ """Test the checkString function.
+ """
assert checkString(None, "NotNone", True) is None
assert checkString("None", "NotNone", True) is None
assert checkString("None", "NotNone", False) == "None"
@@ -21,8 +23,12 @@ def testCheckString():
assert checkString(1.0, "NotNone", False) == "NotNone"
assert checkString(True, "NotNone", False) == "NotNone"
-@pytest.mark.core
-def testCheckInt():
+# END Test testBaseCommon_CheckString
+
+@pytest.mark.base
+def testBaseCommon_CheckInt():
+ """Test the checkInt function.
+ """
assert checkInt(None, 3, True) is None
assert checkInt("None", 3, True) is None
assert checkInt(None, 3, False) == 3
@@ -30,8 +36,12 @@ def testCheckInt():
assert checkInt(1.0, 3, False) == 1
assert checkInt(True, 3, False) == 1
-@pytest.mark.core
-def testCheckBool():
+# END Test testBaseCommon_CheckInt
+
+@pytest.mark.base
+def testBaseCommon_CheckBool():
+ """Test the checkBool function.
+ """
assert checkBool(None, 3, True) is None
assert checkBool("None", 3, True) is None
assert checkBool("True", False, False)
@@ -44,8 +54,12 @@ def testCheckBool():
assert checkBool(1.0, None, False) is None
assert checkBool(2.0, None, False) is None
-@pytest.mark.core
-def testCheckHandle():
+# END Test testBaseCommon_CheckBool
+
+@pytest.mark.base
+def testBaseCommon_CheckHandle():
+ """Test the checkHandle function.
+ """
assert checkHandle("None", 1, True) is None
assert checkHandle("None", 1, False) == 1
assert checkHandle(None, 1, True) is None
@@ -53,8 +67,12 @@ def testCheckHandle():
assert checkHandle("47666c91c7ccf", None, False) == "47666c91c7ccf"
assert checkHandle("h7666c91c7ccf", None, False) is None
-@pytest.mark.core
-def testColRange():
+# END Test testBaseCommon_CheckHandle
+
+@pytest.mark.base
+def testBaseCommon_ColRange():
+ """Test the colRange function.
+ """
assert colRange([0, 0], [0, 0], 0) is None
assert cmpList(
colRange([200, 50, 0], [50, 200, 0], 1),
@@ -77,14 +95,22 @@ def testColRange():
[[200, 50, 0], [162, 87, 0], [124, 124, 0], [86, 161, 0], [50, 200, 0]]
)
-@pytest.mark.core
-def testFormatTimeStamp():
+# END Test testBaseCommon_ColRange
+
+@pytest.mark.base
+def testBaseCommon_FormatTimeStamp():
+ """Test the formatTimeStamp function.
+ """
tTime = time.mktime(time.gmtime(0))
assert formatTimeStamp(tTime, False) == "1970-01-01 00:00:00"
assert formatTimeStamp(tTime, True) == "1970-01-01 00.00.00"
-@pytest.mark.core
-def testFormatTime():
+# END Test testBaseCommon_FormatTimeStamp
+
+@pytest.mark.base
+def testBaseCommon_FormatTime():
+ """Test the formatTime function.
+ """
assert formatTime("1") == "ERROR"
assert formatTime(1.0) == "ERROR"
assert formatTime(1) == "00:00:01"
@@ -101,8 +127,12 @@ def testFormatTime():
assert formatTime(86400) == "1-00:00:00"
assert formatTime(360000) == "4-04:00:00"
-@pytest.mark.core
-def testFormatInt():
+# END Test testBaseCommon_FormatTime
+
+@pytest.mark.base
+def testBaseCommon_FormatInt():
+ """Test the formatInt function.
+ """
assert formatInt(1000) == "1000"
assert formatInt(1234) == "1.23\u2009k"
assert formatInt(12345) == "12.3\u2009k"
@@ -112,8 +142,12 @@ def testFormatInt():
assert formatInt(123456789) == "123\u2009M"
assert formatInt(1234567890) == "1.23\u2009G"
-@pytest.mark.core
-def testTransferCase():
+# END Test testBaseCommon_FormatInt
+
+@pytest.mark.base
+def testBaseCommon_TransferCase():
+ """Test the transferCase function.
+ """
assert transferCase(1, "TaRgEt") == "TaRgEt"
assert transferCase("source", 1) == 1
assert transferCase("", "TaRgEt") == "TaRgEt"
@@ -122,8 +156,12 @@ def testTransferCase():
assert transferCase("SOURCE", "target") == "TARGET"
assert transferCase("source", "TARGET") == "target"
-@pytest.mark.core
-def testFuzzyTime():
+# END Test testBaseCommon_TransferCase
+
+@pytest.mark.base
+def testBaseCommon_FuzzyTime():
+ """Test the fuzzyTime function.
+ """
assert fuzzyTime(-1) == "in the future"
assert fuzzyTime(0) == "just now"
assert fuzzyTime(29) == "just now"
@@ -152,3 +190,5 @@ def testFuzzyTime():
assert fuzzyTime(29808000) == "a year ago"
assert fuzzyTime(47336399) == "a year ago"
assert fuzzyTime(47336400) == "2 years ago"
+
+# END Test testBaseCommon_FuzzyTime
diff --git a/tests/test_base_config.py b/tests/test_base_config.py
new file mode 100644
index 00000000..fe755646
--- /dev/null
+++ b/tests/test_base_config.py
@@ -0,0 +1,505 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Config Class Tester
+"""
+
+import pytest
+import sys
+import os
+import configparser
+
+from shutil import copyfile
+
+from dummy import causeOSError
+from tools import cmpFiles
+
+from nw.config import Config
+from nw.constants import nwConst, nwFiles
+
+@pytest.mark.base
+def testBaseConfig_Constructor(monkeypatch):
+ """Test config contructor.
+ """
+ # Linux
+ monkeypatch.setattr("sys.platform", "linux")
+ tstConf = Config()
+ assert tstConf.osLinux is True
+ assert tstConf.osDarwin is False
+ assert tstConf.osWindows is False
+ assert tstConf.osUnknown is False
+ monkeypatch.undo()
+
+ # macOS
+ monkeypatch.setattr("sys.platform", "darwin")
+ tstConf = Config()
+ assert tstConf.osLinux is False
+ assert tstConf.osDarwin is True
+ assert tstConf.osWindows is False
+ assert tstConf.osUnknown is False
+ monkeypatch.undo()
+
+ # Windows
+ monkeypatch.setattr("sys.platform", "win32")
+ tstConf = Config()
+ assert tstConf.osLinux is False
+ assert tstConf.osDarwin is False
+ assert tstConf.osWindows is True
+ assert tstConf.osUnknown is False
+ monkeypatch.undo()
+
+ # Cygwin
+ monkeypatch.setattr("sys.platform", "cygwin")
+ tstConf = Config()
+ assert tstConf.osLinux is False
+ assert tstConf.osDarwin is False
+ assert tstConf.osWindows is True
+ assert tstConf.osUnknown is False
+ monkeypatch.undo()
+
+ # Other
+ monkeypatch.setattr("sys.platform", "some_ther_os")
+ tstConf = Config()
+ assert tstConf.osLinux is False
+ assert tstConf.osDarwin is False
+ assert tstConf.osWindows is False
+ assert tstConf.osUnknown is True
+ monkeypatch.undo()
+
+# END Test testBaseConfig_Constructor
+
+@pytest.mark.base
+def testBaseConfig_Init(monkeypatch, tmpDir, fncDir, outDir, refDir):
+ """Test config intialisation.
+ """
+ tstConf = Config()
+
+ confFile = os.path.join(tmpDir, "novelwriter.conf")
+ testFile = os.path.join(outDir, "baseConfig_novelwriter.conf")
+ compFile = os.path.join(refDir, "baseConfig_novelwriter.conf")
+
+ # Make sure we don't have any old conf file
+ if os.path.isfile(confFile):
+ os.unlink(confFile)
+
+ # Let the config class figure out the path
+ monkeypatch.setattr("PyQt5.QtCore.QStandardPaths.writableLocation", lambda *args: fncDir)
+ tstConf.verQtValue = 50600
+ tstConf.initConfig()
+ assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
+ assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
+ assert not os.path.isfile(confFile)
+ tstConf.verQtValue = 50000
+ tstConf.initConfig()
+ assert tstConf.confPath == os.path.join(fncDir, tstConf.appHandle)
+ assert tstConf.dataPath == os.path.join(fncDir, tstConf.appHandle)
+ assert not os.path.isfile(confFile)
+ monkeypatch.undo()
+
+ # Fail to make folders
+ monkeypatch.setattr("os.mkdir", causeOSError)
+
+ tstConfDir = os.path.join(fncDir, "test_conf")
+ tstConf.initConfig(confPath=tstConfDir, dataPath=tmpDir)
+ assert tstConf.confPath is None
+ assert tstConf.dataPath == tmpDir
+ assert not os.path.isfile(confFile)
+
+ tstDataDir = os.path.join(fncDir, "test_data")
+ tstConf.initConfig(confPath=tmpDir, dataPath=tstDataDir)
+ assert tstConf.confPath == tmpDir
+ assert tstConf.dataPath is None
+ assert os.path.isfile(confFile)
+ os.unlink(confFile)
+
+ monkeypatch.undo()
+
+ # Test load/save with no path
+ tstConf.confPath = None
+ assert not tstConf.loadConfig()
+ assert not tstConf.saveConfig()
+
+ # Run again and set the paths directly and correctly
+ # This should create a config file as well
+ monkeypatch.setattr("os.path.expanduser", lambda *args: "")
+ tstConf.spellTool = nwConst.SP_INTERNAL
+ 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, [2, 9])
+ monkeypatch.undo()
+
+ # Load and save with OSError
+ monkeypatch.setattr("builtins.open", causeOSError)
+
+ assert not tstConf.loadConfig()
+ assert tstConf.hasError is True
+ assert tstConf.errData != []
+ assert tstConf.getErrData().startswith("Could not")
+ assert tstConf.hasError is False
+ assert tstConf.errData == []
+
+ assert not tstConf.saveConfig()
+ assert tstConf.hasError is True
+ assert tstConf.errData != []
+ assert tstConf.getErrData().startswith("Could not")
+ assert tstConf.hasError is False
+ assert tstConf.errData == []
+
+ monkeypatch.undo()
+
+ assert tstConf.loadConfig()
+ assert tstConf.saveConfig()
+
+ copyfile(confFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 9])
+
+# END Test testBaseConfig_Init
+
+@pytest.mark.base
+def testBaseConfig_RecentCache(monkeypatch, tmpConf, tmpDir, fncDir):
+ """Test recent cache file.
+ """
+ # Check failing
+ tmpConf.dataPath = None
+ assert not tmpConf.loadRecentCache()
+ assert not tmpConf.saveRecentCache()
+ tmpConf.dataPath = tmpDir
+
+ # Add a couple of values
+ pathOne = os.path.join(fncDir, "projPathOne", nwFiles.PROJ_FILE)
+ pathTwo = os.path.join(fncDir, "projPathTwo", nwFiles.PROJ_FILE)
+ assert tmpConf.updateRecentCache(pathOne, "Proj One", 100, 1600002000)
+ assert tmpConf.updateRecentCache(pathTwo, "Proj Two", 200, 1600005600)
+ assert tmpConf.recentProj == {
+ pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
+ pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
+ }
+
+ # Fail to Save
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert not tmpConf.saveRecentCache()
+ monkeypatch.undo()
+
+ # Save Proper
+ cacheFile = os.path.join(tmpDir, nwFiles.RECENT_FILE)
+ assert tmpConf.saveRecentCache()
+ assert tmpConf.saveRecentCache()
+ assert os.path.isfile(cacheFile)
+
+ # Fail to Load
+ monkeypatch.setattr("builtins.open", causeOSError)
+ tmpConf.recentProj = {}
+ assert not tmpConf.loadRecentCache()
+ assert tmpConf.recentProj == {}
+ monkeypatch.undo()
+
+ # Load Proper
+ tmpConf.recentProj = {}
+ assert tmpConf.loadRecentCache()
+ assert tmpConf.recentProj == {
+ pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
+ pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
+ }
+
+ # Remove Non-Existent Entry
+ assert not tmpConf.removeFromRecentCache("dummy")
+ assert tmpConf.recentProj == {
+ pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
+ pathTwo: {"time": 1600005600, "title": "Proj Two", "words": 200},
+ }
+
+ # Remove Second Entry
+ assert tmpConf.removeFromRecentCache(pathTwo)
+ assert tmpConf.recentProj == {
+ pathOne: {"time": 1600002000, "title": "Proj One", "words": 100},
+ }
+
+# END Test testBaseConfig_RecentCache
+
+@pytest.mark.base
+def testBaseConfig_SetPath(tmpConf, tmpDir):
+ """Test path setters.
+ """
+ # Conf Path
+ assert tmpConf.setConfPath(None)
+ assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow"))
+ assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf"))
+ assert tmpConf.confPath == tmpDir
+ assert tmpConf.confFile == "novelwriter.conf"
+ assert not tmpConf.confChanged
+
+ # Data Path
+ assert tmpConf.setDataPath(None)
+ assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow"))
+ assert tmpConf.setDataPath(tmpDir)
+ assert tmpConf.dataPath == tmpDir
+ assert not tmpConf.confChanged
+
+ # Last Path
+ assert tmpConf.setLastPath(None)
+ assert tmpConf.lastPath == ""
+
+ assert tmpConf.setLastPath(os.path.join(tmpDir, "file.tmp"))
+ assert tmpConf.lastPath == tmpDir
+
+ assert tmpConf.setLastPath("")
+ assert tmpConf.lastPath == ""
+
+# END Test testBaseConfig_SetPath
+
+@pytest.mark.base
+def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
+ """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
+ # ===========
+ tmpConf.guiScale = 1.0
+ assert tmpConf.pxInt(10) == 10
+ assert tmpConf.pxInt(13) == 13
+ assert tmpConf.rpxInt(10) == 10
+ assert tmpConf.rpxInt(13) == 13
+
+ tmpConf.guiScale = 2.0
+ assert tmpConf.pxInt(10) == 20
+ assert tmpConf.pxInt(13) == 26
+ assert tmpConf.rpxInt(10) == 5
+ assert tmpConf.rpxInt(13) == 6
+
+ # Setter + Getter Combos
+ # ======================
+
+ # Window Size
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setWinSize(1205, 655)
+ assert not tmpConf.confChanged
+
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setWinSize(70, 70)
+ assert tmpConf.getWinSize() == [70, 70]
+ assert tmpConf.winGeometry == [35, 35]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setWinSize(70, 70)
+ assert tmpConf.getWinSize() == [70, 70]
+ assert tmpConf.winGeometry == [70, 70]
+
+ assert tmpConf.setWinSize(1200, 650)
+
+ # Project Tree Columns
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setTreeColWidths([10, 20, 25])
+ assert tmpConf.getTreeColWidths() == [10, 20, 24]
+ assert tmpConf.treeColWidth == [5, 10, 12]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setTreeColWidths([10, 20, 25])
+ assert tmpConf.getTreeColWidths() == [10, 20, 25]
+ assert tmpConf.treeColWidth == [10, 20, 25]
+
+ assert tmpConf.setTreeColWidths([200, 50, 30])
+
+ # Project Settings Tree Columns
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setProjColWidths([10, 20, 30])
+ assert tmpConf.getProjColWidths() == [10, 20, 30]
+ assert tmpConf.projColWidth == [5, 10, 15]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setProjColWidths([10, 20, 30])
+ assert tmpConf.getProjColWidths() == [10, 20, 30]
+ assert tmpConf.projColWidth == [10, 20, 30]
+
+ assert tmpConf.setProjColWidths([200, 60, 140])
+
+ # Main Pane Splitter
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setMainPanePos([200, 700])
+ assert tmpConf.getMainPanePos() == [200, 700]
+ assert tmpConf.mainPanePos == [100, 350]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setMainPanePos([200, 700])
+ assert tmpConf.getMainPanePos() == [200, 700]
+ assert tmpConf.mainPanePos == [200, 700]
+
+ assert tmpConf.setMainPanePos([300, 800])
+
+ # Doc Pane Splitter
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setDocPanePos([300, 300])
+ assert tmpConf.getDocPanePos() == [300, 300]
+ assert tmpConf.docPanePos == [150, 150]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setDocPanePos([300, 300])
+ assert tmpConf.getDocPanePos() == [300, 300]
+ assert tmpConf.docPanePos == [300, 300]
+
+ assert tmpConf.setDocPanePos([400, 400])
+
+ # View Pane Splitter
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setViewPanePos([400, 250])
+ assert tmpConf.getViewPanePos() == [400, 250]
+ assert tmpConf.viewPanePos == [200, 125]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setViewPanePos([400, 250])
+ assert tmpConf.getViewPanePos() == [400, 250]
+ assert tmpConf.viewPanePos == [400, 250]
+
+ assert tmpConf.setViewPanePos([500, 150])
+
+ # Outline Pane Splitter
+ tmpConf.guiScale = 2.0
+ assert tmpConf.setOutlinePanePos([400, 250])
+ assert tmpConf.getOutlinePanePos() == [400, 250]
+ assert tmpConf.outlnPanePos == [200, 125]
+
+ tmpConf.guiScale = 1.0
+ assert tmpConf.setOutlinePanePos([400, 250])
+ assert tmpConf.getOutlinePanePos() == [400, 250]
+ assert tmpConf.outlnPanePos == [400, 250]
+
+ assert tmpConf.setOutlinePanePos([500, 150])
+
+ # Getters Only
+ # ============
+ tmpConf.guiScale = 1.0
+ assert tmpConf.getTextWidth() == 600
+ assert tmpConf.getTextMargin() == 40
+ assert tmpConf.getTabWidth() == 40
+ assert tmpConf.getFocusWidth() == 800
+
+ tmpConf.guiScale = 2.0
+ assert tmpConf.getTextWidth() == 1200
+ assert tmpConf.getTextMargin() == 80
+ assert tmpConf.getTabWidth() == 80
+ assert tmpConf.getFocusWidth() == 1600
+
+ # Flag Setters
+ # ============
+ assert not tmpConf.setShowRefPanel(False)
+ assert not tmpConf.showRefPanel
+ assert tmpConf.setShowRefPanel(True)
+
+ assert not tmpConf.setViewComments(False)
+ assert not tmpConf.viewComments
+ assert tmpConf.setViewComments(True)
+
+ assert not tmpConf.setViewSynopsis(False)
+ assert not tmpConf.viewSynopsis
+ assert tmpConf.setViewSynopsis(True)
+
+ # Check Final File
+ # ================
+
+ assert tmpConf.confChanged
+ assert tmpConf.saveConfig()
+ assert not tmpConf.confChanged
+
+ copyfile(confFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 9])
+
+# END Test testBaseConfig_SettersGetters
+
+@pytest.mark.base
+def testBaseConfig_Internal(monkeypatch, tmpConf):
+ """Check internal functions.
+ """
+ # Function _packList
+ assert tmpConf._packList(["A", 1, 2.0, None, False]) == "A, 1, 2.0, None, False"
+
+ # Function _unpackList
+ assert tmpConf._unpackList("1, 2, 3", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 3]
+ assert tmpConf._unpackList("1, 2 ", [0, 0, 0], tmpConf.CNF_I_LST) == [1, 2, 0]
+ assert tmpConf._unpackList("A, B, C", [0, 0, 0], tmpConf.CNF_I_LST) == [0, 0, 0]
+ assert tmpConf._unpackList("1, 2, 3", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["1", "2", "3"]
+ assert tmpConf._unpackList("A, B ", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "Z"]
+ assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_S_LST) == ["A", "B", "C"]
+ assert tmpConf._unpackList("A, B, C", ["X", "Y", "Z"], tmpConf.CNF_STR) == ["X", "Y", "Z"]
+
+ # Function _parseLine
+ cnfParse = configparser.ConfigParser()
+ cnfParse.read_string(
+ "[Main]\n"
+ "val_string = dummy\n"
+ "val_int = 123\n"
+ "val_bool = True\n"
+ "val_list_string = A, B, C\n"
+ "val_list_int = 1, 2, 3\n"
+ )
+
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_string", tmpConf.CNF_STR, "default"
+ ) == "dummy"
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "nope", tmpConf.CNF_STR, "default"
+ ) == "default"
+
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_int", tmpConf.CNF_INT, "0"
+ ) == 123
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "nope", tmpConf.CNF_INT, 0
+ ) == 0
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_string", tmpConf.CNF_INT, 0
+ ) == 0
+
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_bool", tmpConf.CNF_BOOL, False
+ ) is True
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "nope", tmpConf.CNF_BOOL, False
+ ) is False
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_string", tmpConf.CNF_BOOL, False
+ ) is False
+
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_list_string", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"]
+ ) == ["A", "B", "C", "Z"]
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "nope", tmpConf.CNF_S_LST, ["W", "X", "Y", "Z"]
+ ) == ["W", "X", "Y", "Z"]
+
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "val_list_int", tmpConf.CNF_I_LST, [6, 7, 8, 9]
+ ) == [1, 2, 3, 9]
+ assert tmpConf._parseLine(
+ cnfParse, "Main", "nope", tmpConf.CNF_S_LST, [6, 7, 8, 9]
+ ) == [6, 7, 8, 9]
+
+ # Function _checkNone
+ assert tmpConf._checkNone(None) is None
+ assert tmpConf._checkNone("None") is None
+ assert tmpConf._checkNone("stuff") == "stuff"
+
+ # Function _checkOptionalPackages
+ # (Assumes enchant package exists ans is importable)
+ tmpConf._checkOptionalPackages()
+ assert tmpConf.hasEnchant is True
+
+ monkeypatch.setitem(sys.modules, "enchant", None)
+ tmpConf._checkOptionalPackages()
+ assert tmpConf.hasEnchant is False
+ monkeypatch.undo()
+
+ monkeypatch.setattr("shutil.which", lambda *args: "dummy")
+ tmpConf._checkOptionalPackages()
+ assert tmpConf.hasAssistant is True
+ monkeypatch.undo()
+
+ monkeypatch.setattr("shutil.which", lambda *args: None)
+ tmpConf._checkOptionalPackages()
+ assert tmpConf.hasAssistant is False
+ monkeypatch.undo()
+
+# END Test testBaseConfig_Internal
diff --git a/tests/test_base_error.py b/tests/test_base_error.py
new file mode 100644
index 00000000..472faaa2
--- /dev/null
+++ b/tests/test_base_error.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Error Tester
+"""
+
+import nw
+import pytest
+
+from PyQt5.QtWidgets import qApp
+
+from dummy import causeException
+
+from nw.error import NWErrorMessage, exceptionHandler
+
+@pytest.mark.base
+def testBaseError_Dialog(qtbot, monkeypatch, fncDir, tmpDir):
+ """Test the error dialog.
+ """
+ qApp.closeAllWindows()
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+
+ nwErr = NWErrorMessage(nwGUI)
+ qtbot.addWidget(nwErr)
+ nwErr.show()
+
+ # Invalid Error Message
+ nwErr.setMessage(Exception, "Faulty Error", 123)
+ assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..."
+
+ # Valid Error Message
+ monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", lambda: "1.2.3")
+ nwErr.setMessage(Exception, "Fine Error", None)
+ theMessage = nwErr.msgBody.toPlainText()
+ assert theMessage
+ assert "Fine Error" in theMessage
+ assert "Exception" in theMessage
+ assert "(1.2.3)" in theMessage
+ monkeypatch.undo()
+
+ # No kernel version retrieved
+ monkeypatch.setattr("PyQt5.QtCore.QSysInfo.kernelVersion", causeException)
+ nwErr.setMessage(Exception, "Almost Fine Error", None)
+ theMessage = nwErr.msgBody.toPlainText()
+ assert theMessage
+ assert "(Unknown)" in theMessage
+ monkeypatch.undo()
+
+ nwErr._doClose()
+ nwErr.close()
+ nwGUI.closeMain()
+
+# END Test testBaseError_Dialog
+
+@pytest.mark.base
+def testBaseError_Handler(qtbot, monkeypatch, fncDir, tmpDir):
+ """Test the error handler. This test doesn'thave any asserts, but it
+ checks that the error handler handles potential exceptions. The test
+ will fail if excpetions are not handled.
+ """
+ qApp.closeAllWindows()
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+
+ # Normal shutdown
+ monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
+ exceptionHandler(Exception, "Error Message", None)
+ monkeypatch.undo()
+
+ # Should not crash when no GUI is found
+ monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", lambda: [])
+ exceptionHandler(Exception, "Error Message", None)
+ monkeypatch.undo()
+
+ # Should handle qApp failing
+ monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.topLevelWidgets", causeException)
+ exceptionHandler(Exception, "Error Message", None)
+ monkeypatch.undo()
+
+ # Should handle failing to close main GUI
+ monkeypatch.setattr(NWErrorMessage, "exec_", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.qApp.exit", lambda *args: None)
+ monkeypatch.setattr(nwGUI, "closeMain", causeException)
+ exceptionHandler(Exception, "Error Message", None)
+ monkeypatch.undo()
+
+ nwGUI.closeMain()
+
+# END Test testBaseError_Handler
diff --git a/tests/test_base_init.py b/tests/test_base_init.py
new file mode 100644
index 00000000..b691bfa1
--- /dev/null
+++ b/tests/test_base_init.py
@@ -0,0 +1,163 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main Init Tester
+"""
+
+import nw
+import pytest
+import logging
+import sys
+
+from dummy import DummyMain
+
+@pytest.mark.base
+def testBaseInit_Launch(caplog, monkeypatch, tmpDir):
+ """Check launching the main GUI.
+ """
+ monkeypatch.setattr("nw.guimain.GuiMain", DummyMain)
+
+ # Testmode launch
+ nwGUI = nw.main(
+ ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert isinstance(nwGUI, DummyMain)
+
+ # Darwin launch
+ monkeypatch.setitem(sys.modules, "Foundation", None)
+ osDarwin = nw.CONFIG.osDarwin
+ nw.CONFIG.osDarwin = True
+ nwGUI = nw.main(
+ ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert isinstance(nwGUI, DummyMain)
+ assert "Foundation" in caplog.messages[1]
+ nw.CONFIG.osDarwin = osDarwin
+
+ # Normal launch
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationName", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setApplicationVersion", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setWindowIcon", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.setOrganizationDomain", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *args: 0)
+ with pytest.raises(SystemExit) as ex:
+ nw.main(["--config=%s" % tmpDir, "--data=%s" % tmpDir])
+
+ assert ex.value.code == 0
+
+ monkeypatch.undo()
+
+# END Test testBaseInit_Launch
+
+@pytest.mark.base
+def testBaseInit_Options(monkeypatch, tmpDir):
+ """Test command line options for logging level.
+ """
+ monkeypatch.setattr("nw.guimain.GuiMain", DummyMain)
+ monkeypatch.setattr(sys, "argv", [
+ "novelWriter.py", "--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir
+ ])
+
+ # Defaults w/None Args
+ nwGUI = nw.main()
+ assert nw.logger.getEffectiveLevel() == logging.WARNING
+ assert nw.CONFIG.debugInfo is False
+ assert nw.CONFIG.showGUI is False
+ assert nwGUI.closeMain() == "closeMain"
+
+ # Defaults
+ nwGUI = nw.main(
+ ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "--style=Fusion"]
+ )
+ assert nw.logger.getEffectiveLevel() == logging.WARNING
+ assert nw.CONFIG.debugInfo is False
+ assert nwGUI.closeMain() == "closeMain"
+
+ # Log Levels
+ nwGUI = nw.main(
+ ["--testmode", "--info", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nw.logger.getEffectiveLevel() == logging.INFO
+ assert nw.CONFIG.debugInfo is False
+ assert nwGUI.closeMain() == "closeMain"
+
+ nwGUI = nw.main(
+ ["--testmode", "--debug", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nw.logger.getEffectiveLevel() == logging.DEBUG
+ assert nw.CONFIG.debugInfo is True
+ assert nwGUI.closeMain() == "closeMain"
+
+ nwGUI = nw.main(
+ ["--testmode", "--verbose", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nw.logger.getEffectiveLevel() == 5
+ assert nw.CONFIG.debugInfo is True
+ assert nwGUI.closeMain() == "closeMain"
+
+ # Help and Version
+ with pytest.raises(SystemExit) as ex:
+ nwGUI = nw.main(
+ ["--testmode", "--help", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nwGUI.closeMain() == "closeMain"
+ assert ex.value.code == 0
+
+ with pytest.raises(SystemExit) as ex:
+ nwGUI = nw.main(
+ ["--testmode", "--version", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nwGUI.closeMain() == "closeMain"
+ assert ex.value.code == 0
+
+ # Invalid options
+ with pytest.raises(SystemExit) as ex:
+ nwGUI = nw.main(
+ ["--testmode", "--invalid", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+ assert nwGUI.closeMain() == "closeMain"
+ assert ex.value.code == 2
+
+ # Project Path
+ nwGUI = nw.main(
+ ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir, "sample/"]
+ )
+ assert nw.CONFIG.cmdOpen == "sample/"
+ assert nwGUI.closeMain() == "closeMain"
+
+ monkeypatch.undo()
+
+# END Test testBaseInit_Options
+
+@pytest.mark.base
+def testBaseInit_Imports(caplog, monkeypatch, tmpDir):
+ """Check import error handling.
+ """
+ monkeypatch.setattr("nw.guimain.GuiMain", DummyMain)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.__init__", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QApplication.exec_", lambda *args: 0)
+ monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.__init__", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.resize", lambda *args: None)
+ monkeypatch.setattr("PyQt5.QtWidgets.QErrorMessage.showMessage", lambda *args: None)
+ monkeypatch.setitem(sys.modules, "lxml", None)
+ monkeypatch.setattr("sys.hexversion", 0x0)
+ monkeypatch.setattr("nw.CONFIG.verQtValue", 50000)
+ monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000)
+
+ with pytest.raises(SystemExit) as ex:
+ _ = nw.main(
+ ["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]
+ )
+
+ assert ex.value.code & 4 == 4 # Python version not satisfied
+ assert ex.value.code & 8 == 8 # Qt version not satisfied
+ assert ex.value.code & 16 == 16 # PyQt version not satisfied
+ assert ex.value.code & 32 == 32 # lxml package missing
+
+ assert "At least Python" in caplog.messages[0]
+ assert "At least Qt5" in caplog.messages[1]
+ assert "At least PyQt5" in caplog.messages[2]
+ assert "lxml" in caplog.messages[3]
+
+ monkeypatch.undo()
+
+# END Test testBaseInit_Imports
diff --git a/tests/test_config.py b/tests/test_config.py
deleted file mode 100644
index 94ff5d1c..00000000
--- a/tests/test_config.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter Config Class Tester
-"""
-
-import pytest
-import os
-
-from tools import cmpFiles
-
-@pytest.mark.core
-def testConfigCore(tmpConf, tmpDir, refDir):
- refConf = os.path.join(refDir, "novelwriter.conf")
- testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
-
- assert tmpConf.confPath == tmpDir
- assert tmpConf.saveConfig()
- assert cmpFiles(testConf, refConf, [2, 9])
- assert not tmpConf.confChanged
-
- assert tmpConf.loadConfig()
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigSetConfPath(tmpConf, tmpDir):
- assert tmpConf.setConfPath(None)
- assert not tmpConf.setConfPath(os.path.join("somewhere", "over", "the", "rainbow"))
- assert tmpConf.setConfPath(os.path.join(tmpDir, "novelwriter.conf"))
- assert tmpConf.confPath == tmpDir
- assert tmpConf.confFile == "novelwriter.conf"
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigSetDataPath(tmpConf, tmpDir):
- assert tmpConf.setDataPath(None)
- assert not tmpConf.setDataPath(os.path.join("somewhere", "over", "the", "rainbow"))
- assert tmpConf.setDataPath(tmpDir)
- assert tmpConf.dataPath == tmpDir
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigSetWinSize(tmpConf, tmpDir, refDir):
- refConf = os.path.join(refDir, "novelwriter.conf")
- testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
- tmpConf.guiScale = 1.0
-
- assert tmpConf.confPath == tmpDir
- assert tmpConf.setWinSize(1205, 655)
- assert not tmpConf.confChanged
- assert tmpConf.setWinSize(70, 70)
- assert tmpConf.confChanged
- assert tmpConf.setWinSize(1200, 650)
- assert tmpConf.saveConfig()
-
- assert cmpFiles(testConf, refConf, [2, 9])
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigSetTreeColWidths(tmpConf, tmpDir, refDir):
- refConf = os.path.join(refDir, "novelwriter.conf")
- testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
-
- assert tmpConf.confPath == tmpDir
- tmpConf.guiScale = 1.0
-
- assert tmpConf.setTreeColWidths([10, 20, 25])
- assert tmpConf.treeColWidth == [10, 20, 25]
- assert tmpConf.setTreeColWidths([200, 50, 30])
-
- assert tmpConf.setProjColWidths([10, 20, 30])
- assert tmpConf.projColWidth == [10, 20, 30]
- assert tmpConf.setProjColWidths([200, 60, 140])
-
- assert tmpConf.confChanged
- assert tmpConf.saveConfig()
-
- assert cmpFiles(testConf, refConf, [2, 9])
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigSetPanePos(tmpConf, tmpDir, refDir):
- refConf = os.path.join(refDir, "novelwriter.conf")
- testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
-
- assert tmpConf.confPath == tmpDir
-
- tmpConf.guiScale = 2.0
- assert tmpConf.setMainPanePos([200, 700])
- assert tmpConf.mainPanePos == [100, 350]
- assert tmpConf.getMainPanePos() == [200, 700]
-
- assert tmpConf.setDocPanePos([300, 300])
- assert tmpConf.docPanePos == [150, 150]
- assert tmpConf.getDocPanePos() == [300, 300]
-
- assert tmpConf.setViewPanePos([400, 250])
- assert tmpConf.viewPanePos == [200, 125]
- assert tmpConf.getViewPanePos() == [400, 250]
-
- assert tmpConf.setOutlinePanePos([400, 250])
- assert tmpConf.outlnPanePos == [200, 125]
- assert tmpConf.getOutlinePanePos() == [400, 250]
-
- tmpConf.guiScale = 1.0
- assert tmpConf.setMainPanePos([300, 800])
- assert tmpConf.setDocPanePos([400, 400])
- assert tmpConf.setViewPanePos([500, 150])
- assert tmpConf.setOutlinePanePos([500, 150])
-
- assert tmpConf.confChanged
- assert tmpConf.saveConfig()
-
- assert cmpFiles(testConf, refConf, [2, 9])
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigFlags(tmpConf, tmpDir, refDir):
- refConf = os.path.join(refDir, "novelwriter.conf")
- testConf = os.path.join(tmpConf.confPath, "novelwriter.conf")
-
- assert tmpConf.confPath == tmpDir
-
- assert not tmpConf.setShowRefPanel(False)
- assert tmpConf.setShowRefPanel(True)
-
- assert not tmpConf.setViewComments(False)
- assert not tmpConf.viewComments
- assert tmpConf.setViewComments(True)
-
- assert not tmpConf.setViewSynopsis(False)
- assert not tmpConf.viewSynopsis
- assert tmpConf.setViewSynopsis(True)
-
- assert tmpConf.confChanged
- assert tmpConf.saveConfig()
-
- assert cmpFiles(testConf, refConf, [2, 9])
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testTextSizes(tmpConf, tmpDir, refDir):
- assert tmpConf.confPath == tmpDir
-
- tmpConf.guiScale = 2.0
- assert tmpConf.getTextWidth() == 1200
- assert tmpConf.getTextMargin() == 80
- assert tmpConf.getTabWidth() == 80
- assert tmpConf.getFocusWidth() == 1600
- tmpConf.guiScale = 1.0
-
- assert not tmpConf.confChanged
-
-@pytest.mark.core
-def testConfigErrors(tmpConf):
- nonPath = os.path.join("somewhere", "over", "the", "rainbow")
- assert tmpConf.initConfig(nonPath, nonPath)
- assert tmpConf.hasError
- assert not tmpConf.loadConfig()
- assert not tmpConf.saveConfig()
- assert not tmpConf.loadRecentCache()
- assert len(tmpConf.getErrData()) > 0
-
-@pytest.mark.core
-def testConfigInternals(tmpConf):
- assert tmpConf._checkNone(None) is None
- assert tmpConf._checkNone("None") is None
diff --git a/tests/test_core_project.py b/tests/test_core_project.py
index fe847983..3abefbbc 100644
--- a/tests/test_core_project.py
+++ b/tests/test_core_project.py
@@ -22,8 +22,8 @@ def testCoreProject_NewMinimal(fncDir, outDir, refDir, tmpDir, dummyGUI):
default setting, creating a Minimal project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_1_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_1_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_NewMinimal_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_NewMinimal_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
@@ -70,8 +70,8 @@ def testCoreProject_NewCustomA(fncDir, outDir, refDir, dummyGUI):
Custom type with chapters and scenes.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_2_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_2_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_NewCustomA_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_NewCustomA_nwProject.nwx")
projData = {
"projName": "Test Custom",
@@ -111,8 +111,8 @@ def testCoreProject_NewCustomB(fncDir, outDir, refDir, dummyGUI):
Custom type without chapters, but with scenes.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_3_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_3_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_NewCustomB_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_NewCustomB_nwProject.nwx")
projData = {
"projName": "Test Custom",
@@ -238,8 +238,8 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, dummyGUI):
"""Check that new root folders can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_4_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_4_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_NewRoot_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_NewRoot_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
@@ -274,8 +274,8 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, dummyGUI):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(outDir, "coreProject_5_nwProject.nwx")
- compFile = os.path.join(refDir, "coreProject_5_nwProject.nwx")
+ testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
+ compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
theProject = NWProject(dummyGUI)
theProject.projTree.setSeed(42)
diff --git a/tests/test_error.py b/tests/test_error.py
deleted file mode 100644
index 192fb6cb..00000000
--- a/tests/test_error.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter Error Tester
-"""
-
-import nw
-import pytest
-
-from PyQt5.QtWidgets import qApp
-
-from nw.error import NWErrorMessage, exceptionHandler
-
-@pytest.mark.error
-def testErrorDialog(qtbot, fncDir, tmpDir):
- qApp.closeAllWindows()
- nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
-
- nwErr = NWErrorMessage(nwGUI)
- qtbot.addWidget(nwErr)
- nwErr.show()
-
- # Invalid Error
- nwErr.setMessage(Exception, "Faulty Error", 123)
- assert nwErr.msgBody.toPlainText() == "Failed to generate error report ..."
-
- # Valid Error
- nwErr.setMessage(Exception, "First Error", None)
- theMessage = nwErr.msgBody.toPlainText()
- assert theMessage
- assert "First Error" in theMessage
- assert "Exception" in theMessage
- nwErr._doClose()
- nwErr.close()
-
- theMessage = exceptionHandler(Exception, "Second Error", None, testMode=True)
- assert theMessage
- assert "Second Error" in theMessage
- assert "Exception" in theMessage
-
- nwGUI.closeMain()
-
- # qtbot.stopForInteraction()
diff --git a/tests/test_gui_main.py b/tests/test_gui_main.py
index a10c0582..b56c525b 100644
--- a/tests/test_gui_main.py
+++ b/tests/test_gui_main.py
@@ -4,9 +4,7 @@
import nw
import pytest
-import logging
import os
-import sys
from shutil import copyfile
from tools import cmpFiles
@@ -26,82 +24,6 @@ keyDelay = 2
typeDelay = 1
stepDelay = 20
-@pytest.mark.gui
-def testLaunch(qtbot, monkeypatch, fncDir, tmpDir):
-
- # Defaults
- nwGUI = nw.main(
- ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir, "--style=Fusion"]
- )
- assert nw.logger.getEffectiveLevel() == logging.WARNING
- nwGUI.closeMain()
- nwGUI.close()
-
- # Log Levels
- nwGUI = nw.main(
- ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- assert nw.logger.getEffectiveLevel() == logging.INFO
- nwGUI.closeMain()
- nwGUI.close()
-
- nwGUI = nw.main(
- ["--testmode", "--debug", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- assert nw.logger.getEffectiveLevel() == logging.DEBUG
- nwGUI.closeMain()
- nwGUI.close()
-
- nwGUI = nw.main(
- ["--testmode", "--verbose", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- assert nw.logger.getEffectiveLevel() == 5
- nwGUI.closeMain()
- nwGUI.close()
-
- # Help and Version
- with pytest.raises(SystemExit) as ex:
- nwGUI = nw.main(
- ["--testmode", "--help", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- nwGUI.closeMain()
- nwGUI.close()
- assert ex.value.code == 0
-
- with pytest.raises(SystemExit) as ex:
- nwGUI = nw.main(
- ["--testmode", "--version", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- nwGUI.closeMain()
- nwGUI.close()
- assert ex.value.code == 0
-
- # Invalid options
- with pytest.raises(SystemExit) as ex:
- nwGUI = nw.main(
- ["--testmode", "--invalid", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- nwGUI.closeMain()
- nwGUI.close()
- assert ex.value.code == 2
-
- # Simulate import error
- monkeypatch.setitem(sys.modules, "lxml", None)
- monkeypatch.setattr("sys.hexversion", 0x0)
- monkeypatch.setattr("nw.CONFIG.verQtValue", 50000)
- monkeypatch.setattr("nw.CONFIG.verPyQtValue", 50000)
- with pytest.raises(SystemExit) as ex:
- nwGUI = nw.main(
- ["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir]
- )
- nwGUI.closeMain()
- nwGUI.close()
- assert ex.value.code & 4 == 4 # Python version not satisfied
- assert ex.value.code & 8 == 8 # Qt version not satisfied
- assert ex.value.code & 16 == 16 # PyQt version not satisfied
- assert ex.value.code & 32 == 32 # lxml package missing
- monkeypatch.undo()
-
@pytest.mark.gui
def testDocEditor(qtbot, yesToAll, fncDir, nwTempGUI, refDir, tmpDir):