Update test coverage

This commit is contained in:
Veronica Berglyd Olsen
2022-04-17 17:00:43 +02:00
parent 4cfbbf4c68
commit d158de5d82
5 changed files with 474 additions and 233 deletions
+13 -12
View File
@@ -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()
+3 -1
View File
@@ -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)
+9 -2
View File
@@ -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")
+73 -37
View File
@@ -21,6 +21,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
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'<novelWriterXML>'
b'<content count="8">'
b'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" '
b'class="NOVEL"><meta expanded="True"/><name status="s43dac0" '
b'import="i6c52c4">Novel</name></item>'
b'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="s43dac0" '
b'import="i6c52c4">Act One</name></item>'
b'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="300" wordCount="50" '
b'paraCount="2" cursorPos="0"/><name status="s43dac0" import="i6c52c4" '
b'exported="True">Chapter One</name></item>'
b'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" '
b'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="3000" wordCount="500" '
b'paraCount="20" cursorPos="0"/><name status="s43dac0" import="i6c52c4" '
b'exported="True">Scene One</name></item>'
b'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" '
b'class="ARCHIVE"><meta expanded="False"/><name status="s43dac0" '
b'import="i6c52c4">Outtakes</name></item>'
b'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="TRASH" '
b'class="TRASH"><meta expanded="False"/><name status="s43dac0" '
b'import="i6c52c4">Trash</name></item>'
b'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" '
b'class="CHARACTER"><meta expanded="True"/><name status="s43dac0" '
b'import="i6c52c4">Characters</name></item>'
b'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" '
b'type="FILE" class="CHARACTER" layout="NOTE"><meta charCount="2000" wordCount="400" '
b'paraCount="16" cursorPos="0"/><name status="s43dac0" import="i6c52c4" '
b'exported="True">Jane Doe</name></item>'
b'</content>'
b'</novelWriterXML>'
)
assert etree.tostring(nwXML, pretty_print=False, encoding="utf-8") == bytes((
'<novelWriterXML>'
'<content count="8">'
'<item handle="a000000000001" parent="None" root="a000000000001" order="0" type="ROOT" '
'class="NOVEL"><meta expanded="True"/><name status="{s0}" '
'import="{i0}">Novel</name></item>'
'<item handle="b000000000001" parent="a000000000001" root="a000000000001" order="0" '
'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="{s0}" '
'import="{i0}">Act One</name></item>'
'<item handle="c000000000001" parent="b000000000001" root="a000000000001" order="0" '
'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="300" wordCount="50" '
'paraCount="2" cursorPos="0"/><name status="{s0}" import="{i0}" '
'exported="True">Chapter One</name></item>'
'<item handle="c000000000002" parent="b000000000001" root="a000000000001" order="0" '
'type="FILE" class="NOVEL" layout="DOCUMENT"><meta charCount="3000" wordCount="500" '
'paraCount="20" cursorPos="0"/><name status="{s0}" import="{i0}" '
'exported="True">Scene One</name></item>'
'<item handle="a000000000002" parent="None" root="a000000000002" order="0" type="ROOT" '
'class="ARCHIVE"><meta expanded="False"/><name status="{s0}" '
'import="{i0}">Outtakes</name></item>'
'<item handle="a000000000003" parent="None" root="a000000000003" order="0" type="TRASH" '
'class="TRASH"><meta expanded="False"/><name status="{s0}" '
'import="{i0}">Trash</name></item>'
'<item handle="a000000000004" parent="None" root="a000000000004" order="0" type="ROOT" '
'class="CHARACTER"><meta expanded="True"/><name status="{s0}" '
'import="{i0}">Characters</name></item>'
'<item handle="b000000000002" parent="a000000000004" root="a000000000004" order="0" '
'type="FILE" class="CHARACTER" layout="NOTE"><meta charCount="2000" wordCount="400" '
'paraCount="16" cursorPos="0"/><name status="{s0}" import="{i0}" '
'exported="True">Jane Doe</name></item>'
'</content>'
'</novelWriterXML>'
).format(
s0=constData.statusKeys[0], i0=constData.importKeys[0]
), encoding="utf8")
theTree.clear()
assert len(theTree) == 0
+376 -181
View File
@@ -22,9 +22,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
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