diff --git a/.github/workflows/pytest_cov.yml b/.github/workflows/pytest_cov.yml
index 45c51c0d..1969d200 100644
--- a/.github/workflows/pytest_cov.yml
+++ b/.github/workflows/pytest_cov.yml
@@ -26,12 +26,13 @@ jobs:
pip install --upgrade pip
pip install -r requirements.txt
pip install PyVirtualDisplay
+ pip install pytest-timeout
pip install pytest-cov
pip install pytest-xvfb
pip install pytest-qt
pip install codecov
- name: Run Tests
- run: xvfb-run pytest -v --cov=nw
+ run: xvfb-run pytest -v --cov=nw --timeout=60
- name: Upload to Codecov
uses: codecov/codecov-action@v1
@@ -58,7 +59,8 @@ jobs:
pip install --upgrade pip
pip install -r requirements.txt
pip install PyVirtualDisplay
+ pip install pytest-timeout
pip install pytest-xvfb
pip install pytest-qt
- name: Run Tests
- run: xvfb-run pytest -v
+ run: xvfb-run pytest -v --timeout=60
diff --git a/nw/common.py b/nw/common.py
index febac7cc..68825f4d 100644
--- a/nw/common.py
+++ b/nw/common.py
@@ -243,8 +243,6 @@ def fuzzyTime(secDiff):
else:
return "%d years ago" % int(round(secDiff/31557600))
- return "beyond time and space"
-
def makeFileNameSafe(theText):
"""Returns a filename safe version of the text.
"""
diff --git a/nw/config.py b/nw/config.py
index 6e0a34b3..5138a677 100644
--- a/nw/config.py
+++ b/nw/config.py
@@ -54,9 +54,11 @@ class Config:
# Set Application Variables
self.appName = "novelWriter"
self.appHandle = self.appName.lower()
+ self.cmdOpen = None
+
+ # Debug Settings
self.showGUI = True
self.debugInfo = False
- self.cmdOpen = None
# Config Error Handling
self.hasError = False
diff --git a/nw/core/project.py b/nw/core/project.py
index 414c4f18..5bead8f5 100644
--- a/nw/core/project.py
+++ b/nw/core/project.py
@@ -41,7 +41,7 @@ from nw.core.document import NWDoc
from nw.core.status import NWStatus
from nw.core.options import OptionState
from nw.common import (
- checkString, checkBool, checkInt, formatTimeStamp, makeFileNameSafe
+ checkString, checkBool, checkInt, isHandle, formatTimeStamp, makeFileNameSafe
)
from nw.constants import (
nwFiles, nwItemType, nwItemClass, nwItemLayout, nwLabels, nwAlert
@@ -1288,10 +1288,13 @@ class NWProject():
logger.warning("Skipping file %s" % fileItem)
continue
fHandle = fileItem[:13]
+ if not isHandle(fHandle):
+ logger.warning("Skipping file %s" % fileItem)
+ continue
if fHandle in self.projTree:
logger.debug("Checking file %s, handle %s: OK" % (fileItem, fHandle))
else:
- logger.debug("Checking file %s, handle %s: Orphaned" % (fileItem, fHandle))
+ logger.warning("Checking file %s, handle %s: Orphaned" % (fileItem, fHandle))
orphanFiles.append(fHandle)
# Report status
diff --git a/nw/error.py b/nw/error.py
index 7788508f..e1283fbd 100644
--- a/nw/error.py
+++ b/nw/error.py
@@ -35,6 +35,7 @@ class NWErrorMessage(QDialog):
def __init__(self, parent):
QDialog.__init__(self, parent=parent)
+ self.setObjectName("NWErrorMessage")
# Widgets
self.msgIcon = QLabel()
@@ -134,21 +135,18 @@ class NWErrorMessage(QDialog):
# END Class NWErrorMessage
-def exceptionHandler(exType, exValue, exTrace):
+def exceptionHandler(exType, exValue, exTrace, testMode=False):
"""Function to catch unhandled global exceptions.
"""
+ import nw
import logging
from traceback import print_tb
- from nw import CONFIG
from PyQt5.QtWidgets import qApp
logger = logging.getLogger(__name__)
logger.critical("%s: %s" % (exType.__name__, str(exValue)))
print_tb(exTrace)
- if not CONFIG.showGUI:
- return
-
try:
nwGUI = None
for qWin in qApp.topLevelWidgets():
@@ -162,7 +160,8 @@ def exceptionHandler(exType, exValue, exTrace):
errMsg = NWErrorMessage(nwGUI)
errMsg.setMessage(exType, exValue, exTrace)
- errMsg.exec_()
+ if nw.CONFIG.showGUI:
+ errMsg.exec_()
try:
# Try a controlled shudown
@@ -174,7 +173,10 @@ def exceptionHandler(exType, exValue, exTrace):
logger.critical("Could not close the project before exiting")
logger.critical(str(e))
- qApp.exit(1)
+ if testMode:
+ return errMsg.msgBody.toPlainText()
+ else:
+ qApp.exit(1)
except Exception as e:
logger.critical(str(e))
diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py
index d1e21c20..e0490f1a 100644
--- a/nw/gui/preferences.py
+++ b/nw/gui/preferences.py
@@ -39,7 +39,6 @@ from PyQt5.QtWidgets import (
from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog, QuotesDialog
from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant
-from nw.constants import nwQuotes
logger = logging.getLogger(__name__)
@@ -104,7 +103,7 @@ class GuiPreferences(PagedDialog):
validEntries &= retA
needsRestart |= retB
- if needsRestart:
+ if needsRestart and self.mainConf.showGUI:
msgBox = QMessageBox()
msgBox.information(
self, "Preferences",
@@ -817,69 +816,70 @@ class GuiConfigEditAutoReplaceTab(QWidget):
qWidth = self.mainConf.pxInt(40)
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
+ self.quoteSym = {}
## Single Quote Style
- self.quoteSingleStyleO = QLineEdit()
- self.quoteSingleStyleO.setMaxLength(1)
- self.quoteSingleStyleO.setReadOnly(True)
- self.quoteSingleStyleO.setFixedWidth(qWidth)
- self.quoteSingleStyleO.setAlignment(Qt.AlignCenter)
- self.quoteSingleStyleO.setText(self.mainConf.fmtSingleQuotes[0])
+ self.quoteSym["SO"] = QLineEdit()
+ self.quoteSym["SO"].setMaxLength(1)
+ self.quoteSym["SO"].setReadOnly(True)
+ self.quoteSym["SO"].setFixedWidth(qWidth)
+ self.quoteSym["SO"].setAlignment(Qt.AlignCenter)
+ self.quoteSym["SO"].setText(self.mainConf.fmtSingleQuotes[0])
self.btnSingleStyleO = QPushButton("...")
self.btnSingleStyleO.setMaximumWidth(bWidth)
- self.btnSingleStyleO.clicked.connect(self._getSingleOpen)
+ self.btnSingleStyleO.clicked.connect(lambda: self._getQuote("SO"))
self.mainForm.addRow(
"Single quote open style",
- self.quoteSingleStyleO,
+ self.quoteSym["SO"],
"Auto-replaces apostrophe before words.",
theButton=self.btnSingleStyleO
)
- self.quoteSingleStyleC = QLineEdit()
- self.quoteSingleStyleC.setMaxLength(1)
- self.quoteSingleStyleC.setReadOnly(True)
- self.quoteSingleStyleC.setFixedWidth(qWidth)
- self.quoteSingleStyleC.setAlignment(Qt.AlignCenter)
- self.quoteSingleStyleC.setText(self.mainConf.fmtSingleQuotes[1])
+ self.quoteSym["SC"] = QLineEdit()
+ self.quoteSym["SC"].setMaxLength(1)
+ self.quoteSym["SC"].setReadOnly(True)
+ self.quoteSym["SC"].setFixedWidth(qWidth)
+ self.quoteSym["SC"].setAlignment(Qt.AlignCenter)
+ self.quoteSym["SC"].setText(self.mainConf.fmtSingleQuotes[1])
self.btnSingleStyleC = QPushButton("...")
self.btnSingleStyleC.setMaximumWidth(bWidth)
- self.btnSingleStyleC.clicked.connect(self._getSingleClose)
+ self.btnSingleStyleC.clicked.connect(lambda: self._getQuote("SC"))
self.mainForm.addRow(
"Single quote close style",
- self.quoteSingleStyleC,
+ self.quoteSym["SC"],
"Auto-replaces apostrophe after words.",
theButton=self.btnSingleStyleC
)
## Double Quote Style
- self.quoteDoubleStyleO = QLineEdit()
- self.quoteDoubleStyleO.setMaxLength(1)
- self.quoteDoubleStyleO.setReadOnly(True)
- self.quoteDoubleStyleO.setFixedWidth(qWidth)
- self.quoteDoubleStyleO.setAlignment(Qt.AlignCenter)
- self.quoteDoubleStyleO.setText(self.mainConf.fmtDoubleQuotes[0])
+ self.quoteSym["DO"] = QLineEdit()
+ self.quoteSym["DO"].setMaxLength(1)
+ self.quoteSym["DO"].setReadOnly(True)
+ self.quoteSym["DO"].setFixedWidth(qWidth)
+ self.quoteSym["DO"].setAlignment(Qt.AlignCenter)
+ self.quoteSym["DO"].setText(self.mainConf.fmtDoubleQuotes[0])
self.btnDoubleStyleO = QPushButton("...")
self.btnDoubleStyleO.setMaximumWidth(bWidth)
- self.btnDoubleStyleO.clicked.connect(self._getDoubleOpen)
+ self.btnDoubleStyleO.clicked.connect(lambda: self._getQuote("DO"))
self.mainForm.addRow(
"Double quote open style",
- self.quoteDoubleStyleO,
+ self.quoteSym["DO"],
"Auto-replaces straight quotes before words.",
theButton=self.btnDoubleStyleO
)
- self.quoteDoubleStyleC = QLineEdit()
- self.quoteDoubleStyleC.setMaxLength(1)
- self.quoteDoubleStyleC.setReadOnly(True)
- self.quoteDoubleStyleC.setFixedWidth(qWidth)
- self.quoteDoubleStyleC.setAlignment(Qt.AlignCenter)
- self.quoteDoubleStyleC.setText(self.mainConf.fmtDoubleQuotes[1])
+ self.quoteSym["DC"] = QLineEdit()
+ self.quoteSym["DC"].setMaxLength(1)
+ self.quoteSym["DC"].setReadOnly(True)
+ self.quoteSym["DC"].setFixedWidth(qWidth)
+ self.quoteSym["DC"].setAlignment(Qt.AlignCenter)
+ self.quoteSym["DC"].setText(self.mainConf.fmtDoubleQuotes[1])
self.btnDoubleStyleC = QPushButton("...")
self.btnDoubleStyleC.setMaximumWidth(bWidth)
- self.btnDoubleStyleC.clicked.connect(self._getDoubleClose)
+ self.btnDoubleStyleC.clicked.connect(lambda: self._getQuote("DC"))
self.mainForm.addRow(
"Double quote close style",
- self.quoteDoubleStyleC,
+ self.quoteSym["DC"],
"Auto-replaces straight quotes after words.",
theButton=self.btnDoubleStyleC
)
@@ -906,10 +906,10 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.mainConf.doReplaceDash = doReplaceDash
self.mainConf.doReplaceDots = doReplaceDots
- fmtSingleQuotesO = self.quoteSingleStyleO.text()
- fmtSingleQuotesC = self.quoteSingleStyleC.text()
- fmtDoubleQuotesO = self.quoteDoubleStyleO.text()
- fmtDoubleQuotesC = self.quoteDoubleStyleC.text()
+ fmtSingleQuotesO = self.quoteSym["SO"].text()
+ fmtSingleQuotesC = self.quoteSym["SC"].text()
+ fmtDoubleQuotesO = self.quoteSym["DO"].text()
+ fmtDoubleQuotesC = self.quoteSym["DC"].text()
self.mainConf.fmtSingleQuotes[0] = fmtSingleQuotesO
self.mainConf.fmtSingleQuotes[1] = fmtSingleQuotesC
@@ -934,50 +934,12 @@ class GuiConfigEditAutoReplaceTab(QWidget):
self.autoReplaceDots.setEnabled(theState)
return
- def _getSingleOpen(self):
+ def _getQuote(self, qType):
"""Dialog for single quote open.
"""
- qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleO.text())
+ qtBox = QuotesDialog(self, currentQuote=self.quoteSym[qType].text())
if qtBox.exec_() == QDialog.Accepted:
- self.quoteSingleStyleO.setText(qtBox.selectedQuote)
+ self.quoteSym[qType].setText(qtBox.selectedQuote)
return
- def _getSingleClose(self):
- """Dialog for single quote close.
- """
- qtBox = QuotesDialog(self, currentQuote=self.quoteSingleStyleC.text())
- if qtBox.exec_() == QDialog.Accepted:
- self.quoteSingleStyleC.setText(qtBox.selectedQuote)
- return
-
- def _getDoubleOpen(self):
- """Dialog for double quote open.
- """
- qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleO.text())
- if qtBox.exec_() == QDialog.Accepted:
- self.quoteDoubleStyleO.setText(qtBox.selectedQuote)
- return
-
- def _getDoubleClose(self):
- """Dialog for double quote close.
- """
- qtBox = QuotesDialog(self, currentQuote=self.quoteDoubleStyleC.text())
- if qtBox.exec_() == QDialog.Accepted:
- self.quoteDoubleStyleC.setText(qtBox.selectedQuote)
- return
-
- ##
- # Internal Functions
- ##
-
- def _checkQuoteSymbol(self, toCheck):
- """Check that the quote symbols entered are in nwQuotes and is
- therefore a valid quote symbol for this app.
- """
- if len(toCheck) != 1:
- return False
- if toCheck in nwQuotes.SYMBOLS:
- return True
- return False
-
# END Class GuiConfigEditAutoReplaceTab
diff --git a/nw/guimain.py b/nw/guimain.py
index de340304..0da4f5a0 100644
--- a/nw/guimain.py
+++ b/nw/guimain.py
@@ -855,24 +855,32 @@ class GuiMain(QMainWindow):
popMsg = theMessage
logMsg = [theMessage]
- msgBox = QMessageBox()
+ # Write to Log
if theLevel == nwAlert.INFO:
for msgLine in logMsg:
logger.info(msgLine)
- msgBox.information(self, "Information", popMsg)
elif theLevel == nwAlert.WARN:
for msgLine in logMsg:
logger.warning(msgLine)
- msgBox.warning(self, "Warning", popMsg)
elif theLevel == nwAlert.ERROR:
for msgLine in logMsg:
logger.error(msgLine)
- msgBox.critical(self, "Error", popMsg)
elif theLevel == nwAlert.BUG:
for msgLine in logMsg:
logger.error(msgLine)
- popMsg += "
This is a bug!"
- msgBox.critical(self, "Internal Error", popMsg)
+
+ # Popup
+ if self.mainConf.showGUI:
+ msgBox = QMessageBox()
+ if theLevel == nwAlert.INFO:
+ msgBox.information(self, "Information", popMsg)
+ elif theLevel == nwAlert.WARN:
+ msgBox.warning(self, "Warning", popMsg)
+ elif theLevel == nwAlert.ERROR:
+ msgBox.critical(self, "Error", popMsg)
+ elif theLevel == nwAlert.BUG:
+ popMsg += "
This is a bug!"
+ msgBox.critical(self, "Internal Error", popMsg)
return
diff --git a/pytest.ini b/pytest.ini
index f266d081..0c3af3ba 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -1,6 +1,7 @@
[pytest]
markers =
project: Project classes tests
+ error: Test various error handling scenarios
core: Core functionality tests
gui: Qt5 GUI tests
serial
diff --git a/tests/conftest.py b/tests/conftest.py
index 021c888b..61bd1211 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -36,7 +36,7 @@ def nwConf(nwRef, nwTemp):
return theConf
@pytest.fixture(scope="session")
-def tmpConf(nwRef, nwTemp):
+def tmpConf(nwTemp):
theConf = Config()
theConf.initConfig(nwTemp, nwTemp)
theConf.setLastPath("")
diff --git a/tests/nwdummy.py b/tests/nwdummy.py
index 41b25907..313af767 100644
--- a/tests/nwdummy.py
+++ b/tests/nwdummy.py
@@ -10,6 +10,7 @@ class DummyMain():
return
def makeAlert(self, theMessage, theLevel):
+ print("%s: %s" % (str(theLevel), theMessage))
return
def setStatus(self, theMessage):
diff --git a/tests/nwtools.py b/tests/nwtools.py
index a92047ca..0a9516aa 100644
--- a/tests/nwtools.py
+++ b/tests/nwtools.py
@@ -67,3 +67,4 @@ def getGuiItem(theName):
for qWidget in qApp.topLevelWidgets():
if qWidget.objectName() == theName:
return qWidget
+ return None
diff --git a/tests/reference/novelwriter_prefs.conf b/tests/reference/novelwriter_prefs.conf
new file mode 100644
index 00000000..6b894f55
--- /dev/null
+++ b/tests/reference/novelwriter_prefs.conf
@@ -0,0 +1,69 @@
+[Main]
+timestamp = 2020-06-29 17:34:15
+theme = default
+syntax = default_light
+icons = typicons_colour_light
+guidark = True
+guifont = Cantarell
+guifontsize = 12
+
+[Sizes]
+geometry = 1100, 650
+treecols = 120, 30, 50
+projcols = 140, 55, 140
+mainpane = 300, 800
+docpane = 400, 400
+viewpane = 500, 150
+outlinepane = 500, 150
+fullscreen = False
+
+[Project]
+autosaveproject = 40
+autosavedoc = 20
+
+[Editor]
+textfont = Cantarell
+textsize = 13
+fixedwidth = False
+width = 700
+margin = 45
+tabwidth = 45
+focuswidth = 900
+hidefocusfooter = True
+justify = False
+autoselect = False
+autoreplace = False
+repsquotes = True
+repdquotes = True
+repdash = True
+repdots = True
+fmtsinglequote = ‘, ’
+fmtdoublequote = “, ”
+spelltool = internal
+spellcheck = en
+showtabsnspaces = True
+showlineendings = True
+bigdoclimit = 500
+showfullpath = False
+highlightquotes = False
+highlightemph = False
+
+[Backup]
+backuppath =
+backuponclose = True
+askbeforebackup = True
+
+[State]
+showrefpanel = False
+viewcomments = True
+viewsynopsis = True
+searchcase = False
+searchword = False
+searchregex = False
+searchloop = False
+searchnextfile = False
+searchmatchcap = False
+
+[Path]
+lastpath =
+
diff --git a/tests/test_common.py b/tests/test_common.py
index 5a73ad45..41a81091 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -2,10 +2,12 @@
"""novelWriter Common Class Tester
"""
+import time
import pytest
+
from nw.common import (
checkString, checkBool, checkInt, colRange, formatInt, transferCase,
- fuzzyTime, checkHandle
+ fuzzyTime, checkHandle, formatTimeStamp
)
from nwtools import cmpList
@@ -75,6 +77,12 @@ def testColRange():
[[200, 50, 0], [162, 87, 0], [124, 124, 0], [86, 161, 0], [50, 200, 0]]
)
+@pytest.mark.core
+def testFormatTime():
+ 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 testFormatInt():
assert formatInt(1000) == "1000"
diff --git a/tests/test_config.py b/tests/test_config.py
index b58e9697..612352cf 100644
--- a/tests/test_config.py
+++ b/tests/test_config.py
@@ -40,6 +40,7 @@ def testConfigSetDataPath(tmpConf, nwTemp):
def testConfigSetWinSize(tmpConf, nwTemp, nwRef):
refConf = path.join(nwRef, "novelwriter.conf")
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
+ tmpConf.guiScale = 1.0
assert tmpConf.confPath == nwTemp
assert tmpConf.setWinSize(1105, 655)
@@ -58,10 +59,17 @@ def testConfigSetTreeColWidths(tmpConf, nwTemp, nwRef):
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
- assert tmpConf.setTreeColWidths([0, 0, 0])
- assert tmpConf.confChanged
+ tmpConf.guiScale = 1.0
+
+ assert tmpConf.setTreeColWidths([10, 20, 30])
+ assert tmpConf.treeColWidth == [10, 20, 30]
assert tmpConf.setTreeColWidths([120, 30, 50])
+
+ assert tmpConf.setProjColWidths([10, 20, 30])
+ assert tmpConf.projColWidth == [10, 20, 30]
assert tmpConf.setProjColWidths([140, 55, 140])
+
+ assert tmpConf.confChanged
assert tmpConf.saveConfig()
assert cmpFiles(testConf, refConf, [2])
@@ -73,12 +81,31 @@ def testConfigSetPanePos(tmpConf, nwTemp, nwRef):
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
- assert tmpConf.setMainPanePos([0, 0])
- assert tmpConf.confChanged
+
+ 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])
@@ -90,14 +117,37 @@ def testConfigFlags(tmpConf, nwTemp, nwRef):
testConf = path.join(tmpConf.confPath, "novelwriter.conf")
assert tmpConf.confPath == nwTemp
+
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])
assert not tmpConf.confChanged
+@pytest.mark.core
+def testTextSizes(tmpConf, nwTemp, nwRef):
+ assert tmpConf.confPath == nwTemp
+
+ 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 = path.join("somewhere", "over", "the", "rainbow")
diff --git a/tests/test_dialogs.py b/tests/test_dialogs.py
new file mode 100644
index 00000000..07f64e8b
--- /dev/null
+++ b/tests/test_dialogs.py
@@ -0,0 +1,915 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Dialog Class Tester
+"""
+
+import nw
+import pytest
+import json
+from shutil import copyfile
+from nwtools import cmpFiles
+
+from os import path
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QDialogButtonBox, QTreeWidgetItem
+
+from nw.gui import (
+ GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
+ GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard,
+ GuiProjectLoad, GuiPreferences
+)
+from nw.constants import nwItemType, nwItemLayout, nwItemClass
+
+keyDelay = 2
+stepDelay = 20
+
+@pytest.mark.gui
+def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ # Create new, save, open project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
+ nwGUI.mainConf.backupPath = nwFuncTemp
+
+ projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
+ projEdit.show()
+ qtbot.addWidget(projEdit)
+
+ qtbot.wait(stepDelay)
+ projEdit.tabMain.editName.setText("")
+ for c in "Project Name":
+ qtbot.keyClick(projEdit.tabMain.editName, c, delay=keyDelay)
+ for c in "Project Title":
+ qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=keyDelay)
+ for c in "Jane Doe":
+ qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
+ qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
+ for c in "John Doh":
+ qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
+
+ # Test Status Tab
+ qtbot.wait(stepDelay)
+ projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
+ projEdit.tabStatus.listBox.item(2).setSelected(True)
+ qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
+ qtbot.mouseClick(projEdit.tabStatus.newButton, Qt.LeftButton)
+ projEdit.tabStatus.listBox.item(3).setSelected(True)
+ for n in range(8):
+ qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=keyDelay)
+ for c in "Final":
+ qtbot.keyClick(projEdit.tabStatus.editName, c, delay=keyDelay)
+ qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
+
+ # Auto-Replace Tab
+ qtbot.wait(stepDelay)
+ projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
+
+ qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
+ projEdit.tabReplace.listBox.topLevelItem(0).setSelected(True)
+ for c in "Th is ":
+ qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=keyDelay)
+ for c in "With This Stuff ":
+ qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=keyDelay)
+ qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
+
+ qtbot.wait(stepDelay)
+ projEdit.tabReplace.listBox.clearSelection()
+ qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
+
+ newIdx = -1
+ for i in range(projEdit.tabReplace.listBox.topLevelItemCount()):
+ if projEdit.tabReplace.listBox.topLevelItem(i).text(0) == "":
+ newIdx = i
+ break
+
+ assert newIdx >= 0
+ newItem = projEdit.tabReplace.listBox.topLevelItem(newIdx)
+ projEdit.tabReplace.listBox.setCurrentItem(newItem)
+ qtbot.mouseClick(projEdit.tabReplace.delButton, Qt.LeftButton)
+
+ qtbot.wait(stepDelay)
+ projEdit._doSave()
+
+ # Open again, and check project settings
+ projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
+ qtbot.addWidget(projEdit)
+ assert projEdit.tabMain.editName.text() == "Project Name"
+ assert projEdit.tabMain.editTitle.text() == "Project Title"
+ theAuth = projEdit.tabMain.editAuthors.toPlainText().strip().splitlines()
+ assert len(theAuth) == 2
+ assert theAuth[0] == "Jane Doe"
+ assert theAuth[1] == "John Doh"
+
+ projEdit._doClose()
+
+ qtbot.wait(stepDelay)
+ assert nwGUI.saveProject()
+ qtbot.wait(stepDelay)
+
+ # Check the files
+ projFile = path.join(nwFuncTemp, "nwProject.nwx")
+ testFile = path.join(nwTempGUI, "2_nwProject.nwx")
+ refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile, [2, 8, 9, 10])
+
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ # Create new, save, open project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
+ assert nwGUI.openDocument("0e17daca5f3e1")
+
+ itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
+ qtbot.addWidget(itemEdit)
+
+ assert itemEdit.editName.text() == "New Scene"
+ assert itemEdit.editStatus.currentData() == "New"
+ assert itemEdit.editLayout.currentData() == nwItemLayout.SCENE
+
+ for c in "Just a Page":
+ qtbot.keyClick(itemEdit.editName, c, delay=keyDelay)
+ itemEdit.editStatus.setCurrentIndex(1)
+ layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
+ itemEdit.editLayout.setCurrentIndex(layoutIdx)
+
+ itemEdit.editExport.setChecked(False)
+ assert not itemEdit.editExport.isChecked()
+ itemEdit._doSave()
+
+ itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
+ qtbot.addWidget(itemEdit)
+ assert itemEdit.editName.text() == "Just a Page"
+ assert itemEdit.editStatus.currentData() == "Note"
+ assert itemEdit.editLayout.currentData() == nwItemLayout.PAGE
+ itemEdit._doClose()
+
+ # Check that the header is updated
+ nwGUI.docEditor.updateDocInfo("0e17daca5f3e1")
+ assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel › New Chapter › Just a Page"
+ assert not nwGUI.docEditor.setCursorLine("where?")
+ assert nwGUI.docEditor.setCursorLine(2)
+ qtbot.wait(stepDelay)
+ assert nwGUI.docEditor.getCursorPosition() == 15
+
+ qtbot.wait(stepDelay)
+ assert nwGUI.saveProject()
+ qtbot.wait(stepDelay)
+
+ # Check the files
+ projFile = path.join(nwFuncTemp, "nwProject.nwx")
+ testFile = path.join(nwTempGUI, "3_nwProject.nwx")
+ refFile = path.join(nwRef, "gui", "3_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
+
+ nwGUI.closeMain()
+ # qtbot.stopForInteraction()
+
+@pytest.mark.gui
+def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ # Create new, save, close project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
+ assert nwGUI.saveProject()
+ assert nwGUI.closeProject()
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.openProject(nwFuncTemp)
+ qtbot.wait(stepDelay)
+
+ # Add some text to the scene file
+ assert nwGUI.openDocument("0e17daca5f3e1")
+ assert nwGUI.docEditor.insertText(
+ "# Scene One\n\n"
+ "It was the best of times, it was the worst of times, it was the age of wisdom, it was "
+ "the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it "
+ "was the season of Light, it was the season of Darkness, it was the spring of hope, it "
+ "was the winter of despair, we had everything before us, we had nothing before us, we "
+ "were all going direct to Heaven, we were all going direct the other way – in short, the "
+ "period was so far like the present period, that some of its noisiest authorities "
+ "insisted on its being received, for good or for evil, in the superlative degree of "
+ "comparison only.\n\n"
+ )
+ assert nwGUI.saveDocument()
+
+ # Add a note file with some text
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+ assert nwGUI.docEditor.insertText(
+ "# Jane Doe\n\n"
+ "All about Jane.\n\n"
+ )
+ assert nwGUI.saveDocument()
+ qtbot.wait(500) # Ensures that the session length is > 0
+
+ assert nwGUI.saveProject()
+ assert nwGUI.closeProject()
+ qtbot.wait(stepDelay)
+
+ # Open again, and check the stats
+ assert nwGUI.openProject(nwFuncTemp)
+ qtbot.wait(stepDelay)
+
+ nwGUI.mainConf.lastPath = nwFuncTemp
+ sessLog = GuiWritingStats(nwGUI, nwGUI.theProject)
+ sessLog.show()
+ qtbot.wait(stepDelay)
+
+ assert sessLog._saveData(sessLog.FMT_CSV)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ assert len(jsonData) == 2
+ assert jsonData[1]["length"] >= 0
+ assert jsonData[1]["newWords"] == 126
+ assert jsonData[1]["novelWords"] == 127
+ assert jsonData[1]["noteWords"] == 5
+
+ # No Novel Files
+ qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ assert len(jsonData) == 1
+ assert jsonData[0]["length"] >= 0
+ assert jsonData[0]["newWords"] == 5
+ assert jsonData[0]["novelWords"] == 127
+ assert jsonData[0]["noteWords"] == 5
+
+ # No Note Files
+ qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
+ qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ assert len(jsonData) == 2
+ assert jsonData[1]["length"] >= 0
+ assert jsonData[1]["newWords"] == 121
+ assert jsonData[1]["novelWords"] == 127
+ assert jsonData[1]["noteWords"] == 5
+
+ # No Negative Entries
+ qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
+ qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ assert len(jsonData) == 2
+
+ # Un-hide Zero Entries
+ qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
+ qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ assert len(jsonData) == 2
+
+ # Group by Day
+ qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = path.join(nwFuncTemp, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.loads(inFile.read())
+
+ # Check against both 1 and 2 as this can be 2 if test was started just before midnight.
+ # A failed test should in any case produce a 4
+ assert len(jsonData) in (1, 2)
+
+ # qtbot.stopForInteraction()
+
+ sessLog._doClose()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testAboutBox(qtbot, nwFuncTemp, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ msgAbout = GuiAbout(nwGUI)
+ assert msgAbout.pageAbout.document().characterCount() > 100
+ assert msgAbout.pageLicense.document().characterCount() > 100
+
+ # qtbot.stopForInteraction()
+ msgAbout._doClose()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
+
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.openProject(nwLipsum)
+
+ nwGUI.mainConf.lastPath = nwLipsum
+
+ nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
+
+ # Default Settings
+ qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = path.join(nwTempBuild, "1_LoremIpsum.nwd")
+ refFile = path.join(nwRef, "build", "1_LoremIpsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = path.join(nwTempBuild, "1_LoremIpsum.htm")
+ refFile = path.join(nwRef, "build", "1_LoremIpsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # Change Title Formats and Flip Switches
+ nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
+ qtbot.wait(stepDelay)
+ nwBuild.fmtScene.setText(r"Scene %ch%.%sc%: %title%")
+ qtbot.wait(stepDelay)
+ nwBuild.fmtSection.setText(r"%ch%.%sc%.1: %title%")
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.justifyText, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.includeSynopsis, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = path.join(nwTempBuild, "2_LoremIpsum.nwd")
+ refFile = path.join(nwRef, "build", "2_LoremIpsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = path.join(nwTempBuild, "2_LoremIpsum.htm")
+ refFile = path.join(nwRef, "build", "2_LoremIpsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # Putline Mode
+ nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
+ qtbot.wait(stepDelay)
+ nwBuild.fmtScene.setText(r"Scene %sca%: %title%")
+ qtbot.wait(stepDelay)
+ nwBuild.fmtSection.setText(r"Section: %title%")
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = path.join(nwTempBuild, "3_LoremIpsum.nwd")
+ refFile = path.join(nwRef, "build", "3_LoremIpsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = path.join(nwTempBuild, "3_LoremIpsum.htm")
+ refFile = path.join(nwRef, "build", "3_LoremIpsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # qtbot.stopForInteraction()
+ nwBuild._doClose()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
+
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.treeView.setSelectedHandle("45e6b01ca35c1")
+ qtbot.wait(stepDelay)
+
+ nwMerge = GuiDocMerge(nwGUI, nwGUI.theProject)
+ qtbot.wait(stepDelay)
+
+ nwMerge._doMerge()
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.theProject.projTree["73475cb40a568"] is not None
+
+ projFile = path.join(nwLipsum, "content", "73475cb40a568.nwd")
+ testFile = path.join(nwTempGUI, "4_73475cb40a568.nwd")
+ refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # Split By Chapter
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+ nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
+ qtbot.wait(stepDelay)
+ nwSplit.splitLevel.setCurrentIndex(1)
+ qtbot.wait(stepDelay)
+
+ nwSplit._doSplit()
+ assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None
+
+ # This should give us back the file as it was before
+ projFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
+ testFile = path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
+ refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile, [1])
+
+ # Split By Scene
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+ nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
+ qtbot.wait(stepDelay)
+ nwSplit.splitLevel.setCurrentIndex(2)
+ qtbot.wait(stepDelay)
+
+ nwSplit._doSplit()
+
+ assert nwGUI.theProject.projTree["25fc0e7096fc6"] is not None
+ assert nwGUI.theProject.projTree["31489056e0916"] is not None
+ assert nwGUI.theProject.projTree["98010bd9270f9"] is not None
+
+ projFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
+ testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
+ refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "content", "31489056e0916.nwd")
+ testFile = path.join(nwTempGUI, "5_31489056e0916.nwd")
+ refFile = path.join(nwRef, "gui", "5_31489056e0916.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd")
+ testFile = path.join(nwTempGUI, "5_98010bd9270f9.nwd")
+ refFile = path.join(nwRef, "gui", "5_98010bd9270f9.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # Split By Section
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+ nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
+ qtbot.wait(stepDelay)
+ nwSplit.splitLevel.setCurrentIndex(3)
+ qtbot.wait(stepDelay)
+
+ nwSplit._doSplit()
+
+ assert nwGUI.theProject.projTree["1a6562590ef19"] is not None
+ assert nwGUI.theProject.projTree["031b4af5197ec"] is not None
+ assert nwGUI.theProject.projTree["41cfc0d1f2d12"] is not None
+ assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
+ assert nwGUI.theProject.projTree["2fca346db6561"] is not None
+
+ projFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd")
+ testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
+ refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile, [1])
+
+ projFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd")
+ testFile = path.join(nwTempGUI, "5_031b4af5197ec.nwd")
+ refFile = path.join(nwRef, "gui", "5_031b4af5197ec.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
+ testFile = path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
+ refFile = path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
+ testFile = path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
+ refFile = path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ projFile = path.join(nwLipsum, "content", "2fca346db6561.nwd")
+ testFile = path.join(nwTempGUI, "5_2fca346db6561.nwd")
+ refFile = path.join(nwRef, "gui", "5_2fca346db6561.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, refFile)
+
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
+
+ from PyQt5.QtWidgets import QWizard
+ from nw.gui.projwizard import (
+ ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage,
+ ProjWizardCustomPage, ProjWizardFinalPage
+ )
+
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ for wStep in range(3):
+
+ # The Wizard
+ nwWiz = GuiProjectWizard(nwGUI)
+ nwWiz.show()
+ qtbot.waitForWindowShown(nwWiz)
+
+ # Intro Page
+ introPage = nwWiz.currentPage()
+ assert isinstance(introPage, ProjWizardIntroPage)
+ assert not nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ for c in "Test Minimal":
+ qtbot.keyClick(introPage.projName, c, delay=keyDelay)
+
+ qtbot.wait(stepDelay)
+ for c in "Minimal Novel":
+ qtbot.keyClick(introPage.projTitle, c, delay=keyDelay)
+
+ qtbot.wait(stepDelay)
+ for c in "Jane Doe":
+ qtbot.keyClick(introPage.projAuthors, c, delay=keyDelay)
+
+ # Setting projName should activate the button
+ assert nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
+
+ # Folder Page
+ storagePage = nwWiz.currentPage()
+ assert isinstance(storagePage, ProjWizardFolderPage)
+ assert not nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ projPath = path.join(nwTemp, "dummy")
+ for c in projPath:
+ qtbot.keyClick(storagePage.projPath, c, delay=keyDelay)
+
+ # Setting projPath should activate the button
+ assert nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
+
+ # Populate Page
+ popPage = nwWiz.currentPage()
+ assert isinstance(popPage, ProjWizardPopulatePage)
+ assert nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ if wStep == 0:
+ popPage.popMinimal.setChecked(True)
+ elif wStep == 1:
+ popPage.popCustom.setChecked(True)
+ elif wStep == 2:
+ popPage.popSample.setChecked(True)
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
+
+ # Custom Page
+ if wStep == 1:
+ customPage = nwWiz.currentPage()
+ assert isinstance(customPage, ProjWizardCustomPage)
+ assert nwWiz.button(QWizard.NextButton).isEnabled()
+
+ customPage.addPlot.setChecked(True)
+ customPage.addChar.setChecked(True)
+ customPage.addWorld.setChecked(True)
+ customPage.addTime.setChecked(True)
+ customPage.addObject.setChecked(True)
+ customPage.addEntity.setChecked(True)
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
+
+ # Final Page
+ finalPage = nwWiz.currentPage()
+ assert isinstance(finalPage, ProjWizardFinalPage)
+ assert nwWiz.button(QWizard.FinishButton).isEnabled()
+ qtbot.mouseClick(nwWiz.button(QWizard.FinishButton), Qt.LeftButton)
+
+ # Check Data
+ projData = nwGUI._assembleProjectWizardData(nwWiz)
+ assert projData["projName"] == "Test Minimal"
+ assert projData["projTitle"] == "Minimal Novel"
+ assert projData["projAuthors"] == "Jane Doe"
+ assert projData["projPath"] == projPath
+ assert projData["popMinimal"] == (wStep == 0)
+ assert projData["popCustom"] == (wStep == 1)
+ assert projData["popSample"] == (wStep == 2)
+ if wStep == 1:
+ assert projData["addRoots"] == [
+ nwItemClass.PLOT,
+ nwItemClass.CHARACTER,
+ nwItemClass.WORLD,
+ nwItemClass.TIMELINE,
+ nwItemClass.OBJECT,
+ nwItemClass.ENTITY,
+ ]
+ assert projData["numChapters"] == 5
+ assert projData["numScenes"] == 5
+ assert projData["chFolders"]
+ else:
+ assert projData["addRoots"] == []
+ assert projData["numChapters"] == 0
+ assert projData["numScenes"] == 0
+ assert not projData["chFolders"]
+
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testLoadProject(qtbot, nwMinimal, nwTemp):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.openProject(nwMinimal)
+ assert nwGUI.closeProject()
+
+ nwLoad = GuiProjectLoad(nwGUI)
+ nwLoad.show()
+
+ recentCount = nwLoad.listBox.topLevelItemCount()
+ assert recentCount > 0
+
+ selItem = nwLoad.listBox.topLevelItem(0)
+ selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
+ assert isinstance(selItem, QTreeWidgetItem)
+
+ nwLoad.selPath.setText("")
+ nwLoad.listBox.setCurrentItem(selItem)
+ nwLoad._doSelectRecent()
+ assert nwLoad.selPath.text() == selPath
+
+ qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
+ assert nwLoad.openPath == selPath
+ assert nwLoad.openState == nwLoad.OPEN_STATE
+
+ del nwLoad
+ nwLoad = GuiProjectLoad(nwGUI)
+ nwLoad.show()
+
+ qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
+ assert nwLoad.openPath is None
+ assert nwLoad.openState == nwLoad.NONE_STATE
+
+ nwLoad.show()
+ qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
+ assert nwLoad.openPath is None
+ assert nwLoad.openState == nwLoad.NEW_STATE
+
+ nwLoad.show()
+ nwLoad._keyPressDelete()
+ assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
+
+ nwLoad.close()
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+
+@pytest.mark.gui
+def testPreferences(qtbot, nwMinimal, nwTemp, nwRef, tmpConf):
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.openProject(nwMinimal)
+ nwPrefs = GuiPreferences(nwGUI, nwGUI.theProject)
+ nwPrefs.show()
+
+ # Override Config
+ tmpConf.showGUI = False
+ tmpConf.confPath = nwMinimal
+ nwGUI.mainConf = tmpConf
+ nwPrefs.mainConf = tmpConf
+ nwPrefs.tabGeneral.mainConf = tmpConf
+ nwPrefs.tabLayout.mainConf = tmpConf
+ nwPrefs.tabEditing.mainConf = tmpConf
+ nwPrefs.tabAutoRep.mainConf = tmpConf
+
+ # General Settings
+ qtbot.wait(keyDelay)
+ tabGeneral = nwPrefs.tabGeneral
+ nwPrefs._tabBox.setCurrentWidget(tabGeneral)
+ tabGeneral.backupPath = nwTemp
+
+ qtbot.wait(keyDelay)
+ assert not tabGeneral.preferDarkIcons.isChecked()
+ qtbot.mouseClick(tabGeneral.preferDarkIcons, Qt.LeftButton)
+ assert tabGeneral.preferDarkIcons.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert tabGeneral.showFullPath.isChecked()
+ qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton)
+ assert not tabGeneral.showFullPath.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabGeneral.backupOnClose.isChecked()
+ qtbot.mouseClick(tabGeneral.backupOnClose, Qt.LeftButton)
+ assert tabGeneral.backupOnClose.isChecked()
+
+ qtbot.wait(keyDelay)
+ tabGeneral.guiFontSize.setValue(12)
+ tabGeneral.autoSaveDoc.setValue(20)
+ tabGeneral.autoSaveProj.setValue(40)
+
+ # Text Layour Settings
+ qtbot.wait(keyDelay)
+ tabLayout = nwPrefs.tabLayout
+ nwPrefs._tabBox.setCurrentWidget(tabLayout)
+
+ qtbot.wait(keyDelay)
+ tabLayout.textStyleSize.setValue(13)
+ tabLayout.textFlowMax.setValue(700)
+ tabLayout.focusDocWidth.setValue(900)
+ tabLayout.textMargin.setValue(45)
+ tabLayout.tabWidth.setValue(45)
+
+ qtbot.wait(keyDelay)
+ assert not tabLayout.textFlowFixed.isChecked()
+ qtbot.mouseClick(tabLayout.textFlowFixed, Qt.LeftButton)
+ assert tabLayout.textFlowFixed.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabLayout.hideFocusFooter.isChecked()
+ qtbot.mouseClick(tabLayout.hideFocusFooter, Qt.LeftButton)
+ assert tabLayout.hideFocusFooter.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert tabLayout.textJustify.isChecked()
+ qtbot.mouseClick(tabLayout.textJustify, Qt.LeftButton)
+ assert not tabLayout.textJustify.isChecked()
+
+ # Editor Settings
+ qtbot.wait(keyDelay)
+ tabEditing = nwPrefs.tabEditing
+ nwPrefs._tabBox.setCurrentWidget(tabEditing)
+
+ qtbot.wait(keyDelay)
+ assert tabEditing.highlightQuotes.isChecked()
+ qtbot.mouseClick(tabEditing.highlightQuotes, Qt.LeftButton)
+ assert not tabEditing.highlightQuotes.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert tabEditing.highlightEmph.isChecked()
+ qtbot.mouseClick(tabEditing.highlightEmph, Qt.LeftButton)
+ assert not tabEditing.highlightEmph.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabEditing.showTabsNSpaces.isChecked()
+ qtbot.mouseClick(tabEditing.showTabsNSpaces, Qt.LeftButton)
+ assert tabEditing.showTabsNSpaces.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabEditing.showLineEndings.isChecked()
+ qtbot.mouseClick(tabEditing.showLineEndings, Qt.LeftButton)
+ assert tabEditing.showLineEndings.isChecked()
+
+ qtbot.wait(keyDelay)
+ tabEditing.bigDocLimit.setValue(500)
+
+ # Auto-Replace Settings
+ qtbot.wait(keyDelay)
+ tabAutoRep = nwPrefs.tabAutoRep
+ nwPrefs._tabBox.setCurrentWidget(tabAutoRep)
+
+ qtbot.wait(keyDelay)
+ assert tabAutoRep.autoSelect.isChecked()
+ qtbot.mouseClick(tabAutoRep.autoSelect, Qt.LeftButton)
+ assert not tabAutoRep.autoSelect.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert tabAutoRep.autoReplaceMain.isChecked()
+ qtbot.mouseClick(tabAutoRep.autoReplaceMain, Qt.LeftButton)
+ assert not tabAutoRep.autoReplaceMain.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabAutoRep.autoReplaceSQ.isEnabled()
+ assert not tabAutoRep.autoReplaceDQ.isEnabled()
+ assert not tabAutoRep.autoReplaceDash.isEnabled()
+ assert not tabAutoRep.autoReplaceDots.isEnabled()
+
+ # Save and Check Config
+ # qtbot.stopForInteraction()
+ qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
+
+ assert tmpConf.confChanged
+ assert tmpConf.backupPath == nwTemp
+ tmpConf.backupPath = ""
+ tmpConf.lastPath = ""
+
+ assert nwGUI.mainConf.saveConfig()
+
+ nwGUI.closeMain()
+
+ refConf = path.join(nwRef, "novelwriter_prefs.conf")
+ projConf = path.join(nwGUI.mainConf.confPath, "novelwriter.conf")
+ testConf = path.join(nwTemp, "novelwriter_prefs.conf")
+ copyfile(projConf, testConf)
+ ignoreLines = [
+ 2, # Timestamp
+ 11, 12, 13, 14, 15, 16, 17, # Window sizes
+ 7, 25, # Fonts (depends in system default)
+ ]
+ assert cmpFiles(testConf, refConf, ignoreLines)
diff --git a/tests/test_error.py b/tests/test_error.py
new file mode 100644
index 00000000..179efa68
--- /dev/null
+++ b/tests/test_error.py
@@ -0,0 +1,44 @@
+# -*- 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, nwFuncTemp, nwTemp):
+ qApp.closeAllWindows()
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
+ 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.py b/tests/test_gui.py
index 524e74c9..b7d34039 100644
--- a/tests/test_gui.py
+++ b/tests/test_gui.py
@@ -4,29 +4,21 @@
import nw
import pytest
-import json
from shutil import copyfile
from nwtools import cmpFiles
from os import path
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QTextCursor
-from PyQt5.QtWidgets import QAction, QDialogButtonBox, QTreeWidgetItem
+from PyQt5.QtWidgets import QAction, QTreeWidgetItem
-from nw.gui import (
- GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
- GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard,
- GuiProjectLoad
-)
-from nw.constants import (
- nwItemType, nwItemLayout, nwItemClass, nwDocAction, nwUnicode, nwOutline
-)
+from nw.constants import nwItemType, nwDocAction, nwUnicode, nwOutline
keyDelay = 2
stepDelay = 20
@pytest.mark.gui
-def testMainWindows(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
+def testMainWindow(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
qtbot.addWidget(nwGUI)
@@ -305,699 +297,6 @@ def testMainWindows(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
nwGUI.closeMain()
# qtbot.stopForInteraction()
-@pytest.mark.gui
-def testProjectEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Create new, save, open project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
- nwGUI.mainConf.backupPath = nwFuncTemp
-
- projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
- projEdit.show()
- qtbot.addWidget(projEdit)
-
- qtbot.wait(stepDelay)
- projEdit.tabMain.editName.setText("")
- for c in "Project Name":
- qtbot.keyClick(projEdit.tabMain.editName, c, delay=keyDelay)
- for c in "Project Title":
- qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=keyDelay)
- for c in "Jane Doe":
- qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
- qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
- for c in "John Doh":
- qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay)
-
- # Test Status Tab
- qtbot.wait(stepDelay)
- projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
- projEdit.tabStatus.listBox.item(2).setSelected(True)
- qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
- qtbot.mouseClick(projEdit.tabStatus.newButton, Qt.LeftButton)
- projEdit.tabStatus.listBox.item(3).setSelected(True)
- for n in range(8):
- qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=keyDelay)
- for c in "Final":
- qtbot.keyClick(projEdit.tabStatus.editName, c, delay=keyDelay)
- qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
-
- # Auto-Replace Tab
- qtbot.wait(stepDelay)
- projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
-
- qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
- projEdit.tabReplace.listBox.topLevelItem(0).setSelected(True)
- for c in "Th is ":
- qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=keyDelay)
- for c in "With This Stuff ":
- qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=keyDelay)
- qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
-
- qtbot.wait(stepDelay)
- projEdit.tabReplace.listBox.clearSelection()
- qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
-
- newIdx = -1
- for i in range(projEdit.tabReplace.listBox.topLevelItemCount()):
- if projEdit.tabReplace.listBox.topLevelItem(i).text(0) == "":
- newIdx = i
- break
-
- assert newIdx >= 0
- newItem = projEdit.tabReplace.listBox.topLevelItem(newIdx)
- projEdit.tabReplace.listBox.setCurrentItem(newItem)
- qtbot.mouseClick(projEdit.tabReplace.delButton, Qt.LeftButton)
-
- qtbot.wait(stepDelay)
- projEdit._doSave()
-
- # Open again, and check project settings
- projEdit = GuiProjectSettings(nwGUI, nwGUI.theProject)
- qtbot.addWidget(projEdit)
- assert projEdit.tabMain.editName.text() == "Project Name"
- assert projEdit.tabMain.editTitle.text() == "Project Title"
- theAuth = projEdit.tabMain.editAuthors.toPlainText().strip().splitlines()
- assert len(theAuth) == 2
- assert theAuth[0] == "Jane Doe"
- assert theAuth[1] == "John Doh"
-
- projEdit._doClose()
-
- qtbot.wait(stepDelay)
- assert nwGUI.saveProject()
- qtbot.wait(stepDelay)
-
- # Check the files
- projFile = path.join(nwFuncTemp, "nwProject.nwx")
- testFile = path.join(nwTempGUI, "2_nwProject.nwx")
- refFile = path.join(nwRef, "gui", "2_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 8, 9, 10])
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testItemEditor(qtbot, nwFuncTemp, nwTempGUI, nwRef, nwTemp):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Create new, save, open project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
- assert nwGUI.openDocument("0e17daca5f3e1")
-
- itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
- qtbot.addWidget(itemEdit)
-
- assert itemEdit.editName.text() == "New Scene"
- assert itemEdit.editStatus.currentData() == "New"
- assert itemEdit.editLayout.currentData() == nwItemLayout.SCENE
-
- for c in "Just a Page":
- qtbot.keyClick(itemEdit.editName, c, delay=keyDelay)
- itemEdit.editStatus.setCurrentIndex(1)
- layoutIdx = itemEdit.editLayout.findData(nwItemLayout.PAGE)
- itemEdit.editLayout.setCurrentIndex(layoutIdx)
-
- itemEdit.editExport.setChecked(False)
- assert not itemEdit.editExport.isChecked()
- itemEdit._doSave()
-
- itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "0e17daca5f3e1")
- qtbot.addWidget(itemEdit)
- assert itemEdit.editName.text() == "Just a Page"
- assert itemEdit.editStatus.currentData() == "Note"
- assert itemEdit.editLayout.currentData() == nwItemLayout.PAGE
- itemEdit._doClose()
-
- # Check that the header is updated
- nwGUI.docEditor.updateDocInfo("0e17daca5f3e1")
- assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel › New Chapter › Just a Page"
- assert not nwGUI.docEditor.setCursorLine("where?")
- assert nwGUI.docEditor.setCursorLine(2)
- qtbot.wait(stepDelay)
- assert nwGUI.docEditor.getCursorPosition() == 15
-
- qtbot.wait(stepDelay)
- assert nwGUI.saveProject()
- qtbot.wait(stepDelay)
-
- # Check the files
- projFile = path.join(nwFuncTemp, "nwProject.nwx")
- testFile = path.join(nwTempGUI, "3_nwProject.nwx")
- refFile = path.join(nwRef, "gui", "3_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
-
- nwGUI.closeMain()
- # qtbot.stopForInteraction()
-
-@pytest.mark.gui
-def testWritingStatsExport(qtbot, nwFuncTemp, nwTemp):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Create new, save, close project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": nwFuncTemp}, True)
- assert nwGUI.saveProject()
- assert nwGUI.closeProject()
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwFuncTemp)
- qtbot.wait(stepDelay)
-
- # Add some text to the scene file
- assert nwGUI.openDocument("0e17daca5f3e1")
- assert nwGUI.docEditor.insertText(
- "# Scene One\n\n"
- "It was the best of times, it was the worst of times, it was the age of wisdom, it was "
- "the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it "
- "was the season of Light, it was the season of Darkness, it was the spring of hope, it "
- "was the winter of despair, we had everything before us, we had nothing before us, we "
- "were all going direct to Heaven, we were all going direct the other way – in short, the "
- "period was so far like the present period, that some of its noisiest authorities "
- "insisted on its being received, for good or for evil, in the superlative degree of "
- "comparison only.\n\n"
- )
- assert nwGUI.saveDocument()
-
- # Add a note file with some text
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
- assert nwGUI.docEditor.insertText(
- "# Jane Doe\n\n"
- "All about Jane.\n\n"
- )
- assert nwGUI.saveDocument()
- qtbot.wait(500) # Ensures that the session length is > 0
-
- assert nwGUI.saveProject()
- assert nwGUI.closeProject()
- qtbot.wait(stepDelay)
-
- # Open again, and check the stats
- assert nwGUI.openProject(nwFuncTemp)
- qtbot.wait(stepDelay)
-
- nwGUI.mainConf.lastPath = nwFuncTemp
- sessLog = GuiWritingStats(nwGUI, nwGUI.theProject)
- sessLog.show()
- qtbot.wait(stepDelay)
-
- assert sessLog._saveData(sessLog.FMT_CSV)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- assert len(jsonData) == 2
- assert jsonData[1]["length"] >= 0
- assert jsonData[1]["newWords"] == 126
- assert jsonData[1]["novelWords"] == 127
- assert jsonData[1]["noteWords"] == 5
-
- # No Novel Files
- qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- assert len(jsonData) == 1
- assert jsonData[0]["length"] >= 0
- assert jsonData[0]["newWords"] == 5
- assert jsonData[0]["novelWords"] == 127
- assert jsonData[0]["noteWords"] == 5
-
- # No Note Files
- qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
- qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- assert len(jsonData) == 2
- assert jsonData[1]["length"] >= 0
- assert jsonData[1]["newWords"] == 121
- assert jsonData[1]["novelWords"] == 127
- assert jsonData[1]["noteWords"] == 5
-
- # No Negative Entries
- qtbot.mouseClick(sessLog.incNotes, Qt.LeftButton)
- qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- assert len(jsonData) == 2
-
- # Un-hide Zero Entries
- qtbot.mouseClick(sessLog.hideNegative, Qt.LeftButton)
- qtbot.mouseClick(sessLog.hideZeros, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- assert len(jsonData) == 2
-
- # Group by Day
- qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = path.join(nwFuncTemp, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.loads(inFile.read())
-
- # Check against both 1 and 2 as this can be 2 if test was started just before midnight.
- # A failed test should in any case produce a 4
- assert len(jsonData) in (1, 2)
-
- # qtbot.stopForInteraction()
-
- sessLog._doClose()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testAboutBox(qtbot, nwFuncTemp, nwTemp):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwFuncTemp, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- msgAbout = GuiAbout(nwGUI)
- assert msgAbout.pageAbout.document().characterCount() > 100
- assert msgAbout.pageLicense.document().characterCount() > 100
-
- # qtbot.stopForInteraction()
- msgAbout._doClose()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwLipsum)
-
- nwGUI.mainConf.lastPath = nwLipsum
-
- nwBuild = GuiBuildNovel(nwGUI, nwGUI.theProject)
-
- # Default Settings
- qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
-
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = path.join(nwTempBuild, "1_LoremIpsum.nwd")
- refFile = path.join(nwRef, "build", "1_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = path.join(nwTempBuild, "1_LoremIpsum.htm")
- refFile = path.join(nwRef, "build", "1_LoremIpsum.htm")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Change Title Formats and Flip Switches
- nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
- qtbot.wait(stepDelay)
- nwBuild.fmtScene.setText(r"Scene %ch%.%sc%: %title%")
- qtbot.wait(stepDelay)
- nwBuild.fmtSection.setText(r"%ch%.%sc%.1: %title%")
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.justifyText, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.includeSynopsis, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.includeKeywords, Qt.LeftButton)
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
-
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = path.join(nwTempBuild, "2_LoremIpsum.nwd")
- refFile = path.join(nwRef, "build", "2_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = path.join(nwTempBuild, "2_LoremIpsum.htm")
- refFile = path.join(nwRef, "build", "2_LoremIpsum.htm")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Putline Mode
- nwBuild.fmtChapter.setText(r"Chapter %chw%: %title%")
- qtbot.wait(stepDelay)
- nwBuild.fmtScene.setText(r"Scene %sca%: %title%")
- qtbot.wait(stepDelay)
- nwBuild.fmtSection.setText(r"Section: %title%")
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.includeComments, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.noteFiles, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton)
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton)
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
-
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = path.join(nwTempBuild, "3_LoremIpsum.nwd")
- refFile = path.join(nwRef, "build", "3_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = path.join(nwTempBuild, "3_LoremIpsum.htm")
- refFile = path.join(nwRef, "build", "3_LoremIpsum.htm")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # qtbot.stopForInteraction()
- nwBuild._doClose()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testMergeSplitTools(qtbot, nwTempGUI, nwLipsum, nwRef, nwTemp):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.openProject(nwLipsum)
- qtbot.wait(stepDelay)
-
- assert nwGUI.treeView.setSelectedHandle("45e6b01ca35c1")
- qtbot.wait(stepDelay)
-
- nwMerge = GuiDocMerge(nwGUI, nwGUI.theProject)
- qtbot.wait(stepDelay)
-
- nwMerge._doMerge()
- qtbot.wait(stepDelay)
-
- assert nwGUI.theProject.projTree["73475cb40a568"] is not None
-
- projFile = path.join(nwLipsum, "content", "73475cb40a568.nwd")
- testFile = path.join(nwTempGUI, "4_73475cb40a568.nwd")
- refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Split By Chapter
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
- nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
- qtbot.wait(stepDelay)
- nwSplit.splitLevel.setCurrentIndex(1)
- qtbot.wait(stepDelay)
-
- nwSplit._doSplit()
- assert nwGUI.theProject.projTree["71ee45a3c0db9"] is not None
-
- # This should give us back the file as it was before
- projFile = path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
- testFile = path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
- refFile = path.join(nwRef, "gui", "4_73475cb40a568.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [1])
-
- # Split By Scene
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
- nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
- qtbot.wait(stepDelay)
- nwSplit.splitLevel.setCurrentIndex(2)
- qtbot.wait(stepDelay)
-
- nwSplit._doSplit()
-
- assert nwGUI.theProject.projTree["25fc0e7096fc6"] is not None
- assert nwGUI.theProject.projTree["31489056e0916"] is not None
- assert nwGUI.theProject.projTree["98010bd9270f9"] is not None
-
- projFile = path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
- testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
- refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "content", "31489056e0916.nwd")
- testFile = path.join(nwTempGUI, "5_31489056e0916.nwd")
- refFile = path.join(nwRef, "gui", "5_31489056e0916.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "content", "98010bd9270f9.nwd")
- testFile = path.join(nwTempGUI, "5_98010bd9270f9.nwd")
- refFile = path.join(nwRef, "gui", "5_98010bd9270f9.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Split By Section
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
- nwSplit = GuiDocSplit(nwGUI, nwGUI.theProject)
- qtbot.wait(stepDelay)
- nwSplit.splitLevel.setCurrentIndex(3)
- qtbot.wait(stepDelay)
-
- nwSplit._doSplit()
-
- assert nwGUI.theProject.projTree["1a6562590ef19"] is not None
- assert nwGUI.theProject.projTree["031b4af5197ec"] is not None
- assert nwGUI.theProject.projTree["41cfc0d1f2d12"] is not None
- assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
- assert nwGUI.theProject.projTree["2fca346db6561"] is not None
-
- projFile = path.join(nwLipsum, "content", "1a6562590ef19.nwd")
- testFile = path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
- refFile = path.join(nwRef, "gui", "5_25fc0e7096fc6.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [1])
-
- projFile = path.join(nwLipsum, "content", "031b4af5197ec.nwd")
- testFile = path.join(nwTempGUI, "5_031b4af5197ec.nwd")
- refFile = path.join(nwRef, "gui", "5_031b4af5197ec.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
- testFile = path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
- refFile = path.join(nwRef, "gui", "5_41cfc0d1f2d12.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
- testFile = path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
- refFile = path.join(nwRef, "gui", "5_2858dcd1057d3.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = path.join(nwLipsum, "content", "2fca346db6561.nwd")
- testFile = path.join(nwTempGUI, "5_2fca346db6561.nwd")
- refFile = path.join(nwRef, "gui", "5_2fca346db6561.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testNewProjectWizard(qtbot, nwLipsum, nwTemp):
-
- from PyQt5.QtWidgets import QWizard
- from nw.gui.projwizard import (
- ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage,
- ProjWizardCustomPage, ProjWizardFinalPage
- )
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- for wStep in range(3):
-
- # The Wizard
- nwWiz = GuiProjectWizard(nwGUI)
- nwWiz.show()
- qtbot.waitForWindowShown(nwWiz)
-
- # Intro Page
- introPage = nwWiz.currentPage()
- assert isinstance(introPage, ProjWizardIntroPage)
- assert not nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- for c in "Test Minimal":
- qtbot.keyClick(introPage.projName, c, delay=keyDelay)
-
- qtbot.wait(stepDelay)
- for c in "Minimal Novel":
- qtbot.keyClick(introPage.projTitle, c, delay=keyDelay)
-
- qtbot.wait(stepDelay)
- for c in "Jane Doe":
- qtbot.keyClick(introPage.projAuthors, c, delay=keyDelay)
-
- # Setting projName should activate the button
- assert nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
-
- # Folder Page
- storagePage = nwWiz.currentPage()
- assert isinstance(storagePage, ProjWizardFolderPage)
- assert not nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- projPath = path.join(nwTemp, "dummy")
- for c in projPath:
- qtbot.keyClick(storagePage.projPath, c, delay=keyDelay)
-
- # Setting projPath should activate the button
- assert nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
-
- # Populate Page
- popPage = nwWiz.currentPage()
- assert isinstance(popPage, ProjWizardPopulatePage)
- assert nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- if wStep == 0:
- popPage.popMinimal.setChecked(True)
- elif wStep == 1:
- popPage.popCustom.setChecked(True)
- elif wStep == 2:
- popPage.popSample.setChecked(True)
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
-
- # Custom Page
- if wStep == 1:
- customPage = nwWiz.currentPage()
- assert isinstance(customPage, ProjWizardCustomPage)
- assert nwWiz.button(QWizard.NextButton).isEnabled()
-
- customPage.addPlot.setChecked(True)
- customPage.addChar.setChecked(True)
- customPage.addWorld.setChecked(True)
- customPage.addTime.setChecked(True)
- customPage.addObject.setChecked(True)
- customPage.addEntity.setChecked(True)
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
-
- # Final Page
- finalPage = nwWiz.currentPage()
- assert isinstance(finalPage, ProjWizardFinalPage)
- assert nwWiz.button(QWizard.FinishButton).isEnabled()
- qtbot.mouseClick(nwWiz.button(QWizard.FinishButton), Qt.LeftButton)
-
- # Check Data
- projData = nwGUI._assembleProjectWizardData(nwWiz)
- assert projData["projName"] == "Test Minimal"
- assert projData["projTitle"] == "Minimal Novel"
- assert projData["projAuthors"] == "Jane Doe"
- assert projData["projPath"] == projPath
- assert projData["popMinimal"] == (wStep == 0)
- assert projData["popCustom"] == (wStep == 1)
- assert projData["popSample"] == (wStep == 2)
- if wStep == 1:
- assert projData["addRoots"] == [
- nwItemClass.PLOT,
- nwItemClass.CHARACTER,
- nwItemClass.WORLD,
- nwItemClass.TIMELINE,
- nwItemClass.OBJECT,
- nwItemClass.ENTITY,
- ]
- assert projData["numChapters"] == 5
- assert projData["numScenes"] == 5
- assert projData["chFolders"]
- else:
- assert projData["addRoots"] == []
- assert projData["numChapters"] == 0
- assert projData["numScenes"] == 0
- assert not projData["chFolders"]
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
@pytest.mark.gui
def testDocAction(qtbot, nwLipsum, nwTemp):
@@ -1260,57 +559,6 @@ def testInsertMenu(qtbot, nwFuncTemp, nwTemp):
# qtbot.stopForInteraction()
nwGUI.closeMain()
-@pytest.mark.gui
-def testLoadProject(qtbot, nwMinimal, nwTemp):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % nwTemp])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwMinimal)
- assert nwGUI.closeProject()
-
- nwLoad = GuiProjectLoad(nwGUI)
- nwLoad.show()
-
- recentCount = nwLoad.listBox.topLevelItemCount()
- assert recentCount > 0
-
- selItem = nwLoad.listBox.topLevelItem(0)
- selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
- assert isinstance(selItem, QTreeWidgetItem)
-
- nwLoad.selPath.setText("")
- nwLoad.listBox.setCurrentItem(selItem)
- nwLoad._doSelectRecent()
- assert nwLoad.selPath.text() == selPath
-
- qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
- assert nwLoad.openPath == selPath
- assert nwLoad.openState == nwLoad.OPEN_STATE
-
- del nwLoad
- nwLoad = GuiProjectLoad(nwGUI)
- nwLoad.show()
-
- qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
- assert nwLoad.openPath is None
- assert nwLoad.openState == nwLoad.NONE_STATE
-
- nwLoad.show()
- qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
- assert nwLoad.openPath is None
- assert nwLoad.openState == nwLoad.NEW_STATE
-
- nwLoad.show()
- nwLoad._keyPressDelete()
- assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
-
- nwLoad.close()
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
@pytest.mark.gui
def testOutline(qtbot, nwLipsum, nwTemp):
diff --git a/tests/test_item.py b/tests/test_item.py
index a3280c14..d1097161 100644
--- a/tests/test_item.py
+++ b/tests/test_item.py
@@ -237,3 +237,34 @@ def testItemXMLPackUnpack(nwDummy):
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
+
+ # Errors
+
+ ## Not an Item
+ xDummy = etree.SubElement(nwXML, "stuff")
+ assert not theItem.unpackXML(xDummy)
+
+ ## Item without Handle
+ xDummy = etree.SubElement(nwXML, "item", attrib={"stuff": "nah"})
+ assert not theItem.unpackXML(xDummy)
+
+ ## Item with Invalid SubElement
+ xDummy = etree.SubElement(nwXML, "item", attrib={"handle": "0123456789abc"})
+ xParam = etree.SubElement(xDummy, "invalid")
+ xParam.text = "stuff"
+ assert theItem.unpackXML(xDummy) # Passes, but not saved
+
+ # Pack Valid Item
+ xDummy = etree.SubElement(nwXML, "group")
+ theItem._subPack(xDummy, "subGroup", {"one": "two"}, "value", False)
+ assert etree.tostring(xDummy, pretty_print=False, encoding="utf-8") == (
+ b"value"
+ )
+
+ # Pack Not Allowed None
+ xDummy = etree.SubElement(nwXML, "group")
+ assert theItem._subPack(xDummy, "subGroup", {}, None, False) is None
+ assert theItem._subPack(xDummy, "subGroup", {}, "None", False) is None
+ assert etree.tostring(xDummy, pretty_print=False, encoding="utf-8") == (
+ b""
+ )
diff --git a/tests/test_project.py b/tests/test_project.py
index 54d44d6a..17eae3b0 100644
--- a/tests/test_project.py
+++ b/tests/test_project.py
@@ -12,7 +12,7 @@ from nw.core.project import NWProject
from nw.core.document import NWDoc
from nw.core.index import NWIndex
from nw.core.spellcheck import NWSpellEnchant, NWSpellSimple
-from nw.constants import nwItemClass, nwItemLayout, nwFiles
+from nw.constants import nwItemClass, nwItemType, nwItemLayout, nwFiles
@pytest.mark.project
def testProjectNewOpenSave(nwFuncTemp, nwTempProj, nwRef, nwTemp, nwDummy):
@@ -431,3 +431,64 @@ def testProjectOptions(nwDummy, nwLipsum):
assert theOpts.getFloat("GuiWritingStats", "NoName", False) is False
assert theOpts.setValue("GuiWritingStats", "winWidth", "True")
assert theOpts.getFloat("GuiWritingStats", "winWidth", False) is False
+
+@pytest.mark.project
+def testOrphanedFiles(nwDummy, nwLipsum):
+ theProject = NWProject(nwDummy)
+ assert theProject.openProject(nwLipsum)
+ assert theProject.projTree["636b6aa9b697b"] is None
+ assert theProject.closeProject()
+
+ # First Item with Meta Data
+ orphPath = path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
+ with open(orphPath, mode="w", encoding="utf8") as outFile:
+ outFile.write(r"%%~ 5eaea4e8cdee8:15c4492bd5107:WORLD:NOTE:Mars")
+ outFile.write("\n")
+
+ # Second Item without Meta Data
+ orphPath = path.join(nwLipsum, "content", "736b6aa9b697b.nwd")
+ with open(orphPath, mode="w", encoding="utf8") as outFile:
+ outFile.write("\n")
+
+ # Invalid File Name
+ dummyPath = path.join(nwLipsum, "content", "636b6aa9b697b.txt")
+ with open(dummyPath, mode="w", encoding="utf8") as outFile:
+ outFile.write("\n")
+
+ # Invalid File Name
+ dummyPath = path.join(nwLipsum, "content", "636b6aa9b697bb.nwd")
+ with open(dummyPath, mode="w", encoding="utf8") as outFile:
+ outFile.write("\n")
+
+ # Invalid File Name
+ dummyPath = path.join(nwLipsum, "content", "abcdefghijklm.nwd")
+ with open(dummyPath, mode="w", encoding="utf8") as outFile:
+ outFile.write("\n")
+
+ assert theProject.openProject(nwLipsum)
+ assert theProject.projPath is not None
+ assert theProject.projTree["636b6aa9b697bb"] is None
+ assert theProject.projTree["abcdefghijklm"] is None
+
+ # First Item with Meta Data
+ oItem = theProject.projTree["636b6aa9b697b"]
+ assert oItem is not None
+ assert oItem.itemName == "Mars"
+ assert oItem.itemHandle == "636b6aa9b697b"
+ assert oItem.parHandle is None
+ assert oItem.itemClass == nwItemClass.WORLD
+ assert oItem.itemType == nwItemType.FILE
+ assert oItem.itemLayout == nwItemLayout.NOTE
+
+ # Second Item without Meta Data
+ oItem = theProject.projTree["736b6aa9b697b"]
+ assert oItem is not None
+ assert oItem.itemName == "Orphaned File 1"
+ assert oItem.itemHandle == "736b6aa9b697b"
+ assert oItem.parHandle is None
+ assert oItem.itemClass == nwItemClass.NO_CLASS
+ assert oItem.itemType == nwItemType.FILE
+ assert oItem.itemLayout == nwItemLayout.NO_LAYOUT
+
+ assert theProject.saveProject(nwLipsum)
+ assert theProject.closeProject()