Relax moving restrictions in project tree, and improve test coverage of NWItem class

This commit is contained in:
Veronica Berglyd Olsen
2022-04-17 18:32:22 +02:00
parent 50db7fcddb
commit 3f4356467e
3 changed files with 130 additions and 12 deletions
+6 -3
View File
@@ -259,7 +259,7 @@ class NWItem():
return return
## ##
# Methods # Lookup Methods
## ##
def describeMe(self, hLevel=None): def describeMe(self, hLevel=None):
@@ -296,8 +296,7 @@ class NWItem():
return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE, nwItemClass.TRASH) return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE, nwItemClass.TRASH)
def isInactive(self): def isInactive(self):
"""Returns true if the item is in the inactive parts of the """Returns true if the item is in an inactive class.
project.
""" """
return self._class in (nwItemClass.NO_CLASS, nwItemClass.ARCHIVE, nwItemClass.TRASH) return self._class in (nwItemClass.NO_CLASS, nwItemClass.ARCHIVE, nwItemClass.TRASH)
@@ -313,6 +312,10 @@ class NWItem():
stIcon = self.theProject.importItems.icon(self._import) stIcon = self.theProject.importItems.icon(self._import)
return stName, stIcon return stName, stIcon
##
# Special Setters
##
def setImportStatus(self, value): def setImportStatus(self, value):
"""Update the importance or status value based on class. This is """Update the importance or status value based on class. This is
a wrapper setter for setStatus and setImport. a wrapper setter for setStatus and setImport.
+10 -8
View File
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
from novelwriter.core import NWDoc from novelwriter.core import NWDoc
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.constants import trConst, nwLists, nwLabels from novelwriter.constants import trConst, nwLabels
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -790,21 +790,23 @@ class GuiProjectTree(QTreeWidget):
if pItem is not None: if pItem is not None:
pIndex = pItem.indexOfChild(sItem) pIndex = pItem.indexOfChild(sItem)
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole)) # Determine if the drag and drop is allowed:
# - Files can be moved anywhere
# - Folders can only be moved within the same root folder
# - Root folders cannot be moved at all
# - Items cannot be dropped on top of a file (moved inside)
isFile = snItem.itemType == nwItemType.FILE isFile = snItem.itemType == nwItemType.FILE
isRoot = snItem.itemType == nwItemType.ROOT isRoot = snItem.itemType == nwItemType.ROOT
onFile = dnItem.itemType == nwItemType.FILE onFile = dnItem.itemType == nwItemType.FILE
inSame = snItem.itemRoot == dnItem.itemRoot
isSame = snItem.itemClass == dnItem.itemClass allowDrop = inSame or isFile
isNone = snItem.itemClass == nwItemClass.NO_CLASS
isNote = snItem.itemLayout == nwItemLayout.NOTE
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
allowDrop = isSame or isNone or isNote or onFree
allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile) allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile)
if allowDrop and not isRoot: if allowDrop and not isRoot:
logger.debug("Drag'n'drop of item '%s' accepted", sHandle) logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
self.propagateCount(sHandle, 0) self.propagateCount(sHandle, 0)
QTreeWidget.dropEvent(self, theEvent) QTreeWidget.dropEvent(self, theEvent)
self._postItemMove(sHandle, wCount) self._postItemMove(sHandle, wCount)
+114 -1
View File
@@ -220,6 +220,7 @@ def testCoreItem_Methods(mockGUI):
# Status + Icon # Status + Icon
# ============= # =============
theItem.setType("FILE") theItem.setType("FILE")
theItem.setStatus("Note") theItem.setStatus("Note")
theItem.setImport("Minor") theItem.setImport("Minor")
@@ -229,11 +230,19 @@ def testCoreItem_Methods(mockGUI):
assert stT == "Note" assert stT == "Note"
assert isinstance(stI, QIcon) assert isinstance(stI, QIcon)
theItem.setImportStatus("Draft")
stT, stI = theItem.getImportStatus()
assert stT == "Draft"
theItem.setClass("CHARACTER") theItem.setClass("CHARACTER")
stT, stI = theItem.getImportStatus() stT, stI = theItem.getImportStatus()
assert stT == "Minor" assert stT == "Minor"
assert isinstance(stI, QIcon) assert isinstance(stI, QIcon)
theItem.setImportStatus("Major")
stT, stI = theItem.getImportStatus()
assert stT == "Major"
# Representation # Representation
# ============== # ==============
@@ -275,6 +284,8 @@ def testCoreItem_TypeSetter(mockGUI):
assert theItem.itemType == nwItemType.FILE assert theItem.itemType == nwItemType.FILE
theItem.setType("TRASH") theItem.setType("TRASH")
assert theItem.itemType == nwItemType.TRASH assert theItem.itemType == nwItemType.TRASH
# Alternative
theItem.setType(nwItemType.ROOT) theItem.setType(nwItemType.ROOT)
assert theItem.itemType == nwItemType.ROOT assert theItem.itemType == nwItemType.ROOT
@@ -294,28 +305,74 @@ def testCoreItem_ClassSetter(mockGUI):
assert theItem.itemClass == nwItemClass.NO_CLASS assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClass("NONSENSE") theItem.setClass("NONSENSE")
assert theItem.itemClass == nwItemClass.NO_CLASS assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClass("NO_CLASS") theItem.setClass("NO_CLASS")
assert theItem.itemClass == nwItemClass.NO_CLASS assert theItem.itemClass == nwItemClass.NO_CLASS
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is True
theItem.setClass("NOVEL") theItem.setClass("NOVEL")
assert theItem.itemClass == nwItemClass.NOVEL assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.isNovelLike() is True
assert theItem.documentAllowed() is True
assert theItem.isInactive() is False
theItem.setClass("PLOT") theItem.setClass("PLOT")
assert theItem.itemClass == nwItemClass.PLOT assert theItem.itemClass == nwItemClass.PLOT
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("CHARACTER") theItem.setClass("CHARACTER")
assert theItem.itemClass == nwItemClass.CHARACTER assert theItem.itemClass == nwItemClass.CHARACTER
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("WORLD") theItem.setClass("WORLD")
assert theItem.itemClass == nwItemClass.WORLD assert theItem.itemClass == nwItemClass.WORLD
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("TIMELINE") theItem.setClass("TIMELINE")
assert theItem.itemClass == nwItemClass.TIMELINE assert theItem.itemClass == nwItemClass.TIMELINE
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("OBJECT") theItem.setClass("OBJECT")
assert theItem.itemClass == nwItemClass.OBJECT assert theItem.itemClass == nwItemClass.OBJECT
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("ENTITY") theItem.setClass("ENTITY")
assert theItem.itemClass == nwItemClass.ENTITY assert theItem.itemClass == nwItemClass.ENTITY
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("CUSTOM") theItem.setClass("CUSTOM")
assert theItem.itemClass == nwItemClass.CUSTOM assert theItem.itemClass == nwItemClass.CUSTOM
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is False
assert theItem.isInactive() is False
theItem.setClass("ARCHIVE") theItem.setClass("ARCHIVE")
assert theItem.itemClass == nwItemClass.ARCHIVE assert theItem.itemClass == nwItemClass.ARCHIVE
assert theItem.isNovelLike() is True
assert theItem.documentAllowed() is True
assert theItem.isInactive() is True
theItem.setClass("TRASH") theItem.setClass("TRASH")
assert theItem.itemClass == nwItemClass.TRASH assert theItem.itemClass == nwItemClass.TRASH
assert theItem.isNovelLike() is False
assert theItem.documentAllowed() is True
assert theItem.isInactive() is True
# Alternative
theItem.setClass(nwItemClass.NOVEL) theItem.setClass(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NOVEL assert theItem.itemClass == nwItemClass.NOVEL
@@ -344,13 +401,69 @@ def testCoreItem_LayoutSetter(mockGUI):
theItem.setLayout("NOTE") theItem.setLayout("NOTE")
assert theItem.itemLayout == nwItemLayout.NOTE assert theItem.itemLayout == nwItemLayout.NOTE
# Alternatives # Alternative
theItem.setLayout(nwItemLayout.NOTE) theItem.setLayout(nwItemLayout.NOTE)
assert theItem.itemLayout == nwItemLayout.NOTE assert theItem.itemLayout == nwItemLayout.NOTE
# END Test testCoreItem_LayoutSetter # END Test testCoreItem_LayoutSetter
@pytest.mark.core
def testCoreItem_ClassDefaults(mockGUI):
"""Test the setter for the default values.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
# Root items should not have their class updated
theItem.setParent(None)
theItem.setClass(nwItemClass.NO_CLASS)
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NO_CLASS
# Non-root items should have their class updated
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
assert theItem.itemClass == nwItemClass.NO_CLASS
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemClass == nwItemClass.NOVEL
# Non-layout items should have their layout set based on class
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.NO_LAYOUT)
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
theItem.setClassDefaults(nwItemClass.NOVEL)
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.NO_LAYOUT)
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
theItem.setClassDefaults(nwItemClass.PLOT)
assert theItem.itemLayout == nwItemLayout.NOTE
# If documents are not allowed in that class, the layout should be changed
theItem.setParent("0123456789abc")
theItem.setClass(nwItemClass.NO_CLASS)
theItem.setLayout(nwItemLayout.DOCUMENT)
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setClassDefaults(nwItemClass.PLOT)
assert theItem.itemLayout == nwItemLayout.NOTE
# In all cases, status and importance should no longer be None
assert theItem.itemStatus is not None
assert theItem.itemImport is not None
# END Test testCoreItem_ClassDefaults
@pytest.mark.core @pytest.mark.core
def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData): def testCoreItem_XMLPackUnpack(mockGUI, caplog, constData):
"""Test packing and unpacking XML objects for the NWItem class. """Test packing and unpacking XML objects for the NWItem class.