From 70eadd11cbf515e0360f9391d0e7aec6ab1fb064 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 14:44:33 +0100 Subject: [PATCH 1/7] Separated out writing stats test and updated it --- nw/gui/writingstats.py | 48 +++----- tests/README.md | 35 +++--- tests/conftest.py | 50 ++++++-- tests/test_gui_dialogs.py | 145 +--------------------- tests/test_gui_writingstats.py | 216 +++++++++++++++++++++++++++++++++ 5 files changed, 298 insertions(+), 196 deletions(-) create mode 100644 tests/test_gui_writingstats.py 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..772ce216 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,20 +59,21 @@ Available markers are: 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 | Writing Stats GUI | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | diff --git a/tests/conftest.py b/tests/conftest.py index ea9a92b3..aef333ed 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -13,6 +13,8 @@ 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 +57,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 +103,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 ## diff --git a/tests/test_gui_dialogs.py b/tests/test_gui_dialogs.py index 96f64da4..49b7b203 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -4,7 +4,6 @@ import nw import pytest -import json import os import sys @@ -19,11 +18,10 @@ from PyQt5.QtWidgets import ( from nw.gui import ( GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel, - GuiDocMerge, GuiDocSplit, GuiWritingStats, GuiProjectWizard, - GuiProjectLoad, GuiPreferences + GuiDocMerge, GuiDocSplit, GuiProjectWizard, GuiProjectLoad, GuiPreferences ) from nw.gui.custom import QuotesDialog -from nw.constants import nwItemLayout, nwItemClass, nwFiles +from nw.constants import nwItemLayout, nwItemClass keyDelay = 2 typeDelay = 1 @@ -217,145 +215,6 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, fncDir, nwTempGUI, refDir, tmpD # 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]) diff --git a/tests/test_gui_writingstats.py b/tests/test_gui_writingstats.py new file mode 100644 index 00000000..86e8e2c9 --- /dev/null +++ b/tests/test_gui_writingstats.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- +"""novelWriter 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_All(qtbot, monkeypatch, nwGUI, fncDir, fncProj): + """Test the full writing stats tool. + """ + # Block questions dialog + 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_All From e258ff60d0f5b28b1075e93938a5bd62984603e5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 15:51:03 +0100 Subject: [PATCH 2/7] Separated out project settings test and updated it --- nw/gui/projsettings.py | 9 +- ...x => guiProjSettings_Dialog_nwProject.nwx} | 10 +- tests/test_gui_dialogs.py | 119 +-------- tests/test_gui_projsettings.py | 226 ++++++++++++++++++ tests/test_gui_writingstats.py | 8 +- 5 files changed, 241 insertions(+), 131 deletions(-) rename tests/reference/{gui/2_nwProject.nwx => guiProjSettings_Dialog_nwProject.nwx} (93%) create mode 100644 tests/test_gui_projsettings.py 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/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/test_gui_dialogs.py b/tests/test_gui_dialogs.py index 49b7b203..70f996da 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -17,8 +17,8 @@ from PyQt5.QtWidgets import ( ) from nw.gui import ( - GuiProjectSettings, GuiItemEditor, GuiAbout, GuiBuildNovel, - GuiDocMerge, GuiDocSplit, GuiProjectWizard, GuiProjectLoad, GuiPreferences + GuiItemEditor, GuiAbout, GuiBuildNovel, GuiDocMerge, GuiDocSplit, + GuiProjectWizard, GuiProjectLoad, GuiPreferences ) from nw.gui.custom import QuotesDialog from nw.constants import nwItemLayout, nwItemClass @@ -27,121 +27,6 @@ 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]) diff --git a/tests/test_gui_projsettings.py b/tests/test_gui_projsettings.py new file mode 100644 index 00000000..421d8f0c --- /dev/null +++ b/tests/test_gui_projsettings.py @@ -0,0 +1,226 @@ +# -*- 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) + # monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes) + # monkeypatch.setattr(QMessageBox, "warning", lambda *args: QMessageBox.Yes) + # monkeypatch.setattr(QMessageBox, "critical", 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_writingstats.py b/tests/test_gui_writingstats.py index 86e8e2c9..62f8af9f 100644 --- a/tests/test_gui_writingstats.py +++ b/tests/test_gui_writingstats.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -"""novelWriter Dialog Class Tester +"""novelWriter Writing Stats Dialog Class Tester """ import pytest @@ -20,10 +20,10 @@ typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testGuiWritingStats_All(qtbot, monkeypatch, nwGUI, fncDir, fncProj): +def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj): """Test the full writing stats tool. """ - # Block questions dialog + # 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) @@ -213,4 +213,4 @@ def testGuiWritingStats_All(qtbot, monkeypatch, nwGUI, fncDir, fncProj): monkeypatch.undo() -# END Test testGuiWritingStats_All +# END Test testGuiWritingStats_Dialog From 9eb8158c36e16a9905c0aba2c19cb488182fd1db Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 15:59:35 +0100 Subject: [PATCH 3/7] Separated out about test and updated it --- tests/README.md | 38 ++++++++++++++-------------- tests/test_gui_about.py | 52 +++++++++++++++++++++++++++++++++++++++ tests/test_gui_dialogs.py | 46 +++------------------------------- 3 files changed, 75 insertions(+), 61 deletions(-) create mode 100644 tests/test_gui_about.py diff --git a/tests/README.md b/tests/README.md index 772ce216..e285fece 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,21 +59,23 @@ Available markers are: 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` | -| Integration | Writing Stats GUI | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | +| 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 | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` | +| Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | 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_dialogs.py b/tests/test_gui_dialogs.py index 70f996da..191c5e8e 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -13,12 +13,12 @@ from tools import cmpFiles, getGuiItem from PyQt5.QtCore import Qt, QItemSelectionModel from PyQt5.QtWidgets import ( QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog, QAction, - QMessageBox, QFileDialog, QFontDialog + QFileDialog, QFontDialog ) from nw.gui import ( - GuiItemEditor, GuiAbout, GuiBuildNovel, GuiDocMerge, GuiDocSplit, - GuiProjectWizard, GuiProjectLoad, GuiPreferences + GuiItemEditor, GuiBuildNovel, GuiDocMerge, GuiDocSplit, GuiProjectWizard, + GuiProjectLoad, GuiPreferences ) from nw.gui.custom import QuotesDialog from nw.constants import nwItemLayout, nwItemClass @@ -100,46 +100,6 @@ def testItemEditor(qtbot, yesToAll, monkeypatch, fncDir, nwTempGUI, refDir, tmpD # qtbot.stopForInteraction() 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): From c37d4013f8e24d64bbcbea28ec50a8b2fa0472b5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 16:56:15 +0100 Subject: [PATCH 4/7] Refactored build, merge, split, and item editor tests --- tests/README.md | 42 +- tests/conftest.py | 9 - ...tm => guiBuild_Tool_Step1_Lorem_Ipsum.htm} | 0 ...wd => guiBuild_Tool_Step1_Lorem_Ipsum.nwd} | 0 ...tm => guiBuild_Tool_Step2_Lorem_Ipsum.htm} | 0 ...wd => guiBuild_Tool_Step2_Lorem_Ipsum.nwd} | 0 ...tm => guiBuild_Tool_Step3_Lorem_Ipsum.htm} | 0 ...wd => guiBuild_Tool_Step3_Lorem_Ipsum.nwd} | 0 ... => guiBuild_Tool_Step4H_Lorem_Ipsum.json} | 0 ... => guiBuild_Tool_Step4M_Lorem_Ipsum.json} | 0 ...tm => guiBuild_Tool_Step4_Lorem_Ipsum.htm} | 0 ...wd => guiBuild_Tool_Step4_Lorem_Ipsum.nwd} | 0 ...nwx => guiItemEditor_Dialog_nwProject.nwx} | 0 ...b40a568.nwd => guiMerge_73475cb40a568.nwd} | 0 ...f5197ec.nwd => guiSplit_031b4af5197ec.nwd} | 0 ...7096fc6.nwd => guiSplit_25fc0e7096fc6.nwd} | 0 ...d1057d3.nwd => guiSplit_2858dcd1057d3.nwd} | 0 ...6db6561.nwd => guiSplit_2fca346db6561.nwd} | 0 ...56e0916.nwd => guiSplit_31489056e0916.nwd} | 0 ...d1f2d12.nwd => guiSplit_41cfc0d1f2d12.nwd} | 0 ...d9270f9.nwd => guiSplit_98010bd9270f9.nwd} | 0 tests/test_gui_build.py | 209 +++++++++ tests/test_gui_dialogs.py | 423 +----------------- tests/test_gui_itemeditor.py | 92 ++++ tests/test_gui_mergesplit.py | 167 +++++++ tests/test_gui_projsettings.py | 3 - 26 files changed, 492 insertions(+), 453 deletions(-) rename tests/reference/{build/1_LoremIpsum.htm => guiBuild_Tool_Step1_Lorem_Ipsum.htm} (100%) rename tests/reference/{build/1_LoremIpsum.nwd => guiBuild_Tool_Step1_Lorem_Ipsum.nwd} (100%) rename tests/reference/{build/2_LoremIpsum.htm => guiBuild_Tool_Step2_Lorem_Ipsum.htm} (100%) rename tests/reference/{build/2_LoremIpsum.nwd => guiBuild_Tool_Step2_Lorem_Ipsum.nwd} (100%) rename tests/reference/{build/3_LoremIpsum.htm => guiBuild_Tool_Step3_Lorem_Ipsum.htm} (100%) rename tests/reference/{build/3_LoremIpsum.nwd => guiBuild_Tool_Step3_Lorem_Ipsum.nwd} (100%) rename tests/reference/{build/4H_LoremIpsum.json => guiBuild_Tool_Step4H_Lorem_Ipsum.json} (100%) rename tests/reference/{build/4M_LoremIpsum.json => guiBuild_Tool_Step4M_Lorem_Ipsum.json} (100%) rename tests/reference/{build/4_LoremIpsum.htm => guiBuild_Tool_Step4_Lorem_Ipsum.htm} (100%) rename tests/reference/{build/4_LoremIpsum.nwd => guiBuild_Tool_Step4_Lorem_Ipsum.nwd} (100%) rename tests/reference/{gui/3_nwProject.nwx => guiItemEditor_Dialog_nwProject.nwx} (100%) rename tests/reference/{gui/4_73475cb40a568.nwd => guiMerge_73475cb40a568.nwd} (100%) rename tests/reference/{gui/5_031b4af5197ec.nwd => guiSplit_031b4af5197ec.nwd} (100%) rename tests/reference/{gui/5_25fc0e7096fc6.nwd => guiSplit_25fc0e7096fc6.nwd} (100%) rename tests/reference/{gui/5_2858dcd1057d3.nwd => guiSplit_2858dcd1057d3.nwd} (100%) rename tests/reference/{gui/5_2fca346db6561.nwd => guiSplit_2fca346db6561.nwd} (100%) rename tests/reference/{gui/5_31489056e0916.nwd => guiSplit_31489056e0916.nwd} (100%) rename tests/reference/{gui/5_41cfc0d1f2d12.nwd => guiSplit_41cfc0d1f2d12.nwd} (100%) rename tests/reference/{gui/5_98010bd9270f9.nwd => guiSplit_98010bd9270f9.nwd} (100%) create mode 100644 tests/test_gui_build.py create mode 100644 tests/test_gui_itemeditor.py create mode 100644 tests/test_gui_mergesplit.py diff --git a/tests/README.md b/tests/README.md index e285fece..1d319b14 100644 --- a/tests/README.md +++ b/tests/README.md @@ -59,23 +59,25 @@ Available markers are: 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` | -| Integration | About Dialogs | nw/gui/about.py | `-m gui` | `-k testGuiAbout` | -| Integration | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` | -| Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | +| 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 | Item Editor Dialog | nw/gui/itemeditor.py | `-m gui` | `-k testGuiItemEditor` | +| Integration | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` | +| Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | diff --git a/tests/conftest.py b/tests/conftest.py index aef333ed..90de5a98 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -223,12 +223,3 @@ def nwTempGUI(tmpDir): 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/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/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_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 191c5e8e..46c1cc02 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -16,433 +16,14 @@ from PyQt5.QtWidgets import ( QFileDialog, QFontDialog ) -from nw.gui import ( - GuiItemEditor, GuiBuildNovel, GuiDocMerge, GuiDocSplit, GuiProjectWizard, - GuiProjectLoad, GuiPreferences -) +from nw.gui import GuiProjectWizard, GuiProjectLoad, GuiPreferences from nw.gui.custom import QuotesDialog -from nw.constants import nwItemLayout, nwItemClass +from nw.constants import nwItemClass keyDelay = 2 typeDelay = 1 stepDelay = 20 -@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 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): 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_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_projsettings.py b/tests/test_gui_projsettings.py index 421d8f0c..2dcd92cb 100644 --- a/tests/test_gui_projsettings.py +++ b/tests/test_gui_projsettings.py @@ -30,9 +30,6 @@ def testGuiProjSettings_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj, outDi # 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) # Check that we cannot open when there is no project nwGUI.mainMenu.aProjectSettings.activate(QAction.Trigger) From 325c8dc68d57dfefcb4641bed0255b2622be7c9a Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 17:39:28 +0100 Subject: [PATCH 5/7] Split up the main gui test file --- ...=> guiEditor_Main_Final_031b4af5197ec.nwd} | 0 ...=> guiEditor_Main_Final_0e17daca5f3e1.nwd} | 0 ...=> guiEditor_Main_Final_1a6562590ef19.nwd} | 0 ...=> guiEditor_Main_Final_41cfc0d1f2d12.nwd} | 0 ...nwx => guiEditor_Main_Final_nwProject.nwx} | 0 ...x => guiEditor_Main_Initial_nwProject.nwx} | 0 tests/test_gui_doceditor.py | 506 ++++++ tests/test_gui_docviewer.py | 165 ++ tests/test_gui_main.py | 1526 ----------------- tests/test_gui_mainmenu.py | 533 ++++++ tests/test_gui_outline.py | 73 + tests/test_gui_projtree.py | 138 ++ tests/test_gui_theme.py | 170 ++ 13 files changed, 1585 insertions(+), 1526 deletions(-) rename tests/reference/{gui/1_031b4af5197ec.nwd => guiEditor_Main_Final_031b4af5197ec.nwd} (100%) rename tests/reference/{gui/1_0e17daca5f3e1.nwd => guiEditor_Main_Final_0e17daca5f3e1.nwd} (100%) rename tests/reference/{gui/1_1a6562590ef19.nwd => guiEditor_Main_Final_1a6562590ef19.nwd} (100%) rename tests/reference/{gui/1_41cfc0d1f2d12.nwd => guiEditor_Main_Final_41cfc0d1f2d12.nwd} (100%) rename tests/reference/{gui/1_nwProject.nwx => guiEditor_Main_Final_nwProject.nwx} (100%) rename tests/reference/{gui/0_nwProject.nwx => guiEditor_Main_Initial_nwProject.nwx} (100%) create mode 100644 tests/test_gui_doceditor.py create mode 100644 tests/test_gui_docviewer.py delete mode 100644 tests/test_gui_main.py create mode 100644 tests/test_gui_mainmenu.py create mode 100644 tests/test_gui_outline.py create mode 100644 tests/test_gui_projtree.py create mode 100644 tests/test_gui_theme.py 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 100% rename from tests/reference/gui/1_0e17daca5f3e1.nwd rename to tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd 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 100% rename from tests/reference/gui/1_nwProject.nwx rename to tests/reference/guiEditor_Main_Final_nwProject.nwx 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/test_gui_doceditor.py b/tests/test_gui_doceditor.py new file mode 100644 index 00000000..5b09ca95 --- /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 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_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_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_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_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 From 585287b487deb70d32078e7b63839ded241250a3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 18:58:05 +0100 Subject: [PATCH 6/7] Reorganise remainin GUI tests, but no improved coverage --- tests/README.md | 15 +- tests/conftest.py | 41 -- ...s.conf => guiPreferences_novelwriter.conf} | 2 +- tests/test_gui_dialogs.py | 520 +----------------- tests/test_gui_preferences.py | 235 ++++++++ tests/test_gui_projload.py | 99 ++++ tests/test_gui_projwizard.py | 212 +++++++ 7 files changed, 575 insertions(+), 549 deletions(-) rename tests/reference/{novelwriter_prefs.conf => guiPreferences_novelwriter.conf} (98%) create mode 100644 tests/test_gui_preferences.py create mode 100644 tests/test_gui_projload.py create mode 100644 tests/test_gui_projwizard.py diff --git a/tests/README.md b/tests/README.md index 1d319b14..f13d974e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -52,7 +52,9 @@ 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 @@ -78,6 +80,17 @@ The commands for the respective test categories are listed below. | 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 90de5a98..bab0d4f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,8 +9,6 @@ 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 @@ -184,42 +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 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/test_gui_dialogs.py b/tests/test_gui_dialogs.py index 46c1cc02..b87f983c 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -2,511 +2,23 @@ """novelWriter Dialog Class Tester """ -import nw import pytest -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, - QFileDialog, QFontDialog -) - -from nw.gui import GuiProjectWizard, GuiProjectLoad, GuiPreferences from nw.gui.custom import QuotesDialog -from nw.constants import nwItemClass keyDelay = 2 typeDelay = 1 stepDelay = 20 @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() @@ -527,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_preferences.py b/tests/test_gui_preferences.py new file mode 100644 index 00000000..c1f4f0b2 --- /dev/null +++ b/tests/test_gui_preferences.py @@ -0,0 +1,235 @@ +# -*- 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) + + 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_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 From 27696d65e4d23041ed84820da0e4298e2a9c6fdf Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 19:22:38 +0100 Subject: [PATCH 7/7] Remove some unnecessary branches and unused functions --- nw/gui/dochighlight.py | 3 +-- nw/gui/preferences.py | 8 -------- nw/gui/projload.py | 6 +++--- nw/gui/statusbar.py | 12 ++++++------ .../reference/guiEditor_Main_Final_0e17daca5f3e1.nwd | 2 +- tests/reference/guiEditor_Main_Final_nwProject.nwx | 10 +++++----- tests/test_gui_doceditor.py | 2 +- tests/test_gui_preferences.py | 1 + 8 files changed, 18 insertions(+), 26 deletions(-) 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/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/tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd b/tests/reference/guiEditor_Main_Final_0e17daca5f3e1.nwd index bf24dbe6..54fef623 100644 --- a/tests/reference/guiEditor_Main_Final_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/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index 1174c04c..21011e03 100644 --- a/tests/reference/guiEditor_Main_Final_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/test_gui_doceditor.py b/tests/test_gui_doceditor.py index 5b09ca95..9ef62951 100644 --- a/tests/test_gui_doceditor.py +++ b/tests/test_gui_doceditor.py @@ -230,7 +230,7 @@ def testGuiEditor_Main(qtbot, monkeypatch, nwGUI, fncDir, fncProj, refDir, outDi for c in ( "This is another paragraph of much longer dummy text. " - "It is in fact very very dumb 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. ": diff --git a/tests/test_gui_preferences.py b/tests/test_gui_preferences.py index c1f4f0b2..4c8b17ea 100644 --- a/tests/test_gui_preferences.py +++ b/tests/test_gui_preferences.py @@ -209,6 +209,7 @@ def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): # Save and Check Config qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) + nwPrefs._doClose() assert theConf.confChanged theConf.lastPath = ""