Some code flow improvements and main GUI test coverage
This commit is contained in:
@@ -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
|
||||
|
||||
+31
-41
@@ -844,35 +844,33 @@ class GuiMain(QMainWindow):
|
||||
|
||||
def closeMain(self) -> bool:
|
||||
"""Save everything, and close novelWriter."""
|
||||
if SHARED.hasProject:
|
||||
msgYes = SHARED.question("%s<br>%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<br>%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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -20,19 +20,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
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."""
|
||||
|
||||
Reference in New Issue
Block a user