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:
|
if pHandle := self._actHandle:
|
||||||
for key, item in self._treeMap.items():
|
for key, item in self._treeMap.items():
|
||||||
if key.startswith(pHandle):
|
if key.startswith(pHandle):
|
||||||
item.setBackground(self.C_TITLE, brushOff)
|
for i in range(self.columnCount()):
|
||||||
item.setBackground(self.C_WORDS, brushOff)
|
item.setBackground(i, brushOff)
|
||||||
item.setBackground(self.C_EXTRA, brushOff)
|
|
||||||
item.setBackground(self.C_MORE, brushOff)
|
|
||||||
if tHandle:
|
if tHandle:
|
||||||
for key, item in self._treeMap.items():
|
for key, item in self._treeMap.items():
|
||||||
if key.startswith(tHandle):
|
if key.startswith(tHandle):
|
||||||
item.setBackground(self.C_TITLE, brushOn)
|
for i in range(self.columnCount()):
|
||||||
item.setBackground(self.C_WORDS, brushOn)
|
item.setBackground(i, brushOn)
|
||||||
item.setBackground(self.C_EXTRA, brushOn)
|
|
||||||
item.setBackground(self.C_MORE, brushOn)
|
|
||||||
if not didScroll:
|
if not didScroll:
|
||||||
self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter)
|
self.scrollToItem(item, QAbstractItemView.ScrollHint.PositionAtCenter)
|
||||||
didScroll = True
|
didScroll = True
|
||||||
|
|||||||
+31
-41
@@ -844,35 +844,33 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
def closeMain(self) -> bool:
|
def closeMain(self) -> bool:
|
||||||
"""Save everything, and close novelWriter."""
|
"""Save everything, and close novelWriter."""
|
||||||
if SHARED.hasProject:
|
if SHARED.hasProject and SHARED.question("%s<br>%s" % (
|
||||||
msgYes = SHARED.question("%s<br>%s" % (
|
self.tr("Do you want to exit novelWriter?"),
|
||||||
self.tr("Do you want to exit novelWriter?"),
|
self.tr("Changes are saved automatically.")
|
||||||
self.tr("Changes are saved automatically.")
|
)):
|
||||||
))
|
logger.info("Exiting novelWriter")
|
||||||
if not msgYes:
|
|
||||||
return False
|
|
||||||
|
|
||||||
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.showViewerPanel = self.docViewerPanel.isVisible()
|
||||||
CONFIG.setMainPanePos(self.splitMain.sizes())
|
wFull = Qt.WindowState.WindowFullScreen
|
||||||
CONFIG.setOutlinePanePos(self.outlineView.splitSizes())
|
if self.windowState() & wFull != wFull:
|
||||||
if self.docViewerPanel.isVisible():
|
# Ignore window size if in full screen mode
|
||||||
CONFIG.setViewPanePos(self.splitView.sizes())
|
CONFIG.setMainWinSize(self.width(), self.height())
|
||||||
|
|
||||||
CONFIG.showViewerPanel = self.docViewerPanel.isVisible()
|
if SHARED.hasProject:
|
||||||
wFull = Qt.WindowState.WindowFullScreen
|
self.closeProject(True)
|
||||||
if self.windowState() & wFull != wFull:
|
CONFIG.saveConfig()
|
||||||
# Ignore window size if in full screen mode
|
|
||||||
CONFIG.setMainWinSize(self.width(), self.height())
|
|
||||||
|
|
||||||
if SHARED.hasProject:
|
QApplication.quit()
|
||||||
self.closeProject(True)
|
|
||||||
CONFIG.saveConfig()
|
|
||||||
|
|
||||||
QApplication.quit()
|
return True
|
||||||
|
|
||||||
return True
|
return False
|
||||||
|
|
||||||
def closeViewerPanel(self, byUser: bool = True) -> bool:
|
def closeViewerPanel(self, byUser: bool = True) -> bool:
|
||||||
"""Close the document view panel."""
|
"""Close the document view panel."""
|
||||||
@@ -1101,8 +1099,16 @@ class GuiMain(QMainWindow):
|
|||||||
@pyqtSlot(str, nwDocMode)
|
@pyqtSlot(str, nwDocMode)
|
||||||
def _followTag(self, tag: str, mode: nwDocMode) -> None:
|
def _followTag(self, tag: str, mode: nwDocMode) -> None:
|
||||||
"""Follow a tag after user interaction with a link."""
|
"""Follow a tag after user interaction with a link."""
|
||||||
tHandle, sTitle = self._getTagSource(tag)
|
tHandle, sTitle = SHARED.project.index.getTagSource(tag)
|
||||||
if tHandle is not None:
|
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:
|
if mode == nwDocMode.EDIT:
|
||||||
self.openDocument(tHandle, sTitle=sTitle)
|
self.openDocument(tHandle, sTitle=sTitle)
|
||||||
elif mode == nwDocMode.VIEW:
|
elif mode == nwDocMode.VIEW:
|
||||||
@@ -1293,19 +1299,3 @@ class GuiMain(QMainWindow):
|
|||||||
"""Set the window title and add the project's name."""
|
"""Set the window title and add the project's name."""
|
||||||
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
|
self.setWindowTitle(" - ".join(filter(None, [projName, CONFIG.appName])))
|
||||||
return
|
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.setBackupPath(_TMP_ROOT)
|
||||||
CONFIG.setGuiFont(None)
|
CONFIG.setGuiFont(None)
|
||||||
CONFIG.setTextFont(None)
|
CONFIG.setTextFont(None)
|
||||||
|
CONFIG.backupOnClose = False
|
||||||
CONFIG._homePath = _TMP_ROOT
|
CONFIG._homePath = _TMP_ROOT
|
||||||
CONFIG._dLocale = QLocale("en_GB")
|
CONFIG._dLocale = QLocale("en_GB")
|
||||||
|
CONFIG.pdfDocs = _TMP_ROOT / "manual.pdf"
|
||||||
CONFIG.guiLocale = "en_GB"
|
CONFIG.guiLocale = "en_GB"
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -72,6 +74,7 @@ def sessionFixture():
|
|||||||
shutil.rmtree(_TMP_ROOT)
|
shutil.rmtree(_TMP_ROOT)
|
||||||
_TMP_ROOT.mkdir()
|
_TMP_ROOT.mkdir()
|
||||||
_TMP_CONF.mkdir()
|
_TMP_CONF.mkdir()
|
||||||
|
(_TMP_ROOT / "manual.pdf").touch()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -161,6 +164,7 @@ def nwGUI(qtbot, monkeypatch, functionFixture):
|
|||||||
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
|
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
|
||||||
|
|
||||||
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
|
nwGUI = main(["--testmode", f"--config={_TMP_CONF}", f"--data={_TMP_CONF}"])
|
||||||
|
assert nwGUI is not None
|
||||||
qtbot.addWidget(nwGUI)
|
qtbot.addWidget(nwGUI)
|
||||||
resetConfigVars()
|
resetConfigVars()
|
||||||
nwGUI.docEditor.initEditor()
|
nwGUI.docEditor.initEditor()
|
||||||
|
|||||||
@@ -20,19 +20,22 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtGui import QPalette
|
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 import CONFIG, SHARED
|
||||||
|
from novelwriter.constants import nwFiles
|
||||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
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.doceditor import GuiDocEditor
|
||||||
from novelwriter.gui.noveltree import GuiNovelView
|
from novelwriter.gui.noveltree import GuiNovelView
|
||||||
from novelwriter.gui.outline import GuiOutlineView
|
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):
|
def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||||
"""Test various features of the main window."""
|
"""Test various features of the main window."""
|
||||||
buildTestProject(nwGUI, projPath)
|
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
|
assert SHARED.focusMode is False
|
||||||
|
|
||||||
# Focus Mode
|
# Focus Mode
|
||||||
@@ -721,6 +730,20 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
assert SHARED.focusMode is True
|
assert SHARED.focusMode is True
|
||||||
nwGUI.closeDocument()
|
nwGUI.closeDocument()
|
||||||
assert SHARED.focusMode is False
|
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
|
# Full Screen Mode
|
||||||
# ================
|
# ================
|
||||||
@@ -738,8 +761,25 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
nwGUI.sideBar.mSettings.show()
|
nwGUI.sideBar.mSettings.show()
|
||||||
nwGUI.sideBar.mSettings.hide()
|
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
|
# Cannot edit a folder
|
||||||
assert nwGUI.openDocument(C.hChapterDir) is False
|
assert nwGUI.openDocument(C.hChapterDir) is False
|
||||||
@@ -752,6 +792,57 @@ def testGuiMain_Features(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
# qtbot.stop()
|
# 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
|
@pytest.mark.gui
|
||||||
def testGuiMain_FocusView(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
def testGuiMain_FocusView(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||||
"""Test switching focus and view of the main window."""
|
"""Test switching focus and view of the main window."""
|
||||||
|
|||||||
Reference in New Issue
Block a user