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