Relax moving restrictions in project tree, and improve test coverage of NWItem class
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
Reference in New Issue
Block a user