From d158de5d8265c0de1f6bc8e88d0faefdb957bcf6 Mon Sep 17 00:00:00 2001
From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com>
Date: Sun, 17 Apr 2022 17:00:43 +0200
Subject: [PATCH] Update test coverage
---
novelwriter/gui/projtree.py | 25 +-
tests/conftest.py | 4 +-
tests/test_core/test_core_project.py | 11 +-
tests/test_core/test_core_tree.py | 110 ++++--
tests/test_gui/test_gui_projtree.py | 557 ++++++++++++++++++---------
5 files changed, 474 insertions(+), 233 deletions(-)
diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py
index 0f25f68c..5fdf7fcc 100644
--- a/novelwriter/gui/projtree.py
+++ b/novelwriter/gui/projtree.py
@@ -256,6 +256,9 @@ class GuiProjectTree(QTreeWidget):
"""Reveal a newly added project item in the project tree.
"""
nwItem = self.theProject.projTree[tHandle]
+ if nwItem is None:
+ return False
+
trItem = self._addTreeItem(nwItem, nHandle)
if trItem is None:
return False
@@ -459,13 +462,6 @@ class GuiProjectTree(QTreeWidget):
if doPermanent:
logger.debug("Permanently deleting file with handle '%s'", tHandle)
- self.propagateCount(tHandle, 0)
- tIndex = trItemP.indexOfChild(trItemS)
- trItemC = trItemP.takeChild(tIndex)
-
- if self.theParent.docEditor.docHandle() == tHandle:
- self.theParent.closeDocument()
-
delDoc = NWDoc(self.theProject, tHandle)
if not delDoc.deleteDocument():
self.theParent.makeAlert([
@@ -473,6 +469,13 @@ class GuiProjectTree(QTreeWidget):
], nwAlert.ERROR)
return False
+ self.propagateCount(tHandle, 0)
+ tIndex = trItemP.indexOfChild(trItemS)
+ trItemC = trItemP.takeChild(tIndex)
+
+ if self.theParent.docEditor.docHandle() == tHandle:
+ self.theParent.closeDocument()
+
self.theIndex.deleteHandle(tHandle)
self._deleteTreeItem(tHandle)
self._setTreeChanged(True)
@@ -486,13 +489,10 @@ class GuiProjectTree(QTreeWidget):
self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName),
)
if msgYes:
- if pHandle is None:
- logger.warning("File has no parent item")
-
logger.debug("Moving file '%s' to trash", tHandle)
self.propagateCount(tHandle, 0)
- tIndex = trItemP.indexOfChild(trItemS)
+ tIndex = trItemP.indexOfChild(trItemS)
trItemC = trItemP.takeChild(tIndex)
trItemT.addChild(trItemC)
self._updateItemParent(tHandle)
@@ -508,6 +508,7 @@ class GuiProjectTree(QTreeWidget):
if trItemP is None:
logger.error("Could not delete folder")
return False
+
tIndex = trItemP.indexOfChild(trItemS)
if trItemS.childCount() == 0:
trItemP.takeChild(tIndex)
@@ -993,7 +994,7 @@ class GuiProjectTree(QTreeWidget):
"""
if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
nwItem = self.theProject.projTree[tHandle]
- if nwItem.itemClass == nwItemClass.NOVEL:
+ if nwItem.itemClass in nwLists.CLS_NOVEL:
self.novelItemChanged.emit()
else:
self.noteItemChanged.emit()
diff --git a/tests/conftest.py b/tests/conftest.py
index 8551724b..a11db7e2 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -166,7 +166,9 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
"""
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr("novelwriter.CONFIG", fncConf)
- nwGUI = novelwriter.main(["--testmode", "--config=%s" % fncDir, "--data=%s" % fncDir])
+ nwGUI = novelwriter.main(
+ ["--testmode", "--info", "--config=%s" % fncDir, "--data=%s" % fncDir]
+ )
qtbot.addWidget(nwGUI)
nwGUI.show()
qtbot.wait(20)
diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py
index 3d5b37f4..b584679d 100644
--- a/tests/test_core/test_core_project.py
+++ b/tests/test_core/test_core_project.py
@@ -1016,9 +1016,16 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
"""
theProject = NWProject(mockGUI)
- assert theProject.openProject(nwLipsum)
+ assert theProject.openProject(nwLipsum) is True
assert theProject.projTree["636b6aa9b697b"] is None
- assert theProject.closeProject()
+
+ # Add a file with non-existent parent
+ # This file will be renoved from the project on open
+ assert theProject.newFile("Oops", "0000000000000")
+
+ # Save and close
+ assert theProject.saveProject() is True
+ assert theProject.closeProject() is True
# First Item with Meta Data
orphPath = os.path.join(nwLipsum, "content", "636b6aa9b697b.nwd")
diff --git a/tests/test_core/test_core_tree.py b/tests/test_core/test_core_tree.py
index 6cfa9cf0..3e8aeeca 100644
--- a/tests/test_core/test_core_tree.py
+++ b/tests/test_core/test_core_tree.py
@@ -21,6 +21,7 @@ along with this program. If not, see .
import os
import pytest
+import random
from lxml import etree
from hashlib import sha256
@@ -36,6 +37,7 @@ from novelwriter.constants import nwFiles
def mockItems(mockGUI):
"""Create a list of mock items.
"""
+ random.seed(42)
theProject = NWProject(mockGUI)
itemA = NWItem(theProject)
@@ -151,8 +153,28 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
# Check that we have the correct archive and trash folders
assert theTree.trashRoot() == "a000000000003"
assert theTree.archiveRoot() == "a000000000002"
- assert theTree.isTrashRoot("a000000000003")
- assert theTree.isRoot("a000000000002")
+ assert theTree.isTrashRoot("a000000000003") is True
+ assert theTree.isRoot("a000000000002") is True
+
+ # Check the isTrash function
+ assert theTree.isTrash("0000000000000") is True # Doesn't exist
+ assert theTree.isTrash("a000000000003") is True # This the trash folder
+
+ theTree["a000000000003"].setClass(nwItemClass.NO_CLASS)
+ assert theTree.isTrash("a000000000003") is True # This is still trash
+ theTree["a000000000003"].setClass(nwItemClass.TRASH)
+
+ assert theTree.isTrash("b000000000002") is False # This is not trash
+
+ value = theTree["b000000000002"].itemParent
+ theTree["b000000000002"].setParent("a000000000003")
+ assert theTree.isTrash("b000000000002") is True # This is in trash
+ theTree["b000000000002"].setParent(value)
+
+ value = theTree["b000000000002"].itemRoot
+ theTree["b000000000002"].setRoot("a000000000003")
+ assert theTree.isTrash("b000000000002") is True # This is in trash
+ theTree["b000000000002"].setRoot(value)
# Try to add another trash folder
itemT = NWItem(theProject)
@@ -161,7 +183,7 @@ def testCoreTree_BuildTree(mockGUI, mockItems):
itemT._class = nwItemClass.TRASH
itemT._expanded = False
- assert not theTree.append("1234567890abc", None, itemT)
+ assert theTree.append("1234567890abc", None, itemT) is False
assert len(theTree) == len(mockItems)
# Generate handle automatically
@@ -251,12 +273,24 @@ def testCoreTree_Methods(mockGUI, mockItems):
assert theTree.findRoot(nwItemClass.NOVEL) == "a000000000001"
assert theTree.findRoot(nwItemClass.CHARACTER) == "a000000000004"
+ # Add a fake item to root and check that it can handle it
+ theTree._treeRoots["0000000000000"] = NWItem(theProject)
+ assert theTree.findRoot(nwItemClass.WORLD) is None
+ del theTree._treeRoots["0000000000000"]
+
# Get item path
assert theTree.getItemPath("stuff") == []
assert theTree.getItemPath("c000000000001") == [
"c000000000001", "b000000000001", "a000000000001"
]
+ # Cause recursion error
+ maxDepth = theTree.MAX_DEPTH
+ theTree.MAX_DEPTH = 0
+ with pytest.raises(RecursionError):
+ theTree.getItemPath("c000000000001")
+ theTree.MAX_DEPTH = maxDepth
+
# Break the folder parent handle
theTree["b000000000001"]._parent = "stuff"
assert theTree.getItemPath("c000000000001") == [
@@ -381,7 +415,7 @@ def testCoreTree_Reorder(mockGUI, mockItems):
@pytest.mark.core
-def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
+def testCoreTree_XMLPackUnpack(mockGUI, mockItems, constData):
"""Test packing and unpacking the tree to and from XML.
"""
theProject = NWProject(mockGUI)
@@ -395,39 +429,41 @@ def testCoreTree_XMLPackUnpack(mockGUI, mockItems):
nwXML = etree.Element("novelWriterXML")
theTree.packXML(nwXML)
- assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == (
- b''
- b''
- b'- Novel
'
- b'- Act One
'
- b'- Chapter One
'
- b'- Scene One
'
- b'- Outtakes
'
- b'- Trash
'
- b'- Characters
'
- b'- Jane Doe
'
- b''
- b''
- )
+ assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == bytes((
+ ''
+ ''
+ '- Novel
'
+ '- Act One
'
+ '- Chapter One
'
+ '- Scene One
'
+ '- Outtakes
'
+ '- Trash
'
+ '- Characters
'
+ '- Jane Doe
'
+ ''
+ ''
+ ).format(
+ s0=constData.statusKeys[0], i0=constData.importKeys[0]
+ ), encoding="utf8")
theTree.clear()
assert len(theTree) == 0
diff --git a/tests/test_gui/test_gui_projtree.py b/tests/test_gui/test_gui_projtree.py
index 9d43481c..1f1c8c95 100644
--- a/tests/test_gui/test_gui_projtree.py
+++ b/tests/test_gui/test_gui_projtree.py
@@ -22,9 +22,6 @@ along with this program. If not, see .
import pytest
import os
-from tools import writeFile
-
-from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QAction, QMessageBox
from novelwriter.guimain import GuiMain
@@ -33,208 +30,406 @@ from novelwriter.enum import nwItemType, nwItemClass
@pytest.mark.gui
-def testGuiProjTree_TreeItems(qtbot, caplog, monkeypatch, nwGUI, nwMinimal):
+def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir):
"""Test adding and removing items from the project tree.
"""
# Block message box
- monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
- monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
- nwGUI.theProject.projTree.setSeed(42)
nwTree = nwGUI.treeView
- ##
- # Add New Items
- ##
+ # Try to add item with no project
+ assert nwTree.newTreeItem(nwItemType.FILE) is False
- # Try to add and move item with no project
- assert nwTree.newTreeItem(nwItemType.FILE, None) is False
- assert nwTree.moveTreeItem(1) is False
+ # Create a project
+ nwGUI.theProject.projTree.setSeed(42)
+ prjDir = os.path.join(fncDir, "project")
+ assert nwGUI.newProject({"projPath": prjDir}) is True
- # Open a project
- assert nwGUI.openProject(nwMinimal) is True
+ # No itemType set
+ nwTree.clearSelection()
+ assert nwTree.newTreeItem(None) is False
+
+ # Root Items
+ # ==========
+
+ # No class set
+ assert nwTree.newTreeItem(nwItemType.ROOT) is False
+
+ # Create root item
+ assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
+ assert "1a6562590ef19" in nwGUI.theProject.projTree
+
+ # File/Folder Items
+ # =================
# No location selected for new item
nwTree.clearSelection()
- assert nwTree.newTreeItem(nwItemType.FILE, None) is False
- assert nwTree.newTreeItem(nwItemType.FOLDER, None) is False
- assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) is True
+ caplog.clear()
+ assert nwTree.newTreeItem(nwItemType.FILE) is False
+ assert nwTree.newTreeItem(nwItemType.FOLDER) is False
+ assert "Did not find anywhere" in caplog.text
- # No itemType set or ROOT, but no class
- nwTree.clearSelection()
- assert nwTree.newTreeItem(None, None) is False
- assert nwTree.newTreeItem(nwItemType.ROOT, None) is False
+ # Create new folder as child of Novel folder
+ nwTree.setSelectedHandle("73475cb40a568")
+ assert nwTree.newTreeItem(nwItemType.FOLDER) is True
+ assert nwGUI.theProject.projTree["031b4af5197ec"].itemParent == "73475cb40a568"
+ assert nwGUI.theProject.projTree["031b4af5197ec"].itemRoot == "73475cb40a568"
+ assert nwGUI.theProject.projTree["031b4af5197ec"].itemClass == nwItemClass.NOVEL
- # Select a location
- chItem = nwTree._getTreeItem("a6d311a93600a")
- nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
- chItem.setExpanded(True)
+ # Add a new file in the new folder
+ nwTree.setSelectedHandle("031b4af5197ec")
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemParent == "031b4af5197ec"
+ assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemRoot == "73475cb40a568"
+ assert nwGUI.theProject.projTree["41cfc0d1f2d12"].itemClass == nwItemClass.NOVEL
- # Create new item with no class set (defaults to NOVEL)
- assert nwTree.newTreeItem(nwItemType.FILE, None) is True
- assert nwTree.newTreeItem(nwItemType.FOLDER, None) is True
+ # Add a new file next to the other new file
+ nwTree.setSelectedHandle("41cfc0d1f2d12")
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwGUI.theProject.projTree["2858dcd1057d3"].itemParent == "031b4af5197ec"
+ assert nwGUI.theProject.projTree["2858dcd1057d3"].itemRoot == "73475cb40a568"
+ assert nwGUI.theProject.projTree["2858dcd1057d3"].itemClass == nwItemClass.NOVEL
+ assert nwGUI.openDocument("2858dcd1057d3")
+ assert nwGUI.docEditor.getText() == "### New Document\n\n"
- # Check that we have the correct tree order
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
- ]
+ # Add a new file to the characters folder
+ nwTree.setSelectedHandle("71ee45a3c0db9")
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwGUI.theProject.projTree["2fca346db6561"].itemParent == "71ee45a3c0db9"
+ assert nwGUI.theProject.projTree["2fca346db6561"].itemRoot == "71ee45a3c0db9"
+ assert nwGUI.theProject.projTree["2fca346db6561"].itemClass == nwItemClass.CHARACTER
+ assert nwGUI.openDocument("2fca346db6561")
+ assert nwGUI.docEditor.getText() == "# New Note\n\n"
- # Add more roots
- assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True # Duplicate
- assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.CUSTOM) is True # Unique
+ # Make sure the sibling folder bug trap works
+ nwTree.setSelectedHandle("2858dcd1057d3")
+ nwGUI.theProject.projTree["2858dcd1057d3"].setParent(None) # This should not happen
+ caplog.clear()
+ assert nwTree.newTreeItem(nwItemType.FILE) is False
+ assert "Internal error" in caplog.text
+ nwGUI.theProject.projTree["2858dcd1057d3"].setParent("031b4af5197ec")
- # Change max depth and try to add a subfolder that is too deep
- monkeypatch.setattr("novelwriter.constants.nwConst.MAX_DEPTH", 2)
- chItem = nwTree._getTreeItem("71ee45a3c0db9")
- nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
- assert nwTree.newTreeItem(nwItemType.FOLDER, None) is False
+ # Get the trash folder
+ nwTree._addTrashRoot()
+ trashHandle = nwGUI.theProject.trashFolder()
+ nwTree.setSelectedHandle(trashHandle)
+ assert nwTree.newTreeItem(nwItemType.FILE) is False
+ assert "Cannot add new files or folders to the Trash folder" in caplog.text
- ##
- # Move Items
- ##
+ # Other Checks
+ # ============
- nwTree.setSelectedHandle("8c659a11cd429")
+ # Also check error handling in reveal function
+ assert nwTree.revealNewTreeItem("abc") is False
- # Shift focus and try to move item
- monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
- assert nwTree.moveTreeItem(1) is False
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
- ]
- monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
-
- # Move second item up twice (should give same result)
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
- ]
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "8c659a11cd429", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9"
- ]
-
- # Move it back down four times (last two should be the same)
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "8c659a11cd429", "44cb730c42048", "71ee45a3c0db9"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "8c659a11cd429", "71ee45a3c0db9"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
- ]
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
- ]
-
- # Move up twice, and undo
- nwTree._lastMove = {}
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
- nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
- assert nwTree.getTreeFromHandle("a6d311a93600a") == [
- "a6d311a93600a", "f5ab3e30151e1", "44cb730c42048", "71ee45a3c0db9", "8c659a11cd429"
- ]
-
- # Move a root item (top level items are different) twice
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 10
- nwTree.setSelectedHandle("9d5247ab588e0")
-
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 11
-
- nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
- nwTree.flushTreeOrder()
- assert nwGUI.theProject.projTree._treeOrder.index("9d5247ab588e0") == 12
-
- ##
- # Delete and Trash
- ##
-
- # Add some content to the new file
- nwGUI.openDocument("73475cb40a568")
- nwGUI.docEditor.setText("# Hello World\n")
- nwGUI.saveDocument()
- nwGUI.saveProject()
- assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
-
- # Delete the items we added earlier
- nwTree.clearSelection()
- assert nwTree.emptyTrash() is False # No folder yet
- assert nwTree.deleteItem(None) is False
- assert nwTree.deleteItem("1111111111111") is False
- assert nwTree.deleteItem("73475cb40a568") is True # New File
- assert nwTree.deleteItem("71ee45a3c0db9") is True # New Folder
- assert nwTree.deleteItem("811786ad1ae74") is True # Custom Root
- assert "73475cb40a568" in nwGUI.theProject.projTree._treeOrder
- assert "71ee45a3c0db9" not in nwGUI.theProject.projTree._treeOrder
- assert "811786ad1ae74" not in nwGUI.theProject.projTree._treeOrder
-
- # The file is in trash, empty it
- assert os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
- assert nwTree.emptyTrash() is True
- assert nwTree.emptyTrash() is False # Already empty
- assert not os.path.isfile(os.path.join(nwMinimal, "content", "73475cb40a568.nwd"))
- assert "73475cb40a568" not in nwGUI.theProject.projTree._treeOrder
-
- # Should not be allowed to add files and folders to Trash
- trashHandle = nwGUI.theProject.projTree.trashRoot()
- chItem = nwTree._getTreeItem(trashHandle)
- nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
- assert nwTree.newTreeItem(nwItemType.FILE, None) is False
- assert nwTree.newTreeItem(nwItemType.FOLDER, None) is False
-
- # Close the project
- nwGUI.closeProject()
-
- ##
- # Orphaned Files
- ##
-
- # Add an orphaned file
- orphFile = os.path.join(nwMinimal, "content", "1234567890abc.nwd")
- writeFile(orphFile, "# Hello World\n")
-
- # Open the project again
- nwGUI.openProject(nwMinimal)
-
- # Check that the orphaned file was found and added to the tree
- nwTree.flushTreeOrder()
- assert "1234567890abc" in nwGUI.theProject.projTree._treeOrder
- orItem = nwTree._getTreeItem("1234567890abc")
- assert orItem.text(nwTree.C_NAME) == "Recovered File 1"
-
- ##
- # Unexpected Error Handling
- ##
-
- # Add an item with an invalid type
- assert nwTree.newTreeItem(nwItemType.NO_TYPE, nwItemClass.NOVEL) is False
- assert "Failed to add new item" in caplog.messages[-1]
-
- # Add new file after one that has no parent handle
- chItem = nwTree._getTreeItem("44cb730c42048")
- nwTree.setCurrentItem(chItem, QItemSelectionModel.Current)
- nwTree.theProject.projTree["44cb730c42048"]._parent = None
- assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) is False
- nwTree.clearSelection()
-
- # Add a file with no parent, and fail to find a suitable parent item
- monkeypatch.setattr("novelwriter.core.tree.NWTree.findRoot", lambda *a: None)
-
- assert nwTree.newTreeItem(nwItemType.FILE, nwItemClass.NOVEL) is False
- assert nwTree.newTreeItem(nwItemType.FOLDER, nwItemClass.NOVEL) is False
+ # Add an item that cannot be displayed in the tree
+ nHandle = nwGUI.theProject.newFile("Test", None)
+ assert nwTree.revealNewTreeItem(nHandle) is False
+ # Clean up
# qtbot.stopForInteraction()
nwGUI.closeProject()
-# END Test testGuiProjTree_TreeItems
+# END Test testGuiProjTree_NewItems
+
+
+@pytest.mark.gui
+def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir):
+ """Test adding and removing items from the project tree.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
+
+ nwTree = nwGUI.treeView
+
+ # Try to move item with no project
+ assert nwTree.moveTreeItem(1) is False
+
+ # Create a project
+ nwGUI.theProject.projTree.setSeed(42)
+ prjDir = os.path.join(fncDir, "project")
+ assert nwGUI.newProject({"projPath": prjDir}) is True
+
+ # Move Documents
+ # ==============
+
+ # Add some files
+ nwTree.setSelectedHandle("31489056e0916")
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Move item without focus
+ monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
+ assert nwTree.moveTreeItem(1) is False
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+ monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
+
+ # Move with no selections
+ nwTree.clearSelection()
+ assert nwTree.moveTreeItem(1) is False
+
+ # Move second item up twice (should give same result)
+ nwTree.setSelectedHandle("0e17daca5f3e1")
+ assert nwTree.moveTreeItem(-1) is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "0e17daca5f3e1", "98010bd9270f9",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+ assert nwTree.moveTreeItem(-1) is False
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "0e17daca5f3e1", "98010bd9270f9",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Restore via menu entry
+ nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Move fifth item down twice (should give same result)
+ nwTree.setSelectedHandle("031b4af5197ec")
+ assert nwTree.moveTreeItem(1) is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
+ ]
+ assert nwTree.moveTreeItem(1) is False
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
+ ]
+
+ # Restore via menu entry
+ nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Move down again, and restore via undo
+ nwTree.setSelectedHandle("031b4af5197ec")
+ assert nwTree.moveTreeItem(1) is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "41cfc0d1f2d12", "031b4af5197ec",
+ ]
+ nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Root Folder
+ # ===========
+
+ nwTree.setSelectedHandle("73475cb40a568")
+ assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
+
+ # Move novel folder up
+ assert nwTree.moveTreeItem(-1) is False
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
+
+ # Move novel folder down
+ assert nwTree.moveTreeItem(1) is True
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 1
+
+ # Move novel folder up again
+ assert nwTree.moveTreeItem(-1) is True
+ nwTree.flushTreeOrder()
+ assert nwGUI.theProject.projTree._treeOrder.index("73475cb40a568") == 0
+
+ # Clean up
+ # qtbot.stopForInteraction()
+ nwGUI.closeProject()
+
+# END Test testGuiProjTree_MoveItems
+
+
+@pytest.mark.gui
+def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir):
+ """Test adding and removing items from the project tree.
+ """
+ # Block message box
+ monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
+ monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
+
+ nwTree = nwGUI.treeView
+
+ # Try to run with no project
+ assert nwTree.emptyTrash() is False
+ assert nwTree.deleteItem() is False
+
+ # Create a project
+ nwGUI.theProject.projTree.setSeed(42)
+ prjDir = os.path.join(fncDir, "project")
+ assert nwGUI.newProject({"projPath": prjDir}) is True
+
+ # Try emptying the trash already now, when there is no trash folder
+ assert nwTree.emptyTrash() is False
+
+ # Add some files
+ nwTree.setSelectedHandle("31489056e0916")
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.newTreeItem(nwItemType.FILE) is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19", "031b4af5197ec", "41cfc0d1f2d12",
+ ]
+
+ # Delete File
+ # ===========
+
+ # Delete item without focus -> blocked
+ monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
+ nwTree.setSelectedHandle("41cfc0d1f2d12")
+ caplog.clear()
+ assert nwTree.deleteItem() is False
+ assert "blocked" in caplog.text
+ monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
+
+ # No selection made
+ nwTree.clearSelection()
+ caplog.clear()
+ assert nwTree.deleteItem() is False
+ assert "no item to delete" in caplog.text
+
+ # Not a valid handle
+ nwTree.clearSelection()
+ caplog.clear()
+ assert nwTree.deleteItem("0000000000000") is False
+ assert "Could not find tree item" in caplog.text
+
+ # Block adding trash folder
+ funcPointer = nwTree._addTrashRoot
+ nwTree._addTrashRoot = lambda *a: None
+ assert nwTree.deleteItem("41cfc0d1f2d12") is False
+ nwTree._addTrashRoot = funcPointer
+
+ # Delete last two documents, which also adds the trash folder
+ assert nwTree.deleteItem("41cfc0d1f2d12") is True
+ assert nwTree.deleteItem("031b4af5197ec") is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9", "0e17daca5f3e1",
+ "1a6562590ef19"
+ ]
+ trashHandle = nwGUI.theProject.projTree.trashRoot()
+ assert nwTree.getTreeFromHandle(trashHandle) == [
+ trashHandle, "41cfc0d1f2d12", "031b4af5197ec"
+ ]
+
+ # Delete the first file again (permanent), and ask for permission
+ # Also open the document in the editor, which should trigger a close
+ assert os.path.isfile(os.path.join(prjDir, "content", "41cfc0d1f2d12.nwd"))
+ assert "41cfc0d1f2d12" in nwGUI.theProject.projTree
+ assert nwGUI.docEditor.docHandle() is None
+ assert nwGUI.openDocument("41cfc0d1f2d12") is True
+ assert nwGUI.docEditor.docHandle() == "41cfc0d1f2d12"
+ assert nwTree.deleteItem("41cfc0d1f2d12") is True
+ assert nwGUI.docEditor.docHandle() is None
+ assert not os.path.isfile(os.path.join(prjDir, "content", "41cfc0d1f2d12.nwd"))
+ assert "41cfc0d1f2d12" not in nwGUI.theProject.projTree
+ assert nwTree.getTreeFromHandle(trashHandle) == [
+ trashHandle, "031b4af5197ec"
+ ]
+
+ # Try to delete the second document, but block the deletion
+ with monkeypatch.context() as mp:
+ mp.setattr("novelwriter.core.document.NWDoc.deleteDocument", lambda *a: False)
+ assert nwTree.deleteItem("031b4af5197ec") is False
+
+ # Delete proper, and skip asking for permission
+ assert os.path.isfile(os.path.join(prjDir, "content", "031b4af5197ec.nwd"))
+ assert "031b4af5197ec" in nwGUI.theProject.projTree
+ assert nwTree.deleteItem("031b4af5197ec", alreadyAsked=True) is True
+ assert not os.path.isfile(os.path.join(prjDir, "content", "031b4af5197ec.nwd"))
+ assert "031b4af5197ec" not in nwGUI.theProject.projTree
+ assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
+
+ # Delete Folder/Root
+ # ==================
+
+ # Deleting non-empty folders is blocked
+ assert nwTree.deleteItem("31489056e0916") is False # Folder
+ assert nwTree.deleteItem("73475cb40a568") is False # Root
+
+ # Add a folder we can delete
+ nwTree.setSelectedHandle("71ee45a3c0db9") # Character Root
+ assert nwTree.newTreeItem(nwItemType.FOLDER) is True
+ assert "2fca346db6561" in nwGUI.theProject.projTree
+
+ # Try to delete, but block parent item lookup
+ with monkeypatch.context() as mp:
+ mp.setattr("PyQt5.QtWidgets.QTreeWidgetItem.parent", lambda *a: None)
+ caplog.clear()
+ assert nwTree.deleteItem("2fca346db6561") is False
+ assert "Could not delete folder" in caplog.text
+ assert "2fca346db6561" in nwGUI.theProject.projTree
+
+ # Delete folder properly
+ assert nwTree.deleteItem("2fca346db6561") is True
+ assert "2fca346db6561" not in nwGUI.theProject.projTree
+
+ # Delete the Character root
+ assert nwTree.deleteItem("71ee45a3c0db9") is True
+ assert "71ee45a3c0db9" not in nwGUI.theProject.projTree
+
+ # Empty Trash
+ # ===========
+
+ # Try to empty trash that is already empty
+ caplog.clear()
+ assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
+ assert nwTree.emptyTrash() is False
+ assert "already empty" in caplog.text
+
+ # Move the two remaining scene documents to trash
+ assert nwTree.deleteItem("0e17daca5f3e1") is True
+ assert nwTree.deleteItem("1a6562590ef19") is True
+ assert nwTree.getTreeFromHandle("31489056e0916") == [
+ "31489056e0916", "98010bd9270f9"
+ ]
+ assert nwTree.getTreeFromHandle(trashHandle) == [
+ trashHandle, "0e17daca5f3e1", "1a6562590ef19"
+ ]
+
+ # Empty trash, but select no on question
+ with monkeypatch.context() as mp:
+ mp.setattr(QMessageBox, "question", lambda *a: QMessageBox.No)
+ assert nwTree.emptyTrash() is False
+
+ # Empty the trash proper
+ nwTree._setTreeChanged(False)
+ assert nwTree.emptyTrash() is True
+ assert nwTree.getTreeFromHandle(trashHandle) == [trashHandle]
+ assert nwTree._treeChanged is True
+
+ # Clean up
+ # qtbot.stopForInteraction()
+ nwGUI.closeProject()
+
+# END Test testGuiProjTree_DeleteItems