From 0ba0674b9e063d55dea1b80d5c55a9921eef5571 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Thu, 31 Oct 2024 00:07:48 +0100 Subject: [PATCH] Some code flow improvements and main GUI test coverage --- novelwriter/gui/noveltree.py | 12 ++-- novelwriter/guimain.py | 72 ++++++++++------------ tests/conftest.py | 4 ++ tests/test_gui/test_gui_guimain.py | 99 ++++++++++++++++++++++++++++-- 4 files changed, 134 insertions(+), 53 deletions(-) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index c2a28c2b..8db8a1e0 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -553,17 +553,13 @@ class GuiNovelTree(QTreeWidget): if pHandle := self._actHandle: for key, item in self._treeMap.items(): if key.startswith(pHandle): - item.setBackground(self.C_TITLE, brushOff) - item.setBackground(self.C_WORDS, brushOff) - item.setBackground(self.C_EXTRA, brushOff) - item.setBackground(self.C_MORE, brushOff) + for i in range(self.columnCount()): + item.setBackground(i, brushOff) if tHandle: for key, item in self._treeMap.items(): if key.startswith(tHandle): - item.setBackground(self.C_TITLE, brushOn) - item.setBackground(self.C_WORDS, brushOn) - item.setBackground(self.C_EXTRA, brushOn) - item.setBackground(self.C_MORE, brushOn) + for i in range(self.columnCount()): + item.setBackground(i, brushOn) if not didScroll: self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter) didScroll = True diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index ac13b356..11dabf78 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -844,35 +844,33 @@ class GuiMain(QMainWindow): def closeMain(self) -> bool: """Save everything, and close novelWriter.""" - if SHARED.hasProject: - msgYes = SHARED.question("%s
%s" % ( - self.tr("Do you want to exit novelWriter?"), - self.tr("Changes are saved automatically.") - )) - if not msgYes: - return False + if SHARED.hasProject and SHARED.question("%s
%s" % ( + self.tr("Do you want to exit novelWriter?"), + self.tr("Changes are saved automatically.") + )): + logger.info("Exiting novelWriter") - logger.info("Exiting novelWriter") + if not SHARED.focusMode: + CONFIG.setMainPanePos(self.splitMain.sizes()) + CONFIG.setOutlinePanePos(self.outlineView.splitSizes()) + if self.docViewerPanel.isVisible(): + CONFIG.setViewPanePos(self.splitView.sizes()) - if not SHARED.focusMode: - CONFIG.setMainPanePos(self.splitMain.sizes()) - CONFIG.setOutlinePanePos(self.outlineView.splitSizes()) - if self.docViewerPanel.isVisible(): - CONFIG.setViewPanePos(self.splitView.sizes()) + CONFIG.showViewerPanel = self.docViewerPanel.isVisible() + wFull = Qt.WindowState.WindowFullScreen + if self.windowState() & wFull != wFull: + # Ignore window size if in full screen mode + CONFIG.setMainWinSize(self.width(), self.height()) - CONFIG.showViewerPanel = self.docViewerPanel.isVisible() - wFull = Qt.WindowState.WindowFullScreen - if self.windowState() & wFull != wFull: - # Ignore window size if in full screen mode - CONFIG.setMainWinSize(self.width(), self.height()) + if SHARED.hasProject: + self.closeProject(True) + CONFIG.saveConfig() - if SHARED.hasProject: - self.closeProject(True) - CONFIG.saveConfig() + QApplication.quit() - QApplication.quit() + return True - return True + return False def closeViewerPanel(self, byUser: bool = True) -> bool: """Close the document view panel.""" @@ -1101,8 +1099,16 @@ class GuiMain(QMainWindow): @pyqtSlot(str, nwDocMode) def _followTag(self, tag: str, mode: nwDocMode) -> None: """Follow a tag after user interaction with a link.""" - tHandle, sTitle = self._getTagSource(tag) - if tHandle is not None: + tHandle, sTitle = SHARED.project.index.getTagSource(tag) + if tHandle is None: + SHARED.error(self.tr( + "Could not find the reference for tag '{0}'. It either doesn't " + "exist, or the index is out of date. The index can be updated " + "from the Tools menu, or by pressing {1}." + ).format( + tag, "F9" + )) + else: if mode == nwDocMode.EDIT: self.openDocument(tHandle, sTitle=sTitle) elif mode == nwDocMode.VIEW: @@ -1293,19 +1299,3 @@ class GuiMain(QMainWindow): """Set the window title and add the project's name.""" self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName]))) return - - def _getTagSource(self, tag: str) -> tuple[str | None, str | None]: - """Handle the index lookup of a tag and display an alert if the - tag cannot be found. - """ - tHandle, sTitle = SHARED.project.index.getTagSource(tag) - if tHandle is None: - SHARED.error(self.tr( - "Could not find the reference for tag '{0}'. It either doesn't " - "exist, or the index is out of date. The index can be updated " - "from the Tools menu, or by pressing {1}." - ).format( - tag, "F9" - )) - return None, None - return tHandle, sTitle diff --git a/tests/conftest.py b/tests/conftest.py index 4ec3eeb3..ada43e28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -54,8 +54,10 @@ def resetConfigVars(): CONFIG.setBackupPath(_TMP_ROOT) CONFIG.setGuiFont(None) CONFIG.setTextFont(None) + CONFIG.backupOnClose = False CONFIG._homePath = _TMP_ROOT CONFIG._dLocale = QLocale("en_GB") + CONFIG.pdfDocs = _TMP_ROOT / "manual.pdf" CONFIG.guiLocale = "en_GB" return @@ -72,6 +74,7 @@ def sessionFixture(): shutil.rmtree(_TMP_ROOT) _TMP_ROOT.mkdir() _TMP_CONF.mkdir() + (_TMP_ROOT / "manual.pdf").touch() return @@ -161,6 +164,7 @@ def nwGUI(qtbot, monkeypatch, functionFixture): monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes) nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"]) + assert nwGUI is not None qtbot.addWidget(nwGUI) resetConfigVars() nwGUI.docEditor.initEditor() diff --git a/tests/test_gui/test_gui_guimain.py b/tests/test_gui/test_gui_guimain.py index eb87ac31..d7fb5d0d 100644 --- a/tests/test_gui/test_gui_guimain.py +++ b/tests/test_gui/test_gui_guimain.py @@ -20,19 +20,22 @@ along with this program. If not, see . """ from __future__ import annotations +import shutil import sys +from pathlib import Path from shutil import copyfile import pytest from PyQt5.QtCore import Qt from PyQt5.QtGui import QPalette -from PyQt5.QtWidgets import QInputDialog, QMenu +from PyQt5.QtWidgets import QInputDialog, QMenu, QMessageBox from novelwriter import CONFIG, SHARED +from novelwriter.constants import nwFiles from novelwriter.dialogs.editlabel import GuiEditLabel -from novelwriter.enum import nwDocAction, nwFocus, nwItemType, nwView +from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwView from novelwriter.gui.doceditor import GuiDocEditor from novelwriter.gui.noveltree import GuiNovelView from novelwriter.gui.outline import GuiOutlineView @@ -682,6 +685,12 @@ def testGuiMain_Viewing(qtbot, monkeypatch, nwGUI, projPath, mockRnd): def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd): """Test various features of the main window.""" buildTestProject(nwGUI, projPath) + cHandle = SHARED.project.newFile("Jane", C.hCharRoot) + newDoc = SHARED.project.storage.getDocument(cHandle) + newDoc.writeDocument("# Jane\n\n@tag: Jane\n\n") + nwGUI.projView.projTree.revealNewTreeItem(cHandle) + nwGUI.rebuildIndex(beQuiet=True) + assert SHARED.focusMode is False # Focus Mode @@ -721,6 +730,20 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd): assert SHARED.focusMode is True nwGUI.closeDocument() assert SHARED.focusMode is False + nwGUI.openDocument(C.hSceneDoc) + + # Pressing Escape turns off focus mode + nwGUI.toggleFocusMode() + assert SHARED.focusMode is True + qtbot.keyClick(nwGUI, Qt.Key.Key_Escape) + assert SHARED.focusMode is False + + # If search is active, Escape is redirected to editor + nwGUI.toggleFocusMode() + assert SHARED.focusMode is True + nwGUI.docEditor.beginSearch() + qtbot.keyClick(nwGUI, Qt.Key.Key_Escape) + assert SHARED.focusMode is True # Full Screen Mode # ================ @@ -738,8 +761,25 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd): nwGUI.sideBar.mSettings.show() nwGUI.sideBar.mSettings.hide() - # Document Open Errors - # ==================== + # Redirect Tag Open + # ================= + + nwGUI.closeDocument() + nwGUI.closeDocViewer() + assert nwGUI.docEditor.docHandle is None + assert nwGUI.docViewer.docHandle is None + nwGUI._followTag("John", nwDocMode.EDIT) # Doesn't exist + assert nwGUI.docEditor.docHandle is None + assert nwGUI.docViewer.docHandle is None + nwGUI._followTag("Jane", nwDocMode.EDIT) + assert nwGUI.docEditor.docHandle == cHandle + assert nwGUI.docViewer.docHandle is None + nwGUI._followTag("Jane", nwDocMode.VIEW) + assert nwGUI.docEditor.docHandle == cHandle + assert nwGUI.docViewer.docHandle == cHandle + + # Errors Handling + # =============== # Cannot edit a folder assert nwGUI.openDocument(C.hChapterDir) is False @@ -752,6 +792,57 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # qtbot.stop() +@pytest.mark.gui +def testGuiMain_OpenClose(qtbot, monkeypatch, nwGUI, projPath, fncPath, mockRnd): + """Test various features of the main window.""" + buildTestProject(nwGUI, projPath) + nwGUI.openDocument(C.hSceneDoc) + nwGUI.viewDocument(C.hTitlePage) + + # Handle broken index on project open + nwGUI.closeProject() + idxPath: Path = projPath / "meta" / nwFiles.INDEX_FILE + assert idxPath.read_text() != "{}" + idxPath.write_text("{}") + assert idxPath.read_text() == "{}" + + nwGUI.openProject(projPath) + nwGUI.saveProject() + assert idxPath.read_text() != "{}" + assert nwGUI.docEditor.docHandle == C.hSceneDoc + assert nwGUI.docViewer.docHandle == C.hTitlePage + + # Block closing + assert SHARED.hasProject is True + with monkeypatch.context() as mp: + mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + assert nwGUI.openProject(projPath) is False + assert SHARED.hasProject is True + + # Don't open on lockfile question: No + lockPath: Path = projPath / nwFiles.PROJ_LOCK + lockBack: Path = projPath / f"{nwFiles.PROJ_LOCK}.bak" + + shutil.copyfile(lockPath, lockBack) + nwGUI.closeProject() + shutil.copyfile(lockBack, lockPath) + + with monkeypatch.context() as mp: + mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.No) + assert nwGUI.openProject(projPath) is False + + assert nwGUI.openProject(projPath) is True + + # Backup on close + backDir = CONFIG.backupPath() / SHARED.project.data.name + assert not backDir.exists() + + CONFIG.backupOnClose = True + assert nwGUI.openProject(projPath) is True + nwGUI.closeProject() + assert len(list(backDir.glob("*.zip"))) == 1 + + @pytest.mark.gui def testGuiMain_FocusView(qtbot, monkeypatch, nwGUI, projPath, mockRnd): """Test switching focus and view of the main window."""