diff --git a/nw/gui/dochighlight.py b/nw/gui/dochighlight.py
index 309b74a8..51a00f02 100644
--- a/nw/gui/dochighlight.py
+++ b/nw/gui/dochighlight.py
@@ -102,10 +102,9 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.colTrail = QColor(*self.theTheme.colEmph)
self.colTrail.setAlpha(64)
+ self.colEmph = None
if self.mainConf.highlightEmph:
self.colEmph = QColor(*self.theTheme.colEmph)
- else:
- self.colEmph = None
self.hStyles = {
"header1" : self._makeFormat(self.colHead, "bold", 1.8),
diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py
index 389c9f6f..4ae78a98 100644
--- a/nw/gui/preferences.py
+++ b/nw/gui/preferences.py
@@ -806,14 +806,6 @@ class GuiConfigEditEditingTab(QWidget):
# Internal Functions
##
- def _disableComboItem(self, theList, theValue):
- """Disable a list item in the combo box.
- """
- theModel = theList.model()
- anItem = theModel.item(1)
- anItem.setFlags(anItem.flags() ^ Qt.ItemIsEnabled)
- return theModel
-
def _doUpdateSpellTool(self, currIdx):
"""Update the list of dictionaries based on spell tool selected.
"""
diff --git a/nw/gui/projload.py b/nw/gui/projload.py
index 35621032..15fe2825 100644
--- a/nw/gui/projload.py
+++ b/nw/gui/projload.py
@@ -155,14 +155,14 @@ class GuiProjectLoad(QDialog):
logger.verbose("GuiProjectLoad open button clicked")
self._saveSettings()
+ self.openPath = None
+ self.openState = self.NONE_STATE
+
selItems = self.listBox.selectedItems()
if selItems:
self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole)
self.openState = self.OPEN_STATE
self.accept()
- else:
- self.openPath = None
- self.openState = self.NONE_STATE
return
diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py
index d51d8006..e02fece6 100644
--- a/nw/gui/projsettings.py
+++ b/nw/gui/projsettings.py
@@ -220,9 +220,8 @@ class GuiProjectEditMain(QWidget):
"Overrides main preferences."
)
- if self.theProject.projLang is None:
- spellIdx = 0
- else:
+ spellIdx = 0
+ if self.theProject.projLang is not None:
spellIdx = self.spellLang.findData(self.theProject.projLang)
if spellIdx != -1:
self.spellLang.setCurrentIndex(spellIdx)
@@ -515,9 +514,7 @@ class GuiProjectEditStatus(QWidget):
"""Get the currently selected item.
"""
selItem = self.listBox.selectedItems()
- if len(selItem) == 0:
- return None
- if isinstance(selItem[0], QListWidgetItem):
+ if len(selItem) > 0:
return selItem[0]
return None
diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py
index ace755ae..c430da8c 100644
--- a/nw/gui/statusbar.py
+++ b/nw/gui/statusbar.py
@@ -130,6 +130,7 @@ class GuiMainStatus(QStatusBar):
"""Reset all widgets on the status bar to default values.
"""
self.setRefTime(None)
+ self.setLanguage(None)
self.setStats(0, 0)
self.setProjectStatus(None)
self.setDocumentStatus(None)
@@ -234,15 +235,14 @@ class StatusLED(QAbstractButton):
def setState(self, theState):
"""Set the colour state.
"""
- if theState is None:
- self._theCol = self.colNone
- elif theState:
+ self._theCol = self.colNone
+ if theState is True:
self._theCol = self.colTrue
- elif not theState:
+ elif theState is False:
self._theCol = self.colFalse
- else:
- self._theCol = self.colNone
+
self.update()
+
return
##
diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py
index b796bbce..3b5bc5c5 100644
--- a/nw/gui/writingstats.py
+++ b/nw/gui/writingstats.py
@@ -39,7 +39,7 @@ from PyQt5.QtWidgets import (
QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
)
-from nw.common import formatTime
+from nw.common import formatTime, checkInt
from nw.constants import nwConst, nwFiles, nwAlert
from nw.gui.custom import QSwitch
@@ -316,37 +316,30 @@ class GuiWritingStats(QDialog):
if dataFmt == self.FMT_JSON:
fileExt = "json"
textFmt = "JSON Data File"
-
elif dataFmt == self.FMT_CSV:
fileExt = "csv"
textFmt = "CSV Data File"
-
else:
return False
# Generate the file name
- if fileExt:
- saveDir = self.mainConf.lastPath
- if not os.path.isdir(saveDir):
- saveDir = os.path.expanduser("~")
+ saveDir = self.mainConf.lastPath
+ if not os.path.isdir(saveDir):
+ saveDir = os.path.expanduser("~")
- fileName = "sessionStats.%s" % fileExt
- savePath = os.path.join(saveDir, fileName)
+ fileName = "sessionStats.%s" % fileExt
+ savePath = os.path.join(saveDir, fileName)
- dlgOpt = QFileDialog.Options()
- dlgOpt |= QFileDialog.DontUseNativeDialog
- savePath, _ = QFileDialog.getSaveFileName(
- self, "Save Document As", savePath, options=dlgOpt
- )
-
- if not savePath:
- return False
-
- self.mainConf.setLastPath(savePath)
-
- else:
+ dlgOpt = QFileDialog.Options()
+ dlgOpt |= QFileDialog.DontUseNativeDialog
+ savePath, _ = QFileDialog.getSaveFileName(
+ self, "Save Document As", savePath, options=dlgOpt
+ )
+ if not savePath:
return False
+ self.mainConf.setLastPath(savePath)
+
# Do the actual writing
wSuccess = False
errMsg = ""
@@ -366,7 +359,7 @@ class GuiWritingStats(QDialog):
json.dump(jsonData, outFile, indent=2)
wSuccess = True
- elif dataFmt == self.FMT_CSV:
+ if dataFmt == self.FMT_CSV:
outFile.write(
'"Date","Length (sec)","Words Changed","Novel Words","Note Words"\n'
)
@@ -374,11 +367,9 @@ class GuiWritingStats(QDialog):
outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB}\n')
wSuccess = True
- else:
- errMsg = "Unknown format"
-
except Exception as e:
- errMsg = str(e).replace("\n", "
")
+ errMsg = str(e)
+ wSuccess = False
# Report to user
if wSuccess:
@@ -394,7 +385,7 @@ class GuiWritingStats(QDialog):
), nwAlert.ERROR
)
- return True
+ return wSuccess
##
# Internal Functions
@@ -406,6 +397,7 @@ class GuiWritingStats(QDialog):
logger.debug("Loading session log file")
self.logData = []
+ self.wordOffset = 0
ttNovel = 0
ttNotes = 0
@@ -417,7 +409,7 @@ class GuiWritingStats(QDialog):
for inLine in inFile:
if inLine.startswith("#"):
if inLine.startswith("# Offset"):
- self.wordOffset = int(inLine[9:].strip())
+ self.wordOffset = checkInt(inLine[9:].strip(), 0)
logger.verbose(
"Initial word count when log was started is %d" % self.wordOffset
)
diff --git a/tests/README.md b/tests/README.md
index 7d8e378d..f13d974e 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -52,27 +52,45 @@ pytest-3 -v -m core
Available markers are:
-* '`core`' for unit tests covering the classes in the `nw/core` folder
+* `base` for unit tests covering the non-gui classes of the `bw` folder..
+* `core` for unit tests covering the classes in the `nw/core` folder.
+* `gui` for unit and integrations tests covering the classes in the `nw/gui` folder.
## Tests
To filter specific groups of tests, use the `-k` switch.
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` |
-| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
-| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` |
-| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
-| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
-| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
-| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` |
-| Unit | ToHtml class | nw/core/tohtml.py | `-m core` | `-k testCoreToHtml` |
-| Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` |
+| 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` |
+| Unit | NWItem class | nw/core/item.py | `-m core` | `-k testCoreItem` |
+| Unit | NWProject class | nw/core/project.py | `-m core` | `-k testCoreProject` |
+| Unit | NWSpell* classes | nw/core/spellcheck.py | `-m core` | `-k testCoreSpell` |
+| Unit | NWStatus class | nw/core/status.py | `-m core` | `-k testCoreStatus` |
+| Unit | NWTree class | nw/core/tree.py | `-m core` | `-k testCoreTree` |
+| Unit | OptionsState class | nw/core/options.py | `-m core` | `-k testCoreOptions` |
+| Unit | ToHtml class | nw/core/tohtml.py | `-m core` | `-k testCoreToHtml` |
+| Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` |
+| Integration | About Dialogs | nw/gui/about.py | `-m gui` | `-k testGuiAbout` |
+| Integration | Build Novel Project Tool | nw/gui/build.py | `-m gui` | `-k testGuiBuild` |
+| Integration | Document Editor Widget | nw/gui/doceditor.py | `-m gui` | `-k testGuiEditor` |
+| Integration | Document Viewer Widget | nw/gui/docviewer.py | `-m gui` | `-k testGuiViewer` |
+| Integration | Item Editor Dialog | nw/gui/itemeditor.py | `-m gui` | `-k testGuiItemEditor` |
+| Integration | Menu Widgets | nw/gui/mainmenu.py | `-m gui` | `-k testGuiMenu` |
+| Integration | Merge Tool | nw/gui/docmerge.py | `-m gui` | `-k testGuiMergeSplit` |
+| Integration | Outline Widget | nw/gui/outline.py | `-m gui` | `-k testGuiOutline` |
+| Integration | Preferences Dialog | nw/gui/preferences.py | `-m gui` | `-k testGuiPreferences` |
+| Integration | Project Load Dialog | nw/gui/projload.py | `-m gui` | `-k testGuiProjLoad` |
+| Integration | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` |
+| Integration | Project Tree Widget | nw/gui/projtree.py | `-m gui` | `-k testGuiProjTree` |
+| Integration | Theme/Icon Classes | nw/gui/theme.py | `-m gui` | `-k testGuiTheme` |
+| Integration | Split Tool | nw/gui/docsplit.py | `-m gui` | `-k testGuiMergeSplit` |
+| Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` |
+| Integration | Various Dialogs | N/A | `-m gui` | `-k testGuiDialogs` |
diff --git a/tests/conftest.py b/tests/conftest.py
index ea9a92b3..bab0d4f6 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -9,10 +9,10 @@ import os
from dummy import DummyMain
-from PyQt5.QtWidgets import QMessageBox
-
sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)))
+import nw # noqa: E402
+
from nw.config import Config # noqa: E402
##
@@ -55,16 +55,28 @@ def outDir(tmpDir):
def fncDir(tmpDir):
"""A temporary folder for a single test function.
"""
- funcDir = os.path.join(tmpDir, "ftemp")
- if os.path.isdir(funcDir):
- shutil.rmtree(funcDir)
- if not os.path.isdir(funcDir):
- os.mkdir(funcDir)
- yield funcDir
- if os.path.isdir(funcDir):
- shutil.rmtree(funcDir)
+ fncDir = os.path.join(tmpDir, "f_temp")
+ if os.path.isdir(fncDir):
+ shutil.rmtree(fncDir)
+ if not os.path.isdir(fncDir):
+ os.mkdir(fncDir)
+ yield fncDir
+ if os.path.isdir(fncDir):
+ shutil.rmtree(fncDir)
return
+@pytest.fixture(scope="function")
+def fncProj(fncDir):
+ """A temporary folder for a single test function,
+ with a project folder.
+ """
+ prjDir = os.path.join(fncDir, "project")
+ if os.path.isdir(prjDir):
+ shutil.rmtree(prjDir)
+ if not os.path.isdir(prjDir):
+ os.mkdir(prjDir)
+ return prjDir
+
##
# novelWriter Objects
##
@@ -89,6 +101,26 @@ def dummyGUI(tmpConf):
theDummy.mainConf = tmpConf
return theDummy
+@pytest.fixture(scope="function")
+def nwGUI(qtbot, fncDir):
+ """Create an instance of the novelWriter GUI.
+ """
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(20)
+
+ nwGUI.mainConf.lastPath = fncDir
+
+ yield nwGUI
+
+ qtbot.wait(20)
+ nwGUI.closeMain()
+ qtbot.wait(20)
+
+ return
+
##
# Temp Project Folders
##
@@ -150,51 +182,3 @@ def nwOldProj(tmpDir):
if os.path.isdir(oldProjDir):
shutil.rmtree(oldProjDir)
return
-
-##
-# Monkey Patch Dialogs
-##
-
-@pytest.fixture(scope="function")
-def yesToAll(monkeypatch):
- """Make the message boxes/questions always say yes.
- """
- monkeypatch.setattr(
- QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes
- )
- monkeypatch.setattr(
- QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes
- )
- monkeypatch.setattr(
- QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes
- )
- monkeypatch.setattr(
- QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes
- )
- yield
- monkeypatch.undo()
- return
-
-# =============================================================================================== #
-
-##
-# Temporary Test Folders
-##
-
-@pytest.fixture(scope="session")
-def nwTempGUI(tmpDir):
- """A temporary folder for GUI tests.
- """
- guiDir = os.path.join(tmpDir, "gui")
- if not os.path.isdir(guiDir):
- os.mkdir(guiDir)
- return guiDir
-
-@pytest.fixture(scope="session")
-def nwTempBuild(tmpDir):
- """A temporary folder for build tests.
- """
- buildDir = os.path.join(tmpDir, "build")
- if not os.path.isdir(buildDir):
- os.mkdir(buildDir)
- return buildDir
diff --git a/tests/reference/build/1_LoremIpsum.htm b/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm
similarity index 100%
rename from tests/reference/build/1_LoremIpsum.htm
rename to tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.htm
diff --git a/tests/reference/build/1_LoremIpsum.nwd b/tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.nwd
similarity index 100%
rename from tests/reference/build/1_LoremIpsum.nwd
rename to tests/reference/guiBuild_Tool_Step1_Lorem_Ipsum.nwd
diff --git a/tests/reference/build/2_LoremIpsum.htm b/tests/reference/guiBuild_Tool_Step2_Lorem_Ipsum.htm
similarity index 100%
rename from tests/reference/build/2_LoremIpsum.htm
rename to tests/reference/guiBuild_Tool_Step2_Lorem_Ipsum.htm
diff --git a/tests/reference/build/2_LoremIpsum.nwd b/tests/reference/guiBuild_Tool_Step2_Lorem_Ipsum.nwd
similarity index 100%
rename from tests/reference/build/2_LoremIpsum.nwd
rename to tests/reference/guiBuild_Tool_Step2_Lorem_Ipsum.nwd
diff --git a/tests/reference/build/3_LoremIpsum.htm b/tests/reference/guiBuild_Tool_Step3_Lorem_Ipsum.htm
similarity index 100%
rename from tests/reference/build/3_LoremIpsum.htm
rename to tests/reference/guiBuild_Tool_Step3_Lorem_Ipsum.htm
diff --git a/tests/reference/build/3_LoremIpsum.nwd b/tests/reference/guiBuild_Tool_Step3_Lorem_Ipsum.nwd
similarity index 100%
rename from tests/reference/build/3_LoremIpsum.nwd
rename to tests/reference/guiBuild_Tool_Step3_Lorem_Ipsum.nwd
diff --git a/tests/reference/build/4H_LoremIpsum.json b/tests/reference/guiBuild_Tool_Step4H_Lorem_Ipsum.json
similarity index 100%
rename from tests/reference/build/4H_LoremIpsum.json
rename to tests/reference/guiBuild_Tool_Step4H_Lorem_Ipsum.json
diff --git a/tests/reference/build/4M_LoremIpsum.json b/tests/reference/guiBuild_Tool_Step4M_Lorem_Ipsum.json
similarity index 100%
rename from tests/reference/build/4M_LoremIpsum.json
rename to tests/reference/guiBuild_Tool_Step4M_Lorem_Ipsum.json
diff --git a/tests/reference/build/4_LoremIpsum.htm b/tests/reference/guiBuild_Tool_Step4_Lorem_Ipsum.htm
similarity index 100%
rename from tests/reference/build/4_LoremIpsum.htm
rename to tests/reference/guiBuild_Tool_Step4_Lorem_Ipsum.htm
diff --git a/tests/reference/build/4_LoremIpsum.nwd b/tests/reference/guiBuild_Tool_Step4_Lorem_Ipsum.nwd
similarity index 100%
rename from tests/reference/build/4_LoremIpsum.nwd
rename to tests/reference/guiBuild_Tool_Step4_Lorem_Ipsum.nwd
diff --git a/tests/reference/gui/1_031b4af5197ec.nwd b/tests/reference/guiEditor_Main_Final_031b4af5197ec.nwd
similarity index 100%
rename from tests/reference/gui/1_031b4af5197ec.nwd
rename to tests/reference/guiEditor_Main_Final_031b4af5197ec.nwd
diff --git a/tests/reference/gui/1_0e17daca5f3e1.nwd b/tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd
similarity index 58%
rename from tests/reference/gui/1_0e17daca5f3e1.nwd
rename to tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd
index bf24dbe6..54fef623 100644
--- a/tests/reference/gui/1_0e17daca5f3e1.nwd
+++ b/tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd
@@ -21,7 +21,7 @@
This is a paragraph of dummy text.
-This is another paragraph of much longer dummy text. It is in fact very very dumb dummy text! We can also try replacing “quotes”, even single ‘quotes’ are replaced. Isn’t that nice? We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either … How about three hyphens — for long dash? It works too.
+This is another paragraph of much longer dummy text. It is in fact 1 very very DUMB dummy text! We can also try replacing “quotes”, even single ‘quotes’ are replaced. Isn’t that nice? We can hyphen-ate, make dashes – and even longer dashes — if we want. Ellipsis? Not a problem either … How about three hyphens — for long dash? It works too.
“Full line double quoted text.”
diff --git a/tests/reference/gui/1_1a6562590ef19.nwd b/tests/reference/guiEditor_Main_Final_1a6562590ef19.nwd
similarity index 100%
rename from tests/reference/gui/1_1a6562590ef19.nwd
rename to tests/reference/guiEditor_Main_Final_1a6562590ef19.nwd
diff --git a/tests/reference/gui/1_41cfc0d1f2d12.nwd b/tests/reference/guiEditor_Main_Final_41cfc0d1f2d12.nwd
similarity index 100%
rename from tests/reference/gui/1_41cfc0d1f2d12.nwd
rename to tests/reference/guiEditor_Main_Final_41cfc0d1f2d12.nwd
diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx
similarity index 96%
rename from tests/reference/gui/1_nwProject.nwx
rename to tests/reference/guiEditor_Main_Final_nwProject.nwx
index 1174c04c..21011e03 100644
--- a/tests/reference/gui/1_nwProject.nwx
+++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx
@@ -14,8 +14,8 @@
True
0e17daca5f3e1
None
- 113
- 86
+ 114
+ 87
27
@@ -84,10 +84,10 @@
New
True
SCENE
- 464
- 82
+ 466
+ 83
4
- 602
+ 604
-
Plot
diff --git a/tests/reference/gui/0_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx
similarity index 100%
rename from tests/reference/gui/0_nwProject.nwx
rename to tests/reference/guiEditor_Main_Initial_nwProject.nwx
diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/guiItemEditor_Dialog_nwProject.nwx
similarity index 100%
rename from tests/reference/gui/3_nwProject.nwx
rename to tests/reference/guiItemEditor_Dialog_nwProject.nwx
diff --git a/tests/reference/gui/4_73475cb40a568.nwd b/tests/reference/guiMerge_73475cb40a568.nwd
similarity index 100%
rename from tests/reference/gui/4_73475cb40a568.nwd
rename to tests/reference/guiMerge_73475cb40a568.nwd
diff --git a/tests/reference/novelwriter_prefs.conf b/tests/reference/guiPreferences_novelwriter.conf
similarity index 98%
rename from tests/reference/novelwriter_prefs.conf
rename to tests/reference/guiPreferences_novelwriter.conf
index 901a6937..cdbc5337 100644
--- a/tests/reference/novelwriter_prefs.conf
+++ b/tests/reference/guiPreferences_novelwriter.conf
@@ -60,7 +60,7 @@ backuponclose = True
askbeforebackup = True
[State]
-showrefpanel = False
+showrefpanel = True
viewcomments = True
viewsynopsis = True
searchcase = False
diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
similarity index 93%
rename from tests/reference/gui/2_nwProject.nwx
rename to tests/reference/guiProjSettings_Dialog_nwProject.nwx
index 11cad874..ea349d11 100644
--- a/tests/reference/gui/2_nwProject.nwx
+++ b/tests/reference/guiProjSettings_Dialog_nwProject.nwx
@@ -1,5 +1,5 @@
-
+
Project Name
Project Title
@@ -12,7 +12,7 @@
True
False
- None
+ en
True
None
None
@@ -20,6 +20,8 @@
6
0
+ B
+ D
With This Stuff
@@ -33,13 +35,13 @@
New
Note
Finished
- Final
+ Final
New
Minor
Major
- Main
+ Final
diff --git a/tests/reference/gui/5_031b4af5197ec.nwd b/tests/reference/guiSplit_031b4af5197ec.nwd
similarity index 100%
rename from tests/reference/gui/5_031b4af5197ec.nwd
rename to tests/reference/guiSplit_031b4af5197ec.nwd
diff --git a/tests/reference/gui/5_25fc0e7096fc6.nwd b/tests/reference/guiSplit_25fc0e7096fc6.nwd
similarity index 100%
rename from tests/reference/gui/5_25fc0e7096fc6.nwd
rename to tests/reference/guiSplit_25fc0e7096fc6.nwd
diff --git a/tests/reference/gui/5_2858dcd1057d3.nwd b/tests/reference/guiSplit_2858dcd1057d3.nwd
similarity index 100%
rename from tests/reference/gui/5_2858dcd1057d3.nwd
rename to tests/reference/guiSplit_2858dcd1057d3.nwd
diff --git a/tests/reference/gui/5_2fca346db6561.nwd b/tests/reference/guiSplit_2fca346db6561.nwd
similarity index 100%
rename from tests/reference/gui/5_2fca346db6561.nwd
rename to tests/reference/guiSplit_2fca346db6561.nwd
diff --git a/tests/reference/gui/5_31489056e0916.nwd b/tests/reference/guiSplit_31489056e0916.nwd
similarity index 100%
rename from tests/reference/gui/5_31489056e0916.nwd
rename to tests/reference/guiSplit_31489056e0916.nwd
diff --git a/tests/reference/gui/5_41cfc0d1f2d12.nwd b/tests/reference/guiSplit_41cfc0d1f2d12.nwd
similarity index 100%
rename from tests/reference/gui/5_41cfc0d1f2d12.nwd
rename to tests/reference/guiSplit_41cfc0d1f2d12.nwd
diff --git a/tests/reference/gui/5_98010bd9270f9.nwd b/tests/reference/guiSplit_98010bd9270f9.nwd
similarity index 100%
rename from tests/reference/gui/5_98010bd9270f9.nwd
rename to tests/reference/guiSplit_98010bd9270f9.nwd
diff --git a/tests/test_gui_about.py b/tests/test_gui_about.py
new file mode 100644
index 00000000..498e26b5
--- /dev/null
+++ b/tests/test_gui_about.py
@@ -0,0 +1,52 @@
+# -*- coding: utf-8 -*-
+"""novelWriter About Dialog Class Tester
+"""
+
+import pytest
+
+from tools import getGuiItem
+
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.gui import GuiAbout
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiAbout_Dialog(qtbot, monkeypatch, nwGUI):
+ """Test the full about dialogs.
+ """
+ # NW About
+ monkeypatch.setattr(GuiAbout, "exec_", lambda *args: None)
+ nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
+
+ msgAbout = getGuiItem("GuiAbout")
+ assert isinstance(msgAbout, GuiAbout)
+ msgAbout.show()
+
+ assert msgAbout.pageAbout.document().characterCount() > 100
+ assert msgAbout.pageNotes.document().characterCount() > 100
+ assert msgAbout.pageLicense.document().characterCount() > 100
+
+ msgAbout.mainConf.assetPath = "whatever"
+
+ msgAbout._fillNotesPage()
+ assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..."
+
+ msgAbout._fillLicensePage()
+ assert msgAbout.pageLicense.toPlainText() == "Error loading license text ..."
+
+ msgAbout.showReleaseNotes()
+ assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
+
+ # Qt About
+ monkeypatch.setattr(QMessageBox, "aboutQt", lambda *args, **kwargs: None)
+ nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
+
+ # qtbot.stopForInteraction()
+ msgAbout._doClose()
+
+# END Test testGuiAbout_Dialog
diff --git a/tests/test_gui_build.py b/tests/test_gui_build.py
new file mode 100644
index 00000000..701312f8
--- /dev/null
+++ b/tests/test_gui_build.py
@@ -0,0 +1,209 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Build Dialog Class Tester
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles, getGuiItem
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.gui import GuiBuildNovel
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiBuild_Tool(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
+ """Test the build tool.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Check that we cannot open when there is no project
+ nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
+ assert getGuiItem("GuiBuildNovel") is None
+
+ # Open a project
+ assert nwGUI.openProject(nwLipsum)
+ nwGUI.mainConf.lastPath = nwLipsum
+
+ # Open the tool
+ nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
+
+ nwBuild = getGuiItem("GuiBuildNovel")
+ assert isinstance(nwBuild, GuiBuildNovel)
+
+ # Default Settings
+ qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.nwd")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step1_Lorem_Ipsum.htm")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step1_Lorem_Ipsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # 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 = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.nwd")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step2_Lorem_Ipsum.htm")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step2_Lorem_Ipsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # Replace Tabs with Spaces
+ qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+
+ qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
+
+ # Save files that can be compared
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.nwd")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step3_Lorem_Ipsum.htm")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step3_Lorem_Ipsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # 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)
+
+ # Save files that can be compared
+ assert nwBuild._saveDocument(nwBuild.FMT_NWD)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step4_Lorem_Ipsum.nwd")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step4_Lorem_Ipsum.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ assert nwBuild._saveDocument(nwBuild.FMT_HTM)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step4_Lorem_Ipsum.htm")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step4_Lorem_Ipsum.htm")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # Check the JSON files too at this stage
+ assert nwBuild._saveDocument(nwBuild.FMT_JSON_H)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step4H_Lorem_Ipsum.json")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step4H_Lorem_Ipsum.json")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [8])
+
+ assert nwBuild._saveDocument(nwBuild.FMT_JSON_M)
+ projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
+ testFile = os.path.join(outDir, "guiBuild_Tool_Step4M_Lorem_Ipsum.json")
+ compFile = os.path.join(refDir, "guiBuild_Tool_Step4M_Lorem_Ipsum.json")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [8])
+
+ # Save other file types handled by Qt
+ # We assume the export itself by the Qt library works, so we just
+ # check that novelWriter successfully writes the files.
+ assert nwBuild._saveDocument(nwBuild.FMT_ODT)
+ assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.odt"))
+
+ if not nwGUI.mainConf.osDarwin:
+ assert nwBuild._saveDocument(nwBuild.FMT_PDF)
+ assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.pdf"))
+
+ assert nwBuild._saveDocument(nwBuild.FMT_MD)
+ assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.md"))
+
+ assert nwBuild._saveDocument(nwBuild.FMT_TXT)
+ assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.txt"))
+
+ # Close the build tool
+ htmlText = nwBuild.htmlText
+ htmlStyle = nwBuild.htmlStyle
+ nwdText = nwBuild.nwdText
+ buildTime = nwBuild.buildTime
+ nwBuild._doClose()
+
+ # Re-open build dialog from cahce
+ nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
+
+ nwBuild = getGuiItem("GuiBuildNovel")
+ assert isinstance(nwBuild, GuiBuildNovel)
+
+ assert nwBuild.viewCachedDoc()
+ assert nwBuild.htmlText == htmlText
+ assert nwBuild.htmlStyle == htmlStyle
+ assert nwBuild.nwdText == nwdText
+ assert nwBuild.buildTime == buildTime
+
+ nwBuild._doClose()
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiBuild_Tool
diff --git a/tests/test_gui_dialogs.py b/tests/test_gui_dialogs.py
index 96f64da4..b87f983c 100644
--- a/tests/test_gui_dialogs.py
+++ b/tests/test_gui_dialogs.py
@@ -2,1226 +2,23 @@
"""novelWriter Dialog Class Tester
"""
-import nw
import pytest
-import json
-import os
-import sys
-from shutil import copyfile
-from tools import cmpFiles, getGuiItem
+from PyQt5.QtCore import QItemSelectionModel
+from PyQt5.QtWidgets import QListWidgetItem, QDialog, QFileDialog, QMessageBox
-from PyQt5.QtCore import Qt, QItemSelectionModel
-from PyQt5.QtWidgets import (
- QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog, QAction,
- QMessageBox, QFileDialog, QFontDialog
-)
-
-from nw.gui import (
- GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel,
- GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard,
- GuiProjectLoad, GuiPreferences
-)
from nw.gui.custom import QuotesDialog
-from nw.constants import nwItemLayout, nwItemClass, nwFiles
keyDelay = 2
typeDelay = 1
stepDelay = 20
@pytest.mark.gui
-def testProjectSettings(qtbot, monkeypatch, yesToAll, fncDir, nwTempGUI, refDir, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Check that we cannot open when there is no project
- nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
- assert getGuiItem("GuiProjectSettings") is None
-
- # Create new project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": fncDir})
- nwGUI.mainConf.backupPath = fncDir
-
- # Get the dialog object
- monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
- monkeypatch.setattr(GuiProjectSettings, "result", lambda *args: QDialog.Accepted)
- nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
-
- projEdit = getGuiItem("GuiProjectSettings")
- assert isinstance(projEdit, GuiProjectSettings)
- projEdit.show()
- qtbot.addWidget(projEdit)
-
- # Main settings
- qtbot.wait(stepDelay)
- projEdit.tabMain.editName.setText("")
- for c in "Project Name":
- qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay)
- for c in "Project Title":
- qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=typeDelay)
- for c in "Jane Doe":
- qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
- qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
- for c in "John Doh":
- qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
-
- # 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=typeDelay)
- for c in "Final":
- qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay)
- 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=typeDelay)
- for c in "With This Stuff ":
- qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay)
- 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
- nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
-
- projEdit = getGuiItem("GuiProjectSettings")
- assert isinstance(projEdit, GuiProjectSettings)
-
- 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 = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(nwTempGUI, "2_nwProject.nwx")
- refFile = os.path.join(refDir, "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, yesToAll, monkeypatch, fncDir, nwTempGUI, refDir, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- 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": fncDir})
- assert nwGUI.openDocument("0e17daca5f3e1")
- assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
-
- monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
- nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
-
- itemEdit = getGuiItem("GuiItemEditor")
- assert isinstance(itemEdit, GuiItemEditor)
- itemEdit.show()
-
- 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=typeDelay)
- 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()
-
- nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
-
- itemEdit = getGuiItem("GuiItemEditor")
- assert isinstance(itemEdit, GuiItemEditor)
- itemEdit.show()
-
- 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 = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(nwTempGUI, "3_nwProject.nwx")
- refFile = os.path.join(refDir, "gui", "3_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testWritingStatsExport(qtbot, monkeypatch, yesToAll, fncDir, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- 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": fncDir})
- qtbot.wait(200)
- assert nwGUI.saveProject()
- assert nwGUI.closeProject()
- qtbot.wait(stepDelay)
-
- sessFile = os.path.join(fncDir, "meta", nwFiles.SESS_STATS)
- with open(sessFile, mode="w+", encoding="utf-8") as outFile:
- outFile.write(
- "# Start Time End Time Novel Notes\n"
- "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
- "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
- "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
- "2020-01-06 21:00:00 2020-01-06 21:00:10 125 5\n"
- )
-
- # Open again, and check the stats
- assert nwGUI.openProject(fncDir)
- qtbot.wait(stepDelay)
-
- nwGUI.mainConf.lastPath = fncDir
- nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
-
- sessLog = getGuiItem("GuiWritingStats")
- assert isinstance(sessLog, GuiWritingStats)
- qtbot.wait(stepDelay)
-
- monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: ("", ""))
- assert not sessLog._saveData(sessLog.FMT_CSV)
-
- monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, ""))
- assert sessLog._saveData(sessLog.FMT_CSV)
- qtbot.wait(100)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(100)
-
- jsonStats = os.path.join(fncDir, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.load(inFile)
-
- qtbot.wait(stepDelay)
-
- assert len(jsonData) == 3
- assert jsonData[1]["length"] >= 14.0
- assert jsonData[1]["newWords"] == 119
- assert jsonData[1]["novelWords"] == 125
- assert jsonData[1]["noteWords"] == 0
-
- # No Novel Files
- qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = os.path.join(fncDir, "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"] >= 14.0
- assert jsonData[0]["newWords"] == 5
- assert jsonData[0]["novelWords"] == 125
- 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 = os.path.join(fncDir, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.load(inFile)
-
- assert len(jsonData) == 2
- assert jsonData[1]["length"] >= 14.0
- assert jsonData[1]["newWords"] == 119
- assert jsonData[1]["novelWords"] == 125
- assert jsonData[1]["noteWords"] == 0
-
- # 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 = os.path.join(fncDir, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.load(inFile)
-
- assert len(jsonData) == 3
-
- # 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 = os.path.join(fncDir, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.load(inFile)
-
- assert len(jsonData) == 4
-
- # Group by Day
- qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
- qtbot.wait(stepDelay)
- assert sessLog._saveData(sessLog.FMT_JSON)
- qtbot.wait(stepDelay)
-
- jsonStats = os.path.join(fncDir, "sessionStats.json")
- with open(jsonStats, mode="r", encoding="utf-8") as inFile:
- jsonData = json.load(inFile)
-
- # 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) == 3
-
- # qtbot.stopForInteraction()
-
- sessLog._doClose()
- assert nwGUI.closeProject()
- qtbot.wait(stepDelay)
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testAboutBox(qtbot, monkeypatch, fncDir, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # NW About
- monkeypatch.setattr(GuiAbout, "exec_", lambda *args: None)
- nwGUI.mainMenu.aAboutNW.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
-
- msgAbout = getGuiItem("GuiAbout")
- assert isinstance(msgAbout, GuiAbout)
- msgAbout.show()
-
- assert msgAbout.pageAbout.document().characterCount() > 100
- assert msgAbout.pageNotes.document().characterCount() > 100
- assert msgAbout.pageLicense.document().characterCount() > 100
-
- msgAbout.mainConf.assetPath = "whatever"
-
- msgAbout._fillNotesPage()
- assert msgAbout.pageNotes.toPlainText() == "Error loading release notes text ..."
-
- msgAbout._fillLicensePage()
- assert msgAbout.pageLicense.toPlainText() == "Error loading license text ..."
-
- msgAbout.showReleaseNotes()
- assert msgAbout.tabBox.currentWidget() == msgAbout.pageNotes
-
- # Qt About
- monkeypatch.setattr(QMessageBox, "aboutQt", lambda *args, **kwargs: None)
- nwGUI.mainMenu.aAboutQt.activate(QAction.Trigger)
-
- # qtbot.stopForInteraction()
- msgAbout._doClose()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testBuildTool(qtbot, yesToAll, nwTempBuild, nwLipsum, refDir, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Check that we cannot open when there is no project
- nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
- assert getGuiItem("GuiBuildNovel") is None
-
- # Open a project
- assert nwGUI.openProject(nwLipsum)
- nwGUI.mainConf.lastPath = nwLipsum
-
- # Open the tool
- nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
-
- nwBuild = getGuiItem("GuiBuildNovel")
- assert isinstance(nwBuild, GuiBuildNovel)
-
- # Default Settings
- qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
-
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
-
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = os.path.join(nwTempBuild, "1_LoremIpsum.nwd")
- refFile = os.path.join(refDir, "build", "1_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = os.path.join(nwTempBuild, "1_LoremIpsum.htm")
- refFile = os.path.join(refDir, "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 = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = os.path.join(nwTempBuild, "2_LoremIpsum.nwd")
- refFile = os.path.join(refDir, "build", "2_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = os.path.join(nwTempBuild, "2_LoremIpsum.htm")
- refFile = os.path.join(refDir, "build", "2_LoremIpsum.htm")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Replace Tabs with Spaces
- qtbot.mouseClick(nwBuild.replaceTabs, Qt.LeftButton)
- qtbot.wait(stepDelay)
-
- qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)
-
- # Save files that can be compared
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = os.path.join(nwTempBuild, "3_LoremIpsum.nwd")
- refFile = os.path.join(refDir, "build", "3_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = os.path.join(nwTempBuild, "3_LoremIpsum.htm")
- refFile = os.path.join(refDir, "build", "3_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)
-
- # Save files that can be compared
- assert nwBuild._saveDocument(nwBuild.FMT_NWD)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.nwd")
- testFile = os.path.join(nwTempBuild, "4_LoremIpsum.nwd")
- refFile = os.path.join(refDir, "build", "4_LoremIpsum.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- assert nwBuild._saveDocument(nwBuild.FMT_HTM)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.htm")
- testFile = os.path.join(nwTempBuild, "4_LoremIpsum.htm")
- refFile = os.path.join(refDir, "build", "4_LoremIpsum.htm")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Check the JSON files too at this stage
- assert nwBuild._saveDocument(nwBuild.FMT_JSON_H)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
- testFile = os.path.join(nwTempBuild, "4H_LoremIpsum.json")
- refFile = os.path.join(refDir, "build", "4H_LoremIpsum.json")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [8])
-
- assert nwBuild._saveDocument(nwBuild.FMT_JSON_M)
- projFile = os.path.join(nwLipsum, "Lorem Ipsum.json")
- testFile = os.path.join(nwTempBuild, "4M_LoremIpsum.json")
- refFile = os.path.join(refDir, "build", "4M_LoremIpsum.json")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [8])
-
- # Save other file types handled by Qt
- # We assume the export itself by the Qt library works, so we just
- # check that novelWriter successfully writes the files.
- assert nwBuild._saveDocument(nwBuild.FMT_ODT)
- assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.odt"))
-
- if not nwGUI.mainConf.osDarwin:
- assert nwBuild._saveDocument(nwBuild.FMT_PDF)
- assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.pdf"))
-
- assert nwBuild._saveDocument(nwBuild.FMT_MD)
- assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.md"))
-
- assert nwBuild._saveDocument(nwBuild.FMT_TXT)
- assert os.path.isfile(os.path.join(nwLipsum, "Lorem Ipsum.txt"))
-
- # Close the build tool
- htmlText = nwBuild.htmlText
- htmlStyle = nwBuild.htmlStyle
- nwdText = nwBuild.nwdText
- buildTime = nwBuild.buildTime
- nwBuild._doClose()
-
- # Re-open build dialog from cahce
- nwGUI.mainMenu.aBuildProject.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiBuildNovel") is not None, timeout=1000)
-
- nwBuild = getGuiItem("GuiBuildNovel")
- assert isinstance(nwBuild, GuiBuildNovel)
-
- assert nwBuild.viewCachedDoc()
- assert nwBuild.htmlText == htmlText
- assert nwBuild.htmlStyle == htmlStyle
- assert nwBuild.nwdText == nwdText
- assert nwBuild.buildTime == buildTime
-
- nwBuild._doClose()
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testMergeSplitTools(qtbot, monkeypatch, yesToAll, nwTempGUI, nwLipsum, refDir, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- 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)
-
- monkeypatch.setattr(GuiDocMerge, "exec_", lambda *args: None)
- nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000)
-
- nwMerge = getGuiItem("GuiDocMerge")
- assert isinstance(nwMerge, GuiDocMerge)
- nwMerge.show()
- qtbot.wait(stepDelay)
-
- nwMerge._doMerge()
- qtbot.wait(stepDelay)
-
- assert nwGUI.theProject.projTree["73475cb40a568"] is not None
-
- projFile = os.path.join(nwLipsum, "content", "73475cb40a568.nwd")
- testFile = os.path.join(nwTempGUI, "4_73475cb40a568.nwd")
- refFile = os.path.join(refDir, "gui", "4_73475cb40a568.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Split By Chapter
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
-
- monkeypatch.setattr(GuiDocSplit, "exec_", lambda *args: None)
- nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
-
- nwSplit = getGuiItem("GuiDocSplit")
- assert isinstance(nwSplit, GuiDocSplit)
- nwSplit.show()
- 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 = os.path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
- testFile = os.path.join(nwTempGUI, "4_71ee45a3c0db9.nwd")
- refFile = os.path.join(refDir, "gui", "4_73475cb40a568.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [1, 2, 3])
-
- # Split By Scene
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
-
- nwSplit = getGuiItem("GuiDocSplit")
- assert isinstance(nwSplit, GuiDocSplit)
- 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 = os.path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
- testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
- refFile = os.path.join(refDir, "gui", "5_25fc0e7096fc6.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "content", "31489056e0916.nwd")
- testFile = os.path.join(nwTempGUI, "5_31489056e0916.nwd")
- refFile = os.path.join(refDir, "gui", "5_31489056e0916.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "content", "98010bd9270f9.nwd")
- testFile = os.path.join(nwTempGUI, "5_98010bd9270f9.nwd")
- refFile = os.path.join(refDir, "gui", "5_98010bd9270f9.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # Split By Section
- assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
-
- nwSplit = getGuiItem("GuiDocSplit")
- assert isinstance(nwSplit, GuiDocSplit)
- 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 = os.path.join(nwLipsum, "content", "1a6562590ef19.nwd")
- testFile = os.path.join(nwTempGUI, "5_25fc0e7096fc6.nwd")
- refFile = os.path.join(refDir, "gui", "5_25fc0e7096fc6.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [1, 2, 3])
-
- projFile = os.path.join(nwLipsum, "content", "031b4af5197ec.nwd")
- testFile = os.path.join(nwTempGUI, "5_031b4af5197ec.nwd")
- refFile = os.path.join(refDir, "gui", "5_031b4af5197ec.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
- testFile = os.path.join(nwTempGUI, "5_41cfc0d1f2d12.nwd")
- refFile = os.path.join(refDir, "gui", "5_41cfc0d1f2d12.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
- testFile = os.path.join(nwTempGUI, "5_2858dcd1057d3.nwd")
- refFile = os.path.join(refDir, "gui", "5_2858dcd1057d3.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(nwLipsum, "content", "2fca346db6561.nwd")
- testFile = os.path.join(nwTempGUI, "5_2fca346db6561.nwd")
- refFile = os.path.join(refDir, "gui", "5_2fca346db6561.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
-@pytest.mark.gui
-def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir):
-
- if sys.platform.startswith("darwin"):
- # Disable for macOS because the test segfaults on QWizard.show()
- return
-
- from PyQt5.QtWidgets import QWizard
- from nw.gui.projwizard import (
- ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage,
- ProjWizardCustomPage, ProjWizardFinalPage
- )
-
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- ##
- # Test New Project Function
- ##
-
- # New with a project open should cause an error
- assert nwGUI.openProject(nwMinimal)
- assert not nwGUI.newProject()
-
- # Close project, but call with invalid path
- assert nwGUI.closeProject()
- monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None)
- assert not nwGUI.newProject()
-
- # Now, with an empty dictionary
- monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {})
- assert not nwGUI.newProject()
-
- # Now, with a non-empty folder
- monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal})
- assert not nwGUI.newProject()
-
- monkeypatch.undo()
-
- ##
- # Test the Wizard
- ##
-
- monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None)
- nwGUI.mainConf.lastPath = " "
-
- nwGUI.closeProject()
- nwGUI.showNewProjectDialog()
- qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000)
-
- nwWiz = getGuiItem("GuiProjectWizard")
- assert isinstance(nwWiz, GuiProjectWizard)
- nwWiz.show()
- qtbot.wait(stepDelay)
-
- for wStep in range(4):
- # This does not actually create the project, it just generates the
- # dictionary that defines it.
-
- # Intro Page
- introPage = nwWiz.currentPage()
- assert isinstance(introPage, ProjWizardIntroPage)
- assert not nwWiz.button(QWizard.NextButton).isEnabled()
-
- qtbot.wait(stepDelay)
- for c in ("Test Minimal %d" % wStep):
- qtbot.keyClick(introPage.projName, c, delay=typeDelay)
-
- qtbot.wait(stepDelay)
- for c in "Minimal Novel":
- qtbot.keyClick(introPage.projTitle, c, delay=typeDelay)
-
- qtbot.wait(stepDelay)
- for c in "Jane Doe":
- qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay)
-
- # 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()
-
- if wStep == 0:
- # Check invalid path first, the first time we reach here
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "")
- qtbot.wait(stepDelay)
- qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
- assert storagePage.projPath.text() == ""
-
- # Then, we always return nwMinimal as path
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal)
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
- projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep)
- assert storagePage.projPath.text() == projPath
-
- # 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.popCustom.setChecked(True)
- elif wStep == 3:
- popPage.popSample.setChecked(True)
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
-
- # Custom Page
- if wStep == 1 or wStep == 2:
- 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)
-
- if wStep == 2:
- customPage.numChapters.setValue(0)
- customPage.numScenes.setValue(10)
- customPage.chFolders.setChecked(False)
-
- 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() # But we don't click it
-
- # Check Data
- projData = nwGUI._assembleProjectWizardData(nwWiz)
- assert projData["projName"] == "Test Minimal %d" % wStep
- assert projData["projTitle"] == "Minimal Novel"
- assert projData["projAuthors"] == "Jane Doe"
- assert projData["projPath"] == projPath
- assert projData["popMinimal"] == (wStep == 0)
- assert projData["popCustom"] == (wStep == 1 or wStep == 2)
- assert projData["popSample"] == (wStep == 3)
- if wStep == 1 or wStep == 2:
- assert projData["addRoots"] == [
- nwItemClass.PLOT,
- nwItemClass.CHARACTER,
- nwItemClass.WORLD,
- nwItemClass.TIMELINE,
- nwItemClass.OBJECT,
- nwItemClass.ENTITY,
- ]
- if wStep == 1:
- assert projData["numChapters"] == 5
- assert projData["numScenes"] == 5
- assert projData["chFolders"]
- else:
- assert projData["numChapters"] == 0
- assert projData["numScenes"] == 10
- assert not projData["chFolders"]
- else:
- assert projData["addRoots"] == []
- assert projData["numChapters"] == 0
- assert projData["numScenes"] == 0
- assert not projData["chFolders"]
-
- # Restart the wizard for next iteration
- nwWiz.restart()
-
- nwWiz.reject()
- nwWiz.close()
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwMinimal)
- assert nwGUI.closeProject()
-
- qtbot.wait(stepDelay)
- monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
- monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
- nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
-
- nwLoad = getGuiItem("GuiProjectLoad")
- assert isinstance(nwLoad, GuiProjectLoad)
- nwLoad.show()
-
- qtbot.wait(stepDelay)
- recentCount = nwLoad.listBox.topLevelItemCount()
- assert recentCount > 0
-
- qtbot.wait(stepDelay)
- selItem = nwLoad.listBox.topLevelItem(0)
- selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
- assert isinstance(selItem, QTreeWidgetItem)
-
- qtbot.wait(stepDelay)
- nwLoad.selPath.setText("")
- nwLoad.listBox.setCurrentItem(selItem)
- nwLoad._doSelectRecent()
- assert nwLoad.selPath.text() == selPath
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
- assert nwLoad.openPath == selPath
- assert nwLoad.openState == nwLoad.OPEN_STATE
-
- # Just create a new project load from scratch for the rest of the test
- del nwLoad
-
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
-
- qtbot.wait(stepDelay)
- nwLoad = getGuiItem("GuiProjectLoad")
- assert isinstance(nwLoad, GuiProjectLoad)
- nwLoad.show()
-
- qtbot.wait(stepDelay)
- qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
- assert nwLoad.openPath is None
- assert nwLoad.openState == nwLoad.NONE_STATE
-
- qtbot.wait(stepDelay)
- nwLoad.show()
- qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
- assert nwLoad.openPath is None
- assert nwLoad.openState == nwLoad.NEW_STATE
-
- qtbot.wait(stepDelay)
- nwLoad.show()
- nwLoad._keyPressDelete()
- assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
-
- getFile = os.path.join(nwMinimal, "nwProject.nwx")
- monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None))
- qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
- assert nwLoad.openPath == nwMinimal
- assert nwLoad.openState == nwLoad.OPEN_STATE
- # qtbot.stopForInteraction()
-
- nwLoad.close()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, refDir, tmpConf):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwMinimal)
-
- monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None)
- monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted)
- nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
- qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
-
- nwPrefs = getGuiItem("GuiPreferences")
- assert isinstance(nwPrefs, GuiPreferences)
- nwPrefs.show()
-
- # Override Config
- tmpConf.confPath = nwMinimal
- tmpConf.showGUI = False
- nwGUI.mainConf = tmpConf
- nwPrefs.mainConf = tmpConf
- nwPrefs.tabGeneral.mainConf = tmpConf
- nwPrefs.tabProjects.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)
-
- 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.hideVScroll.isChecked()
- qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton)
- assert tabGeneral.hideVScroll.isChecked()
-
- qtbot.wait(keyDelay)
- assert not tabGeneral.hideHScroll.isChecked()
- qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton)
- assert tabGeneral.hideHScroll.isChecked()
-
- # Check font button
- monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True))
- qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton)
-
- qtbot.wait(keyDelay)
- tabGeneral.guiFontSize.setValue(12)
-
- # Projects Settings
- qtbot.wait(keyDelay)
- tabProjects = nwPrefs.tabProjects
- nwPrefs._tabBox.setCurrentWidget(tabProjects)
- tabProjects.backupPath = "no/where"
-
- qtbot.wait(keyDelay)
- assert not tabProjects.backupOnClose.isChecked()
- qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton)
- assert tabProjects.backupOnClose.isChecked()
-
- # Check Browse button
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "")
- assert not tabProjects._backupFolder()
- monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir")
- qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton)
-
- qtbot.wait(keyDelay)
- tabProjects.autoSaveDoc.setValue(20)
- tabProjects.autoSaveProj.setValue(40)
-
- # Text Layout Settings
- qtbot.wait(keyDelay)
- tabLayout = nwPrefs.tabLayout
- nwPrefs._tabBox.setCurrentWidget(tabLayout)
-
- qtbot.wait(keyDelay)
- qtbot.mouseClick(tabLayout.fontButton, Qt.LeftButton)
-
- 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()
-
- qtbot.wait(keyDelay)
- assert tabLayout.scrollPastEnd.isChecked()
- qtbot.mouseClick(tabLayout.scrollPastEnd, Qt.LeftButton)
- assert not tabLayout.scrollPastEnd.isChecked()
-
- qtbot.wait(keyDelay)
- assert not tabLayout.autoScroll.isChecked()
- qtbot.mouseClick(tabLayout.autoScroll, Qt.LeftButton)
- assert tabLayout.autoScroll.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()
-
- monkeypatch.setattr(QuotesDialog, "selectedQuote", "'")
- monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted)
- qtbot.mouseClick(tabAutoRep.btnDoubleStyleC, Qt.LeftButton)
-
- # Save and Check Config
- qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
-
- assert tmpConf.confChanged
- tmpConf.lastPath = ""
-
- assert nwGUI.mainConf.saveConfig()
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
-
- refConf = os.path.join(refDir, "novelwriter_prefs.conf")
- projConf = os.path.join(nwGUI.mainConf.confPath, "novelwriter.conf")
- testConf = os.path.join(tmpDir, "novelwriter_prefs.conf")
- copyfile(projConf, testConf)
- ignoreLines = [
- 2, # Timestamp
- 9, # Release Notes
- 12, 13, 14, 15, 16, 17, 18, # Window sizes
- 7, 28, # Fonts (depends on system default)
- ]
- assert cmpFiles(testConf, refConf, ignoreLines)
-
-@pytest.mark.gui
-def testQuotesDialog(qtbot, yesToAll, nwMinimal, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
+def testGuiDialogs_Quotes(qtbot, monkeypatch, nwGUI, nwMinimal):
+ """Test the quote symbols dialog.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
nwQuot = QuotesDialog(nwGUI)
nwQuot.show()
@@ -1242,20 +39,16 @@ def testQuotesDialog(qtbot, yesToAll, nwMinimal, tmpDir):
# qtbot.stopForInteraction()
nwQuot._doReject()
nwQuot.close()
- nwGUI.closeMain()
- nwGUI.close()
+
+# END Test testDialogs_Quotes
@pytest.mark.gui
-def testDialogsOpenClose(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
+def testGuiDialogs_Other(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir):
+ """Various other dialog tests.
+ """
monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: tmpDir)
assert nwGUI.selectProjectPath() == tmpDir
# qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
+
+# END Test testGuiDialogs_Other
diff --git a/tests/test_gui_doceditor.py b/tests/test_gui_doceditor.py
new file mode 100644
index 00000000..9ef62951
--- /dev/null
+++ b/tests/test_gui_doceditor.py
@@ -0,0 +1,506 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Editor Class Tester
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.constants import nwItemType, nwDocAction
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
+ """Test the document editor.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Create new, save, close project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj})
+ assert nwGUI.saveProject()
+ assert nwGUI.closeProject()
+
+ assert len(nwGUI.theProject.projTree) == 0
+ assert len(nwGUI.theProject.projTree._treeOrder) == 0
+ assert len(nwGUI.theProject.projTree._treeRoots) == 0
+ assert nwGUI.theProject.projTree.trashRoot() is None
+ assert nwGUI.theProject.projPath is None
+ assert nwGUI.theProject.projMeta is None
+ assert nwGUI.theProject.projFile == "nwProject.nwx"
+ assert nwGUI.theProject.projName == ""
+ assert nwGUI.theProject.bookTitle == ""
+ assert len(nwGUI.theProject.bookAuthors) == 0
+ assert not nwGUI.theProject.spellCheck
+
+ # Check the files
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiEditor_Main_Initial_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiEditor_Main_Initial_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+ qtbot.wait(stepDelay)
+
+ # qtbot.stopForInteraction()
+
+ # Re-open project
+ assert nwGUI.openProject(fncProj)
+ qtbot.wait(stepDelay)
+
+ # Check that we loaded the data
+ assert len(nwGUI.theProject.projTree) == 8
+ assert len(nwGUI.theProject.projTree._treeOrder) == 8
+ assert len(nwGUI.theProject.projTree._treeRoots) == 4
+ assert nwGUI.theProject.projTree.trashRoot() is None
+ assert nwGUI.theProject.projPath == fncProj
+ assert nwGUI.theProject.projMeta == os.path.join(fncProj, "meta")
+ assert nwGUI.theProject.projFile == "nwProject.nwx"
+ assert nwGUI.theProject.projName == "New Project"
+ assert nwGUI.theProject.bookTitle == ""
+ assert len(nwGUI.theProject.bookAuthors) == 0
+ assert not nwGUI.theProject.spellCheck
+
+ # Check that tree items have been created
+ assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None
+ assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None
+ assert nwGUI.treeView._getTreeItem("31489056e0916") is not None
+ assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None
+ assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
+ assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None
+ assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
+ assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
+
+ nwGUI.mainMenu.aSpellCheck.setChecked(True)
+ assert nwGUI.mainMenu._toggleSpellCheck()
+
+ # Change some settings
+ nwGUI.mainConf.hideHScroll = True
+ nwGUI.mainConf.hideVScroll = True
+ nwGUI.mainConf.scrollPastEnd = True
+ nwGUI.mainConf.autoScrollPos = 80
+ nwGUI.mainConf.autoScroll = True
+
+ # Add a Character File
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.setFocus(2)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Jane Doe":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file about Jane.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Add a Plot File
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.setFocus(2)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Main Plot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file detailing the main plot.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Add a World File
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
+ nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ assert nwGUI.openSelectedItem()
+
+ # Add Some Text
+ nwGUI.docEditor.replaceText("Hello World!")
+ assert nwGUI.docEditor.getText() == "Hello World!"
+ nwGUI.docEditor.replaceText("")
+
+ # Type something into the document
+ nwGUI.setFocus(2)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Main Location":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@tag: Home":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "This is a file describing Jane's home.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ # Trigger autosaves before making more changes
+ nwGUI._autoSaveDocument()
+ nwGUI._autoSaveProject()
+
+ # Select the 'New Scene' file
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
+ nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True)
+ nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
+ assert nwGUI.openSelectedItem()
+
+ # Type something into the document
+ nwGUI.setFocus(2)
+ qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
+ for c in "# Novel":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "## Chapter":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "@pov: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@plot: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "### Scene":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "% How about a comment?":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@pov: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@plot: MainPlot":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ for c in "@location: Home":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "#### Some Section":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "@char: Jane":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "This is a paragraph of dummy text.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in (
+ "This is another paragraph of much longer dummy text. "
+ "It is in fact 1 very very DUMB dummy text! "
+ ):
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "Isn't that nice? ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "Ellipsis? Not a problem either ... ":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ for c in "How about three hyphens - -":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
+ for c in "- for long dash? It works too.":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "\"Full line double quoted text.\"":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ for c in "'Full line single quoted text.'":
+ qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+ qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
+
+ qtbot.wait(stepDelay)
+ nwGUI.docEditor.wCounter.run()
+ qtbot.wait(stepDelay)
+
+ # Save the document
+ assert nwGUI.docEditor.docChanged
+ assert nwGUI.saveDocument()
+ assert not nwGUI.docEditor.docChanged
+ qtbot.wait(stepDelay)
+ nwGUI.rebuildIndex()
+ qtbot.wait(stepDelay)
+
+ # Open and view the edited document
+ nwGUI.setFocus(3)
+ assert nwGUI.openDocument("0e17daca5f3e1")
+ assert nwGUI.viewDocument("0e17daca5f3e1")
+ qtbot.wait(stepDelay)
+ assert nwGUI.saveProject()
+ assert nwGUI.closeDocViewer()
+ qtbot.wait(stepDelay)
+
+ # Check a Quick Create and Delete
+ assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
+ newHandle = nwGUI.treeView.getSelectedHandle()
+ assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
+ assert nwGUI.treeView.deleteItem()
+ assert nwGUI.treeView.setSelectedHandle(newHandle)
+ assert nwGUI.treeView.deleteItem()
+ assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
+ assert nwGUI.saveProject()
+
+ # Check the files
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+
+ projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "1a6562590ef19.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_1a6562590ef19.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_0e17daca5f3e1.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(fncProj, "content", "41cfc0d1f2d12.nwd")
+ testFile = os.path.join(outDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
+ compFile = os.path.join(refDir, "guiEditor_Main_Final_41cfc0d1f2d12.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_Main
+
+@pytest.mark.gui
+def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, nwLipsum):
+ """Test the document editor search functionality.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+ assert nwGUI.openDocument("4c4f28287af27")
+ origText = nwGUI.docEditor.getText()
+ qtbot.wait(stepDelay)
+
+ # Select the Word "est"
+ assert nwGUI.docEditor.setCursorPosition(618)
+ nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == "est"
+
+ # Activate Search
+ nwGUI.mainMenu.aFind.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.isVisible()
+ assert nwGUI.docEditor.docSearch.getSearchText() == "est"
+
+ # Find Next by Enter
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
+ qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=keyDelay)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 1272) < 3
+
+ # Find Next by Button
+ qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 1486) < 3
+
+ # Activate Loop Search
+ nwGUI.docEditor.docSearch.toggleLoop.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleLoop.isChecked()
+ assert nwGUI.docEditor.docSearch.doLoop
+
+ # Find Next by Menu Search > Find Next
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
+
+ # Close Search
+ nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
+ assert not nwGUI.docEditor.docSearch.isVisible()
+ assert nwGUI.docEditor.setCursorPosition(15)
+
+ # Toggle Search Again with Header Button
+ qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=keyDelay)
+ assert nwGUI.docEditor.docSearch.setSearchText("")
+ assert nwGUI.docEditor.docSearch.isVisible()
+
+ # Enable RegEx Search
+ nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleRegEx.isChecked()
+ assert nwGUI.docEditor.docSearch.isRegEx
+
+ # Set Invalid RegEx
+ assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus[")
+ qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
+ assert nwGUI.docEditor.getCursorPosition() < 3 # No result
+
+ # Set Valid RegEx
+ assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus")
+ qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 196) < 3
+
+ # Find Next and then Prev
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 297) < 3
+ nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 196) < 3
+
+ # Make RegEx Case Sensitive
+ nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleCase.isChecked()
+ assert nwGUI.docEditor.docSearch.isCaseSense
+
+ # Find Next (One Result)
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 599) < 3
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 599) < 3
+
+ # Trigger Replace
+ nwGUI.mainMenu.aReplace.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.setReplaceText("foo")
+
+ # Disable RegEx Case Sensitive
+ nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger)
+ assert not nwGUI.docEditor.docSearch.toggleCase.isChecked()
+ assert not nwGUI.docEditor.docSearch.isCaseSense
+
+ # Toggle Replace Preserve Case
+ nwGUI.docEditor.docSearch.toggleMatchCap.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleMatchCap.isChecked()
+ assert nwGUI.docEditor.docSearch.doMatchCap
+
+ # Replace "Sus" with "Foo" via Menu
+ nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[596:607] == "Foopendisse"
+
+ # Find Next to Loop File
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+
+ # Replace "sus" with "foo" via Replace Button
+ qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=keyDelay)
+ assert nwGUI.docEditor.getText()[193:201] == "foocipit"
+
+ # Revert Last Two Replaces
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO)
+ assert nwGUI.docEditor.docAction(nwDocAction.UNDO)
+ assert nwGUI.docEditor.getText() == origText
+
+ # Disable RegEx Search
+ nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger)
+ assert not nwGUI.docEditor.docSearch.toggleRegEx.isChecked()
+ assert not nwGUI.docEditor.docSearch.isRegEx
+
+ # Close Search and Select "est" Again
+ nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
+ assert nwGUI.docEditor.setCursorPosition(618)
+ nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == "est"
+
+ # Activate Search Again
+ nwGUI.mainMenu.aFind.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.isVisible()
+ assert nwGUI.docEditor.docSearch.getSearchText() == "est"
+
+ # Enable Full Word Search
+ nwGUI.docEditor.docSearch.toggleWord.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleWord.isChecked()
+ assert nwGUI.docEditor.docSearch.isWholeWord
+
+ # Only One Match
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
+
+ # Enable Next Doc Search
+ nwGUI.docEditor.docSearch.toggleProject.activate(QAction.Trigger)
+ assert nwGUI.docEditor.docSearch.toggleProject.isChecked()
+ assert nwGUI.docEditor.docSearch.doNextFile
+
+ # Next Match
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert nwGUI.docEditor.theHandle == "2426c6f0ca922" # Next document
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
+ nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
+ assert abs(nwGUI.docEditor.getCursorPosition() - 1127) < 3
+
+ # Toggle Replace
+ nwGUI.docEditor._beginReplace()
+
+ # MonkeyPatch the focus cycle. We can't really test this very well, other than
+ # check that the tabs aren't captured when the main editor has focus
+ monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: True)
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False)
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False)
+ assert not nwGUI.docEditor.focusNextPrevChild(True)
+
+ monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: False)
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False)
+ assert nwGUI.docEditor.focusNextPrevChild(True)
+
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False)
+ monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: True)
+ assert nwGUI.docEditor.focusNextPrevChild(True)
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiEditor_Search
diff --git a/tests/test_gui_docviewer.py b/tests/test_gui_docviewer.py
new file mode 100644
index 00000000..3af74afa
--- /dev/null
+++ b/tests/test_gui_docviewer.py
@@ -0,0 +1,165 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Viewer Class Tester
+"""
+
+import pytest
+
+from PyQt5.QtCore import Qt, QUrl
+from PyQt5.QtGui import QTextCursor
+from PyQt5.QtWidgets import qApp, QAction, QMessageBox
+
+from nw.constants import nwDocAction
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
+ """Test the document viewer.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Open project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+
+ # Rebuild the index as it isn't automatically copied
+ assert nwGUI.theIndex.tagIndex == {}
+ assert nwGUI.theIndex.refIndex == {}
+ nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
+ assert nwGUI.theIndex.tagIndex != {}
+ assert nwGUI.theIndex.refIndex != {}
+
+ # Select a document in the project tree
+ assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
+
+ # Middle-click the selected item
+ theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
+ theRect = nwGUI.treeView.visualItemRect(theItem)
+ qtbot.mouseClick(nwGUI.treeView.viewport(), Qt.MidButton, pos=theRect.center())
+ assert nwGUI.docViewer.theHandle == "88243afbe5ed8"
+
+ # Reload the text
+ origText = nwGUI.docViewer.toPlainText()
+ nwGUI.docViewer.setPlainText("Oops, all gone!")
+ nwGUI.docViewer.docHeader._refreshDocument()
+ assert nwGUI.docViewer.toPlainText() == origText
+
+ # Cursor line
+ assert not nwGUI.docViewer.setCursorLine("not a number")
+ assert nwGUI.docViewer.setCursorLine(3)
+ theCursor = nwGUI.docViewer.textCursor()
+ assert theCursor.position() == 40
+
+ # Cursor position
+ assert not nwGUI.docViewer.setCursorPosition("not a number")
+ assert nwGUI.docViewer.setCursorPosition(100)
+
+ # Select word
+ nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
+
+ qClip = qApp.clipboard()
+ qClip.clear()
+
+ # Cut
+ assert nwGUI.docViewer.docAction(nwDocAction.CUT)
+ assert qClip.text() == "laoreet"
+ qClip.clear()
+
+ # Copy
+ assert nwGUI.docViewer.docAction(nwDocAction.COPY)
+ assert qClip.text() == "laoreet"
+ qClip.clear()
+
+ # Select Paragraph
+ assert nwGUI.docViewer.docAction(nwDocAction.SEL_PARA)
+ theCursor = nwGUI.docViewer.textCursor()
+ assert theCursor.selectedText() == (
+ "Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, "
+ "eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et "
+ "mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. "
+ "Etiam finibus nisi vel mi molestie consectetur."
+ )
+
+ # Select All
+ assert nwGUI.docViewer.docAction(nwDocAction.SEL_ALL)
+ theCursor = nwGUI.docViewer.textCursor()
+ assert len(theCursor.selectedText()) == 3061
+
+ # Other actions
+ assert not nwGUI.docViewer.docAction(nwDocAction.NO_ACTION)
+
+ # Close document
+ nwGUI.docViewer.docHeader._closeDocument()
+ assert nwGUI.docViewer.theHandle is None
+
+ # Action on no document
+ assert not nwGUI.docViewer.docAction(nwDocAction.COPY)
+
+ # Open again via menu
+ assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
+ nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
+
+ # Select "Bod" link
+ assert nwGUI.docViewer.setCursorPosition(27)
+ nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
+ theRect = nwGUI.docViewer.cursorRect()
+ # qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.LeftButton, pos=theRect.center(), delay=100)
+ nwGUI.docViewer._linkClicked(QUrl("#char=Bod"))
+ assert nwGUI.docViewer.theHandle == "4c4f28287af27"
+
+ # Click mouse nav buttons
+ qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.BackButton, pos=theRect.center(), delay=100)
+ assert nwGUI.docViewer.theHandle == "88243afbe5ed8"
+ qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.ForwardButton, pos=theRect.center(), delay=100)
+ assert nwGUI.docViewer.theHandle == "4c4f28287af27"
+
+ # Scroll bar default on empty document
+ nwGUI.docViewer.clear()
+ assert nwGUI.docViewer.getScrollPosition() == 0
+ nwGUI.docViewer.reloadText()
+
+ # Change document title
+ nwItem = nwGUI.theProject.projTree["4c4f28287af27"]
+ nwItem.setName("Test Title")
+ assert nwItem.itemName == "Test Title"
+ nwGUI.docViewer.updateDocInfo("4c4f28287af27")
+ assert nwGUI.docViewer.docHeader.theTitle.text() == "Characters › Test Title"
+
+ # Ttile without full path
+ nwGUI.mainConf.showFullPath = False
+ nwGUI.docViewer.updateDocInfo("4c4f28287af27")
+ assert nwGUI.docViewer.docHeader.theTitle.text() == "Test Title"
+ nwGUI.mainConf.showFullPath = True
+
+ # Document footer show/hide references
+ viewState = nwGUI.viewMeta.isVisible()
+ nwGUI.docViewer.docFooter._doShowHide()
+ assert nwGUI.viewMeta.isVisible() is not viewState
+ nwGUI.docViewer.docFooter._doShowHide()
+ assert nwGUI.viewMeta.isVisible() is viewState
+
+ # Document footer sticky
+ viewState = nwGUI.docViewer.stickyRef
+ nwGUI.docViewer.docFooter._doToggleSticky(not viewState)
+ assert nwGUI.docViewer.stickyRef is not viewState
+ nwGUI.docViewer.docFooter._doToggleSticky(viewState)
+ assert nwGUI.docViewer.stickyRef is viewState
+
+ # Document footer show/hide synopsis
+ assert nwGUI.viewDocument("f96ec11c6a3da")
+ assert len(nwGUI.docViewer.toPlainText()) == 4315
+ nwGUI.docViewer.docFooter._doToggleSynopsis(False)
+ assert len(nwGUI.docViewer.toPlainText()) == 4099
+
+ # Document footer show/hide comments
+ assert nwGUI.viewDocument("846352075de7d")
+ assert len(nwGUI.docViewer.toPlainText()) == 675
+ nwGUI.docViewer.docFooter._doToggleComments(False)
+ assert len(nwGUI.docViewer.toPlainText()) == 635
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiViewer_Main
diff --git a/tests/test_gui_itemeditor.py b/tests/test_gui_itemeditor.py
new file mode 100644
index 00000000..ae27e3c0
--- /dev/null
+++ b/tests/test_gui_itemeditor.py
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Item Editor Dialog Class Tester
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles, getGuiItem
+
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.gui import GuiItemEditor
+from nw.constants import nwItemLayout
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDir):
+ """Test the full item editor dialog.
+ """
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiItemEditor_Dialog_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiItemEditor_Dialog_nwProject.nwx")
+
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Create new, save, open project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj})
+ assert nwGUI.openDocument("0e17daca5f3e1")
+ assert nwGUI.treeView.setSelectedHandle("0e17daca5f3e1", doScroll=True)
+
+ monkeypatch.setattr(GuiItemEditor, "exec_", lambda *args: None)
+ nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
+
+ itemEdit = getGuiItem("GuiItemEditor")
+ assert isinstance(itemEdit, GuiItemEditor)
+ itemEdit.show()
+
+ 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=typeDelay)
+ 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()
+
+ nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
+
+ itemEdit = getGuiItem("GuiItemEditor")
+ assert isinstance(itemEdit, GuiItemEditor)
+ itemEdit.show()
+
+ 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
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiItemEditor_Dialog
diff --git a/tests/test_gui_main.py b/tests/test_gui_main.py
deleted file mode 100644
index b56c525b..00000000
--- a/tests/test_gui_main.py
+++ /dev/null
@@ -1,1526 +0,0 @@
-# -*- coding: utf-8 -*-
-"""novelWriter Main GUI Class Tester
-"""
-
-import nw
-import pytest
-import os
-
-from shutil import copyfile
-from tools import cmpFiles
-
-from PyQt5.QtCore import Qt, QUrl, QPoint, QItemSelectionModel
-from PyQt5.QtGui import QTextCursor, QColor, QPixmap, QIcon, QTextBlock
-from PyQt5.QtWidgets import (
- qApp, QAction, QTreeWidgetItem, QStyle, QFileDialog, QMessageBox
-)
-
-from nw.constants import (
- nwItemType, nwItemClass, nwUnicode, nwOutline, nwDocAction, nwDocInsert,
- nwKeyWords
-)
-
-keyDelay = 2
-typeDelay = 1
-stepDelay = 20
-
-@pytest.mark.gui
-def testDocEditor(qtbot, yesToAll, fncDir, nwTempGUI, refDir, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- 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": fncDir})
- assert nwGUI.saveProject()
- assert nwGUI.closeProject()
-
- assert len(nwGUI.theProject.projTree) == 0
- assert len(nwGUI.theProject.projTree._treeOrder) == 0
- assert len(nwGUI.theProject.projTree._treeRoots) == 0
- assert nwGUI.theProject.projTree.trashRoot() is None
- assert nwGUI.theProject.projPath is None
- assert nwGUI.theProject.projMeta is None
- assert nwGUI.theProject.projFile == "nwProject.nwx"
- assert nwGUI.theProject.projName == ""
- assert nwGUI.theProject.bookTitle == ""
- assert len(nwGUI.theProject.bookAuthors) == 0
- assert not nwGUI.theProject.spellCheck
-
- # Check the files
- projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(nwTempGUI, "0_nwProject.nwx")
- refFile = os.path.join(refDir, "gui", "0_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
- qtbot.wait(stepDelay)
-
- # qtbot.stopForInteraction()
-
- # Re-open project
- assert nwGUI.openProject(fncDir)
- qtbot.wait(stepDelay)
-
- # Check that we loaded the data
- assert len(nwGUI.theProject.projTree) == 8
- assert len(nwGUI.theProject.projTree._treeOrder) == 8
- assert len(nwGUI.theProject.projTree._treeRoots) == 4
- assert nwGUI.theProject.projTree.trashRoot() is None
- assert nwGUI.theProject.projPath == fncDir
- assert nwGUI.theProject.projMeta == os.path.join(fncDir, "meta")
- assert nwGUI.theProject.projFile == "nwProject.nwx"
- assert nwGUI.theProject.projName == "New Project"
- assert nwGUI.theProject.bookTitle == ""
- assert len(nwGUI.theProject.bookAuthors) == 0
- assert not nwGUI.theProject.spellCheck
-
- # Check that tree items have been created
- assert nwGUI.treeView._getTreeItem("73475cb40a568") is not None
- assert nwGUI.treeView._getTreeItem("25fc0e7096fc6") is not None
- assert nwGUI.treeView._getTreeItem("31489056e0916") is not None
- assert nwGUI.treeView._getTreeItem("98010bd9270f9") is not None
- assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
- assert nwGUI.treeView._getTreeItem("44cb730c42048") is not None
- assert nwGUI.treeView._getTreeItem("71ee45a3c0db9") is not None
- assert nwGUI.treeView._getTreeItem("811786ad1ae74") is not None
-
- nwGUI.mainMenu.aSpellCheck.setChecked(True)
- assert nwGUI.mainMenu._toggleSpellCheck()
-
- # Change some settings
- nwGUI.mainConf.hideHScroll = True
- nwGUI.mainConf.hideVScroll = True
- nwGUI.mainConf.scrollPastEnd = True
- nwGUI.mainConf.autoScrollPos = 80
- nwGUI.mainConf.autoScroll = True
-
- # Add a Character File
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("71ee45a3c0db9").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.setFocus(2)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Jane Doe":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file about Jane.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Add a Plot File
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("44cb730c42048").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.setFocus(2)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Main Plot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file detailing the main plot.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Add a World File
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("811786ad1ae74").setSelected(True)
- nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- assert nwGUI.openSelectedItem()
-
- # Add Some Text
- nwGUI.docEditor.replaceText("Hello World!")
- assert nwGUI.docEditor.getText() == "Hello World!"
- nwGUI.docEditor.replaceText("")
-
- # Type something into the document
- nwGUI.setFocus(2)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Main Location":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@tag: Home":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "This is a file describing Jane's home.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- # Trigger autosaves before making more changes
- nwGUI._autoSaveDocument()
- nwGUI._autoSaveProject()
-
- # Select the 'New Scene' file
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("73475cb40a568").setExpanded(True)
- nwGUI.treeView._getTreeItem("31489056e0916").setExpanded(True)
- nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
- assert nwGUI.openSelectedItem()
-
- # Type something into the document
- nwGUI.setFocus(2)
- qtbot.keyClick(nwGUI.docEditor, "a", modifier=Qt.ControlModifier, delay=keyDelay)
- for c in "# Novel":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "## Chapter":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "@pov: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@plot: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "### Scene":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "% How about a comment?":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@pov: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@plot: MainPlot":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- for c in "@location: Home":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "#### Some Section":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "@char: Jane":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "This is a paragraph of dummy text.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in (
- "This is another paragraph of much longer dummy text. "
- "It is in fact very very dumb dummy text! "
- ):
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "We can also try replacing \"quotes\", even single 'quotes' are replaced. ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "Isn't that nice? ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "We can hyphen-ate, make dashes -- and even longer dashes --- if we want. ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "Ellipsis? Not a problem either ... ":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- for c in "How about three hyphens - -":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Left, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Backspace, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Right, delay=keyDelay)
- for c in "- for long dash? It works too.":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "\"Full line double quoted text.\"":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- for c in "'Full line single quoted text.'":
- qtbot.keyClick(nwGUI.docEditor, c, delay=typeDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
- qtbot.keyClick(nwGUI.docEditor, Qt.Key_Return, delay=keyDelay)
-
- qtbot.wait(stepDelay)
- nwGUI.docEditor.wCounter.run()
- qtbot.wait(stepDelay)
-
- # Save the document
- assert nwGUI.docEditor.docChanged
- assert nwGUI.saveDocument()
- assert not nwGUI.docEditor.docChanged
- qtbot.wait(stepDelay)
- nwGUI.rebuildIndex()
- qtbot.wait(stepDelay)
-
- # Open and view the edited document
- nwGUI.setFocus(3)
- assert nwGUI.openDocument("0e17daca5f3e1")
- assert nwGUI.viewDocument("0e17daca5f3e1")
- qtbot.wait(stepDelay)
- assert nwGUI.saveProject()
- assert nwGUI.closeDocViewer()
- qtbot.wait(stepDelay)
-
- # Check a Quick Create and Delete
- assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
- newHandle = nwGUI.treeView.getSelectedHandle()
- assert nwGUI.theProject.projTree["2858dcd1057d3"] is not None
- assert nwGUI.treeView.deleteItem()
- assert nwGUI.treeView.setSelectedHandle(newHandle)
- assert nwGUI.treeView.deleteItem()
- assert nwGUI.theProject.projTree["2fca346db6561"] is not None # Trash
- assert nwGUI.saveProject()
-
- # Check the files
- projFile = os.path.join(fncDir, "nwProject.nwx")
- testFile = os.path.join(nwTempGUI, "1_nwProject.nwx")
- refFile = os.path.join(refDir, "gui", "1_nwProject.nwx")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile, [2, 6, 7, 8])
-
- projFile = os.path.join(fncDir, "content", "031b4af5197ec.nwd")
- testFile = os.path.join(nwTempGUI, "1_031b4af5197ec.nwd")
- refFile = os.path.join(refDir, "gui", "1_031b4af5197ec.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(fncDir, "content", "1a6562590ef19.nwd")
- testFile = os.path.join(nwTempGUI, "1_1a6562590ef19.nwd")
- refFile = os.path.join(refDir, "gui", "1_1a6562590ef19.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(fncDir, "content", "0e17daca5f3e1.nwd")
- testFile = os.path.join(nwTempGUI, "1_0e17daca5f3e1.nwd")
- refFile = os.path.join(refDir, "gui", "1_0e17daca5f3e1.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- projFile = os.path.join(fncDir, "content", "41cfc0d1f2d12.nwd")
- testFile = os.path.join(nwTempGUI, "1_41cfc0d1f2d12.nwd")
- refFile = os.path.join(refDir, "gui", "1_41cfc0d1f2d12.nwd")
- copyfile(projFile, testFile)
- assert cmpFiles(testFile, refFile)
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testDocViewer(qtbot, yesToAll, nwLipsum, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Open project
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.openProject(nwLipsum)
-
- # Rebuild the index as it isn't automatically copied
- assert nwGUI.theIndex.tagIndex == {}
- assert nwGUI.theIndex.refIndex == {}
- nwGUI.mainMenu.aRebuildIndex.activate(QAction.Trigger)
- assert nwGUI.theIndex.tagIndex != {}
- assert nwGUI.theIndex.refIndex != {}
-
- # Select a document in the project tree
- assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
-
- # Middle-click the selected item
- theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
- theRect = nwGUI.treeView.visualItemRect(theItem)
- qtbot.mouseClick(nwGUI.treeView.viewport(), Qt.MidButton, pos=theRect.center())
- assert nwGUI.docViewer.theHandle == "88243afbe5ed8"
-
- # Reload the text
- origText = nwGUI.docViewer.toPlainText()
- nwGUI.docViewer.setPlainText("Oops, all gone!")
- nwGUI.docViewer.docHeader._refreshDocument()
- assert nwGUI.docViewer.toPlainText() == origText
-
- # Cursor line
- assert not nwGUI.docViewer.setCursorLine("not a number")
- assert nwGUI.docViewer.setCursorLine(3)
- theCursor = nwGUI.docViewer.textCursor()
- assert theCursor.position() == 40
-
- # Cursor position
- assert not nwGUI.docViewer.setCursorPosition("not a number")
- assert nwGUI.docViewer.setCursorPosition(100)
-
- # Select word
- nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
-
- qClip = qApp.clipboard()
- qClip.clear()
-
- # Cut
- assert nwGUI.docViewer.docAction(nwDocAction.CUT)
- assert qClip.text() == "laoreet"
- qClip.clear()
-
- # Copy
- assert nwGUI.docViewer.docAction(nwDocAction.COPY)
- assert qClip.text() == "laoreet"
- qClip.clear()
-
- # Select Paragraph
- assert nwGUI.docViewer.docAction(nwDocAction.SEL_PARA)
- theCursor = nwGUI.docViewer.textCursor()
- assert theCursor.selectedText() == (
- "Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, "
- "eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et "
- "mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. "
- "Etiam finibus nisi vel mi molestie consectetur."
- )
-
- # Select All
- assert nwGUI.docViewer.docAction(nwDocAction.SEL_ALL)
- theCursor = nwGUI.docViewer.textCursor()
- assert len(theCursor.selectedText()) == 3061
-
- # Other actions
- assert not nwGUI.docViewer.docAction(nwDocAction.NO_ACTION)
-
- # Close document
- nwGUI.docViewer.docHeader._closeDocument()
- assert nwGUI.docViewer.theHandle is None
-
- # Action on no document
- assert not nwGUI.docViewer.docAction(nwDocAction.COPY)
-
- # Open again via menu
- assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
- nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
-
- # Select "Bod" link
- assert nwGUI.docViewer.setCursorPosition(27)
- nwGUI.docViewer._makeSelection(QTextCursor.WordUnderCursor)
- theRect = nwGUI.docViewer.cursorRect()
- # qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.LeftButton, pos=theRect.center(), delay=100)
- nwGUI.docViewer._linkClicked(QUrl("#char=Bod"))
- assert nwGUI.docViewer.theHandle == "4c4f28287af27"
-
- # Click mouse nav buttons
- qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.BackButton, pos=theRect.center(), delay=100)
- assert nwGUI.docViewer.theHandle == "88243afbe5ed8"
- qtbot.mouseClick(nwGUI.docViewer.viewport(), Qt.ForwardButton, pos=theRect.center(), delay=100)
- assert nwGUI.docViewer.theHandle == "4c4f28287af27"
-
- # Scroll bar default on empty document
- nwGUI.docViewer.clear()
- assert nwGUI.docViewer.getScrollPosition() == 0
- nwGUI.docViewer.reloadText()
-
- # Change document title
- nwItem = nwGUI.theProject.projTree["4c4f28287af27"]
- nwItem.setName("Test Title")
- assert nwItem.itemName == "Test Title"
- nwGUI.docViewer.updateDocInfo("4c4f28287af27")
- assert nwGUI.docViewer.docHeader.theTitle.text() == "Characters › Test Title"
-
- # Ttile without full path
- nwGUI.mainConf.showFullPath = False
- nwGUI.docViewer.updateDocInfo("4c4f28287af27")
- assert nwGUI.docViewer.docHeader.theTitle.text() == "Test Title"
- nwGUI.mainConf.showFullPath = True
-
- # Document footer show/hide references
- viewState = nwGUI.viewMeta.isVisible()
- nwGUI.docViewer.docFooter._doShowHide()
- assert nwGUI.viewMeta.isVisible() is not viewState
- nwGUI.docViewer.docFooter._doShowHide()
- assert nwGUI.viewMeta.isVisible() is viewState
-
- # Document footer sticky
- viewState = nwGUI.docViewer.stickyRef
- nwGUI.docViewer.docFooter._doToggleSticky(not viewState)
- assert nwGUI.docViewer.stickyRef is not viewState
- nwGUI.docViewer.docFooter._doToggleSticky(viewState)
- assert nwGUI.docViewer.stickyRef is viewState
-
- # Document footer show/hide synopsis
- assert nwGUI.viewDocument("f96ec11c6a3da")
- assert len(nwGUI.docViewer.toPlainText()) == 4315
- nwGUI.docViewer.docFooter._doToggleSynopsis(False)
- assert len(nwGUI.docViewer.toPlainText()) == 4099
-
- # Document footer show/hide comments
- assert nwGUI.viewDocument("846352075de7d")
- assert len(nwGUI.docViewer.toPlainText()) == 675
- nwGUI.docViewer.docFooter._doToggleComments(False)
- assert len(nwGUI.docViewer.toPlainText()) == 635
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testProjectTree(qtbot, yesToAll, nwMinimal, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- nwGUI.theProject.projTree.setSeed(42)
- nwTree = nwGUI.treeView
-
- # No location selected for new item
- assert not nwTree.newTreeItem(nwItemType.FILE, None)
- assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
-
- # Select a location
- chItem = nwTree._getTreeItem("a6d311a93600a")
- nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
- chItem.setExpanded(True)
-
- # Create new item with no class set
- assert nwTree.newTreeItem(nwItemType.FILE, None)
- assert nwTree.newTreeItem(nwItemType.FOLDER, None)
-
- # Add roots
- assert not nwTree.newTreeItem(nwItemType.ROOT, None) # Defaults to NOVEL
- assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
- assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
-
- # Check that we have the correct tree order
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "73475cb40a568", "44cb730c42048"
- ]
-
- # Move second item up twice (should give same result)
- nwTree.setSelectedHandle("8c659a11cd429")
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048"
- ]
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048"
- ]
-
- # Move it back down four times (last to should be the same)
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "73475cb40a568", "44cb730c42048"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "8c659a11cd429", "44cb730c42048"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048", "8c659a11cd429"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048", "8c659a11cd429"
- ]
-
- # Move a root item (top level items are different) twice
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 9
- nwTree.setSelectedHandle("9d5247ab588e0")
-
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
-
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
-
- # Add some content to the new file
- nwGUI.openDocument("73475cb40a568")
- nwGUI.docEditor.setText("# Hello World\n")
- nwGUI.saveDocument()
- assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
-
- # Delete the items we added earlier
- nwTree.clearSelection()
- assert not nwTree.emptyTrash() # No folder yet
- assert not nwTree.deleteItem(None)
- assert not nwTree.deleteItem("1111111111111")
- assert nwTree.deleteItem("73475cb40a568") # New File
- assert nwTree.deleteItem("44cb730c42048") # New Folder
- assert nwTree.deleteItem("71ee45a3c0db9") # Custom Root
- assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
- assert "44cb730c42048" not in nwGUI.theProject.projTree._treeOrder
- assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
-
- # The file is in trash, empty it
- assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
- assert nwTree.emptyTrash()
- assert not nwTree.emptyTrash() # Already empty
- assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
- assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
-
- # Close the project
- nwGUI.closeProject()
-
- # Add an orphaned file
- orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
- with open(orphFile, mode="w+", encoding="utf8") as outFile:
- outFile.write("# Hello World\n")
-
- # Open the project again
- nwGUI.openProject(nwMinimal)
-
- # Check that the orphaned file was found and added to the tree
- assert nwTree.orphRoot is not None
- nwTree.flushTreeOrder()
- assert "1234567890abc" not in nwGUI.theProject.projTree._treeOrder
- orItem = nwTree._getTreeItem("1234567890abc")
- assert orItem.text(nwTree.C_NAME) == "Orphaned File 1"
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testEditFormatMenu(qtbot, yesToAll, nwLipsum, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- # Test Document Action with No Project
- assert not nwGUI.docEditor.docAction(nwDocAction.COPY)
-
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.openProject(nwLipsum)
- qtbot.wait(stepDelay)
-
- # Split By Chapter
- assert nwGUI.openDocument("4c4f28287af27")
- assert nwGUI.docEditor.setCursorPosition(30)
-
- cleanText = nwGUI.docEditor.getText()[27:74]
-
- # Bold
- nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
- fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:78] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Italic
- nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
- fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:76] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Strikethrough
- nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
- fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:78] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Should get us back to plain
- nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Double Quotes
- nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger)
- fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:76] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Single Quotes
- nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger)
- fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:76] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Block Formats
- assert nwGUI.docEditor.setCursorPosition(30)
- nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger)
- fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:76] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger)
- fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:77] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger)
- fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:78] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger)
- fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:79] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
- fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:76] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Check comment with no space before text
- assert nwGUI.docEditor.setCursorPosition(27)
- assert nwGUI.docEditor.insertText("%")
- fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:75] == fmtStr
- qtbot.wait(stepDelay)
-
- nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Undo/Redo
- nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
- fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
- assert nwGUI.docEditor.getText()[27:75] == fmtStr
- qtbot.wait(stepDelay)
- nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:74] == cleanText
- qtbot.wait(stepDelay)
-
- # Cut, Copy and Paste
- assert nwGUI.docEditor.setCursorPosition(27)
- nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
-
- nwGUI.mainMenu.aEditCut.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:77] == (
- " nec erat ut nulla posuere commodo. Curabitur nisi"
- )
-
- nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:77] == (
- "Pellentesque nec erat ut nulla posuere commodo. Cu"
- )
-
- assert nwGUI.docEditor.setCursorPosition(27)
- nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
-
- nwGUI.mainMenu.aEditCopy.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:77] == (
- "Pellentesque nec erat ut nulla posuere commodo. Cu"
- )
-
- assert nwGUI.docEditor.setCursorPosition(27)
- nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[27:77] == (
- "PellentesquePellentesque nec erat ut nulla posuere"
- )
- nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
-
- # Select Paragraph/All
- assert nwGUI.docEditor.setCursorPosition(30)
- nwGUI.mainMenu.aSelectPar.activate(QAction.Trigger)
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == (
- "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
- "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
- "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
- "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
- "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
- "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
- "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
- )
-
- assert nwGUI.docEditor.setCursorPosition(30)
- nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
- theCursor = nwGUI.docEditor.textCursor()
- assert len(theCursor.selectedText()) == 1883
-
- # Clear the Text
- nwGUI.docEditor.clear()
- assert nwGUI.docEditor.isEmpty()
-
- # Replace Quotes
- nwGUI.docEditor.setText((
- "### New Text\n\n"
- "Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
- ))
-
- nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
- nwGUI.mainMenu.aFmtReplSng.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == (
- "### New Text\n\n"
- "Text with ‘single’ quotes and ‘tricky stuff’s’.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
- )
-
- nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
- nwGUI.mainMenu.aFmtReplDbl.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == (
- "### New Text\n\n"
- "Text with ‘single’ quotes and ‘tricky stuff’s’.\n\n"
- "Also text with “double” quotes which are “less tricky”.\n\n"
- )
-
- # Test Invalid Document Action
- assert not nwGUI.docEditor.docAction(nwDocAction.NO_ACTION)
-
- # Test Invalid Formats
- nwGUI.docEditor.setText((
- "### New Text\n\n"
- "@tag: Bod\n\n"
- "Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
- ))
-
- # Cannot Format Tag
- assert nwGUI.docEditor.setCursorPosition(17)
- assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
-
- # Cannot Format Empty Line
- assert nwGUI.docEditor.setCursorPosition(13)
- assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
-
- # Invalid Action
- assert nwGUI.docEditor.setCursorPosition(30)
- assert not nwGUI.docEditor._formatBlock(nwDocAction.NO_ACTION)
-
- # Ensure No Changes
- assert nwGUI.docEditor.getText() == (
- "### New Text\n\n"
- "@tag: Bod\n\n"
- "Text with 'single' quotes and 'tricky stuff's'.\n\n"
- "Also text with \"double\" quotes which are \"less tricky\".\n\n"
- )
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testContextMenu(qtbot, yesToAll, nwLipsum, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.openProject(nwLipsum)
- assert nwGUI.openDocument("4c4f28287af27")
- qtbot.wait(stepDelay)
-
- # Editor Context Menu
- theCursor = nwGUI.docEditor.textCursor()
- theCursor.setPosition(100)
- nwGUI.docEditor.setTextCursor(theCursor)
- theRect = nwGUI.docEditor.cursorRect()
-
- nwGUI.docEditor._openContextMenu(theRect.bottomRight())
- qtbot.mouseClick(nwGUI.docEditor, Qt.LeftButton, pos=theRect.topLeft())
-
- nwGUI.docEditor._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == "imperdiet"
-
- nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == (
- "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
- "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
- "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
- "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
- "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
- "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
- "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
- )
-
- # Viewer Context Menu
- assert nwGUI.viewDocument("4c4f28287af27")
-
- theCursor = nwGUI.docViewer.textCursor()
- theCursor.setPosition(100)
- nwGUI.docViewer.setTextCursor(theCursor)
- theRect = nwGUI.docViewer.cursorRect()
-
- nwGUI.docViewer._openContextMenu(theRect.bottomRight())
- qtbot.mouseClick(nwGUI.docViewer, Qt.LeftButton, pos=theRect.topLeft())
-
- nwGUI.docViewer._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
- theCursor = nwGUI.docViewer.textCursor()
- assert theCursor.selectedText() == "imperdiet"
-
- nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == (
- "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
- "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
- "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
- "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
- "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
- "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
- "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
- )
-
- # Navigation History
- assert nwGUI.viewDocument("04468803b92e1")
- assert nwGUI.docViewer.theHandle == "04468803b92e1"
- assert nwGUI.docViewer.docHeader.backButton.isEnabled()
- assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
-
- qtbot.mouseClick(nwGUI.docViewer.docHeader.backButton, Qt.LeftButton)
- assert nwGUI.docViewer.theHandle == "4c4f28287af27"
- assert not nwGUI.docViewer.docHeader.backButton.isEnabled()
- assert nwGUI.docViewer.docHeader.forwardButton.isEnabled()
-
- qtbot.mouseClick(nwGUI.docViewer.docHeader.forwardButton, Qt.LeftButton)
- assert nwGUI.docViewer.theHandle == "04468803b92e1"
- assert nwGUI.docViewer.docHeader.backButton.isEnabled()
- assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testInsertMenu(qtbot, monkeypatch, fncDir, tmpDir):
- nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.newProject({"projPath": fncDir})
-
- assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
-
- nwGUI.setFocus(1)
- nwGUI.treeView.clearSelection()
- nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
- assert nwGUI.openSelectedItem()
- nwGUI.docEditor.clear()
-
- # Test Faulty Inserts
- assert nwGUI.docEditor.insertText("hello world")
- assert nwGUI.docEditor.getText() == "hello world"
- nwGUI.docEditor.clear()
-
- assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
- assert nwGUI.docEditor.isEmpty()
-
- assert not nwGUI.docEditor.insertText(None)
- assert nwGUI.docEditor.isEmpty()
-
- # qtbot.stopForInteraction()
-
- # Check Menu Entries
- nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwUnicode.U_ENDASH
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsEMDash.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwUnicode.U_EMDASH
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsQuoteLS.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[0]
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsQuoteRS.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[1]
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsQuoteLD.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[0]
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsQuoteRD.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[1]
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsMSApos.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == " \n"
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger)
- if nwGUI.mainConf.verQtValue >= 50900:
- assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP
- else:
- assert nwGUI.docEditor.getText() == " "
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsThinSpace.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == nwUnicode.U_THNSP
- nwGUI.docEditor.clear()
-
- nwGUI.mainMenu.aInsThinNBSpace.activate(QAction.Trigger)
- if nwGUI.mainConf.verQtValue >= 50900:
- assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP
- else:
- assert nwGUI.docEditor.getText() == " "
- nwGUI.docEditor.clear()
-
- ##
- # Insert Keywords
- ##
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TAG_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.POV_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.POV_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.CHAR_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CHAR_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.PLOT_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.PLOT_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.TIME_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TIME_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.WORLD_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.WORLD_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.OBJECT_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.OBJECT_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.ENTITY_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.ENTITY_KEY
-
- nwGUI.docEditor.setText("Stuff")
- nwGUI.mainMenu.mInsKWItems[nwKeyWords.CUSTOM_KEY][0].activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CUSTOM_KEY
-
- # Faulty Keyword Inserts
- assert not nwGUI.docEditor.insertKeyWord("blabla")
- monkeypatch.setattr(QTextBlock, "isValid", lambda *args, **kwards: False)
- assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY)
- monkeypatch.undo()
-
- nwGUI.docEditor.clear()
-
- ##
- # Insert text from file
- ##
-
- nwGUI.closeDocument()
-
- # First, with no path
- monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: ("", ""))
- assert not nwGUI.importDocument()
-
- # Then with a path, but an invalid one
- monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (" ", ""))
- assert not nwGUI.importDocument()
-
- # Then a valid path, but bot a file that exists
- theFile = os.path.join(tmpDir, "import.txt")
- monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (theFile, ""))
- assert not nwGUI.importDocument()
-
- # Create the file and try again, but with no target document open
- with open(theFile, mode="w+", encoding="utf8") as outFile:
- outFile.write("Foo")
- assert not nwGUI.importDocument()
-
- # Open the document from before, and add some text to it
- nwGUI.openDocument("0e17daca5f3e1")
- nwGUI.docEditor.setText("Bar")
- assert nwGUI.docEditor.getText() == "Bar"
-
- # The document isn't empty, so the message box should pop
- monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.No)
- assert not nwGUI.importDocument()
- assert nwGUI.docEditor.getText() == "Bar"
-
- # Finally, accept the replaced text, this time we use the menu entry to trigger it
- monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes)
- nwGUI.mainMenu.aImportFile.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText() == "Foo"
-
- ##
- # Reveal file location
- ##
-
- theMessage = ""
-
- def recordMsg(*args):
- nonlocal theMessage
- theMessage = args[3]
- return None
-
- assert not theMessage
- monkeypatch.setattr(QMessageBox, "information", recordMsg)
- nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
-
- theBits = theMessage.split("
")
- assert len(theBits) == 3
- assert theBits[0] == "File details for the currently open file"
- assert theBits[1] == "Handle: 0e17daca5f3e1"
- assert theBits[2] == "Location: %s" % os.path.join(fncDir, "content", "0e17daca5f3e1.nwd")
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testTextSearch(qtbot, monkeypatch, yesToAll, nwLipsum, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- nwGUI.theProject.projTree.setSeed(42)
- assert nwGUI.openProject(nwLipsum)
- assert nwGUI.openDocument("4c4f28287af27")
- origText = nwGUI.docEditor.getText()
- qtbot.wait(stepDelay)
-
- # Select the Word "est"
- assert nwGUI.docEditor.setCursorPosition(618)
- nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == "est"
-
- # Activate Search
- nwGUI.mainMenu.aFind.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.isVisible()
- assert nwGUI.docEditor.docSearch.getSearchText() == "est"
-
- # Find Next by Enter
- monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
- qtbot.keyClick(nwGUI.docEditor.docSearch.searchBox, Qt.Key_Return, delay=keyDelay)
- assert abs(nwGUI.docEditor.getCursorPosition() - 1272) < 3
-
- # Find Next by Button
- qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
- assert abs(nwGUI.docEditor.getCursorPosition() - 1486) < 3
-
- # Activate Loop Search
- nwGUI.docEditor.docSearch.toggleLoop.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleLoop.isChecked()
- assert nwGUI.docEditor.docSearch.doLoop
-
- # Find Next by Menu Search > Find Next
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
-
- # Close Search
- nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
- assert not nwGUI.docEditor.docSearch.isVisible()
- assert nwGUI.docEditor.setCursorPosition(15)
-
- # Toggle Search Again with Header Button
- qtbot.mouseClick(nwGUI.docEditor.docHeader.searchButton, Qt.LeftButton, delay=keyDelay)
- assert nwGUI.docEditor.docSearch.setSearchText("")
- assert nwGUI.docEditor.docSearch.isVisible()
-
- # Enable RegEx Search
- nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleRegEx.isChecked()
- assert nwGUI.docEditor.docSearch.isRegEx
-
- # Set Invalid RegEx
- assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus[")
- qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
- assert nwGUI.docEditor.getCursorPosition() < 3 # No result
-
- # Set Valid RegEx
- assert nwGUI.docEditor.docSearch.setSearchText(r"\bSus")
- qtbot.mouseClick(nwGUI.docEditor.docSearch.searchButton, Qt.LeftButton, delay=keyDelay)
- assert abs(nwGUI.docEditor.getCursorPosition() - 196) < 3
-
- # Find Next and then Prev
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 297) < 3
- nwGUI.mainMenu.aFindPrev.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 196) < 3
-
- # Make RegEx Case Sensitive
- nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleCase.isChecked()
- assert nwGUI.docEditor.docSearch.isCaseSense
-
- # Find Next (One Result)
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 599) < 3
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 599) < 3
-
- # Trigger Replace
- nwGUI.mainMenu.aReplace.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.setReplaceText("foo")
-
- # Disable RegEx Case Sensitive
- nwGUI.docEditor.docSearch.toggleCase.activate(QAction.Trigger)
- assert not nwGUI.docEditor.docSearch.toggleCase.isChecked()
- assert not nwGUI.docEditor.docSearch.isCaseSense
-
- # Toggle Replace Preserve Case
- nwGUI.docEditor.docSearch.toggleMatchCap.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleMatchCap.isChecked()
- assert nwGUI.docEditor.docSearch.doMatchCap
-
- # Replace "Sus" with "Foo" via Menu
- nwGUI.mainMenu.aReplaceNext.activate(QAction.Trigger)
- assert nwGUI.docEditor.getText()[596:607] == "Foopendisse"
-
- # Find Next to Loop File
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
-
- # Replace "sus" with "foo" via Replace Button
- qtbot.mouseClick(nwGUI.docEditor.docSearch.replaceButton, Qt.LeftButton, delay=keyDelay)
- assert nwGUI.docEditor.getText()[193:201] == "foocipit"
-
- # Revert Last Two Replaces
- assert nwGUI.docEditor.docAction(nwDocAction.UNDO)
- assert nwGUI.docEditor.docAction(nwDocAction.UNDO)
- assert nwGUI.docEditor.getText() == origText
-
- # Disable RegEx Search
- nwGUI.docEditor.docSearch.toggleRegEx.activate(QAction.Trigger)
- assert not nwGUI.docEditor.docSearch.toggleRegEx.isChecked()
- assert not nwGUI.docEditor.docSearch.isRegEx
-
- # Close Search and Select "est" Again
- nwGUI.docEditor.docSearch.cancelSearch.activate(QAction.Trigger)
- assert nwGUI.docEditor.setCursorPosition(618)
- nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
- theCursor = nwGUI.docEditor.textCursor()
- assert theCursor.selectedText() == "est"
-
- # Activate Search Again
- nwGUI.mainMenu.aFind.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.isVisible()
- assert nwGUI.docEditor.docSearch.getSearchText() == "est"
-
- # Enable Full Word Search
- nwGUI.docEditor.docSearch.toggleWord.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleWord.isChecked()
- assert nwGUI.docEditor.docSearch.isWholeWord
-
- # Only One Match
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
-
- # Enable Next Doc Search
- nwGUI.docEditor.docSearch.toggleProject.activate(QAction.Trigger)
- assert nwGUI.docEditor.docSearch.toggleProject.isChecked()
- assert nwGUI.docEditor.docSearch.doNextFile
-
- # Next Match
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert nwGUI.docEditor.theHandle == "2426c6f0ca922" # Next document
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 620) < 3
- nwGUI.mainMenu.aFindNext.activate(QAction.Trigger)
- assert abs(nwGUI.docEditor.getCursorPosition() - 1127) < 3
-
- # Toggle Replace
- nwGUI.docEditor._beginReplace()
-
- # MonkeyPatch the focus cycle. We can't really test this very well, other than
- # check that the tabs aren't captured when the main editor has focus
- monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: True)
- monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False)
- monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False)
- assert not nwGUI.docEditor.focusNextPrevChild(True)
-
- monkeypatch.setattr(nwGUI.docEditor, "hasFocus", lambda: False)
- monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: True)
- monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: False)
- assert nwGUI.docEditor.focusNextPrevChild(True)
-
- monkeypatch.setattr(nwGUI.docEditor.docSearch.searchBox, "hasFocus", lambda: False)
- monkeypatch.setattr(nwGUI.docEditor.docSearch.replaceBox, "hasFocus", lambda: True)
- assert nwGUI.docEditor.focusNextPrevChild(True)
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testOutline(qtbot, yesToAll, nwLipsum, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwLipsum, "--data=%s" % tmpDir])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(stepDelay)
-
- assert nwGUI.openProject(nwLipsum)
- nwGUI.mainConf.lastPath = nwLipsum
-
- nwGUI.rebuildIndex()
- nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj)
-
- assert nwGUI.projView.topLevelItemCount() > 0
-
- # Context Menu
- nwGUI.projView._headerRightClick(QPoint(1, 1))
- nwGUI.projView.headerMenu.actionMap[nwOutline.CCOUNT].activate(QAction.Trigger)
- nwGUI.projView.headerMenu.close()
- qtbot.mouseClick(nwGUI.projView, Qt.LeftButton)
-
- nwGUI.projView._loadHeaderState()
- assert not nwGUI.projView.colHidden[nwOutline.CCOUNT]
-
- # First Item
- nwGUI.rebuildOutline()
- selItem = nwGUI.projView.topLevelItem(0)
- assert isinstance(selItem, QTreeWidgetItem)
-
- nwGUI.projView.setCurrentItem(selItem)
- assert nwGUI.projMeta.titleLabel.text() == "Title"
- assert nwGUI.projMeta.titleValue.text() == "Lorem Ipsum"
- assert nwGUI.projMeta.fileValue.text() == "Lorem Ipsum"
- assert nwGUI.projMeta.itemValue.text() == "Finished"
-
- assert nwGUI.projMeta.cCValue.text() == "230"
- assert nwGUI.projMeta.wCValue.text() == "40"
- assert nwGUI.projMeta.pCValue.text() == "3"
-
- # Scene One
- actItem = nwGUI.projView.topLevelItem(1)
- chpItem = actItem.child(0)
- selItem = chpItem.child(0)
-
- nwGUI.projView.setCurrentItem(selItem)
- assert nwGUI.projMeta.titleLabel.text() == "Scene"
- assert nwGUI.projMeta.titleValue.text() == "Scene One"
- assert nwGUI.projMeta.fileValue.text() == "Scene One"
- assert nwGUI.projMeta.itemValue.text() == "Finished"
-
- # Click POV Link
- assert nwGUI.projMeta.povKeyValue.text() == "Bod"
- nwGUI.projMeta._tagClicked("#pov=Bod")
- assert nwGUI.docViewer.theHandle == "4c4f28287af27"
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
-
-@pytest.mark.gui
-def testThemes(qtbot, yesToAll, nwMinimal, tmpDir):
-
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(500)
-
- # Change Settings
- assert nw.CONFIG.confPath == nwMinimal
- nw.CONFIG.guiTheme = "default_dark"
- nw.CONFIG.guiSyntax = "tomorrow_night_eighties"
- nw.CONFIG.guiIcons = "typicons_colour_dark"
- nw.CONFIG.guiDark = True
- nw.CONFIG.guiFont = "Cantarell"
- nw.CONFIG.guiFontSize = 11
- nw.CONFIG.confChanged = True
- assert nw.CONFIG.saveConfig()
-
- nwGUI.closeMain()
- nwGUI.close()
- del nwGUI
-
- # Re-open
- assert nw.CONFIG.confPath == nwMinimal
- nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
- assert nwGUI.mainConf.confPath == nwMinimal
- qtbot.addWidget(nwGUI)
- nwGUI.show()
- qtbot.waitForWindowShown(nwGUI)
- qtbot.wait(500)
-
- assert nw.CONFIG.guiTheme == "default_dark"
- assert nw.CONFIG.guiSyntax == "tomorrow_night_eighties"
- assert nw.CONFIG.guiIcons == "typicons_colour_dark"
- assert nw.CONFIG.guiDark is True
- assert nw.CONFIG.guiFont == "Cantarell"
- assert nw.CONFIG.guiFontSize == 11
-
- # Check GUI Colours
- thePalette = nwGUI.palette()
- assert thePalette.window().color() == QColor(54, 54, 54)
- assert thePalette.windowText().color() == QColor(174, 174, 174)
- assert thePalette.base().color() == QColor(62, 62, 62)
- assert thePalette.alternateBase().color() == QColor(67, 67, 67)
- assert thePalette.text().color() == QColor(174, 174, 174)
- assert thePalette.toolTipBase().color() == QColor(255, 255, 192)
- assert thePalette.toolTipText().color() == QColor(21, 21, 13)
- assert thePalette.button().color() == QColor(62, 62, 62)
- assert thePalette.buttonText().color() == QColor(174, 174, 174)
- assert thePalette.brightText().color() == QColor(174, 174, 174)
- assert thePalette.highlight().color() == QColor(44, 152, 247)
- assert thePalette.highlightedText().color() == QColor(255, 255, 255)
- assert thePalette.link().color() == QColor(44, 152, 247)
- assert thePalette.linkVisited().color() == QColor(44, 152, 247)
-
- assert nwGUI.theTheme.treeWCount == [197, 200, 198]
- assert nwGUI.theTheme.statNone == [150, 152, 150]
- assert nwGUI.theTheme.statSaved == [39, 135, 78]
- assert nwGUI.theTheme.statUnsaved == [138, 32, 32]
-
- # Check Syntax Colours
- assert nwGUI.theTheme.colBack == [45, 45, 45]
- assert nwGUI.theTheme.colText == [204, 204, 204]
- assert nwGUI.theTheme.colLink == [102, 153, 204]
- assert nwGUI.theTheme.colHead == [102, 153, 204]
- assert nwGUI.theTheme.colHeadH == [102, 153, 204]
- assert nwGUI.theTheme.colEmph == [249, 145, 57]
- assert nwGUI.theTheme.colDialN == [242, 119, 122]
- assert nwGUI.theTheme.colDialD == [153, 204, 153]
- assert nwGUI.theTheme.colDialS == [255, 204, 102]
- assert nwGUI.theTheme.colHidden == [153, 153, 153]
- assert nwGUI.theTheme.colKey == [242, 119, 122]
- assert nwGUI.theTheme.colVal == [204, 153, 204]
- assert nwGUI.theTheme.colSpell == [242, 119, 122]
- assert nwGUI.theTheme.colTagErr == [153, 204, 153]
- assert nwGUI.theTheme.colRepTag == [102, 204, 204]
- assert nwGUI.theTheme.colMod == [249, 145, 57]
-
- # Test Icon class
- theIcons = nwGUI.theTheme.theIcons
- nw.CONFIG.guiIcons = "invalid"
- assert not theIcons.updateTheme()
- nw.CONFIG.guiIcons = "typicons_colour_dark"
- assert theIcons.updateTheme()
-
- # Ask for a non-existent key
- anImg = theIcons.loadDecoration("nonsense", 20, 20)
- assert isinstance(anImg, QPixmap)
- assert anImg.isNull()
-
- # Add a non-existent file and request it
- theIcons.DECO_MAP["nonsense"] = "nofile.jpg"
- anImg = theIcons.loadDecoration("nonsense", 20, 20)
- assert isinstance(anImg, QPixmap)
- assert anImg.isNull()
-
- # Get a real image, with different size parameters
- anImg = theIcons.loadDecoration("wiz-back", 20, None)
- assert isinstance(anImg, QPixmap)
- assert not anImg.isNull()
- assert anImg.width() == 20
- assert anImg.height() >= 56
-
- anImg = theIcons.loadDecoration("wiz-back", None, 70)
- assert isinstance(anImg, QPixmap)
- assert not anImg.isNull()
- assert anImg.height() == 70
- assert anImg.width() >= 24
-
- anImg = theIcons.loadDecoration("wiz-back", 30, 70)
- assert isinstance(anImg, QPixmap)
- assert not anImg.isNull()
- assert anImg.height() == 70
- assert anImg.width() == 30
-
- anImg = theIcons.loadDecoration("wiz-back", None, None)
- assert isinstance(anImg, QPixmap)
- assert not anImg.isNull()
- assert anImg.height() >= 1500
- assert anImg.width() >= 500
-
- # Load icons
- anIcon = theIcons.getIcon("nonsense")
- assert isinstance(anIcon, QIcon)
- assert anIcon.isNull()
-
- anIcon = theIcons.getIcon("novelwriter")
- assert isinstance(anIcon, QIcon)
- assert not anIcon.isNull()
-
- # Add dummy icons and test alternative load paths
- theIcons.ICON_MAP["testicon1"] = (QStyle.SP_DriveHDIcon, None)
- anIcon = theIcons.getIcon("testicon1")
- assert isinstance(anIcon, QIcon)
- assert not anIcon.isNull()
-
- theIcons.ICON_MAP["testicon2"] = (None, "folder")
- anIcon = theIcons.getIcon("testicon2")
- assert isinstance(anIcon, QIcon)
-
- theIcons.ICON_MAP["testicon3"] = (None, None)
- anIcon = theIcons.getIcon("testicon3")
- assert isinstance(anIcon, QIcon)
- assert anIcon.isNull()
-
- # qtbot.stopForInteraction()
- nwGUI.closeMain()
- nwGUI.close()
diff --git a/tests/test_gui_mainmenu.py b/tests/test_gui_mainmenu.py
new file mode 100644
index 00000000..33408505
--- /dev/null
+++ b/tests/test_gui_mainmenu.py
@@ -0,0 +1,533 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Main Menu Class Tester
+"""
+
+import pytest
+import os
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtGui import QTextCursor, QTextBlock
+from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
+
+from nw.constants import nwUnicode, nwDocAction, nwDocInsert, nwKeyWords
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiMenu_EditFormat(qtbot, monkeypatch, nwGUI, nwLipsum):
+ """Test the main menu Edit and Format entries.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Test Document Action with No Project
+ assert not nwGUI.docEditor.docAction(nwDocAction.COPY)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+ qtbot.wait(stepDelay)
+
+ # Split By Chapter
+ assert nwGUI.openDocument("4c4f28287af27")
+ assert nwGUI.docEditor.setCursorPosition(30)
+
+ cleanText = nwGUI.docEditor.getText()[27:74]
+
+ # Bold
+ nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
+ fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:78] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Italic
+ nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
+ fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:76] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Strikethrough
+ nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
+ fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:78] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtStrike.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Should get us back to plain
+ nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtEmph.activate(QAction.Trigger)
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtStrong.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Double Quotes
+ nwGUI.mainMenu.aFmtDQuote.activate(QAction.Trigger)
+ fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:76] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Single Quotes
+ nwGUI.mainMenu.aFmtSQuote.activate(QAction.Trigger)
+ fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:76] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Block Formats
+ assert nwGUI.docEditor.setCursorPosition(30)
+ nwGUI.mainMenu.aFmtHead1.activate(QAction.Trigger)
+ fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:76] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtHead2.activate(QAction.Trigger)
+ fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:77] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtHead3.activate(QAction.Trigger)
+ fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:78] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtHead4.activate(QAction.Trigger)
+ fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:79] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtComment.activate(QAction.Trigger)
+ fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:76] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Check comment with no space before text
+ assert nwGUI.docEditor.setCursorPosition(27)
+ assert nwGUI.docEditor.insertText("%")
+ fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:75] == fmtStr
+ qtbot.wait(stepDelay)
+
+ nwGUI.mainMenu.aFmtNoFormat.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Undo/Redo
+ nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
+ fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
+ assert nwGUI.docEditor.getText()[27:75] == fmtStr
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aEditRedo.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:74] == cleanText
+ qtbot.wait(stepDelay)
+
+ # Cut, Copy and Paste
+ assert nwGUI.docEditor.setCursorPosition(27)
+ nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
+
+ nwGUI.mainMenu.aEditCut.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:77] == (
+ " nec erat ut nulla posuere commodo. Curabitur nisi"
+ )
+
+ nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:77] == (
+ "Pellentesque nec erat ut nulla posuere commodo. Cu"
+ )
+
+ assert nwGUI.docEditor.setCursorPosition(27)
+ nwGUI.docEditor._makeSelection(QTextCursor.WordUnderCursor)
+
+ nwGUI.mainMenu.aEditCopy.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:77] == (
+ "Pellentesque nec erat ut nulla posuere commodo. Cu"
+ )
+
+ assert nwGUI.docEditor.setCursorPosition(27)
+ nwGUI.mainMenu.aEditPaste.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText()[27:77] == (
+ "PellentesquePellentesque nec erat ut nulla posuere"
+ )
+ nwGUI.mainMenu.aEditUndo.activate(QAction.Trigger)
+
+ # Select Paragraph/All
+ assert nwGUI.docEditor.setCursorPosition(30)
+ nwGUI.mainMenu.aSelectPar.activate(QAction.Trigger)
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == (
+ "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
+ "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
+ "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
+ "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
+ "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
+ "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
+ "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
+ )
+
+ assert nwGUI.docEditor.setCursorPosition(30)
+ nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
+ theCursor = nwGUI.docEditor.textCursor()
+ assert len(theCursor.selectedText()) == 1883
+
+ # Clear the Text
+ nwGUI.docEditor.clear()
+ assert nwGUI.docEditor.isEmpty()
+
+ # Replace Quotes
+ nwGUI.docEditor.setText((
+ "### New Text\n\n"
+ "Text with 'single' quotes and 'tricky stuff's'.\n\n"
+ "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ ))
+
+ nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
+ nwGUI.mainMenu.aFmtReplSng.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == (
+ "### New Text\n\n"
+ "Text with ‘single’ quotes and ‘tricky stuff’s’.\n\n"
+ "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ )
+
+ nwGUI.mainMenu.aSelectAll.activate(QAction.Trigger)
+ nwGUI.mainMenu.aFmtReplDbl.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == (
+ "### New Text\n\n"
+ "Text with ‘single’ quotes and ‘tricky stuff’s’.\n\n"
+ "Also text with “double” quotes which are “less tricky”.\n\n"
+ )
+
+ # Test Invalid Document Action
+ assert not nwGUI.docEditor.docAction(nwDocAction.NO_ACTION)
+
+ # Test Invalid Formats
+ nwGUI.docEditor.setText((
+ "### New Text\n\n"
+ "@tag: Bod\n\n"
+ "Text with 'single' quotes and 'tricky stuff's'.\n\n"
+ "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ ))
+
+ # Cannot Format Tag
+ assert nwGUI.docEditor.setCursorPosition(17)
+ assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
+
+ # Cannot Format Empty Line
+ assert nwGUI.docEditor.setCursorPosition(13)
+ assert not nwGUI.docEditor._formatBlock(nwDocAction.BLOCK_TXT)
+
+ # Invalid Action
+ assert nwGUI.docEditor.setCursorPosition(30)
+ assert not nwGUI.docEditor._formatBlock(nwDocAction.NO_ACTION)
+
+ # Ensure No Changes
+ assert nwGUI.docEditor.getText() == (
+ "### New Text\n\n"
+ "@tag: Bod\n\n"
+ "Text with 'single' quotes and 'tricky stuff's'.\n\n"
+ "Also text with \"double\" quotes which are \"less tricky\".\n\n"
+ )
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMenu_EditFormat
+
+@pytest.mark.gui
+def testGuiMenu_ContextMenus(qtbot, monkeypatch, nwGUI, nwLipsum):
+ """Test the context menus.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+ assert nwGUI.openDocument("4c4f28287af27")
+ qtbot.wait(stepDelay)
+
+ # Editor Context Menu
+ theCursor = nwGUI.docEditor.textCursor()
+ theCursor.setPosition(100)
+ nwGUI.docEditor.setTextCursor(theCursor)
+ theRect = nwGUI.docEditor.cursorRect()
+
+ nwGUI.docEditor._openContextMenu(theRect.bottomRight())
+ qtbot.mouseClick(nwGUI.docEditor, Qt.LeftButton, pos=theRect.topLeft())
+
+ nwGUI.docEditor._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == "imperdiet"
+
+ nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == (
+ "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
+ "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
+ "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
+ "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
+ "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
+ "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
+ "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
+ )
+
+ # Viewer Context Menu
+ assert nwGUI.viewDocument("4c4f28287af27")
+
+ theCursor = nwGUI.docViewer.textCursor()
+ theCursor.setPosition(100)
+ nwGUI.docViewer.setTextCursor(theCursor)
+ theRect = nwGUI.docViewer.cursorRect()
+
+ nwGUI.docViewer._openContextMenu(theRect.bottomRight())
+ qtbot.mouseClick(nwGUI.docViewer, Qt.LeftButton, pos=theRect.topLeft())
+
+ nwGUI.docViewer._makePosSelection(QTextCursor.WordUnderCursor, theRect.center())
+ theCursor = nwGUI.docViewer.textCursor()
+ assert theCursor.selectedText() == "imperdiet"
+
+ nwGUI.docEditor._makePosSelection(QTextCursor.BlockUnderCursor, theRect.center())
+ theCursor = nwGUI.docEditor.textCursor()
+ assert theCursor.selectedText() == (
+ "Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta "
+ "imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit "
+ "placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. "
+ "Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. "
+ "Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, "
+ "rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin "
+ "nunc lacus, imperdiet nec posuere ac, interdum non lectus."
+ )
+
+ # Navigation History
+ assert nwGUI.viewDocument("04468803b92e1")
+ assert nwGUI.docViewer.theHandle == "04468803b92e1"
+ assert nwGUI.docViewer.docHeader.backButton.isEnabled()
+ assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
+
+ qtbot.mouseClick(nwGUI.docViewer.docHeader.backButton, Qt.LeftButton)
+ assert nwGUI.docViewer.theHandle == "4c4f28287af27"
+ assert not nwGUI.docViewer.docHeader.backButton.isEnabled()
+ assert nwGUI.docViewer.docHeader.forwardButton.isEnabled()
+
+ qtbot.mouseClick(nwGUI.docViewer.docHeader.forwardButton, Qt.LeftButton)
+ assert nwGUI.docViewer.theHandle == "04468803b92e1"
+ assert nwGUI.docViewer.docHeader.backButton.isEnabled()
+ assert not nwGUI.docViewer.docHeader.forwardButton.isEnabled()
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMenu_ContextMenus
+
+@pytest.mark.gui
+def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
+ """Test the Insert menu.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj})
+
+ assert nwGUI.treeView._getTreeItem("0e17daca5f3e1") is not None
+
+ nwGUI.setFocus(1)
+ nwGUI.treeView.clearSelection()
+ nwGUI.treeView._getTreeItem("0e17daca5f3e1").setSelected(True)
+ assert nwGUI.openSelectedItem()
+ nwGUI.docEditor.clear()
+
+ # Test Faulty Inserts
+ assert nwGUI.docEditor.insertText("hello world")
+ assert nwGUI.docEditor.getText() == "hello world"
+ nwGUI.docEditor.clear()
+
+ assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
+ assert nwGUI.docEditor.isEmpty()
+
+ assert not nwGUI.docEditor.insertText(None)
+ assert nwGUI.docEditor.isEmpty()
+
+ # qtbot.stopForInteraction()
+
+ # Check Menu Entries
+ nwGUI.mainMenu.aInsENDash.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwUnicode.U_ENDASH
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsEMDash.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwUnicode.U_EMDASH
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsEllipsis.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwUnicode.U_HELLIP
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsQuoteLS.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[0]
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsQuoteRS.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtSingleQuotes[1]
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsQuoteLD.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[0]
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsQuoteRD.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwGUI.mainConf.fmtDoubleQuotes[1]
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsMSApos.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwUnicode.U_MAPOSS
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsHardBreak.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == " \n"
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsNBSpace.activate(QAction.Trigger)
+ if nwGUI.mainConf.verQtValue >= 50900:
+ assert nwGUI.docEditor.getText() == nwUnicode.U_NBSP
+ else:
+ assert nwGUI.docEditor.getText() == " "
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsThinSpace.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == nwUnicode.U_THNSP
+ nwGUI.docEditor.clear()
+
+ nwGUI.mainMenu.aInsThinNBSpace.activate(QAction.Trigger)
+ if nwGUI.mainConf.verQtValue >= 50900:
+ assert nwGUI.docEditor.getText() == nwUnicode.U_THNBSP
+ else:
+ assert nwGUI.docEditor.getText() == " "
+ nwGUI.docEditor.clear()
+
+ ##
+ # Insert Keywords
+ ##
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.TAG_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TAG_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.POV_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.POV_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.CHAR_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CHAR_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.PLOT_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.PLOT_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.TIME_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.TIME_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.WORLD_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.WORLD_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.OBJECT_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.OBJECT_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.ENTITY_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.ENTITY_KEY
+
+ nwGUI.docEditor.setText("Stuff")
+ nwGUI.mainMenu.mInsKWItems[nwKeyWords.CUSTOM_KEY][0].activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Stuff\n%s: " % nwKeyWords.CUSTOM_KEY
+
+ # Faulty Keyword Inserts
+ assert not nwGUI.docEditor.insertKeyWord("blabla")
+ monkeypatch.setattr(QTextBlock, "isValid", lambda *args, **kwards: False)
+ assert not nwGUI.docEditor.insertKeyWord(nwKeyWords.TAG_KEY)
+ monkeypatch.undo()
+
+ nwGUI.docEditor.clear()
+
+ ##
+ # Insert text from file
+ ##
+
+ nwGUI.closeDocument()
+
+ # First, with no path
+ monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: ("", ""))
+ assert not nwGUI.importDocument()
+
+ # Then with a path, but an invalid one
+ monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (" ", ""))
+ assert not nwGUI.importDocument()
+
+ # Then a valid path, but bot a file that exists
+ theFile = os.path.join(fncDir, "import.txt")
+ monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwards: (theFile, ""))
+ assert not nwGUI.importDocument()
+
+ # Create the file and try again, but with no target document open
+ with open(theFile, mode="w+", encoding="utf8") as outFile:
+ outFile.write("Foo")
+ assert not nwGUI.importDocument()
+
+ # Open the document from before, and add some text to it
+ nwGUI.openDocument("0e17daca5f3e1")
+ nwGUI.docEditor.setText("Bar")
+ assert nwGUI.docEditor.getText() == "Bar"
+
+ # The document isn't empty, so the message box should pop
+ monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.No)
+ assert not nwGUI.importDocument()
+ assert nwGUI.docEditor.getText() == "Bar"
+
+ # Finally, accept the replaced text, this time we use the menu entry to trigger it
+ monkeypatch.setattr(QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes)
+ nwGUI.mainMenu.aImportFile.activate(QAction.Trigger)
+ assert nwGUI.docEditor.getText() == "Foo"
+
+ ##
+ # Reveal file location
+ ##
+
+ theMessage = ""
+
+ def recordMsg(*args):
+ nonlocal theMessage
+ theMessage = args[3]
+ return None
+
+ assert not theMessage
+ monkeypatch.setattr(QMessageBox, "information", recordMsg)
+ nwGUI.mainMenu.aFileDetails.activate(QAction.Trigger)
+
+ theBits = theMessage.split("
")
+ assert len(theBits) == 3
+ assert theBits[0] == "File details for the currently open file"
+ assert theBits[1] == "Handle: 0e17daca5f3e1"
+ assert theBits[2] == "Location: %s" % os.path.join(fncProj, "content", "0e17daca5f3e1.nwd")
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMenu_Insert
diff --git a/tests/test_gui_mergesplit.py b/tests/test_gui_mergesplit.py
new file mode 100644
index 00000000..9024a0d9
--- /dev/null
+++ b/tests/test_gui_mergesplit.py
@@ -0,0 +1,167 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Merge and Split Dialog Classes Tester
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles, getGuiItem
+
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.gui import GuiDocMerge, GuiDocSplit
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiMergeSplit_Tools(qtbot, monkeypatch, nwGUI, nwLipsum, refDir, outDir):
+ """Test the full merge and split tools.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwLipsum)
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.treeView.setSelectedHandle("45e6b01ca35c1")
+ qtbot.wait(stepDelay)
+
+ monkeypatch.setattr(GuiDocMerge, "exec_", lambda *args: None)
+ nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiDocMerge") is not None, timeout=1000)
+
+ nwMerge = getGuiItem("GuiDocMerge")
+ assert isinstance(nwMerge, GuiDocMerge)
+ nwMerge.show()
+ qtbot.wait(stepDelay)
+
+ nwMerge._doMerge()
+ qtbot.wait(stepDelay)
+
+ assert nwGUI.theProject.projTree["73475cb40a568"] is not None
+
+ projFile = os.path.join(nwLipsum, "content", "73475cb40a568.nwd")
+ testFile = os.path.join(outDir, "guiMerge_73475cb40a568.nwd")
+ compFile = os.path.join(refDir, "guiMerge_73475cb40a568.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # Split By Chapter
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+
+ monkeypatch.setattr(GuiDocSplit, "exec_", lambda *args: None)
+ nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
+
+ nwSplit = getGuiItem("GuiDocSplit")
+ assert isinstance(nwSplit, GuiDocSplit)
+ nwSplit.show()
+ 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 = os.path.join(nwLipsum, "content", "71ee45a3c0db9.nwd")
+ testFile = os.path.join(outDir, "guiMerge_71ee45a3c0db9.nwd")
+ compFile = os.path.join(refDir, "guiMerge_73475cb40a568.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [1, 2, 3])
+
+ # Split By Scene
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
+
+ nwSplit = getGuiItem("GuiDocSplit")
+ assert isinstance(nwSplit, GuiDocSplit)
+ 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 = os.path.join(nwLipsum, "content", "25fc0e7096fc6.nwd")
+ testFile = os.path.join(outDir, "guiSplit_25fc0e7096fc6.nwd")
+ compFile = os.path.join(refDir, "guiSplit_25fc0e7096fc6.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "content", "31489056e0916.nwd")
+ testFile = os.path.join(outDir, "guiSplit_31489056e0916.nwd")
+ compFile = os.path.join(refDir, "guiSplit_31489056e0916.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "content", "98010bd9270f9.nwd")
+ testFile = os.path.join(outDir, "guiSplit_98010bd9270f9.nwd")
+ compFile = os.path.join(refDir, "guiSplit_98010bd9270f9.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # Split By Section
+ assert nwGUI.treeView.setSelectedHandle("73475cb40a568")
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiDocSplit") is not None, timeout=1000)
+
+ nwSplit = getGuiItem("GuiDocSplit")
+ assert isinstance(nwSplit, GuiDocSplit)
+ 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 = os.path.join(nwLipsum, "content", "1a6562590ef19.nwd")
+ testFile = os.path.join(outDir, "guiSplit_1a6562590ef19.nwd")
+ compFile = os.path.join(refDir, "guiSplit_25fc0e7096fc6.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [1, 2, 3])
+
+ projFile = os.path.join(nwLipsum, "content", "031b4af5197ec.nwd")
+ testFile = os.path.join(outDir, "guiSplit_031b4af5197ec.nwd")
+ compFile = os.path.join(refDir, "guiSplit_031b4af5197ec.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "content", "41cfc0d1f2d12.nwd")
+ testFile = os.path.join(outDir, "guiSplit_41cfc0d1f2d12.nwd")
+ compFile = os.path.join(refDir, "guiSplit_41cfc0d1f2d12.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "content", "2858dcd1057d3.nwd")
+ testFile = os.path.join(outDir, "guiSplit_2858dcd1057d3.nwd")
+ compFile = os.path.join(refDir, "guiSplit_2858dcd1057d3.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ projFile = os.path.join(nwLipsum, "content", "2fca346db6561.nwd")
+ testFile = os.path.join(outDir, "guiSplit_2fca346db6561.nwd")
+ compFile = os.path.join(refDir, "guiSplit_2fca346db6561.nwd")
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile)
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiMergeSplit_Tools
diff --git a/tests/test_gui_outline.py b/tests/test_gui_outline.py
new file mode 100644
index 00000000..cf93c111
--- /dev/null
+++ b/tests/test_gui_outline.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Outline Class Tester
+"""
+
+import pytest
+
+from PyQt5.QtCore import Qt, QPoint
+from PyQt5.QtWidgets import QAction, QTreeWidgetItem, QMessageBox
+
+from nw.constants import nwOutline
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
+ """Test the outline view.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ assert nwGUI.openProject(nwLipsum)
+ nwGUI.mainConf.lastPath = nwLipsum
+
+ nwGUI.rebuildIndex()
+ nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj)
+
+ assert nwGUI.projView.topLevelItemCount() > 0
+
+ # Context Menu
+ nwGUI.projView._headerRightClick(QPoint(1, 1))
+ nwGUI.projView.headerMenu.actionMap[nwOutline.CCOUNT].activate(QAction.Trigger)
+ nwGUI.projView.headerMenu.close()
+ qtbot.mouseClick(nwGUI.projView, Qt.LeftButton)
+
+ nwGUI.projView._loadHeaderState()
+ assert not nwGUI.projView.colHidden[nwOutline.CCOUNT]
+
+ # First Item
+ nwGUI.rebuildOutline()
+ selItem = nwGUI.projView.topLevelItem(0)
+ assert isinstance(selItem, QTreeWidgetItem)
+
+ nwGUI.projView.setCurrentItem(selItem)
+ assert nwGUI.projMeta.titleLabel.text() == "Title"
+ assert nwGUI.projMeta.titleValue.text() == "Lorem Ipsum"
+ assert nwGUI.projMeta.fileValue.text() == "Lorem Ipsum"
+ assert nwGUI.projMeta.itemValue.text() == "Finished"
+
+ assert nwGUI.projMeta.cCValue.text() == "230"
+ assert nwGUI.projMeta.wCValue.text() == "40"
+ assert nwGUI.projMeta.pCValue.text() == "3"
+
+ # Scene One
+ actItem = nwGUI.projView.topLevelItem(1)
+ chpItem = actItem.child(0)
+ selItem = chpItem.child(0)
+
+ nwGUI.projView.setCurrentItem(selItem)
+ assert nwGUI.projMeta.titleLabel.text() == "Scene"
+ assert nwGUI.projMeta.titleValue.text() == "Scene One"
+ assert nwGUI.projMeta.fileValue.text() == "Scene One"
+ assert nwGUI.projMeta.itemValue.text() == "Finished"
+
+ # Click POV Link
+ assert nwGUI.projMeta.povKeyValue.text() == "Bod"
+ nwGUI.projMeta._tagClicked("#pov=Bod")
+ assert nwGUI.docViewer.theHandle == "4c4f28287af27"
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiOutline_Main
diff --git a/tests/test_gui_preferences.py b/tests/test_gui_preferences.py
new file mode 100644
index 00000000..4c8b17ea
--- /dev/null
+++ b/tests/test_gui_preferences.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Dialog Class Tester
+"""
+
+import nw
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles, getGuiItem
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import (
+ QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog, QMessageBox
+)
+
+from nw.gui import GuiPreferences
+from nw.config import Config
+from nw.gui.custom import QuotesDialog
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
+ """Test the load project wizard.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
+
+ # Must create a clean config and GUI object as the test-wide
+ # nw.CONFIG object is created on import an can be tainted by other tests
+ confFile = os.path.join(fncDir, "novelwriter.conf")
+ if os.path.isfile(confFile):
+ os.unlink(confFile)
+ theConf = Config()
+ theConf.initConfig(fncDir, fncDir)
+ theConf.setLastPath("")
+ origConf = nw.CONFIG
+ nw.CONFIG = theConf
+
+ nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(20)
+
+ theConf = nwGUI.mainConf
+ assert theConf.confPath == fncDir
+
+ monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aPreferences.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000)
+
+ nwPrefs = getGuiItem("GuiPreferences")
+ assert isinstance(nwPrefs, GuiPreferences)
+ nwPrefs.show()
+ assert nwPrefs.mainConf.confPath == fncDir
+
+ # qtbot.stopForInteraction()
+ # General Settings
+ qtbot.wait(keyDelay)
+ tabGeneral = nwPrefs.tabGeneral
+ nwPrefs._tabBox.setCurrentWidget(tabGeneral)
+
+ 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.hideVScroll.isChecked()
+ qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton)
+ assert tabGeneral.hideVScroll.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabGeneral.hideHScroll.isChecked()
+ qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton)
+ assert tabGeneral.hideHScroll.isChecked()
+
+ # Check font button
+ monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True))
+ qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton)
+
+ qtbot.wait(keyDelay)
+ tabGeneral.guiFontSize.setValue(12)
+
+ # Projects Settings
+ qtbot.wait(keyDelay)
+ tabProjects = nwPrefs.tabProjects
+ nwPrefs._tabBox.setCurrentWidget(tabProjects)
+ tabProjects.backupPath = "no/where"
+
+ qtbot.wait(keyDelay)
+ assert not tabProjects.backupOnClose.isChecked()
+ qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton)
+ assert tabProjects.backupOnClose.isChecked()
+
+ # Check Browse button
+ monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "")
+ assert not tabProjects._backupFolder()
+ monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir")
+ qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton)
+
+ qtbot.wait(keyDelay)
+ tabProjects.autoSaveDoc.setValue(20)
+ tabProjects.autoSaveProj.setValue(40)
+
+ # Text Layout Settings
+ qtbot.wait(keyDelay)
+ tabLayout = nwPrefs.tabLayout
+ nwPrefs._tabBox.setCurrentWidget(tabLayout)
+
+ qtbot.wait(keyDelay)
+ qtbot.mouseClick(tabLayout.fontButton, Qt.LeftButton)
+
+ 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()
+
+ qtbot.wait(keyDelay)
+ assert tabLayout.scrollPastEnd.isChecked()
+ qtbot.mouseClick(tabLayout.scrollPastEnd, Qt.LeftButton)
+ assert not tabLayout.scrollPastEnd.isChecked()
+
+ qtbot.wait(keyDelay)
+ assert not tabLayout.autoScroll.isChecked()
+ qtbot.mouseClick(tabLayout.autoScroll, Qt.LeftButton)
+ assert tabLayout.autoScroll.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()
+
+ monkeypatch.setattr(QuotesDialog, "selectedQuote", "'")
+ monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted)
+ qtbot.mouseClick(tabAutoRep.btnDoubleStyleC, Qt.LeftButton)
+
+ # Save and Check Config
+ qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton)
+ nwPrefs._doClose()
+
+ assert theConf.confChanged
+ theConf.lastPath = ""
+
+ assert nwGUI.mainConf.saveConfig()
+ projFile = os.path.join(fncDir, "novelwriter.conf")
+ testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf")
+ compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
+ copyfile(projFile, testFile)
+ ignoreLines = [
+ 2, # Timestamp
+ 9, # Release Notes
+ 12, 13, 14, 15, 16, 17, 18, # Window sizes
+ 7, 28, # Fonts (depends on system default)
+ ]
+ assert cmpFiles(testFile, compFile, ignoreLines)
+
+ # Clean up
+ nw.CONFIG = origConf
+ nwGUI.closeMain()
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiPreferences_Main
diff --git a/tests/test_gui_projload.py b/tests/test_gui_projload.py
new file mode 100644
index 00000000..488d7798
--- /dev/null
+++ b/tests/test_gui_projload.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Dialog Class Tester
+"""
+
+import pytest
+import os
+
+from tools import getGuiItem
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import (
+ QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog,
+ QMessageBox
+)
+
+from nw.gui import GuiProjectLoad
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
+ """Test the load project wizard.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ assert nwGUI.openProject(nwMinimal)
+ assert nwGUI.closeProject()
+
+ qtbot.wait(stepDelay)
+ monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
+
+ nwLoad = getGuiItem("GuiProjectLoad")
+ assert isinstance(nwLoad, GuiProjectLoad)
+ nwLoad.show()
+
+ qtbot.wait(stepDelay)
+ recentCount = nwLoad.listBox.topLevelItemCount()
+ assert recentCount > 0
+
+ qtbot.wait(stepDelay)
+ selItem = nwLoad.listBox.topLevelItem(0)
+ selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole)
+ assert isinstance(selItem, QTreeWidgetItem)
+
+ qtbot.wait(stepDelay)
+ nwLoad.selPath.setText("")
+ nwLoad.listBox.setCurrentItem(selItem)
+ nwLoad._doSelectRecent()
+ assert nwLoad.selPath.text() == selPath
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton)
+ assert nwLoad.openPath == selPath
+ assert nwLoad.openState == nwLoad.OPEN_STATE
+
+ # Just create a new project load from scratch for the rest of the test
+ del nwLoad
+
+ qtbot.wait(stepDelay)
+ nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000)
+
+ qtbot.wait(stepDelay)
+ nwLoad = getGuiItem("GuiProjectLoad")
+ assert isinstance(nwLoad, GuiProjectLoad)
+ nwLoad.show()
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton)
+ assert nwLoad.openPath is None
+ assert nwLoad.openState == nwLoad.NONE_STATE
+
+ qtbot.wait(stepDelay)
+ nwLoad.show()
+ qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton)
+ assert nwLoad.openPath is None
+ assert nwLoad.openState == nwLoad.NEW_STATE
+
+ qtbot.wait(stepDelay)
+ nwLoad.show()
+ nwLoad._keyPressDelete()
+ assert nwLoad.listBox.topLevelItemCount() == recentCount - 1
+
+ getFile = os.path.join(nwMinimal, "nwProject.nwx")
+ monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None))
+ qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton)
+ assert nwLoad.openPath == nwMinimal
+ assert nwLoad.openState == nwLoad.OPEN_STATE
+
+ nwLoad.close()
+ # qtbot.stopForInteraction()
+
+# END Test testGuiLoadProject_Main
diff --git a/tests/test_gui_projsettings.py b/tests/test_gui_projsettings.py
new file mode 100644
index 00000000..2dcd92cb
--- /dev/null
+++ b/tests/test_gui_projsettings.py
@@ -0,0 +1,223 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Project Settings Dialog Class Tester
+"""
+
+import pytest
+import os
+
+from shutil import copyfile
+from tools import cmpFiles, getGuiItem
+
+from PyQt5.QtGui import QColor
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import (
+ QDialog, QAction, QMessageBox, QColorDialog, QListWidgetItem
+)
+
+from nw.gui import GuiProjectSettings
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDir, refDir):
+ """Test the full project settings dialog.
+ """
+ projFile = os.path.join(fncProj, "nwProject.nwx")
+ testFile = os.path.join(outDir, "guiProjSettings_Dialog_nwProject.nwx")
+ compFile = os.path.join(refDir, "guiProjSettings_Dialog_nwProject.nwx")
+
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ # Check that we cannot open when there is no project
+ nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+ assert getGuiItem("GuiProjectSettings") is None
+
+ # Create new project
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.newProject({"projPath": fncProj})
+ nwGUI.mainConf.backupPath = fncDir
+
+ nwGUI.theProject.setSpellLang("en")
+ nwGUI.theProject.setBookAuthors("Jane Smith\nJohn Smith")
+ nwGUI.theProject.setAutoReplace({"A": "B", "C": "D"})
+
+ # Get the dialog object
+ monkeypatch.setattr(GuiProjectSettings, "exec_", lambda *args: None)
+ monkeypatch.setattr(GuiProjectSettings, "result", lambda *args: QDialog.Accepted)
+ nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
+
+ projEdit = getGuiItem("GuiProjectSettings")
+ assert isinstance(projEdit, GuiProjectSettings)
+ projEdit.show()
+ qtbot.addWidget(projEdit)
+
+ # Settings Tab
+ # ============
+
+ assert projEdit.tabMain.editName.text() == "New Project"
+ assert projEdit.tabMain.editTitle.text() == ""
+ assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Smith\nJohn Smith\n"
+ assert projEdit.tabMain.spellLang.currentData() == "en"
+ assert projEdit.tabMain.doBackup.isChecked() is False
+
+ qtbot.wait(stepDelay)
+ projEdit.tabMain.editName.setText("")
+ for c in "Project Name":
+ qtbot.keyClick(projEdit.tabMain.editName, c, delay=typeDelay)
+ for c in "Project Title":
+ qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=typeDelay)
+
+ projEdit.tabMain.editAuthors.clear()
+ for c in "Jane Doe":
+ qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
+ qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
+ for c in "John Doh":
+ qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=typeDelay)
+ qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay)
+
+ qtbot.wait(stepDelay)
+ assert projEdit.tabMain.editName.text() == "Project Name"
+ assert projEdit.tabMain.editTitle.text() == "Project Title"
+ assert projEdit.tabMain.editAuthors.toPlainText() == "Jane Doe\nJohn Doh\n"
+
+ # Status Tab
+ # ==========
+
+ projEdit._tabBox.setCurrentWidget(projEdit.tabStatus)
+
+ assert projEdit.tabStatus.colChanged is False
+ assert projEdit.tabStatus.getNewList() is None
+ assert projEdit.tabStatus.listBox.count() == 4
+
+ # Fake drag'n'drop should change changed status
+ projEdit.tabStatus._rowsMoved()
+ assert projEdit.tabStatus.colChanged is True
+ projEdit.tabStatus.colChanged = False
+
+ projEdit.tabStatus.listBox.clearSelection()
+ assert projEdit.tabStatus._getSelectedItem() is None
+ projEdit.tabStatus.listBox.item(0).setSelected(True)
+ assert isinstance(projEdit.tabStatus._getSelectedItem(), QListWidgetItem)
+
+ # Can't delete the first item (it's in use)
+ projEdit.tabStatus.listBox.item(0).setSelected(True)
+ qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
+ assert projEdit.tabStatus.listBox.count() == 4
+
+ # Can delete the third item
+ projEdit.tabStatus.listBox.item(2).setSelected(True)
+ qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton)
+ assert projEdit.tabStatus.listBox.count() == 3
+
+ # Add a new item
+ monkeypatch.setattr(QColorDialog, "getColor", lambda *args: QColor(20, 30, 40))
+ 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=typeDelay)
+ for c in "Final":
+ qtbot.keyClick(projEdit.tabStatus.editName, c, delay=typeDelay)
+ qtbot.mouseClick(projEdit.tabStatus.colButton, Qt.LeftButton)
+ qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton)
+ assert projEdit.tabStatus.listBox.count() == 4
+ qtbot.wait(stepDelay)
+
+ assert projEdit.tabStatus.colChanged is True
+ assert projEdit.tabStatus.getNewList() == [
+ ("New", 100, 100, 100, "New"),
+ ("Note", 200, 50, 0, "Note"),
+ ("Finished", 50, 200, 0, "Finished"),
+ ("Final", 20, 30, 40, None)
+ ]
+
+ # Importance Tab
+ # ==============
+
+ projEdit._tabBox.setCurrentWidget(projEdit.tabImport)
+ projEdit.tabImport.listBox.item(3).setSelected(True)
+ qtbot.mouseClick(projEdit.tabImport.delButton, Qt.LeftButton)
+ qtbot.mouseClick(projEdit.tabImport.newButton, Qt.LeftButton)
+ projEdit.tabImport.listBox.item(3).setSelected(True)
+ for n in range(8):
+ qtbot.keyClick(projEdit.tabImport.editName, Qt.Key_Backspace, delay=typeDelay)
+ for c in "Final":
+ qtbot.keyClick(projEdit.tabImport.editName, c, delay=typeDelay)
+ qtbot.mouseClick(projEdit.tabImport.saveButton, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+
+ # Auto-Replace Tab
+ # ================
+
+ qtbot.wait(stepDelay)
+ projEdit._tabBox.setCurrentWidget(projEdit.tabReplace)
+
+ assert projEdit.tabReplace.listBox.topLevelItem(0).text(0) == ""
+ assert projEdit.tabReplace.listBox.topLevelItem(0).text(1) == "B"
+ assert projEdit.tabReplace.listBox.topLevelItem(1).text(0) == ""
+ assert projEdit.tabReplace.listBox.topLevelItem(1).text(1) == "D"
+
+ qtbot.mouseClick(projEdit.tabReplace.addButton, Qt.LeftButton)
+ projEdit.tabReplace.listBox.topLevelItem(2).setSelected(True)
+ projEdit.tabReplace.editKey.setText("")
+ for c in "Th is ":
+ qtbot.keyClick(projEdit.tabReplace.editKey, c, delay=typeDelay)
+ projEdit.tabReplace.editValue.setText("")
+ for c in "With This Stuff ":
+ qtbot.keyClick(projEdit.tabReplace.editValue, c, delay=typeDelay)
+ qtbot.mouseClick(projEdit.tabReplace.saveButton, Qt.LeftButton)
+
+ qtbot.wait(stepDelay)
+ projEdit.tabReplace.listBox.clearSelection()
+ assert not projEdit.tabReplace._saveEntry()
+ assert not projEdit.tabReplace._delEntry()
+ 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)
+
+ # Save & Check
+ # ============
+
+ projEdit._doSave()
+
+ # Open again, and check project settings
+ nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectSettings") is not None, timeout=1000)
+
+ projEdit = getGuiItem("GuiProjectSettings")
+ assert isinstance(projEdit, GuiProjectSettings)
+
+ 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
+ copyfile(projFile, testFile)
+ assert cmpFiles(testFile, compFile, [2, 8, 9, 10])
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiProjSettings_Dialog
diff --git a/tests/test_gui_projtree.py b/tests/test_gui_projtree.py
new file mode 100644
index 00000000..94726348
--- /dev/null
+++ b/tests/test_gui_projtree.py
@@ -0,0 +1,138 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Project Tree Class Tester
+"""
+
+import pytest
+import os
+
+from PyQt5.QtCore import QItemSelectionModel
+from PyQt5.QtWidgets import QAction, QMessageBox
+
+from nw.constants import nwItemType, nwItemClass
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiProjTree_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
+ """Test the project tree.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI.theProject.projTree.setSeed(42)
+ assert nwGUI.openProject(nwMinimal)
+ nwTree = nwGUI.treeView
+
+ # No location selected for new item
+ assert not nwTree.newTreeItem(nwItemType.FILE, None)
+ assert not nwTree.newTreeItem(nwItemType.FOLDER, None)
+
+ # Select a location
+ chItem = nwTree._getTreeItem("a6d311a93600a")
+ nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
+ chItem.setExpanded(True)
+
+ # Create new item with no class set
+ assert nwTree.newTreeItem(nwItemType.FILE, None)
+ assert nwTree.newTreeItem(nwItemType.FOLDER, None)
+
+ # Add roots
+ assert not nwTree.newTreeItem(nwItemType.ROOT, None) # Defaults to NOVEL
+ assert not nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) # Duplicate
+ assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) # Valid
+
+ # Check that we have the correct tree order
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "73475cb40a568", "44cb730c42048"
+ ]
+
+ # Move second item up twice (should give same result)
+ nwTree.setSelectedHandle("8c659a11cd429")
+ nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048"
+ ]
+ nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048"
+ ]
+
+ # Move it back down four times (last to should be the same)
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "73475cb40a568", "44cb730c42048"
+ ]
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "8c659a11cd429", "44cb730c42048"
+ ]
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048", "8c659a11cd429"
+ ]
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("a6d311a93600a") == [
+ "a6d311a93600a", "f5ab3e30151e1", "73475cb40a568", "44cb730c42048", "8c659a11cd429"
+ ]
+
+ # Move a root item (top level items are different) twice
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 9
+ nwTree.setSelectedHandle("9d5247ab588e0")
+
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
+
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
+
+ # Add some content to the new file
+ nwGUI.openDocument("73475cb40a568")
+ nwGUI.docEditor.setText("# Hello World\n")
+ nwGUI.saveDocument()
+ assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
+
+ # Delete the items we added earlier
+ nwTree.clearSelection()
+ assert not nwTree.emptyTrash() # No folder yet
+ assert not nwTree.deleteItem(None)
+ assert not nwTree.deleteItem("1111111111111")
+ assert nwTree.deleteItem("73475cb40a568") # New File
+ assert nwTree.deleteItem("44cb730c42048") # New Folder
+ assert nwTree.deleteItem("71ee45a3c0db9") # Custom Root
+ assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
+ assert "44cb730c42048" not in nwGUI.theProject.projTree._treeOrder
+ assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
+
+ # The file is in trash, empty it
+ assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
+ assert nwTree.emptyTrash()
+ assert not nwTree.emptyTrash() # Already empty
+ assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
+ assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
+
+ # Close the project
+ nwGUI.closeProject()
+
+ # Add an orphaned file
+ orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
+ with open(orphFile, mode="w+", encoding="utf8") as outFile:
+ outFile.write("# Hello World\n")
+
+ # Open the project again
+ nwGUI.openProject(nwMinimal)
+
+ # Check that the orphaned file was found and added to the tree
+ assert nwTree.orphRoot is not None
+ nwTree.flushTreeOrder()
+ assert "1234567890abc" not in nwGUI.theProject.projTree._treeOrder
+ orItem = nwTree._getTreeItem("1234567890abc")
+ assert orItem.text(nwTree.C_NAME) == "Orphaned File 1"
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiProjTree_Main
diff --git a/tests/test_gui_projwizard.py b/tests/test_gui_projwizard.py
new file mode 100644
index 00000000..e855b002
--- /dev/null
+++ b/tests/test_gui_projwizard.py
@@ -0,0 +1,212 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Project Wizard Class Tester
+"""
+
+import pytest
+import os
+import sys
+
+from tools import getGuiItem
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox
+
+from nw.gui import GuiProjectWizard
+from nw.constants import nwItemClass
+from nw.gui.projwizard import (
+ ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage,
+ ProjWizardCustomPage, ProjWizardFinalPage
+)
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal):
+ """Test the new project wizard.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ if sys.platform.startswith("darwin"):
+ # Disable for macOS because the test segfaults on QWizard.show()
+ return
+
+ ##
+ # Test New Project Function
+ ##
+
+ # New with a project open should cause an error
+ assert nwGUI.openProject(nwMinimal)
+ assert not nwGUI.newProject()
+
+ # Close project, but call with invalid path
+ assert nwGUI.closeProject()
+ monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None)
+ assert not nwGUI.newProject()
+
+ # Now, with an empty dictionary
+ monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {})
+ assert not nwGUI.newProject()
+
+ # Now, with a non-empty folder
+ monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal})
+ assert not nwGUI.newProject()
+
+ monkeypatch.undo()
+
+ ##
+ # Test the Wizard
+ ##
+
+ monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None)
+ nwGUI.mainConf.lastPath = " "
+
+ nwGUI.closeProject()
+ nwGUI.showNewProjectDialog()
+ qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000)
+
+ nwWiz = getGuiItem("GuiProjectWizard")
+ assert isinstance(nwWiz, GuiProjectWizard)
+ nwWiz.show()
+ qtbot.wait(stepDelay)
+
+ for wStep in range(4):
+ # This does not actually create the project, it just generates the
+ # dictionary that defines it.
+
+ # Intro Page
+ introPage = nwWiz.currentPage()
+ assert isinstance(introPage, ProjWizardIntroPage)
+ assert not nwWiz.button(QWizard.NextButton).isEnabled()
+
+ qtbot.wait(stepDelay)
+ for c in ("Test Minimal %d" % wStep):
+ qtbot.keyClick(introPage.projName, c, delay=typeDelay)
+
+ qtbot.wait(stepDelay)
+ for c in "Minimal Novel":
+ qtbot.keyClick(introPage.projTitle, c, delay=typeDelay)
+
+ qtbot.wait(stepDelay)
+ for c in "Jane Doe":
+ qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay)
+
+ # 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()
+
+ if wStep == 0:
+ # Check invalid path first, the first time we reach here
+ monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "")
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
+ assert storagePage.projPath.text() == ""
+
+ # Then, we always return nwMinimal as path
+ monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal)
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100)
+ projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep)
+ assert storagePage.projPath.text() == projPath
+
+ # 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.popCustom.setChecked(True)
+ elif wStep == 3:
+ popPage.popSample.setChecked(True)
+
+ qtbot.wait(stepDelay)
+ qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton)
+
+ # Custom Page
+ if wStep == 1 or wStep == 2:
+ 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)
+
+ if wStep == 2:
+ customPage.numChapters.setValue(0)
+ customPage.numScenes.setValue(10)
+ customPage.chFolders.setChecked(False)
+
+ 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() # But we don't click it
+
+ # Check Data
+ projData = nwGUI._assembleProjectWizardData(nwWiz)
+ assert projData["projName"] == "Test Minimal %d" % wStep
+ assert projData["projTitle"] == "Minimal Novel"
+ assert projData["projAuthors"] == "Jane Doe"
+ assert projData["projPath"] == projPath
+ assert projData["popMinimal"] == (wStep == 0)
+ assert projData["popCustom"] == (wStep == 1 or wStep == 2)
+ assert projData["popSample"] == (wStep == 3)
+ if wStep == 1 or wStep == 2:
+ assert projData["addRoots"] == [
+ nwItemClass.PLOT,
+ nwItemClass.CHARACTER,
+ nwItemClass.WORLD,
+ nwItemClass.TIMELINE,
+ nwItemClass.OBJECT,
+ nwItemClass.ENTITY,
+ ]
+ if wStep == 1:
+ assert projData["numChapters"] == 5
+ assert projData["numScenes"] == 5
+ assert projData["chFolders"]
+ else:
+ assert projData["numChapters"] == 0
+ assert projData["numScenes"] == 10
+ assert not projData["chFolders"]
+ else:
+ assert projData["addRoots"] == []
+ assert projData["numChapters"] == 0
+ assert projData["numScenes"] == 0
+ assert not projData["chFolders"]
+
+ # Restart the wizard for next iteration
+ nwWiz.restart()
+
+ nwWiz.reject()
+ nwWiz.close()
+
+ # qtbot.stopForInteraction()
+
+# END Test testGuiProjectWizard_Main
diff --git a/tests/test_gui_theme.py b/tests/test_gui_theme.py
new file mode 100644
index 00000000..4214af59
--- /dev/null
+++ b/tests/test_gui_theme.py
@@ -0,0 +1,170 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Main GUI Class Tester
+"""
+
+import nw
+import pytest
+
+from PyQt5.QtGui import QColor, QPixmap, QIcon
+from PyQt5.QtWidgets import QStyle, QMessageBox
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
+ """Test the theme and icon classes.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(500)
+
+ # Change Settings
+ assert nw.CONFIG.confPath == nwMinimal
+ nw.CONFIG.guiTheme = "default_dark"
+ nw.CONFIG.guiSyntax = "tomorrow_night_eighties"
+ nw.CONFIG.guiIcons = "typicons_colour_dark"
+ nw.CONFIG.guiDark = True
+ nw.CONFIG.guiFont = "Cantarell"
+ nw.CONFIG.guiFontSize = 11
+ nw.CONFIG.confChanged = True
+ assert nw.CONFIG.saveConfig()
+
+ nwGUI.closeMain()
+ nwGUI.close()
+ del nwGUI
+
+ # Re-open
+ assert nw.CONFIG.confPath == nwMinimal
+ nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal])
+ assert nwGUI.mainConf.confPath == nwMinimal
+ qtbot.addWidget(nwGUI)
+ nwGUI.show()
+ qtbot.waitForWindowShown(nwGUI)
+ qtbot.wait(500)
+
+ assert nw.CONFIG.guiTheme == "default_dark"
+ assert nw.CONFIG.guiSyntax == "tomorrow_night_eighties"
+ assert nw.CONFIG.guiIcons == "typicons_colour_dark"
+ assert nw.CONFIG.guiDark is True
+ assert nw.CONFIG.guiFont == "Cantarell"
+ assert nw.CONFIG.guiFontSize == 11
+
+ # Check GUI Colours
+ thePalette = nwGUI.palette()
+ assert thePalette.window().color() == QColor(54, 54, 54)
+ assert thePalette.windowText().color() == QColor(174, 174, 174)
+ assert thePalette.base().color() == QColor(62, 62, 62)
+ assert thePalette.alternateBase().color() == QColor(67, 67, 67)
+ assert thePalette.text().color() == QColor(174, 174, 174)
+ assert thePalette.toolTipBase().color() == QColor(255, 255, 192)
+ assert thePalette.toolTipText().color() == QColor(21, 21, 13)
+ assert thePalette.button().color() == QColor(62, 62, 62)
+ assert thePalette.buttonText().color() == QColor(174, 174, 174)
+ assert thePalette.brightText().color() == QColor(174, 174, 174)
+ assert thePalette.highlight().color() == QColor(44, 152, 247)
+ assert thePalette.highlightedText().color() == QColor(255, 255, 255)
+ assert thePalette.link().color() == QColor(44, 152, 247)
+ assert thePalette.linkVisited().color() == QColor(44, 152, 247)
+
+ assert nwGUI.theTheme.treeWCount == [197, 200, 198]
+ assert nwGUI.theTheme.statNone == [150, 152, 150]
+ assert nwGUI.theTheme.statSaved == [39, 135, 78]
+ assert nwGUI.theTheme.statUnsaved == [138, 32, 32]
+
+ # Check Syntax Colours
+ assert nwGUI.theTheme.colBack == [45, 45, 45]
+ assert nwGUI.theTheme.colText == [204, 204, 204]
+ assert nwGUI.theTheme.colLink == [102, 153, 204]
+ assert nwGUI.theTheme.colHead == [102, 153, 204]
+ assert nwGUI.theTheme.colHeadH == [102, 153, 204]
+ assert nwGUI.theTheme.colEmph == [249, 145, 57]
+ assert nwGUI.theTheme.colDialN == [242, 119, 122]
+ assert nwGUI.theTheme.colDialD == [153, 204, 153]
+ assert nwGUI.theTheme.colDialS == [255, 204, 102]
+ assert nwGUI.theTheme.colHidden == [153, 153, 153]
+ assert nwGUI.theTheme.colKey == [242, 119, 122]
+ assert nwGUI.theTheme.colVal == [204, 153, 204]
+ assert nwGUI.theTheme.colSpell == [242, 119, 122]
+ assert nwGUI.theTheme.colTagErr == [153, 204, 153]
+ assert nwGUI.theTheme.colRepTag == [102, 204, 204]
+ assert nwGUI.theTheme.colMod == [249, 145, 57]
+
+ # Test Icon class
+ theIcons = nwGUI.theTheme.theIcons
+ nw.CONFIG.guiIcons = "invalid"
+ assert not theIcons.updateTheme()
+ nw.CONFIG.guiIcons = "typicons_colour_dark"
+ assert theIcons.updateTheme()
+
+ # Ask for a non-existent key
+ anImg = theIcons.loadDecoration("nonsense", 20, 20)
+ assert isinstance(anImg, QPixmap)
+ assert anImg.isNull()
+
+ # Add a non-existent file and request it
+ theIcons.DECO_MAP["nonsense"] = "nofile.jpg"
+ anImg = theIcons.loadDecoration("nonsense", 20, 20)
+ assert isinstance(anImg, QPixmap)
+ assert anImg.isNull()
+
+ # Get a real image, with different size parameters
+ anImg = theIcons.loadDecoration("wiz-back", 20, None)
+ assert isinstance(anImg, QPixmap)
+ assert not anImg.isNull()
+ assert anImg.width() == 20
+ assert anImg.height() >= 56
+
+ anImg = theIcons.loadDecoration("wiz-back", None, 70)
+ assert isinstance(anImg, QPixmap)
+ assert not anImg.isNull()
+ assert anImg.height() == 70
+ assert anImg.width() >= 24
+
+ anImg = theIcons.loadDecoration("wiz-back", 30, 70)
+ assert isinstance(anImg, QPixmap)
+ assert not anImg.isNull()
+ assert anImg.height() == 70
+ assert anImg.width() == 30
+
+ anImg = theIcons.loadDecoration("wiz-back", None, None)
+ assert isinstance(anImg, QPixmap)
+ assert not anImg.isNull()
+ assert anImg.height() >= 1500
+ assert anImg.width() >= 500
+
+ # Load icons
+ anIcon = theIcons.getIcon("nonsense")
+ assert isinstance(anIcon, QIcon)
+ assert anIcon.isNull()
+
+ anIcon = theIcons.getIcon("novelwriter")
+ assert isinstance(anIcon, QIcon)
+ assert not anIcon.isNull()
+
+ # Add dummy icons and test alternative load paths
+ theIcons.ICON_MAP["testicon1"] = (QStyle.SP_DriveHDIcon, None)
+ anIcon = theIcons.getIcon("testicon1")
+ assert isinstance(anIcon, QIcon)
+ assert not anIcon.isNull()
+
+ theIcons.ICON_MAP["testicon2"] = (None, "folder")
+ anIcon = theIcons.getIcon("testicon2")
+ assert isinstance(anIcon, QIcon)
+
+ theIcons.ICON_MAP["testicon3"] = (None, None)
+ anIcon = theIcons.getIcon("testicon3")
+ assert isinstance(anIcon, QIcon)
+ assert anIcon.isNull()
+
+ # qtbot.stopForInteraction()
+ nwGUI.closeMain()
+ nwGUI.close()
+
+# END Test testGuiTheme_Main
diff --git a/tests/test_gui_writingstats.py b/tests/test_gui_writingstats.py
new file mode 100644
index 00000000..62f8af9f
--- /dev/null
+++ b/tests/test_gui_writingstats.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+"""novelWriter Writing Stats Dialog Class Tester
+"""
+
+import pytest
+import json
+import os
+
+from tools import getGuiItem, writeFile
+from dummy import causeOSError
+
+from PyQt5.QtCore import Qt
+from PyQt5.QtWidgets import QAction, QFileDialog, QMessageBox
+
+from nw.gui import GuiWritingStats
+from nw.constants import nwFiles
+
+keyDelay = 2
+typeDelay = 1
+stepDelay = 20
+
+@pytest.mark.gui
+def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
+ """Test the full writing stats tool.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *args: QMessageBox.Yes)
+
+ # Create a project to work on
+ assert nwGUI.newProject({"projPath": fncProj})
+ qtbot.wait(100)
+ assert nwGUI.saveProject()
+ sessFile = os.path.join(fncProj, "meta", nwFiles.SESS_STATS)
+
+ # Open the Writing Stats dialog
+ nwGUI.mainConf.lastPath = ""
+ nwGUI.mainMenu.aWritingStats.activate(QAction.Trigger)
+ qtbot.waitUntil(lambda: getGuiItem("GuiWritingStats") is not None, timeout=1000)
+
+ sessLog = getGuiItem("GuiWritingStats")
+ assert isinstance(sessLog, GuiWritingStats)
+ qtbot.wait(stepDelay)
+
+ # Test Loading
+ # ============
+
+ # No initial logfile
+ assert not os.path.isfile(sessFile)
+ assert not sessLog._loadLogFile()
+
+ # Make a test log file
+ writeFile(sessFile, (
+ "# Offset 123\n"
+ "# Start Time End Time Novel Notes\n"
+ "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
+ "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
+ "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
+ "2020-01-06 21:00:00 2020-01-06 21:00:10 125 5\n"
+ ))
+ assert os.path.isfile(sessFile)
+ assert sessLog._loadLogFile()
+ assert sessLog.wordOffset == 123
+ assert len(sessLog.logData) == 4
+
+ # Make sure a faulty file can still be read
+ writeFile(sessFile, (
+ "# Offset abc123\n"
+ "# Start Time End Time Novel Notes\n"
+ "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
+ "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
+ "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
+ "2020-01-06 21:00:00 2020-01-06 21:00:10 125\n"
+ ))
+ assert sessLog._loadLogFile()
+ assert sessLog.wordOffset == 0
+ assert len(sessLog.logData) == 3
+
+ # Test Exporting
+ # ==============
+
+ writeFile(sessFile, (
+ "# Start Time End Time Novel Notes\n"
+ "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
+ "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
+ "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
+ "2020-01-06 21:00:00 2020-01-06 21:00:10 125 5\n"
+ "2020-01-08 21:00:00 2020-01-08 21:00:10 120 5\n"
+ ))
+ sessLog.populateGUI()
+
+ # Make the saving fail
+ monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda *args, **kwargs: ("", ""))
+ assert not sessLog._saveData(sessLog.FMT_CSV)
+ assert not sessLog._saveData(sessLog.FMT_JSON)
+ assert not sessLog._saveData(None)
+
+ # Make the save succeed
+ monkeypatch.setattr("os.path.expanduser", lambda *args: fncDir)
+ monkeypatch.setattr(QFileDialog, "getSaveFileName", lambda ss, tt, pp, options: (pp, ""))
+
+ assert sessLog._saveData(sessLog.FMT_CSV)
+ qtbot.wait(100)
+
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(100)
+
+ assert nwGUI.mainConf.lastPath == fncDir
+
+ # Check the exported files
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.load(inFile)
+
+ assert len(jsonData) == 4
+ assert jsonData[1]["length"] >= 14.0
+ assert jsonData[1]["newWords"] == 119
+ assert jsonData[1]["novelWords"] == 125
+ assert jsonData[1]["noteWords"] == 0
+
+ # Test Filters
+ # ============
+
+ # No Novel Files
+ qtbot.mouseClick(sessLog.incNovel, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ assert sessLog.novelWords.text() == "{:n}".format(120)
+ assert sessLog.notesWords.text() == "{:n}".format(5)
+ assert sessLog.totalWords.text() == "{:n}".format(125)
+
+ jsonStats = os.path.join(fncDir, "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"] >= 14.0
+ assert jsonData[0]["newWords"] == 5
+ assert jsonData[0]["novelWords"] == 125
+ 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 = os.path.join(fncDir, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.load(inFile)
+
+ assert len(jsonData) == 3
+ assert jsonData[1]["length"] >= 14.0
+ assert jsonData[1]["newWords"] == 119
+ assert jsonData[1]["novelWords"] == 125
+ assert jsonData[1]["noteWords"] == 0
+
+ # 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 = os.path.join(fncDir, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.load(inFile)
+
+ assert len(jsonData) == 3
+
+ # 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 = os.path.join(fncDir, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.load(inFile)
+
+ assert len(jsonData) == 5
+
+ # Group by Day
+ qtbot.mouseClick(sessLog.groupByDay, Qt.LeftButton)
+ qtbot.wait(stepDelay)
+ assert sessLog._saveData(sessLog.FMT_JSON)
+ qtbot.wait(stepDelay)
+
+ jsonStats = os.path.join(fncDir, "sessionStats.json")
+ with open(jsonStats, mode="r", encoding="utf-8") as inFile:
+ jsonData = json.load(inFile)
+
+ # 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) == 4
+
+ # IOError
+ # =======
+ monkeypatch.setattr("builtins.open", causeOSError)
+ assert not sessLog._saveData(sessLog.FMT_CSV)
+
+ # qtbot.stopForInteraction()
+
+ sessLog._doClose()
+ assert nwGUI.closeProject()
+ qtbot.wait(stepDelay)
+
+ monkeypatch.undo()
+
+# END Test testGuiWritingStats_Dialog