From 585287b487deb70d32078e7b63839ded241250a3 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sat, 12 Dec 2020 18:58:05 +0100 Subject: [PATCH] Reorganise remainin GUI tests, but no improved coverage --- tests/README.md | 15 +- tests/conftest.py | 41 -- ...s.conf => guiPreferences_novelwriter.conf} | 2 +- tests/test_gui_dialogs.py | 520 +----------------- tests/test_gui_preferences.py | 235 ++++++++ tests/test_gui_projload.py | 99 ++++ tests/test_gui_projwizard.py | 212 +++++++ 7 files changed, 575 insertions(+), 549 deletions(-) rename tests/reference/{novelwriter_prefs.conf => guiPreferences_novelwriter.conf} (98%) create mode 100644 tests/test_gui_preferences.py create mode 100644 tests/test_gui_projload.py create mode 100644 tests/test_gui_projwizard.py diff --git a/tests/README.md b/tests/README.md index 1d319b14..f13d974e 100644 --- a/tests/README.md +++ b/tests/README.md @@ -52,7 +52,9 @@ pytest-3 -v -m core Available markers are: -* '`core`' for unit tests covering the classes in the `nw/core` folder +* `base` for unit tests covering the non-gui classes of the `bw` folder.. +* `core` for unit tests covering the classes in the `nw/core` folder. +* `gui` for unit and integrations tests covering the classes in the `nw/gui` folder. ## Tests @@ -78,6 +80,17 @@ The commands for the respective test categories are listed below. | Unit | Tokenizer class | nw/core/tokenizer.py | `-m core` | `-k testCoreToken` | | Integration | About Dialogs | nw/gui/about.py | `-m gui` | `-k testGuiAbout` | | Integration | Build Novel Project Tool | nw/gui/build.py | `-m gui` | `-k testGuiBuild` | +| Integration | Document Editor Widget | nw/gui/doceditor.py | `-m gui` | `-k testGuiEditor` | +| Integration | Document Viewer Widget | nw/gui/docviewer.py | `-m gui` | `-k testGuiViewer` | | Integration | Item Editor Dialog | nw/gui/itemeditor.py | `-m gui` | `-k testGuiItemEditor` | +| Integration | Menu Widgets | nw/gui/mainmenu.py | `-m gui` | `-k testGuiMenu` | +| Integration | Merge Tool | nw/gui/docmerge.py | `-m gui` | `-k testGuiMergeSplit` | +| Integration | Outline Widget | nw/gui/outline.py | `-m gui` | `-k testGuiOutline` | +| Integration | Preferences Dialog | nw/gui/preferences.py | `-m gui` | `-k testGuiPreferences` | +| Integration | Project Load Dialog | nw/gui/projload.py | `-m gui` | `-k testGuiProjLoad` | | Integration | Project Settings Dialog | nw/gui/projsettings.py | `-m gui` | `-k testGuiProjSettings` | +| Integration | Project Tree Widget | nw/gui/projtree.py | `-m gui` | `-k testGuiProjTree` | +| Integration | Theme/Icon Classes | nw/gui/theme.py | `-m gui` | `-k testGuiTheme` | +| Integration | Split Tool | nw/gui/docsplit.py | `-m gui` | `-k testGuiMergeSplit` | | Integration | Writing Stats Dialog | nw/gui/writingstats.py | `-m gui` | `-k testGuiWritingStats` | +| Integration | Various Dialogs | N/A | `-m gui` | `-k testGuiDialogs` | diff --git a/tests/conftest.py b/tests/conftest.py index 90de5a98..bab0d4f6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,8 +9,6 @@ import os from dummy import DummyMain -from PyQt5.QtWidgets import QMessageBox - sys.path.insert(1, os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir))) import nw # noqa: E402 @@ -184,42 +182,3 @@ def nwOldProj(tmpDir): if os.path.isdir(oldProjDir): shutil.rmtree(oldProjDir) return - -## -# Monkey Patch Dialogs -## - -@pytest.fixture(scope="function") -def yesToAll(monkeypatch): - """Make the message boxes/questions always say yes. - """ - monkeypatch.setattr( - QMessageBox, "question", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "information", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "warning", lambda *args, **kwargs: QMessageBox.Yes - ) - monkeypatch.setattr( - QMessageBox, "critical", lambda *args, **kwargs: QMessageBox.Yes - ) - yield - monkeypatch.undo() - return - -# =============================================================================================== # - -## -# Temporary Test Folders -## - -@pytest.fixture(scope="session") -def nwTempGUI(tmpDir): - """A temporary folder for GUI tests. - """ - guiDir = os.path.join(tmpDir, "gui") - if not os.path.isdir(guiDir): - os.mkdir(guiDir) - return guiDir diff --git a/tests/reference/novelwriter_prefs.conf b/tests/reference/guiPreferences_novelwriter.conf similarity index 98% rename from tests/reference/novelwriter_prefs.conf rename to tests/reference/guiPreferences_novelwriter.conf index 901a6937..cdbc5337 100644 --- a/tests/reference/novelwriter_prefs.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -60,7 +60,7 @@ backuponclose = True askbeforebackup = True [State] -showrefpanel = False +showrefpanel = True viewcomments = True viewsynopsis = True searchcase = False diff --git a/tests/test_gui_dialogs.py b/tests/test_gui_dialogs.py index 46c1cc02..b87f983c 100644 --- a/tests/test_gui_dialogs.py +++ b/tests/test_gui_dialogs.py @@ -2,511 +2,23 @@ """novelWriter Dialog Class Tester """ -import nw import pytest -import os -import sys -from shutil import copyfile -from tools import cmpFiles, getGuiItem +from PyQt5.QtCore import QItemSelectionModel +from PyQt5.QtWidgets import QListWidgetItem, QDialog, QFileDialog, QMessageBox -from PyQt5.QtCore import Qt, QItemSelectionModel -from PyQt5.QtWidgets import ( - QDialogButtonBox, QTreeWidgetItem, QListWidgetItem, QDialog, QAction, - QFileDialog, QFontDialog -) - -from nw.gui import GuiProjectWizard, GuiProjectLoad, GuiPreferences from nw.gui.custom import QuotesDialog -from nw.constants import nwItemClass keyDelay = 2 typeDelay = 1 stepDelay = 20 @pytest.mark.gui -def testNewProjectWizard(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): - - if sys.platform.startswith("darwin"): - # Disable for macOS because the test segfaults on QWizard.show() - return - - from PyQt5.QtWidgets import QWizard - from nw.gui.projwizard import ( - ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage, - ProjWizardCustomPage, ProjWizardFinalPage - ) - - nwGUI = nw.main(["--testmode", "--config=%s" % tmpDir, "--data=%s" % tmpDir]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) - - ## - # Test New Project Function - ## - - # New with a project open should cause an error - assert nwGUI.openProject(nwMinimal) - assert not nwGUI.newProject() - - # Close project, but call with invalid path - assert nwGUI.closeProject() - monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None) - assert not nwGUI.newProject() - - # Now, with an empty dictionary - monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {}) - assert not nwGUI.newProject() - - # Now, with a non-empty folder - monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal}) - assert not nwGUI.newProject() - - monkeypatch.undo() - - ## - # Test the Wizard - ## - - monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None) - nwGUI.mainConf.lastPath = " " - - nwGUI.closeProject() - nwGUI.showNewProjectDialog() - qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000) - - nwWiz = getGuiItem("GuiProjectWizard") - assert isinstance(nwWiz, GuiProjectWizard) - nwWiz.show() - qtbot.wait(stepDelay) - - for wStep in range(4): - # This does not actually create the project, it just generates the - # dictionary that defines it. - - # Intro Page - introPage = nwWiz.currentPage() - assert isinstance(introPage, ProjWizardIntroPage) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.wait(stepDelay) - for c in ("Test Minimal %d" % wStep): - qtbot.keyClick(introPage.projName, c, delay=typeDelay) - - qtbot.wait(stepDelay) - for c in "Minimal Novel": - qtbot.keyClick(introPage.projTitle, c, delay=typeDelay) - - qtbot.wait(stepDelay) - for c in "Jane Doe": - qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay) - - # Setting projName should activate the button - assert nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Folder Page - storagePage = nwWiz.currentPage() - assert isinstance(storagePage, ProjWizardFolderPage) - assert not nwWiz.button(QWizard.NextButton).isEnabled() - - if wStep == 0: - # Check invalid path first, the first time we reach here - monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "") - qtbot.wait(stepDelay) - qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) - assert storagePage.projPath.text() == "" - - # Then, we always return nwMinimal as path - monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal) - - qtbot.wait(stepDelay) - qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) - projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep) - assert storagePage.projPath.text() == projPath - - # Setting projPath should activate the button - assert nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Populate Page - popPage = nwWiz.currentPage() - assert isinstance(popPage, ProjWizardPopulatePage) - assert nwWiz.button(QWizard.NextButton).isEnabled() - - qtbot.wait(stepDelay) - if wStep == 0: - popPage.popMinimal.setChecked(True) - elif wStep == 1: - popPage.popCustom.setChecked(True) - elif wStep == 2: - popPage.popCustom.setChecked(True) - elif wStep == 3: - popPage.popSample.setChecked(True) - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Custom Page - if wStep == 1 or wStep == 2: - customPage = nwWiz.currentPage() - assert isinstance(customPage, ProjWizardCustomPage) - assert nwWiz.button(QWizard.NextButton).isEnabled() - - customPage.addPlot.setChecked(True) - customPage.addChar.setChecked(True) - customPage.addWorld.setChecked(True) - customPage.addTime.setChecked(True) - customPage.addObject.setChecked(True) - customPage.addEntity.setChecked(True) - - if wStep == 2: - customPage.numChapters.setValue(0) - customPage.numScenes.setValue(10) - customPage.chFolders.setChecked(False) - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) - - # Final Page - finalPage = nwWiz.currentPage() - assert isinstance(finalPage, ProjWizardFinalPage) - assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it - - # Check Data - projData = nwGUI._assembleProjectWizardData(nwWiz) - assert projData["projName"] == "Test Minimal %d" % wStep - assert projData["projTitle"] == "Minimal Novel" - assert projData["projAuthors"] == "Jane Doe" - assert projData["projPath"] == projPath - assert projData["popMinimal"] == (wStep == 0) - assert projData["popCustom"] == (wStep == 1 or wStep == 2) - assert projData["popSample"] == (wStep == 3) - if wStep == 1 or wStep == 2: - assert projData["addRoots"] == [ - nwItemClass.PLOT, - nwItemClass.CHARACTER, - nwItemClass.WORLD, - nwItemClass.TIMELINE, - nwItemClass.OBJECT, - nwItemClass.ENTITY, - ] - if wStep == 1: - assert projData["numChapters"] == 5 - assert projData["numScenes"] == 5 - assert projData["chFolders"] - else: - assert projData["numChapters"] == 0 - assert projData["numScenes"] == 10 - assert not projData["chFolders"] - else: - assert projData["addRoots"] == [] - assert projData["numChapters"] == 0 - assert projData["numScenes"] == 0 - assert not projData["chFolders"] - - # Restart the wizard for next iteration - nwWiz.restart() - - nwWiz.reject() - nwWiz.close() - - # qtbot.stopForInteraction() - nwGUI.closeMain() - nwGUI.close() - -@pytest.mark.gui -def testLoadProject(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) - - assert nwGUI.openProject(nwMinimal) - assert nwGUI.closeProject() - - qtbot.wait(stepDelay) - monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None) - monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted) - nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) - - nwLoad = getGuiItem("GuiProjectLoad") - assert isinstance(nwLoad, GuiProjectLoad) - nwLoad.show() - - qtbot.wait(stepDelay) - recentCount = nwLoad.listBox.topLevelItemCount() - assert recentCount > 0 - - qtbot.wait(stepDelay) - selItem = nwLoad.listBox.topLevelItem(0) - selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole) - assert isinstance(selItem, QTreeWidgetItem) - - qtbot.wait(stepDelay) - nwLoad.selPath.setText("") - nwLoad.listBox.setCurrentItem(selItem) - nwLoad._doSelectRecent() - assert nwLoad.selPath.text() == selPath - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton) - assert nwLoad.openPath == selPath - assert nwLoad.openState == nwLoad.OPEN_STATE - - # Just create a new project load from scratch for the rest of the test - del nwLoad - - qtbot.wait(stepDelay) - nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) - - qtbot.wait(stepDelay) - nwLoad = getGuiItem("GuiProjectLoad") - assert isinstance(nwLoad, GuiProjectLoad) - nwLoad.show() - - qtbot.wait(stepDelay) - qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton) - assert nwLoad.openPath is None - assert nwLoad.openState == nwLoad.NONE_STATE - - qtbot.wait(stepDelay) - nwLoad.show() - qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton) - assert nwLoad.openPath is None - assert nwLoad.openState == nwLoad.NEW_STATE - - qtbot.wait(stepDelay) - nwLoad.show() - nwLoad._keyPressDelete() - assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 - - getFile = os.path.join(nwMinimal, "nwProject.nwx") - monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None)) - qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton) - assert nwLoad.openPath == nwMinimal - assert nwLoad.openState == nwLoad.OPEN_STATE - # qtbot.stopForInteraction() - - nwLoad.close() - nwGUI.closeMain() - nwGUI.close() - -@pytest.mark.gui -def testPreferences(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir, refDir, tmpConf): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) - - assert nwGUI.openProject(nwMinimal) - - monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None) - monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted) - nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) - qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) - - nwPrefs = getGuiItem("GuiPreferences") - assert isinstance(nwPrefs, GuiPreferences) - nwPrefs.show() - - # Override Config - tmpConf.confPath = nwMinimal - tmpConf.showGUI = False - nwGUI.mainConf = tmpConf - nwPrefs.mainConf = tmpConf - nwPrefs.tabGeneral.mainConf = tmpConf - nwPrefs.tabProjects.mainConf = tmpConf - nwPrefs.tabLayout.mainConf = tmpConf - nwPrefs.tabEditing.mainConf = tmpConf - nwPrefs.tabAutoRep.mainConf = tmpConf - - # General Settings - qtbot.wait(keyDelay) - tabGeneral = nwPrefs.tabGeneral - nwPrefs._tabBox.setCurrentWidget(tabGeneral) - - qtbot.wait(keyDelay) - assert not tabGeneral.preferDarkIcons.isChecked() - qtbot.mouseClick(tabGeneral.preferDarkIcons, Qt.LeftButton) - assert tabGeneral.preferDarkIcons.isChecked() - - qtbot.wait(keyDelay) - assert tabGeneral.showFullPath.isChecked() - qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton) - assert not tabGeneral.showFullPath.isChecked() - - qtbot.wait(keyDelay) - assert not tabGeneral.hideVScroll.isChecked() - qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton) - assert tabGeneral.hideVScroll.isChecked() - - qtbot.wait(keyDelay) - assert not tabGeneral.hideHScroll.isChecked() - qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton) - assert tabGeneral.hideHScroll.isChecked() - - # Check font button - monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True)) - qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton) - - qtbot.wait(keyDelay) - tabGeneral.guiFontSize.setValue(12) - - # Projects Settings - qtbot.wait(keyDelay) - tabProjects = nwPrefs.tabProjects - nwPrefs._tabBox.setCurrentWidget(tabProjects) - tabProjects.backupPath = "no/where" - - qtbot.wait(keyDelay) - assert not tabProjects.backupOnClose.isChecked() - qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton) - assert tabProjects.backupOnClose.isChecked() - - # Check Browse button - monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "") - assert not tabProjects._backupFolder() - monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir") - qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton) - - qtbot.wait(keyDelay) - tabProjects.autoSaveDoc.setValue(20) - tabProjects.autoSaveProj.setValue(40) - - # Text Layout Settings - qtbot.wait(keyDelay) - tabLayout = nwPrefs.tabLayout - nwPrefs._tabBox.setCurrentWidget(tabLayout) - - qtbot.wait(keyDelay) - qtbot.mouseClick(tabLayout.fontButton, Qt.LeftButton) - - qtbot.wait(keyDelay) - tabLayout.textStyleSize.setValue(13) - tabLayout.textFlowMax.setValue(700) - tabLayout.focusDocWidth.setValue(900) - tabLayout.textMargin.setValue(45) - tabLayout.tabWidth.setValue(45) - - qtbot.wait(keyDelay) - assert not tabLayout.textFlowFixed.isChecked() - qtbot.mouseClick(tabLayout.textFlowFixed, Qt.LeftButton) - assert tabLayout.textFlowFixed.isChecked() - - qtbot.wait(keyDelay) - assert not tabLayout.hideFocusFooter.isChecked() - qtbot.mouseClick(tabLayout.hideFocusFooter, Qt.LeftButton) - assert tabLayout.hideFocusFooter.isChecked() - - qtbot.wait(keyDelay) - assert tabLayout.textJustify.isChecked() - qtbot.mouseClick(tabLayout.textJustify, Qt.LeftButton) - assert not tabLayout.textJustify.isChecked() - - qtbot.wait(keyDelay) - assert tabLayout.scrollPastEnd.isChecked() - qtbot.mouseClick(tabLayout.scrollPastEnd, Qt.LeftButton) - assert not tabLayout.scrollPastEnd.isChecked() - - qtbot.wait(keyDelay) - assert not tabLayout.autoScroll.isChecked() - qtbot.mouseClick(tabLayout.autoScroll, Qt.LeftButton) - assert tabLayout.autoScroll.isChecked() - - # Editor Settings - qtbot.wait(keyDelay) - tabEditing = nwPrefs.tabEditing - nwPrefs._tabBox.setCurrentWidget(tabEditing) - - qtbot.wait(keyDelay) - assert tabEditing.highlightQuotes.isChecked() - qtbot.mouseClick(tabEditing.highlightQuotes, Qt.LeftButton) - assert not tabEditing.highlightQuotes.isChecked() - - qtbot.wait(keyDelay) - assert tabEditing.highlightEmph.isChecked() - qtbot.mouseClick(tabEditing.highlightEmph, Qt.LeftButton) - assert not tabEditing.highlightEmph.isChecked() - - qtbot.wait(keyDelay) - assert not tabEditing.showTabsNSpaces.isChecked() - qtbot.mouseClick(tabEditing.showTabsNSpaces, Qt.LeftButton) - assert tabEditing.showTabsNSpaces.isChecked() - - qtbot.wait(keyDelay) - assert not tabEditing.showLineEndings.isChecked() - qtbot.mouseClick(tabEditing.showLineEndings, Qt.LeftButton) - assert tabEditing.showLineEndings.isChecked() - - qtbot.wait(keyDelay) - tabEditing.bigDocLimit.setValue(500) - - # Auto-Replace Settings - qtbot.wait(keyDelay) - tabAutoRep = nwPrefs.tabAutoRep - nwPrefs._tabBox.setCurrentWidget(tabAutoRep) - - qtbot.wait(keyDelay) - assert tabAutoRep.autoSelect.isChecked() - qtbot.mouseClick(tabAutoRep.autoSelect, Qt.LeftButton) - assert not tabAutoRep.autoSelect.isChecked() - - qtbot.wait(keyDelay) - assert tabAutoRep.autoReplaceMain.isChecked() - qtbot.mouseClick(tabAutoRep.autoReplaceMain, Qt.LeftButton) - assert not tabAutoRep.autoReplaceMain.isChecked() - - qtbot.wait(keyDelay) - assert not tabAutoRep.autoReplaceSQ.isEnabled() - assert not tabAutoRep.autoReplaceDQ.isEnabled() - assert not tabAutoRep.autoReplaceDash.isEnabled() - assert not tabAutoRep.autoReplaceDots.isEnabled() - - monkeypatch.setattr(QuotesDialog, "selectedQuote", "'") - monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted) - qtbot.mouseClick(tabAutoRep.btnDoubleStyleC, Qt.LeftButton) - - # Save and Check Config - qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) - - assert tmpConf.confChanged - tmpConf.lastPath = "" - - assert nwGUI.mainConf.saveConfig() - - # qtbot.stopForInteraction() - nwGUI.closeMain() - - refConf = os.path.join(refDir, "novelwriter_prefs.conf") - projConf = os.path.join(nwGUI.mainConf.confPath, "novelwriter.conf") - testConf = os.path.join(tmpDir, "novelwriter_prefs.conf") - copyfile(projConf, testConf) - ignoreLines = [ - 2, # Timestamp - 9, # Release Notes - 12, 13, 14, 15, 16, 17, 18, # Window sizes - 7, 28, # Fonts (depends on system default) - ] - assert cmpFiles(testConf, refConf, ignoreLines) - -@pytest.mark.gui -def testQuotesDialog(qtbot, yesToAll, nwMinimal, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) +def testGuiDialogs_Quotes(qtbot, monkeypatch, nwGUI, nwMinimal): + """Test the quote symbols dialog. + """ + # Block message box + monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) nwQuot = QuotesDialog(nwGUI) nwQuot.show() @@ -527,20 +39,16 @@ def testQuotesDialog(qtbot, yesToAll, nwMinimal, tmpDir): # qtbot.stopForInteraction() nwQuot._doReject() nwQuot.close() - nwGUI.closeMain() - nwGUI.close() + +# END Test testDialogs_Quotes @pytest.mark.gui -def testDialogsOpenClose(qtbot, monkeypatch, yesToAll, nwMinimal, tmpDir): - nwGUI = nw.main(["--testmode", "--config=%s" % nwMinimal, "--data=%s" % tmpDir, nwMinimal]) - qtbot.addWidget(nwGUI) - nwGUI.show() - qtbot.waitForWindowShown(nwGUI) - qtbot.wait(stepDelay) - +def testGuiDialogs_Other(qtbot, monkeypatch, nwGUI, nwMinimal, tmpDir): + """Various other dialog tests. + """ monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: tmpDir) assert nwGUI.selectProjectPath() == tmpDir # qtbot.stopForInteraction() - nwGUI.closeMain() - nwGUI.close() + +# END Test testGuiDialogs_Other diff --git a/tests/test_gui_preferences.py b/tests/test_gui_preferences.py new file mode 100644 index 00000000..c1f4f0b2 --- /dev/null +++ b/tests/test_gui_preferences.py @@ -0,0 +1,235 @@ +# -*- coding: utf-8 -*- +"""novelWriter Dialog Class Tester +""" + +import nw +import pytest +import os + +from shutil import copyfile +from tools import cmpFiles, getGuiItem + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QDialogButtonBox, QDialog, QAction, QFileDialog, QFontDialog, QMessageBox +) + +from nw.gui import GuiPreferences +from nw.config import Config +from nw.gui.custom import QuotesDialog + +keyDelay = 2 +typeDelay = 1 +stepDelay = 20 + +@pytest.mark.gui +def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): + """Test the load project wizard. + """ + # Block message box + monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) + monkeypatch.setattr(QMessageBox, "information", lambda *args: QMessageBox.Yes) + + # Must create a clean config and GUI object as the test-wide + # nw.CONFIG object is created on import an can be tainted by other tests + confFile = os.path.join(fncDir, "novelwriter.conf") + if os.path.isfile(confFile): + os.unlink(confFile) + theConf = Config() + theConf.initConfig(fncDir, fncDir) + theConf.setLastPath("") + origConf = nw.CONFIG + nw.CONFIG = theConf + + nwGUI = nw.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir]) + qtbot.addWidget(nwGUI) + nwGUI.show() + qtbot.waitForWindowShown(nwGUI) + qtbot.wait(20) + + theConf = nwGUI.mainConf + assert theConf.confPath == fncDir + + monkeypatch.setattr(GuiPreferences, "exec_", lambda *args: None) + monkeypatch.setattr(GuiPreferences, "result", lambda *args: QDialog.Accepted) + nwGUI.mainMenu.aPreferences.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiPreferences") is not None, timeout=1000) + + nwPrefs = getGuiItem("GuiPreferences") + assert isinstance(nwPrefs, GuiPreferences) + nwPrefs.show() + assert nwPrefs.mainConf.confPath == fncDir + + # qtbot.stopForInteraction() + # General Settings + qtbot.wait(keyDelay) + tabGeneral = nwPrefs.tabGeneral + nwPrefs._tabBox.setCurrentWidget(tabGeneral) + + qtbot.wait(keyDelay) + assert not tabGeneral.preferDarkIcons.isChecked() + qtbot.mouseClick(tabGeneral.preferDarkIcons, Qt.LeftButton) + assert tabGeneral.preferDarkIcons.isChecked() + + qtbot.wait(keyDelay) + assert tabGeneral.showFullPath.isChecked() + qtbot.mouseClick(tabGeneral.showFullPath, Qt.LeftButton) + assert not tabGeneral.showFullPath.isChecked() + + qtbot.wait(keyDelay) + assert not tabGeneral.hideVScroll.isChecked() + qtbot.mouseClick(tabGeneral.hideVScroll, Qt.LeftButton) + assert tabGeneral.hideVScroll.isChecked() + + qtbot.wait(keyDelay) + assert not tabGeneral.hideHScroll.isChecked() + qtbot.mouseClick(tabGeneral.hideHScroll, Qt.LeftButton) + assert tabGeneral.hideHScroll.isChecked() + + # Check font button + monkeypatch.setattr(QFontDialog, "getFont", lambda font, obj: (font, True)) + qtbot.mouseClick(tabGeneral.fontButton, Qt.LeftButton) + + qtbot.wait(keyDelay) + tabGeneral.guiFontSize.setValue(12) + + # Projects Settings + qtbot.wait(keyDelay) + tabProjects = nwPrefs.tabProjects + nwPrefs._tabBox.setCurrentWidget(tabProjects) + tabProjects.backupPath = "no/where" + + qtbot.wait(keyDelay) + assert not tabProjects.backupOnClose.isChecked() + qtbot.mouseClick(tabProjects.backupOnClose, Qt.LeftButton) + assert tabProjects.backupOnClose.isChecked() + + # Check Browse button + monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "") + assert not tabProjects._backupFolder() + monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *args, **kwargs: "some/dir") + qtbot.mouseClick(tabProjects.backupGetPath, Qt.LeftButton) + + qtbot.wait(keyDelay) + tabProjects.autoSaveDoc.setValue(20) + tabProjects.autoSaveProj.setValue(40) + + # Text Layout Settings + qtbot.wait(keyDelay) + tabLayout = nwPrefs.tabLayout + nwPrefs._tabBox.setCurrentWidget(tabLayout) + + qtbot.wait(keyDelay) + qtbot.mouseClick(tabLayout.fontButton, Qt.LeftButton) + + qtbot.wait(keyDelay) + tabLayout.textStyleSize.setValue(13) + tabLayout.textFlowMax.setValue(700) + tabLayout.focusDocWidth.setValue(900) + tabLayout.textMargin.setValue(45) + tabLayout.tabWidth.setValue(45) + + qtbot.wait(keyDelay) + assert not tabLayout.textFlowFixed.isChecked() + qtbot.mouseClick(tabLayout.textFlowFixed, Qt.LeftButton) + assert tabLayout.textFlowFixed.isChecked() + + qtbot.wait(keyDelay) + assert not tabLayout.hideFocusFooter.isChecked() + qtbot.mouseClick(tabLayout.hideFocusFooter, Qt.LeftButton) + assert tabLayout.hideFocusFooter.isChecked() + + qtbot.wait(keyDelay) + assert tabLayout.textJustify.isChecked() + qtbot.mouseClick(tabLayout.textJustify, Qt.LeftButton) + assert not tabLayout.textJustify.isChecked() + + qtbot.wait(keyDelay) + assert tabLayout.scrollPastEnd.isChecked() + qtbot.mouseClick(tabLayout.scrollPastEnd, Qt.LeftButton) + assert not tabLayout.scrollPastEnd.isChecked() + + qtbot.wait(keyDelay) + assert not tabLayout.autoScroll.isChecked() + qtbot.mouseClick(tabLayout.autoScroll, Qt.LeftButton) + assert tabLayout.autoScroll.isChecked() + + # Editor Settings + qtbot.wait(keyDelay) + tabEditing = nwPrefs.tabEditing + nwPrefs._tabBox.setCurrentWidget(tabEditing) + + qtbot.wait(keyDelay) + assert tabEditing.highlightQuotes.isChecked() + qtbot.mouseClick(tabEditing.highlightQuotes, Qt.LeftButton) + assert not tabEditing.highlightQuotes.isChecked() + + qtbot.wait(keyDelay) + assert tabEditing.highlightEmph.isChecked() + qtbot.mouseClick(tabEditing.highlightEmph, Qt.LeftButton) + assert not tabEditing.highlightEmph.isChecked() + + qtbot.wait(keyDelay) + assert not tabEditing.showTabsNSpaces.isChecked() + qtbot.mouseClick(tabEditing.showTabsNSpaces, Qt.LeftButton) + assert tabEditing.showTabsNSpaces.isChecked() + + qtbot.wait(keyDelay) + assert not tabEditing.showLineEndings.isChecked() + qtbot.mouseClick(tabEditing.showLineEndings, Qt.LeftButton) + assert tabEditing.showLineEndings.isChecked() + + qtbot.wait(keyDelay) + tabEditing.bigDocLimit.setValue(500) + + # Auto-Replace Settings + qtbot.wait(keyDelay) + tabAutoRep = nwPrefs.tabAutoRep + nwPrefs._tabBox.setCurrentWidget(tabAutoRep) + + qtbot.wait(keyDelay) + assert tabAutoRep.autoSelect.isChecked() + qtbot.mouseClick(tabAutoRep.autoSelect, Qt.LeftButton) + assert not tabAutoRep.autoSelect.isChecked() + + qtbot.wait(keyDelay) + assert tabAutoRep.autoReplaceMain.isChecked() + qtbot.mouseClick(tabAutoRep.autoReplaceMain, Qt.LeftButton) + assert not tabAutoRep.autoReplaceMain.isChecked() + + qtbot.wait(keyDelay) + assert not tabAutoRep.autoReplaceSQ.isEnabled() + assert not tabAutoRep.autoReplaceDQ.isEnabled() + assert not tabAutoRep.autoReplaceDash.isEnabled() + assert not tabAutoRep.autoReplaceDots.isEnabled() + + monkeypatch.setattr(QuotesDialog, "selectedQuote", "'") + monkeypatch.setattr(QuotesDialog, "exec_", lambda *args: QDialog.Accepted) + qtbot.mouseClick(tabAutoRep.btnDoubleStyleC, Qt.LeftButton) + + # Save and Check Config + qtbot.mouseClick(nwPrefs.buttonBox.button(QDialogButtonBox.Ok), Qt.LeftButton) + + assert theConf.confChanged + theConf.lastPath = "" + + assert nwGUI.mainConf.saveConfig() + projFile = os.path.join(fncDir, "novelwriter.conf") + testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf") + compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf") + copyfile(projFile, testFile) + ignoreLines = [ + 2, # Timestamp + 9, # Release Notes + 12, 13, 14, 15, 16, 17, 18, # Window sizes + 7, 28, # Fonts (depends on system default) + ] + assert cmpFiles(testFile, compFile, ignoreLines) + + # Clean up + nw.CONFIG = origConf + nwGUI.closeMain() + + # qtbot.stopForInteraction() + +# END Test testGuiPreferences_Main diff --git a/tests/test_gui_projload.py b/tests/test_gui_projload.py new file mode 100644 index 00000000..488d7798 --- /dev/null +++ b/tests/test_gui_projload.py @@ -0,0 +1,99 @@ +# -*- coding: utf-8 -*- +"""novelWriter Dialog Class Tester +""" + +import pytest +import os + +from tools import getGuiItem + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QDialogButtonBox, QTreeWidgetItem, QDialog, QAction, QFileDialog, + QMessageBox +) + +from nw.gui import GuiProjectLoad + +keyDelay = 2 +typeDelay = 1 +stepDelay = 20 + +@pytest.mark.gui +def testGuiLoadProject_Main(qtbot, monkeypatch, nwGUI, nwMinimal): + """Test the load project wizard. + """ + # Block message box + monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) + + assert nwGUI.openProject(nwMinimal) + assert nwGUI.closeProject() + + qtbot.wait(stepDelay) + monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *args: None) + monkeypatch.setattr(GuiProjectLoad, "result", lambda *args: QDialog.Accepted) + nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) + + nwLoad = getGuiItem("GuiProjectLoad") + assert isinstance(nwLoad, GuiProjectLoad) + nwLoad.show() + + qtbot.wait(stepDelay) + recentCount = nwLoad.listBox.topLevelItemCount() + assert recentCount > 0 + + qtbot.wait(stepDelay) + selItem = nwLoad.listBox.topLevelItem(0) + selPath = selItem.data(nwLoad.C_NAME, Qt.UserRole) + assert isinstance(selItem, QTreeWidgetItem) + + qtbot.wait(stepDelay) + nwLoad.selPath.setText("") + nwLoad.listBox.setCurrentItem(selItem) + nwLoad._doSelectRecent() + assert nwLoad.selPath.text() == selPath + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Open), Qt.LeftButton) + assert nwLoad.openPath == selPath + assert nwLoad.openState == nwLoad.OPEN_STATE + + # Just create a new project load from scratch for the rest of the test + del nwLoad + + qtbot.wait(stepDelay) + nwGUI.mainMenu.aOpenProject.activate(QAction.Trigger) + qtbot.waitUntil(lambda: getGuiItem("GuiProjectLoad") is not None, timeout=1000) + + qtbot.wait(stepDelay) + nwLoad = getGuiItem("GuiProjectLoad") + assert isinstance(nwLoad, GuiProjectLoad) + nwLoad.show() + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwLoad.buttonBox.button(QDialogButtonBox.Cancel), Qt.LeftButton) + assert nwLoad.openPath is None + assert nwLoad.openState == nwLoad.NONE_STATE + + qtbot.wait(stepDelay) + nwLoad.show() + qtbot.mouseClick(nwLoad.newButton, Qt.LeftButton) + assert nwLoad.openPath is None + assert nwLoad.openState == nwLoad.NEW_STATE + + qtbot.wait(stepDelay) + nwLoad.show() + nwLoad._keyPressDelete() + assert nwLoad.listBox.topLevelItemCount() == recentCount - 1 + + getFile = os.path.join(nwMinimal, "nwProject.nwx") + monkeypatch.setattr(QFileDialog, "getOpenFileName", lambda *args, **kwargs: (getFile, None)) + qtbot.mouseClick(nwLoad.browseButton, Qt.LeftButton) + assert nwLoad.openPath == nwMinimal + assert nwLoad.openState == nwLoad.OPEN_STATE + + nwLoad.close() + # qtbot.stopForInteraction() + +# END Test testGuiLoadProject_Main diff --git a/tests/test_gui_projwizard.py b/tests/test_gui_projwizard.py new file mode 100644 index 00000000..e855b002 --- /dev/null +++ b/tests/test_gui_projwizard.py @@ -0,0 +1,212 @@ +# -*- coding: utf-8 -*- +"""novelWriter Project Wizard Class Tester +""" + +import pytest +import os +import sys + +from tools import getGuiItem + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import QFileDialog, QWizard, QMessageBox + +from nw.gui import GuiProjectWizard +from nw.constants import nwItemClass +from nw.gui.projwizard import ( + ProjWizardIntroPage, ProjWizardFolderPage, ProjWizardPopulatePage, + ProjWizardCustomPage, ProjWizardFinalPage +) + +keyDelay = 2 +typeDelay = 1 +stepDelay = 20 + +@pytest.mark.gui +def testGuiProjectWizard_Main(qtbot, monkeypatch, nwGUI, nwMinimal): + """Test the new project wizard. + """ + # Block message box + monkeypatch.setattr(QMessageBox, "question", lambda *args: QMessageBox.Yes) + + if sys.platform.startswith("darwin"): + # Disable for macOS because the test segfaults on QWizard.show() + return + + ## + # Test New Project Function + ## + + # New with a project open should cause an error + assert nwGUI.openProject(nwMinimal) + assert not nwGUI.newProject() + + # Close project, but call with invalid path + assert nwGUI.closeProject() + monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: None) + assert not nwGUI.newProject() + + # Now, with an empty dictionary + monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {}) + assert not nwGUI.newProject() + + # Now, with a non-empty folder + monkeypatch.setattr(nwGUI, "showNewProjectDialog", lambda *args: {"projPath": nwMinimal}) + assert not nwGUI.newProject() + + monkeypatch.undo() + + ## + # Test the Wizard + ## + + monkeypatch.setattr(GuiProjectWizard, "exec_", lambda *args: None) + nwGUI.mainConf.lastPath = " " + + nwGUI.closeProject() + nwGUI.showNewProjectDialog() + qtbot.waitUntil(lambda: getGuiItem("GuiProjectWizard") is not None, timeout=1000) + + nwWiz = getGuiItem("GuiProjectWizard") + assert isinstance(nwWiz, GuiProjectWizard) + nwWiz.show() + qtbot.wait(stepDelay) + + for wStep in range(4): + # This does not actually create the project, it just generates the + # dictionary that defines it. + + # Intro Page + introPage = nwWiz.currentPage() + assert isinstance(introPage, ProjWizardIntroPage) + assert not nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + for c in ("Test Minimal %d" % wStep): + qtbot.keyClick(introPage.projName, c, delay=typeDelay) + + qtbot.wait(stepDelay) + for c in "Minimal Novel": + qtbot.keyClick(introPage.projTitle, c, delay=typeDelay) + + qtbot.wait(stepDelay) + for c in "Jane Doe": + qtbot.keyClick(introPage.projAuthors, c, delay=typeDelay) + + # Setting projName should activate the button + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Folder Page + storagePage = nwWiz.currentPage() + assert isinstance(storagePage, ProjWizardFolderPage) + assert not nwWiz.button(QWizard.NextButton).isEnabled() + + if wStep == 0: + # Check invalid path first, the first time we reach here + monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: "") + qtbot.wait(stepDelay) + qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) + assert storagePage.projPath.text() == "" + + # Then, we always return nwMinimal as path + monkeypatch.setattr(QFileDialog, "getExistingDirectory", lambda *a, **kw: nwMinimal) + + qtbot.wait(stepDelay) + qtbot.mouseClick(storagePage.browseButton, Qt.LeftButton, delay=100) + projPath = os.path.join(nwMinimal, "Test Minimal %d" % wStep) + assert storagePage.projPath.text() == projPath + + # Setting projPath should activate the button + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Populate Page + popPage = nwWiz.currentPage() + assert isinstance(popPage, ProjWizardPopulatePage) + assert nwWiz.button(QWizard.NextButton).isEnabled() + + qtbot.wait(stepDelay) + if wStep == 0: + popPage.popMinimal.setChecked(True) + elif wStep == 1: + popPage.popCustom.setChecked(True) + elif wStep == 2: + popPage.popCustom.setChecked(True) + elif wStep == 3: + popPage.popSample.setChecked(True) + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Custom Page + if wStep == 1 or wStep == 2: + customPage = nwWiz.currentPage() + assert isinstance(customPage, ProjWizardCustomPage) + assert nwWiz.button(QWizard.NextButton).isEnabled() + + customPage.addPlot.setChecked(True) + customPage.addChar.setChecked(True) + customPage.addWorld.setChecked(True) + customPage.addTime.setChecked(True) + customPage.addObject.setChecked(True) + customPage.addEntity.setChecked(True) + + if wStep == 2: + customPage.numChapters.setValue(0) + customPage.numScenes.setValue(10) + customPage.chFolders.setChecked(False) + + qtbot.wait(stepDelay) + qtbot.mouseClick(nwWiz.button(QWizard.NextButton), Qt.LeftButton) + + # Final Page + finalPage = nwWiz.currentPage() + assert isinstance(finalPage, ProjWizardFinalPage) + assert nwWiz.button(QWizard.FinishButton).isEnabled() # But we don't click it + + # Check Data + projData = nwGUI._assembleProjectWizardData(nwWiz) + assert projData["projName"] == "Test Minimal %d" % wStep + assert projData["projTitle"] == "Minimal Novel" + assert projData["projAuthors"] == "Jane Doe" + assert projData["projPath"] == projPath + assert projData["popMinimal"] == (wStep == 0) + assert projData["popCustom"] == (wStep == 1 or wStep == 2) + assert projData["popSample"] == (wStep == 3) + if wStep == 1 or wStep == 2: + assert projData["addRoots"] == [ + nwItemClass.PLOT, + nwItemClass.CHARACTER, + nwItemClass.WORLD, + nwItemClass.TIMELINE, + nwItemClass.OBJECT, + nwItemClass.ENTITY, + ] + if wStep == 1: + assert projData["numChapters"] == 5 + assert projData["numScenes"] == 5 + assert projData["chFolders"] + else: + assert projData["numChapters"] == 0 + assert projData["numScenes"] == 10 + assert not projData["chFolders"] + else: + assert projData["addRoots"] == [] + assert projData["numChapters"] == 0 + assert projData["numScenes"] == 0 + assert not projData["chFolders"] + + # Restart the wizard for next iteration + nwWiz.restart() + + nwWiz.reject() + nwWiz.close() + + # qtbot.stopForInteraction() + +# END Test testGuiProjectWizard_Main