Clean up config and improve project tree test coverage (#1607)

This commit is contained in:
Veronica Berglyd Olsen
2023-11-17 21:38:42 +01:00
committed by GitHub
4 changed files with 192 additions and 87 deletions
+4 -24
View File
@@ -181,7 +181,7 @@ class Config:
# State # State
self.showRefPanel = True # The reference panel for the viewer is visible self.showRefPanel = True # The reference panel for the viewer is visible
self.showEditToolBar = False # The document editor toolbar visibility self.showEditToolBar = False # The document editor toolbar visibility
self.useShortcodes = False # Use shorcodes for basic formatting self.useShortcodes = False # Use shortcodes for basic formatting
self.viewComments = True # Comments are shown in the viewer self.viewComments = True # Comments are shown in the viewer
self.viewSynopsis = True # Synopsis is shown in the viewer self.viewSynopsis = True # Synopsis is shown in the viewer
@@ -392,8 +392,7 @@ class Config:
return self._appPath / "assets" return self._appPath / "assets"
def lastPath(self) -> Path: def lastPath(self) -> Path:
"""Return the last path used by the user, but ensure it exists. """Return the last path used by the user, if it exists."""
"""
if isinstance(self._lastPath, Path): if isinstance(self._lastPath, Path):
if self._lastPath.is_dir(): if self._lastPath.is_dir():
return self._lastPath return self._lastPath
@@ -401,9 +400,8 @@ class Config:
def backupPath(self) -> Path: def backupPath(self) -> Path:
"""Return the backup path.""" """Return the backup path."""
if isinstance(self._backupPath, Path): if isinstance(self._backupPath, Path) and self._backupPath.is_dir():
if self._backupPath.is_dir(): return self._backupPath
return self._backupPath
return self._backPath return self._backPath
def errorText(self) -> str: def errorText(self) -> str:
@@ -614,24 +612,6 @@ class Config:
self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile) self.searchNextFile = conf.rdBool(sec, "searchnextfile", self.searchNextFile)
self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap) self.searchMatchCap = conf.rdBool(sec, "searchmatchcap", self.searchMatchCap)
# Deprecated Settings or Locations as of 2.0
# ToDo: These will be loaded for a few minor releases until the users have converted them
self.guiFont = conf.rdStr("Main", "guifont", self.guiFont)
self.guiFontSize = conf.rdInt("Main", "guifontsize", self.guiFontSize)
self.guiLocale = conf.rdStr("Main", "guilang", self.guiLocale)
self._backupPath = conf.rdPath("Backup", "backuppath", self._backupPath)
self.backupOnClose = conf.rdBool("Backup", "backuponclose", self.backupOnClose)
self.askBeforeBackup = conf.rdBool("Backup", "askbeforebackup", self.askBeforeBackup)
fmtSingleQuotes = conf.rdStrList(sec, "fmtsinglequote", [])
fmtDoubleQuotes = conf.rdStrList(sec, "fmtdoublequote", [])
if isinstance(fmtSingleQuotes, list) and len(fmtSingleQuotes) == 2:
self.fmtSQuoteOpen = fmtSingleQuotes[0]
self.fmtSQuoteClose = fmtSingleQuotes[1]
if isinstance(fmtDoubleQuotes, list) and len(fmtDoubleQuotes) == 2:
self.fmtDQuoteOpen = fmtDoubleQuotes[0]
self.fmtDQuoteClose = fmtDoubleQuotes[1]
# Check Values # Check Values
# ============ # ============
+5 -5
View File
@@ -511,7 +511,7 @@ class GuiProjectTree(QTreeWidget):
self.setDragDropMode(QAbstractItemView.InternalMove) self.setDragDropMode(QAbstractItemView.InternalMove)
self.setDropIndicatorShown(True) self.setDropIndicatorShown(True)
# Disable built-in autoscroll as it isn't working in some Qt # Disable built-in auto scroll as it isn't working in some Qt
# releases (see #1561) and instead use our own implementation # releases (see #1561) and instead use our own implementation
self.setAutoScroll(False) self.setAutoScroll(False)
@@ -533,7 +533,7 @@ class GuiProjectTree(QTreeWidget):
self.itemDoubleClicked.connect(self._treeDoubleClick) self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._treeSelectionChange) self.itemSelectionChanged.connect(self._treeSelectionChange)
# Autoscroll # Auto Scroll
self._scrollMargin = SHARED.theme.baseIconSize self._scrollMargin = SHARED.theme.baseIconSize
self._scrollDirection = 0 self._scrollDirection = 0
self._scrollTimer = QTimer() self._scrollTimer = QTimer()
@@ -1415,7 +1415,7 @@ class GuiProjectTree(QTreeWidget):
return return
def dragMoveEvent(self, event: QDragMoveEvent) -> None: def dragMoveEvent(self, event: QDragMoveEvent) -> None:
"""Capture the drag move event to enable edge autoscroll.""" """Capture the drag move event to enable edge auto scroll."""
y = event.pos().y() y = event.pos().y()
if y < self._scrollMargin: if y < self._scrollMargin:
if not self._scrollTimer.isActive(): if not self._scrollTimer.isActive():
@@ -1429,8 +1429,8 @@ class GuiProjectTree(QTreeWidget):
return return
def dropEvent(self, event: QDropEvent) -> None: def dropEvent(self, event: QDropEvent) -> None:
"""Overload the drop item event to ensure relevant data has been """Overload the drop item event to ensure the drag and drop
updated. action is allowed, and update relevant data.
""" """
sHandle = self.getSelectedHandle() sHandle = self.getSelectedHandle()
sItem = self._getTreeItem(sHandle) if sHandle else None sItem = self._getTreeItem(sHandle) if sHandle else None
+9 -7
View File
@@ -45,7 +45,7 @@ def testBaseConfig_Constructor(monkeypatch):
assert tstConf.osWindows is False assert tstConf.osWindows is False
assert tstConf.osUnknown is False assert tstConf.osUnknown is False
# macOS # MacOS
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("sys.platform", "darwin") mp.setattr("sys.platform", "darwin")
tstConf = Config() tstConf = Config()
@@ -225,6 +225,11 @@ def testBaseConfig_Methods(fncPath):
tmpStuff.rmdir() tmpStuff.rmdir()
assert tstConf.lastPath() == Path.home().absolute() assert tstConf.lastPath() == Path.home().absolute()
# Backup Path
assert tstConf.backupPath() == tstConf._backPath
tstConf.setBackupPath(fncPath)
assert tstConf.backupPath() == fncPath
# Recent Projects # Recent Projects
assert isinstance(tstConf.recentProjects, RecentProjects) assert isinstance(tstConf.recentProjects, RecentProjects)
@@ -233,8 +238,7 @@ def testBaseConfig_Methods(fncPath):
@pytest.mark.base @pytest.mark.base
def testBaseConfig_SettersGetters(fncPath): def testBaseConfig_SettersGetters(fncPath):
"""Set various sizes and positions """Set various sizes and positions."""
"""
tstConf = Config() tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath) tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
@@ -358,8 +362,7 @@ def testBaseConfig_SettersGetters(fncPath):
@pytest.mark.base @pytest.mark.base
def testBaseConfig_Internal(monkeypatch, fncPath): def testBaseConfig_Internal(monkeypatch, fncPath):
"""Check internal functions. """Check internal functions."""
"""
tstConf = Config() tstConf = Config()
tstConf.initConfig(confPath=fncPath, dataPath=fncPath) tstConf.initConfig(confPath=fncPath, dataPath=fncPath)
@@ -381,8 +384,7 @@ def testBaseConfig_Internal(monkeypatch, fncPath):
@pytest.mark.base @pytest.mark.base
def testBaseConfig_RecentCache(monkeypatch, tstPaths): def testBaseConfig_RecentCache(monkeypatch, tstPaths):
"""Test recent cache file. """Test recent cache file."""
"""
cacheFile = tstPaths.cnfDir / nwFiles.RECENT_FILE cacheFile = tstPaths.cnfDir / nwFiles.RECENT_FILE
recent = RecentProjects(CONFIG) recent = RecentProjects(CONFIG)
+174 -51
View File
@@ -27,8 +27,9 @@ from pathlib import Path
from tools import C, buildTestProject from tools import C, buildTestProject
from mocked import causeOSError from mocked import causeOSError
from PyQt5.QtCore import Qt from PyQt5.QtGui import QDragMoveEvent, QDropEvent
from PyQt5.QtWidgets import QMessageBox, QMenu, QTreeWidgetItem, QDialog from PyQt5.QtCore import QMimeData, QPoint, QTimer, Qt
from PyQt5.QtWidgets import QMessageBox, QMenu, QTreeWidget, QTreeWidgetItem, QDialog
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
@@ -46,7 +47,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
projView = nwGUI.projView projView = nwGUI.projView
projTree = nwGUI.projView.projTree projTree = nwGUI.projView.projTree
theProject = SHARED.project project = SHARED.project
# Try to add item with no project # Try to add item with no project
assert projView.projTree.newTreeItem(nwItemType.FILE) is False assert projView.projTree.newTreeItem(nwItemType.FILE) is False
@@ -66,7 +67,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
# Create root item # Create root item
assert projView.projTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True assert projView.projTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert "0000000000010" in theProject.tree assert "0000000000010" in project.tree
# File/Folder Items # File/Folder Items
# ================= # =================
@@ -81,51 +82,51 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
# Create new folder as child of Novel folder # Create new folder as child of Novel folder
projView.setSelectedHandle(C.hNovelRoot) projView.setSelectedHandle(C.hNovelRoot)
assert projView.projTree.newTreeItem(nwItemType.FOLDER) is True assert projView.projTree.newTreeItem(nwItemType.FOLDER) is True
assert theProject.tree["0000000000011"].itemParent == C.hNovelRoot assert project.tree["0000000000011"].itemParent == C.hNovelRoot # type: ignore
assert theProject.tree["0000000000011"].itemRoot == C.hNovelRoot assert project.tree["0000000000011"].itemRoot == C.hNovelRoot # type: ignore
assert theProject.tree["0000000000011"].itemClass == nwItemClass.NOVEL assert project.tree["0000000000011"].itemClass == nwItemClass.NOVEL # type: ignore
# Add a new file in the new folder # Add a new file in the new folder
projView.setSelectedHandle("0000000000011") projView.setSelectedHandle("0000000000011")
assert projView.projTree.newTreeItem(nwItemType.FILE) is True assert projView.projTree.newTreeItem(nwItemType.FILE) is True
assert theProject.tree["0000000000012"].itemParent == "0000000000011" assert project.tree["0000000000012"].itemParent == "0000000000011" # type: ignore
assert theProject.tree["0000000000012"].itemRoot == C.hNovelRoot assert project.tree["0000000000012"].itemRoot == C.hNovelRoot # type: ignore
assert theProject.tree["0000000000012"].itemClass == nwItemClass.NOVEL assert project.tree["0000000000012"].itemClass == nwItemClass.NOVEL # type: ignore
# Add a new chapter next to the other new file # Add a new chapter next to the other new file
projView.setSelectedHandle("0000000000012") projView.setSelectedHandle("0000000000012")
assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=2) is True assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=2) is True
assert theProject.tree["0000000000013"].itemParent == "0000000000011" assert project.tree["0000000000013"].itemParent == "0000000000011" # type: ignore
assert theProject.tree["0000000000013"].itemRoot == C.hNovelRoot assert project.tree["0000000000013"].itemRoot == C.hNovelRoot # type: ignore
assert theProject.tree["0000000000013"].itemClass == nwItemClass.NOVEL assert project.tree["0000000000013"].itemClass == nwItemClass.NOVEL # type: ignore
assert nwGUI.openDocument("0000000000013") assert nwGUI.openDocument("0000000000013")
assert nwGUI.docEditor.getText() == "## New Chapter\n\n" assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
# Add a new scene next to the other new file # Add a new scene next to the other new file
projView.setSelectedHandle("0000000000012") projView.setSelectedHandle("0000000000012")
assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=3) is True assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=3) is True
assert theProject.tree["0000000000014"].itemParent == "0000000000011" assert project.tree["0000000000014"].itemParent == "0000000000011" # type: ignore
assert theProject.tree["0000000000014"].itemRoot == C.hNovelRoot assert project.tree["0000000000014"].itemRoot == C.hNovelRoot # type: ignore
assert theProject.tree["0000000000014"].itemClass == nwItemClass.NOVEL assert project.tree["0000000000014"].itemClass == nwItemClass.NOVEL # type: ignore
assert nwGUI.openDocument("0000000000014") assert nwGUI.openDocument("0000000000014")
assert nwGUI.docEditor.getText() == "### New Scene\n\n" assert nwGUI.docEditor.getText() == "### New Scene\n\n"
# Add a new file to the characters folder # Add a new file to the characters folder
projView.setSelectedHandle(C.hCharRoot) projView.setSelectedHandle(C.hCharRoot)
assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) is True assert projView.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) is True
assert theProject.tree["0000000000015"].itemParent == C.hCharRoot assert project.tree["0000000000015"].itemParent == C.hCharRoot # type: ignore
assert theProject.tree["0000000000015"].itemRoot == C.hCharRoot assert project.tree["0000000000015"].itemRoot == C.hCharRoot # type: ignore
assert theProject.tree["0000000000015"].itemClass == nwItemClass.CHARACTER assert project.tree["0000000000015"].itemClass == nwItemClass.CHARACTER # type: ignore
assert nwGUI.openDocument("0000000000015") assert nwGUI.openDocument("0000000000015")
assert nwGUI.docEditor.getText() == "# New Note\n\n" assert nwGUI.docEditor.getText() == "# New Note\n\n"
# Make sure the sibling folder bug trap works # Make sure the sibling folder bug trap works
projView.setSelectedHandle("0000000000013") projView.setSelectedHandle("0000000000013")
theProject.tree["0000000000013"].setParent(None) # This should not happen project.tree["0000000000013"].setParent(None) # This should not happen # type: ignore
caplog.clear() caplog.clear()
assert projView.projTree.newTreeItem(nwItemType.FILE) is False assert projView.projTree.newTreeItem(nwItemType.FILE) is False
assert "Internal error" in caplog.text assert "Internal error" in caplog.text
theProject.tree["0000000000013"].setParent("0000000000011") project.tree["0000000000013"].setParent("0000000000011") # type: ignore
# Cancel during creation # Cancel during creation
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -135,7 +136,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
# Get the trash folder # Get the trash folder
projView.projTree._addTrashRoot() projView.projTree._addTrashRoot()
trashHandle = theProject.trashFolder() trashHandle = project.trashFolder()
projView.setSelectedHandle(trashHandle) projView.setSelectedHandle(trashHandle)
assert projView.projTree.newTreeItem(nwItemType.FILE) is False assert projView.projTree.newTreeItem(nwItemType.FILE) is False
assert "Cannot add new files or folders to the Trash folder" in caplog.text assert "Cannot add new files or folders to the Trash folder" in caplog.text
@@ -147,7 +148,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("Stuff", True)) mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("Stuff", True))
projTree.renameTreeItem(C.hPlotRoot) is True projTree.renameTreeItem(C.hPlotRoot) is True
assert theProject.tree[C.hPlotRoot].itemName == "Stuff" assert project.tree[C.hPlotRoot].itemName == "Stuff" # type: ignore
# Rename invalid folder # Rename invalid folder
projTree.renameTreeItem("0000000000000") is False projTree.renameTreeItem("0000000000000") is False
@@ -159,7 +160,7 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, projPath, mockRn
assert projView.projTree.revealNewTreeItem("abc") is False assert projView.projTree.revealNewTreeItem("abc") is False
# Add an item that cannot be displayed in the tree # Add an item that cannot be displayed in the tree
nHandle = theProject.newFile("Test", None) nHandle = project.newFile("Test", None) # type: ignore
assert projView.projTree.revealNewTreeItem(nHandle) is False assert projView.projTree.revealNewTreeItem(nHandle) is False
# Adding an invalid item directly to the tree should also fail # Adding an invalid item directly to the tree should also fail
@@ -550,7 +551,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
SHARED.project.newFile("SubNote", hNovelNote) SHARED.project.newFile("SubNote", hNovelNote)
projTree.revealNewTreeItem(hSubNote) projTree.revealNewTreeItem(hSubNote)
assert SHARED.project.tree[hSubNote].itemParent == hNovelNote assert SHARED.project.tree[hSubNote].itemParent == hNovelNote # type: ignore
def itemPos(tHandle): def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center() return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
@@ -579,6 +580,7 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
# ===================== # =====================
# Trigger the dedicated functions the menu entries connect to # Trigger the dedicated functions the menu entries connect to
nwItem = SHARED.project.tree[hNovelNote] nwItem = SHARED.project.tree[hNovelNote]
assert nwItem is not None
# Toggle active flag # Toggle active flag
assert nwItem.isActive is True assert nwItem.isActive is True
@@ -619,17 +621,17 @@ def testGuiProjTree_ContextMenu(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No) mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT) projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
assert SHARED.project.tree[hNewFolderOne].isFolderType() assert SHARED.project.tree[hNewFolderOne].isFolderType() # type: ignore
# Convert the first folder to a document # Convert the first folder to a document
projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT) projTree._covertFolderToFile(hNewFolderOne, nwItemLayout.DOCUMENT)
assert SHARED.project.tree[hNewFolderOne].isFileType() assert SHARED.project.tree[hNewFolderOne].isFileType() # type: ignore
assert SHARED.project.tree[hNewFolderOne].isDocumentLayout() assert SHARED.project.tree[hNewFolderOne].isDocumentLayout() # type: ignore
# Convert the second folder to a note # Convert the second folder to a note
projTree._covertFolderToFile(hNewFolderTwo, nwItemLayout.NOTE) projTree._covertFolderToFile(hNewFolderTwo, nwItemLayout.NOTE)
assert SHARED.project.tree[hNewFolderTwo].isFileType() assert SHARED.project.tree[hNewFolderTwo].isFileType() # type: ignore
assert SHARED.project.tree[hNewFolderTwo].isNoteLayout() assert SHARED.project.tree[hNewFolderTwo].isNoteLayout() # type: ignore
# qtbot.stop() # qtbot.stop()
@@ -646,19 +648,18 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QDialog.Accepted) monkeypatch.setattr(GuiDocMerge, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiDocMerge, "getData", lambda *a: mergeData) monkeypatch.setattr(GuiDocMerge, "getData", lambda *a: mergeData)
# Create a project
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
theProject = SHARED.project project = SHARED.project
projTree = nwGUI.projView.projTree projTree = nwGUI.projView.projTree
mergedDoc1 = "0000000000014" mergedDoc1 = "0000000000014"
# Create File to Merge # Create File to Merge
hChapter1 = theProject.newFile("Chapter 1", C.hNovelRoot) hChapter1 = project.newFile("Chapter 1", C.hNovelRoot)
hSceneOne11 = theProject.newFile("Scene 1.1", hChapter1) hSceneOne11 = project.newFile("Scene 1.1", hChapter1) # type: ignore
hSceneOne12 = theProject.newFile("Scene 1.2", hChapter1) hSceneOne12 = project.newFile("Scene 1.2", hChapter1) # type: ignore
hSceneOne13 = theProject.newFile("Scene 1.3", hChapter1) hSceneOne13 = project.newFile("Scene 1.3", hChapter1) # type: ignore
docText1 = "\n\n".join(ipsumText[0:2]) + "\n\n" docText1 = "\n\n".join(ipsumText[0:2]) + "\n\n"
docText2 = "\n\n".join(ipsumText[1:3]) + "\n\n" docText2 = "\n\n".join(ipsumText[1:3]) + "\n\n"
@@ -671,10 +672,10 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
lenText4 = len(docText4) lenText4 = len(docText4)
lenAll = lenText1 + lenText2 + lenText3 + lenText4 lenAll = lenText1 + lenText2 + lenText3 + lenText4
theProject.writeNewFile(hChapter1, 2, True, docText1) project.writeNewFile(hChapter1, 2, True, docText1) # type: ignore
theProject.writeNewFile(hSceneOne11, 3, True, docText2) project.writeNewFile(hSceneOne11, 3, True, docText2) # type: ignore
theProject.writeNewFile(hSceneOne12, 3, True, docText3) project.writeNewFile(hSceneOne12, 3, True, docText3) # type: ignore
theProject.writeNewFile(hSceneOne13, 3, True, docText4) project.writeNewFile(hSceneOne13, 3, True, docText4) # type: ignore
projTree.revealNewTreeItem(hChapter1) projTree.revealNewTreeItem(hChapter1)
projTree.revealNewTreeItem(hSceneOne11) projTree.revealNewTreeItem(hSceneOne11)
@@ -705,7 +706,7 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
# The merge goes through # The merge goes through
assert projTree._mergeDocuments(hChapter1, True) is True assert projTree._mergeDocuments(hChapter1, True) is True
assert len(theProject.storage.getDocument(mergedDoc1).readDocument()) > lenAll assert len(project.storage.getDocument(mergedDoc1).readDocument()) > lenAll # type: ignore
# Merge to Existing Doc # Merge to Existing Doc
# ===================== # =====================
@@ -724,13 +725,13 @@ def testGuiProjTree_MergeDocuments(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
# Successful merge, and move to trash # Successful merge, and move to trash
mergeData["moveToTrash"] = True mergeData["moveToTrash"] = True
assert len(theProject.storage.getDocument(hChapter1).readDocument()) < lenAll assert len(project.storage.getDocument(hChapter1).readDocument()) < lenAll # type: ignore
assert projTree._mergeDocuments(hChapter1, False) is True assert projTree._mergeDocuments(hChapter1, False) is True
assert len(theProject.storage.getDocument(hChapter1).readDocument()) > lenAll assert len(project.storage.getDocument(hChapter1).readDocument()) > lenAll # type: ignore
assert theProject.tree.isTrash(hSceneOne11) assert project.tree.isTrash(hSceneOne11) # type: ignore
assert theProject.tree.isTrash(hSceneOne12) assert project.tree.isTrash(hSceneOne12) # type: ignore
assert theProject.tree.isTrash(hSceneOne13) assert project.tree.isTrash(hSceneOne13) # type: ignore
# qtbot.stop() # qtbot.stop()
@@ -770,7 +771,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
) )
hSplitDoc = theProject.newFile("Split Doc", C.hNovelRoot) hSplitDoc = theProject.newFile("Split Doc", C.hNovelRoot)
theProject.writeNewFile(hSplitDoc, 1, True, docText) theProject.writeNewFile(hSplitDoc, 1, True, docText) # type: ignore
projTree.revealNewTreeItem(hSplitDoc, nHandle=C.hNovelRoot, wordCount=True) projTree.revealNewTreeItem(hSplitDoc, nHandle=C.hNovelRoot, wordCount=True)
docText = f"# Split Doc\n\n{docText}" docText = f"# Split Doc\n\n{docText}"
@@ -835,7 +836,7 @@ def testGuiProjTree_SplitDocument(qtbot, monkeypatch, nwGUI, projPath, mockRnd,
assert tHandle in theProject.tree assert tHandle in theProject.tree
assert (projPath / "content" / f"{tHandle}.nwd").is_file() assert (projPath / "content" / f"{tHandle}.nwd").is_file()
assert theProject.tree.isTrash(hSplitDoc) is True assert theProject.tree.isTrash(hSplitDoc) is True # type: ignore
# Cancelled by user # Cancelled by user
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
@@ -880,7 +881,7 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
assert projTree._duplicateFromHandle(C.hNovelRoot) is True assert projTree._duplicateFromHandle(C.hNovelRoot) is True
assert len(SHARED.project.tree) == 21 assert len(SHARED.project.tree) == 21
# Check tree order that all items are next to eachother # Check tree order that all items are next to each other
assert SHARED.project.tree._order == [ assert SHARED.project.tree._order == [
C.hNovelRoot, C.hTitlePage, "0000000000010", C.hChapterDir, C.hChapterDoc, C.hSceneDoc, C.hNovelRoot, C.hTitlePage, "0000000000010", C.hChapterDir, C.hChapterDoc, C.hSceneDoc,
"0000000000011", "0000000000012", "0000000000013", "0000000000014", "0000000000015", "0000000000011", "0000000000012", "0000000000013", "0000000000014", "0000000000015",
@@ -904,14 +905,136 @@ def testGuiProjTree_Duplicate(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mock
# END Test testGuiProjTree_Duplicate # END Test testGuiProjTree_Duplicate
@pytest.mark.gui
def testGuiProjTree_AutoScroll(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd):
"""Test the auto scroll feature."""
buildTestProject(nwGUI, projPath)
projTree: GuiProjectTree = nwGUI.projView.projTree
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda parent, text: (text, True))
monkeypatch.setattr(QTreeWidget, "dragMoveEvent", lambda *a, **k: None)
monkeypatch.setattr(QTimer, "isActive", lambda *a: False)
monkeypatch.setattr(QTimer, "start", lambda *a: None)
projTree.setSelectedHandle(C.hChapterDir, True)
projTree._getTreeItem(C.hChapterDir).setExpanded(True) # type: ignore
for i in range(100):
projTree.newTreeItem(nwItemType.FILE, None, 3, False)
projTree.setSelectedHandle("0000000000015", True)
nwGUI.resize(500, 500)
action = Qt.DropAction.MoveAction
mime = QMimeData()
mouse = Qt.MouseButton.LeftButton
modifier = Qt.KeyboardModifier.NoModifier
# Scroll Down
h = projTree.height()
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, h-1), action, mime, mouse, modifier))
assert projTree._scrollDirection == 1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, h-2), action, mime, mouse, modifier))
assert projTree._scrollDirection == 1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, h-3), action, mime, mouse, modifier))
assert projTree._scrollDirection == 1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, h-4), action, mime, mouse, modifier))
assert projTree._scrollDirection == 1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
# Scroll Up
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, 1), action, mime, mouse, modifier))
assert projTree._scrollDirection == -1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, 2), action, mime, mouse, modifier))
assert projTree._scrollDirection == -1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, 3), action, mime, mouse, modifier))
assert projTree._scrollDirection == -1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
projTree.dragMoveEvent(QDragMoveEvent(QPoint(1, 4), action, mime, mouse, modifier))
assert projTree._scrollDirection == -1
projTree._doAutoScroll()
assert projTree._scrollDirection == 0
# qtbot.stop()
# END Test testGuiProjTree_AutoScroll
@pytest.mark.gui
def testGuiProjTree_DragAndDrop(qtbot, monkeypatch, caplog, nwGUI: GuiMain, projPath, mockRnd):
"""Test the auto scroll feature."""
buildTestProject(nwGUI, projPath)
projTree: GuiProjectTree = nwGUI.projView.projTree
monkeypatch.setattr(QTreeWidget, "dropEvent", lambda *a, **k: None)
projTree.setSelectedHandle(C.hSceneDoc, True)
projTree._getTreeItem(C.hChapterDir).setExpanded(True) # type: ignore
nPos = projTree.visualItemRect(projTree._getTreeItem(C.hNovelRoot)).bottomLeft()
action = Qt.DropAction.MoveAction
mime = QMimeData()
mouse = Qt.MouseButton.LeftButton
modifier = Qt.KeyboardModifier.NoModifier
# Move an item
# The actual move is blocked, but the undo history should record
# the event, although without an actual move implied
assert projTree._lastMove == {}
event = QDropEvent(nPos, action, mime, mouse, modifier)
projTree.dropEvent(event)
assert projTree._lastMove == {
"item": projTree._getTreeItem(C.hSceneDoc),
"parent": projTree._getTreeItem(C.hChapterDir),
"index": 1,
}
# Invalid location
caplog.clear()
event = QDropEvent(QPoint(1000, 1000), action, mime, mouse, modifier)
projTree.dropEvent(event)
assert event.isAccepted() is False
assert "Invalid drop location" in caplog.text
# No item selected
caplog.clear()
event = QDropEvent(nPos, action, mime, mouse, modifier)
projTree.clearSelection()
projTree.dropEvent(event)
assert event.isAccepted() is False
assert "Invalid drag and drop event" in caplog.text
# Root item selected
caplog.clear()
event = QDropEvent(nPos, action, mime, mouse, modifier)
projTree.clearSelection()
projTree.setSelectedHandle(C.hNovelRoot, True)
projTree.dropEvent(event)
assert event.isAccepted() is False
assert "Invalid drag and drop event" in caplog.text
# qtbot.stop()
# END Test testGuiProjTree_DragAndDrop
@pytest.mark.gui @pytest.mark.gui
def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd): def testGuiProjTree_Other(qtbot, monkeypatch, nwGUI: GuiMain, projPath, mockRnd):
"""Test various parts of the project tree class not covered by """Test various parts of the project tree class not covered by
other tests. other tests.
""" """
# Create a project
buildTestProject(nwGUI, projPath) buildTestProject(nwGUI, projPath)
projView: GuiProjectView = nwGUI.projView projView: GuiProjectView = nwGUI.projView
projTree: GuiProjectTree = nwGUI.projView.projTree projTree: GuiProjectTree = nwGUI.projView.projTree