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."""