Fix tests broken by xml rewrite

This commit is contained in:
Veronica Berglyd Olsen
2022-11-01 18:31:03 +01:00
parent 5b7f2b2b55
commit 494b94430c
4 changed files with 355 additions and 482 deletions
+9 -7
View File
@@ -190,7 +190,7 @@ class NWItem:
if "handle" in data: if "handle" in data:
self.setHandle(data["handle"]) self.setHandle(data["handle"])
else: else:
logger.error("XML item entry does not have a handle") logger.error("Item does not have a handle")
return False return False
self.setName(data.get("label", "")) self.setName(data.get("label", ""))
@@ -217,12 +217,14 @@ class NWItem:
self._parent = None # Root items cannot have a parent self._parent = None # Root items cannot have a parent
if self._type != nwItemType.FILE: if self._type != nwItemType.FILE:
self._heading = "H0" # Only files have headers # Reset values that should only be set for files
self._active = False # Can only be True for files self._layout = nwItemLayout.NO_LAYOUT
self._charCount = 0 # Only set for files self._heading = "H0"
self._wordCount = 0 # Only set for files self._active = False
self._paraCount = 0 # Only set for files self._charCount = 0
self._cursorPos = 0 # Only set for files self._wordCount = 0
self._paraCount = 0
self._cursorPos = 0
return True return True
+181 -241
View File
@@ -21,8 +21,6 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import pytest import pytest
from lxml import etree
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from tools import C, buildTestProject from tools import C, buildTestProject
@@ -497,257 +495,199 @@ def testCoreItem_ClassDefaults(mockGUI):
@pytest.mark.core @pytest.mark.core
@pytest.mark.skip def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd): """Test packing and unpacking entries for the NWItem class.
"""Test packing and unpacking XML objects for the NWItem class.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
nwXML = etree.Element("novelWriterXML") theProject.data.itemStatus.write(None, "New", (100, 100, 100))
theProject.data.itemImport.write(None, "New", (100, 100, 100))
statusKeys = ["s000000", "s000001", "s000002", "s000003"] # Invalid
importKeys = ["i000004", "i000005", "i000006", "i000007"] theItem = NWItem(theProject)
assert theItem.unpack({}) is False
# File # File
# ====
theItem = NWItem(theProject) theItem = NWItem(theProject)
theItem.setHandle("0123456789abc") assert theItem.unpack({
theItem.setParent("0123456789abc") "label": "A File",
theItem.setRoot("0123456789abc") "handle": "0000000000003",
theItem.setOrder(1) "parent": "0000000000002",
theItem.setName("A Name") "root": "0000000000001",
theItem.setClass("NOVEL") "order": 1,
theItem.setType("FILE") "type": "FILE",
theItem.setImport(importKeys[3]) "class": "NOVEL",
theItem.setLayout("NOTE") "layout": "DOCUMENT",
theItem.setActive(False) "expanded": True,
theItem.setParaCount(3) "status": None,
theItem.setWordCount(5) "import": None,
theItem.setCharCount(7) "heading": "H1",
theItem.setCursorPos(11) "charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": False,
}) is True
# Pack assert theItem.itemName == "A File"
xContent = etree.SubElement(nwXML, "content") assert theItem.itemHandle == "0000000000003"
theItem.packXML(xContent) assert theItem.itemParent == "0000000000002"
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( assert theItem.itemRoot == "0000000000001"
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FILE" class="NOVEL" layout="NOTE"><meta expanded="False" mainHeading="H0" '
b'charCount="7" wordCount="5" paraCount="3" cursorPos="11"/><name status="None" '
b'import="%s" active="False">A Name</name></item>'
b'</content>'
) % bytes(importKeys[3], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1 assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.FILE
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemLayout == nwItemLayout.DOCUMENT
assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False assert theItem.isActive is False
assert theItem.paraCount == 3
assert theItem.wordCount == 5
assert theItem.charCount == 7
assert theItem.cursorPos == 11
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE
assert theItem.itemLayout == nwItemLayout.NOTE
assert theItem.itemStatus == statusKeys[0] # Was None, should now be default
assert theItem.itemImport == importKeys[3]
# Folder
# ======
theItem = NWItem(theProject)
theItem.setHandle("0123456789abc")
theItem.setParent("0123456789abc")
theItem.setRoot("0123456789abc")
theItem.setOrder(1)
theItem.setName("A Name")
theItem.setClass("NOVEL")
theItem.setType("FOLDER")
theItem.setStatus(statusKeys[1])
theItem.setLayout("NOTE")
theItem.setExpanded(True)
theItem.setActive(False)
theItem.setParaCount(3)
theItem.setWordCount(5)
theItem.setCharCount(7)
theItem.setCursorPos(11)
# Pack
xContent = etree.SubElement(nwXML, "content")
theItem.packXML(xContent)
assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == (
b'<content>'
b'<item handle="0123456789abc" parent="0123456789abc" root="0123456789abc" order="1" '
b'type="FOLDER" class="NOVEL"><meta expanded="True"/><name status="%s" '
b'import="None">A Name</name></item>'
b'</content>'
) % bytes(statusKeys[1], encoding="utf8")
# Unpack
theItem = NWItem(theProject)
assert theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "0123456789abc"
assert theItem.itemParent == "0123456789abc"
assert theItem.itemRoot == "0123456789abc"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True assert theItem.isExpanded is True
assert theItem.isActive is True assert theItem.mainHeading == "H1"
assert theItem.paraCount == 0 assert theItem.charCount == 100
assert theItem.wordCount == 0 assert theItem.wordCount == 20
assert theItem.charCount == 0 assert theItem.paraCount == 2
assert theItem.cursorPos == 0
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == statusKeys[1]
assert theItem.itemImport == importKeys[0] # Was None, should now be default
# Errors
# ======
# Not an Item
mockXml = etree.SubElement(nwXML, "stuff")
assert theItem.unpackXML(mockXml) is False
# Item without Handle
mockXml = etree.SubElement(nwXML, "item", attrib={"stuff": "nah"})
assert theItem.unpackXML(mockXml) is False
# Item with Invalid SubElement is Accepted w/Error
mockXml = etree.SubElement(nwXML, "item", attrib={"handle": "0123456789abc"})
xParam = etree.SubElement(mockXml, "invalid")
xParam.text = "stuff"
caplog.clear()
assert theItem.unpackXML(mockXml) is True
assert "Unknown tag 'invalid'" in caplog.text
# Pack Valid Item
mockXml = etree.SubElement(nwXML, "group")
theItem._subPack(mockXml, "subGroup", {"one": "two"}, "value", False)
assert etree.tostring(mockXml, pretty_print=False, encoding="utf-8") == (
b"<group><subGroup one=\"two\">value</subGroup></group>"
)
# Pack Not Allowed None
mockXml = etree.SubElement(nwXML, "group")
assert theItem._subPack(mockXml, "subGroup", {}, None, False) is None
assert theItem._subPack(mockXml, "subGroup", {}, "None", False) is None
assert etree.tostring(mockXml, pretty_print=False, encoding="utf-8") == (
b"<group/>"
)
# END Test testCoreItem_XMLPackUnpack
@pytest.mark.core
@pytest.mark.skip
def testCoreItem_ConvertFromFmt12(mockGUI):
"""Test the setter for all the nwItemLayout values for the NWItem
class using the class names that were present in file format 1.2.
"""
theProject = NWProject(mockGUI)
theItem = NWItem(theProject)
# Deprecated Layouts
theItem.setLayout("TITLE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PAGE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("BOOK")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("PARTITION")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("UNNUMBERED")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("CHAPTER")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("SCENE")
assert theItem.itemLayout == nwItemLayout.DOCUMENT
theItem.setLayout("MUMBOJUMBO")
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# END Test testCoreItem_ConvertFromFmt12
@pytest.mark.core
@pytest.mark.skip
def testCoreItem_ConvertFromFmt13(mockGUI):
"""Test packing and unpacking XML objects for the NWItem class from
format version 1.3
"""
theProject = NWProject(mockGUI)
# Make Version 1.3 XML
nwXML = etree.Element("novelWriterXML")
xContent = etree.SubElement(nwXML, "content")
# Folder
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "a000000000001",
"order": "1",
"parent": "b000000000001",
})
NWItem._subPack(xPack, "name", text="Folder")
NWItem._subPack(xPack, "type", text="FOLDER")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "expanded", text="True")
# Unpack Folder
theItem = NWItem(theProject)
theItem.unpackXML(xContent[0])
assert theItem.itemHandle == "a000000000001"
assert theItem.itemParent == "b000000000001"
assert theItem.itemOrder == 1
assert theItem.isExpanded is True
assert theItem.isActive is True
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
# File
xPack = etree.SubElement(xContent, "item", attrib={
"handle": "c000000000001",
"order": "2",
"parent": "a000000000001",
})
NWItem._subPack(xPack, "name", text="Scene")
NWItem._subPack(xPack, "type", text="FILE")
NWItem._subPack(xPack, "class", text="NOVEL")
NWItem._subPack(xPack, "status", text="New")
NWItem._subPack(xPack, "exported", text="True")
NWItem._subPack(xPack, "layout", text="DOCUMENT")
NWItem._subPack(xPack, "charCount", text="600")
NWItem._subPack(xPack, "wordCount", text="100")
NWItem._subPack(xPack, "paraCount", text="6")
NWItem._subPack(xPack, "cursorPos", text="50")
# Unpack File
theItem = NWItem(theProject)
theItem.unpackXML(xContent[1])
assert theItem.itemHandle == "c000000000001"
assert theItem.itemParent == "a000000000001"
assert theItem.itemOrder == 2
assert theItem.isExpanded is False
assert theItem.isActive is True
assert theItem.charCount == 600
assert theItem.wordCount == 100
assert theItem.paraCount == 6
assert theItem.cursorPos == 50 assert theItem.cursorPos == 50
assert theItem.pack() == {
"name": "A File",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": "1",
"type": "FILE",
"class": "NOVEL",
"layout": "DOCUMENT",
},
"metaAttr": {
"expanded": "True",
"heading": "H1",
"charCount": "100",
"wordCount": "20",
"paraCount": "2",
"cursorPos": "50",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
"active": "False",
}
}
# Folder
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Folder",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "FOLDER",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": "",
"import": "",
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
}) is True
assert theItem.itemName == "A Folder"
assert theItem.itemHandle == "0000000000003"
assert theItem.itemParent == "0000000000002"
assert theItem.itemRoot == "0000000000001"
assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.FOLDER
assert theItem.itemClass == nwItemClass.NOVEL assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemType == nwItemType.FILE assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemLayout == nwItemLayout.DOCUMENT assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False
assert theItem.isExpanded is True
assert theItem.mainHeading == "H0"
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
# Deprecated Type assert theItem.pack() == {
theItem.setType("TRASH") "name": "A Folder",
"itemAttr": {
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": "1",
"type": "FOLDER",
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
}
}
# Root
theItem = NWItem(theProject)
assert theItem.unpack({
"label": "A Novel",
"handle": "0000000000003",
"parent": "0000000000002",
"root": "0000000000001",
"order": 1,
"type": "ROOT",
"class": "NOVEL",
"layout": "DOCUMENT",
"expanded": True,
"status": None,
"import": None,
"heading": "H1",
"charCount": 100,
"wordCount": 20,
"paraCount": 2,
"cursorPos": 50,
"active": True,
}) is True
assert theItem.itemName == "A Novel"
assert theItem.itemHandle == "0000000000003"
assert theItem.itemParent is None
assert theItem.itemRoot == "0000000000003"
assert theItem.itemOrder == 1
assert theItem.itemType == nwItemType.ROOT assert theItem.itemType == nwItemType.ROOT
assert theItem.itemClass == nwItemClass.NOVEL
assert theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert theItem.itemStatus == "s000000"
assert theItem.itemImport == "i000001"
assert theItem.isActive is False
assert theItem.isExpanded is True
assert theItem.mainHeading == "H0"
assert theItem.charCount == 0
assert theItem.wordCount == 0
assert theItem.paraCount == 0
assert theItem.cursorPos == 0
# END Test testCoreItem_ConvertFromFmt13 assert theItem.pack() == {
"name": "A Novel",
"itemAttr": {
"handle": "0000000000003",
"parent": "None",
"root": "0000000000003",
"order": "1",
"type": "ROOT",
"class": "NOVEL",
},
"metaAttr": {
"expanded": "True",
},
"nameAttr": {
"status": "s000000",
"import": "i000001",
}
}
# END Test testCoreItem_PackUnpack
+113 -199
View File
@@ -20,13 +20,14 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import os import os
import shutil
import pytest import pytest
from shutil import copyfile from shutil import copyfile
from zipfile import ZipFile from zipfile import ZipFile
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from mock import causeOSError from mock import causeOSError
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp from novelwriter.common import formatTimeStamp
@@ -36,6 +37,7 @@ from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc from novelwriter.core.document import NWDoc
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
@pytest.mark.core @pytest.mark.core
@@ -380,140 +382,90 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI,
@pytest.mark.core @pytest.mark.core
@pytest.mark.skip def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd):
def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
"""Test opening a project. """Test opening a project.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir)
# Rename the project file to check handling # Rename the project file to check handling
rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) rName = os.path.join(fncDir, nwFiles.PROJ_FILE)
wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj") wName = os.path.join(fncDir, nwFiles.PROJ_FILE+"_sdfghj")
os.rename(rName, wName) os.rename(rName, wName)
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(fncDir) is False
os.rename(wName, rName) os.rename(wName, rName)
# Fail on folder structure check # Fail on folder structure check
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError) mp.setattr("os.mkdir", causeOSError)
assert theProject.openProject(nwMinimal) is False shutil.rmtree(os.path.join(fncDir, "meta"))
assert theProject.openProject(fncDir) is False
# Fail on lock file # Fail on lock file
theProject.setProjectPath(nwMinimal) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal) is False assert theProject.openProject(fncDir) is False
# Fail to read lockfile (which still opens the project) # Fail to read lockfile (which still opens the project)
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr("builtins.open", causeOSError)
assert theProject.openProject(nwMinimal) is True caplog.clear()
assert theProject.openProject(fncDir) is True
assert "Failed to check lock file" in caplog.text
assert theProject.closeProject() assert theProject.closeProject()
# Force open with lockfile # Force open with lockfile
theProject.setProjectPath(nwMinimal) theProject.setProjectPath(fncDir)
assert theProject._writeLockFile() assert theProject._writeLockFile()
assert theProject.openProject(nwMinimal, overrideLock=True) is True assert theProject.openProject(fncDir, overrideLock=True) is True
assert theProject.closeProject() assert theProject.closeProject()
# Make a junk XML file # Not a novelwriter XML file
oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig") with monkeypatch.context() as mp:
bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak") mp.setattr(ProjectXMLReader, "read", lambda *a: False)
os.rename(rName, oName) mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE))
writeFile(rName, "stuff") assert theProject.openProject(fncDir) is False
assert theProject.openProject(nwMinimal) is False assert "Project file does not appear" in mockGUI.lastAlert
# Also write a jun XML backup file # Unknown project file version
writeFile(bName, "stuff") with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is False mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.UNKNOWN_VERSION))
assert theProject.openProject(fncDir) is False
assert "Unknown or unsupported novelWriter project file" in mockGUI.lastAlert
# Wrong root item # Other parse error
writeFile(rName, "<not_novelWriterXML></not_novelWriterXML>\n") with monkeypatch.context() as mp:
assert theProject.openProject(nwMinimal) is False mp.setattr(ProjectXMLReader, "read", lambda *a: False)
mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.CANNOT_PARSE))
assert theProject.openProject(fncDir) is False
assert "Failed to parse project xml" in mockGUI.lastAlert
# Wrong file version # Won't convert legacy file
writeFile(rName, ( with monkeypatch.context() as mp:
"<?xml version='0.0' encoding='utf-8'?>\n" mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY))
"<novelWriterXML " mockGUI.askResponse = False
"appVersion=\"1.0\" " assert theProject.openProject(fncDir) is False
"hexVersion=\"0x01000000\" " assert "The file format of your project is about to be" in mockGUI.lastQuestion[1]
"fileVersion=\"1.0\" " mockGUI.askResponse = True
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
mockGUI.undo()
# Future file version # Won't convert legacy file
writeFile(rName, ( with monkeypatch.context() as mp:
"<?xml version='1.0' encoding='utf-8'?>\n" mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: "0x99999999"))
"<novelWriterXML " mockGUI.askResponse = False
"appVersion=\"1.0\" " assert theProject.openProject(fncDir) is False
"hexVersion=\"0x01000000\" " assert "This project was saved by a newer version" in mockGUI.lastQuestion[1]
"fileVersion=\"99.99\" " mockGUI.askResponse = True
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is False
# Update file version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0xffffffff\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
))
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
assert mockGUI.lastQuestion[0] == "File Version"
mockGUI.undo()
# Larger hex version
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0xffffffff\" "
"fileVersion=\"%s\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"</novelWriterXML>\n"
) % theProject.FILE_VERSION)
mockGUI.askResponse = False
assert theProject.openProject(nwMinimal) is False
assert mockGUI.lastQuestion[0] == "Version Conflict"
mockGUI.undo()
# Test skipping XML entries
writeFile(rName, (
"<?xml version='1.0' encoding='utf-8'?>\n"
"<novelWriterXML "
"appVersion=\"1.0\" "
"hexVersion=\"0x01000000\" "
"fileVersion=\"1.2\" "
"timeStamp=\"2020-01-01 00:00:00\">\n"
"<project><stuff/></project>\n"
"<settings><stuff/></settings>\n"
"</novelWriterXML>\n"
))
assert theProject.openProject(nwMinimal) is True
assert theProject.closeProject()
# Clean up XML files
os.unlink(rName)
os.unlink(bName)
os.rename(oName, rName)
# Add some legacy stuff that cannot be removed # Add some legacy stuff that cannot be removed
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr(theProject, "_legacyDataFolder", causeOSError) mp.setattr(theProject, "_legacyDataFolder", causeOSError)
os.mkdir(os.path.join(nwMinimal, "data_0")) os.mkdir(os.path.join(fncDir, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff") writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff") writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear() mockGUI.clear()
assert theProject.openProject(nwMinimal) is True assert theProject.openProject(fncDir) is True
assert "There was an error updating the project." in mockGUI.lastAlert assert "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject() assert theProject.closeProject()
@@ -522,57 +474,31 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
@pytest.mark.core @pytest.mark.core
@pytest.mark.skip def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir):
def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir):
"""Test saving a project. """Test saving a project.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
testFile = os.path.join(nwMinimal, "nwProject.nwx")
backFile = os.path.join(nwMinimal, "nwProject.bak")
compFile = os.path.join(refDir, os.path.pardir, "minimal", "nwProject.nwx")
# Nothing to save # Nothing to save
assert theProject.saveProject() is False assert theProject.saveProject() is False
# Open test project mockRnd.reset()
assert theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Fail on folder structure check # Fail on folder structure check
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *a: False) mp.setattr("os.mkdir", causeOSError)
shutil.rmtree(os.path.join(fncDir, "meta"))
assert theProject.saveProject() is False assert theProject.saveProject() is False
# Fail on open file # Fail writing
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("builtins.open", causeOSError) mp.setattr(ProjectXMLWriter, "write", lambda *a: False)
assert theProject.saveProject() is False assert theProject.saveProject() is False
# Fail on creating .bak file # Save with and without autosave
with monkeypatch.context() as mp: assert theProject.saveProject(autoSave=False) is True
mp.setattr("os.replace", causeOSError)
assert theProject.saveProject() is False
assert os.path.isfile(backFile) is False
# Successful save
saveCount = theProject.data.saveCount
autoCount = theProject.data.autoCount
assert theProject.saveProject() is True
assert theProject.data.saveCount == saveCount + 1
assert theProject.data.autoCount == autoCount
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Check that a second save creates a .bak file
assert os.path.isfile(backFile) is True
# Successful autosave
saveCount = theProject.data.saveCount
autoCount = theProject.data.autoCount
assert theProject.saveProject(autoSave=True) is True assert theProject.saveProject(autoSave=True) is True
assert theProject.data.saveCount == saveCount
assert theProject.data.autoCount == autoCount + 1
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
# Close test project
assert theProject.closeProject() assert theProject.closeProject()
# END Test testCoreProject_Save # END Test testCoreProject_Save
@@ -683,11 +609,11 @@ def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI):
@pytest.mark.core @pytest.mark.core
def testCoreProject_AccessItems(nwMinimal, mockGUI): def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd):
"""Test helper functions for the project folder. """Test helper functions for the project folder.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Storage Objects # Storage Objects
assert isinstance(theProject.index, NWIndex) assert isinstance(theProject.index, NWIndex)
@@ -696,34 +622,34 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
# Move Novel ROOT to after its files # Move Novel ROOT to after its files
oldOrder = [ oldOrder = [
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"a35baf2e93843", # FILE: Title Page C.hPlotRoot,
"a6d311a93600a", # FOLDER: New Chapter C.hCharRoot,
"f5ab3e30151e1", # FILE: New Chapter C.hWorldRoot,
"8c659a11cd429", # FILE: New Scene C.hTitlePage,
"7695ce551d265", # ROOT: Plot C.hChapterDir,
"afb3043c7b2b3", # ROOT: Characters C.hChapterDoc,
"9d5247ab588e0", # ROOT: World C.hSceneDoc,
] ]
newOrder = [ newOrder = [
"a35baf2e93843", # FILE: Title Page C.hTitlePage,
"f5ab3e30151e1", # FILE: New Chapter C.hChapterDoc,
"8c659a11cd429", # FILE: New Scene C.hSceneDoc,
"a6d311a93600a", # FOLDER: New Chapter C.hChapterDir,
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"7695ce551d265", # ROOT: Plot C.hPlotRoot,
"afb3043c7b2b3", # ROOT: Characters C.hCharRoot,
"9d5247ab588e0", # ROOT: World C.hWorldRoot,
] ]
assert theProject.tree.handles() == oldOrder assert theProject.tree.handles() == oldOrder
assert theProject.setTreeOrder(newOrder) assert theProject.setTreeOrder(newOrder)
assert theProject.tree.handles() == newOrder assert theProject.tree.handles() == newOrder
# Add a non-existing item # Add a non-existing item
theProject.tree._treeOrder.append("01234567789abc") theProject.tree._treeOrder.append(C.hInvalid)
# Add an item with a non-existent parent # Add an item with a non-existent parent
nHandle = theProject.newFile("Test File", "a6d311a93600a") nHandle = theProject.newFile("Test File", C.hChapterDir)
theProject.tree[nHandle].setParent("cba9876543210") theProject.tree[nHandle].setParent("cba9876543210")
assert theProject.tree[nHandle].itemParent == "cba9876543210" assert theProject.tree[nHandle].itemParent == "cba9876543210"
@@ -732,15 +658,15 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
retOrder.append(tItem.itemHandle) retOrder.append(tItem.itemHandle)
assert retOrder == [ assert retOrder == [
"a508bb932959c", # ROOT: Novel C.hNovelRoot,
"7695ce551d265", # ROOT: Plot C.hPlotRoot,
"afb3043c7b2b3", # ROOT: Characters C.hCharRoot,
"9d5247ab588e0", # ROOT: World C.hWorldRoot,
nHandle, # FILE: Test File nHandle,
"a35baf2e93843", # FILE: Title Page C.hTitlePage,
"a6d311a93600a", # FOLDER: New Chapter C.hChapterDir,
"f5ab3e30151e1", # FILE: New Chapter C.hChapterDoc,
"8c659a11cd429", # FILE: New Scene C.hSceneDoc,
] ]
assert theProject.tree[nHandle].itemParent is None assert theProject.tree[nHandle].itemParent is None
@@ -748,28 +674,28 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
@pytest.mark.core @pytest.mark.core
@pytest.mark.skip
def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
"""Test the status and importance flag handling. """Test the status and importance flag handling.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
mockRnd.reset()
buildTestProject(theProject, fncDir) buildTestProject(theProject, fncDir)
statusKeys = ["s000008", "s000009", "s00000a", "s00000b"] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = ["i00000c", "i00000d", "i00000e", "i00000f"] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
# Change Status # Change Status
# ============= # =============
theProject.tree["0000000000014"].setStatus(statusKeys[3]) theProject.tree[C.hNovelRoot].setStatus(statusKeys[3])
theProject.tree["0000000000015"].setStatus(statusKeys[2]) theProject.tree[C.hPlotRoot].setStatus(statusKeys[2])
theProject.tree["0000000000016"].setStatus(statusKeys[1]) theProject.tree[C.hCharRoot].setStatus(statusKeys[1])
theProject.tree["0000000000017"].setStatus(statusKeys[3]) theProject.tree[C.hWorldRoot].setStatus(statusKeys[3])
assert theProject.tree["0000000000014"].itemStatus == statusKeys[3] assert theProject.tree[C.hNovelRoot].itemStatus == statusKeys[3]
assert theProject.tree["0000000000015"].itemStatus == statusKeys[2] assert theProject.tree[C.hPlotRoot].itemStatus == statusKeys[2]
assert theProject.tree["0000000000016"].itemStatus == statusKeys[1] assert theProject.tree[C.hCharRoot].itemStatus == statusKeys[1]
assert theProject.tree["0000000000017"].itemStatus == statusKeys[3] assert theProject.tree[C.hWorldRoot].itemStatus == statusKeys[3]
newList = [ newList = [
{"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)}, {"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)},
@@ -792,8 +718,8 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4) assert theProject.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4)
# Check the new entry # Check the new entry
lastKey = theProject.data.itemStatus.check("s000018") lastKey = theProject.data.itemStatus.check("s000010")
assert lastKey == "s000018" assert lastKey == "s000010"
assert theProject.data.itemStatus.name(lastKey) == "Finished" assert theProject.data.itemStatus.name(lastKey) == "Finished"
assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5) assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5)
@@ -804,7 +730,7 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
# Change Importance # Change Importance
# ================= # =================
fHandle = theProject.newFile("Jane Doe", "0000000000012") fHandle = theProject.newFile("Jane Doe", C.hCharRoot)
theProject.tree[fHandle].setImport(importKeys[3]) theProject.tree[fHandle].setImport(importKeys[3])
assert theProject.tree[fHandle].itemImport == importKeys[3] assert theProject.tree[fHandle].itemImport == importKeys[3]
@@ -829,8 +755,8 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.data.itemImport.cols(importKeys[3]) == (4, 4, 4) assert theProject.data.itemImport.cols(importKeys[3]) == (4, 4, 4)
# Check the new entry # Check the new entry
lastKey = theProject.data.itemImport.check("i00001a") lastKey = theProject.data.itemImport.check("i000012")
assert lastKey == "i00001a" assert lastKey == "i000012"
assert theProject.data.itemImport.name(lastKey) == "Max" assert theProject.data.itemImport.name(lastKey) == "Max"
assert theProject.data.itemImport.cols(lastKey) == (5, 5, 5) assert theProject.data.itemImport.cols(lastKey) == (5, 5, 5)
@@ -854,18 +780,6 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
assert theProject.saveProject() is True assert theProject.saveProject() is True
assert theProject.closeProject() is True assert theProject.closeProject() is True
# This should restore the default status/import labels
assert theProject.openProject(fncDir) is True
assert theProject.saveProject() is True
assert theProject.data.itemStatus.name("s000023") == "New"
assert theProject.data.itemStatus.name("s000024") == "Note"
assert theProject.data.itemStatus.name("s000025") == "Draft"
assert theProject.data.itemStatus.name("s000026") == "Finished"
assert theProject.data.itemImport.name("i000027") == "New"
assert theProject.data.itemImport.name("i000028") == "Minor"
assert theProject.data.itemImport.name("i000029") == "Major"
assert theProject.data.itemImport.name("i00002a") == "Main"
# END Test testCoreProject_StatusImport # END Test testCoreProject_StatusImport
@@ -1268,14 +1182,14 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
@pytest.mark.core @pytest.mark.core
def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir): def testCoreProject_Backup(monkeypatch, mockGUI, fncDir, tmpDir):
"""Test the automated backup feature of the project class. The test """Test the automated backup feature of the project class. The test
creates a backup of the Minimal test project, and then unzips the creates a backup of the Minimal test project, and then unzips the
backupd file and checks that the project XML file is identical to backupd file and checks that the project XML file is identical to
the original file. the original file.
""" """
theProject = NWProject(mockGUI) theProject = NWProject(mockGUI)
assert theProject.openProject(nwMinimal) buildTestProject(theProject, fncDir)
# Test faulty settings # Test faulty settings
@@ -1299,11 +1213,11 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Same folder as project (causes infinite loop in zipping) # Same folder as project (causes infinite loop in zipping)
theProject.mainConf.backupPath = nwMinimal theProject.mainConf.backupPath = fncDir
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Subfolder of project (causes infinite loop in zipping) # Subfolder of project (causes infinite loop in zipping)
theProject.mainConf.backupPath = os.path.join(nwMinimal, "subdir") theProject.mainConf.backupPath = os.path.join(fncDir, "subdir")
assert theProject.zipIt(doNotify=False) is False assert theProject.zipIt(doNotify=False) is False
# Set a valid folder # Set a valid folder
@@ -1335,7 +1249,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir):
# Check that the main project file was restored # Check that the main project file was restored
assert cmpFiles( assert cmpFiles(
os.path.join(nwMinimal, "nwProject.nwx"), os.path.join(fncDir, "nwProject.nwx"),
os.path.join(tmpDir, "extract", "nwProject.nwx") os.path.join(tmpDir, "extract", "nwProject.nwx")
) )
+52 -35
View File
@@ -20,23 +20,21 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
import pytest import pytest
import random
from lxml import etree from tools import C
from PyQt5.QtGui import QIcon from PyQt5.QtGui import QIcon
from novelwriter.core.status import NWStatus from novelwriter.core.status import NWStatus
statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"] statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished]
importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain]
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Internal(): def testCoreStatus_Internal(mockRnd):
"""Test all the internal functions of the NWStatus class. """Test all the internal functions of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theImport = NWStatus(NWStatus.IMPORT) theImport = NWStatus(NWStatus.IMPORT)
@@ -87,10 +85,9 @@ def testCoreStatus_Internal():
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Iterator(): def testCoreStatus_Iterator(mockRnd):
"""Test the iterator functions of the NWStatus class. """Test the iterator functions of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100)) theStatus.write(None, "New", (100, 100, 100))
@@ -132,10 +129,9 @@ def testCoreStatus_Iterator():
@pytest.mark.core @pytest.mark.core
def testCoreStatus_Entries(): def testCoreStatus_Entries(mockRnd):
"""Test all the simple setters for the NWStatus class. """Test all the simple setters for the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
# Write # Write
@@ -300,11 +296,9 @@ def testCoreStatus_Entries():
@pytest.mark.core @pytest.mark.core
@pytest.mark.skip def testCoreStatus_PackUnpack(mockRnd):
def testCoreStatus_XMLPackUnpack(): """Test all the pack/unpack of the NWStatus class.
"""Test all the XML pack/unpack of the NWStatus class.
""" """
random.seed(42)
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
theStatus.write(None, "New", (100, 100, 100)) theStatus.write(None, "New", (100, 100, 100))
theStatus.write(None, "Note", (200, 50, 0)) theStatus.write(None, "Note", (200, 50, 0))
@@ -316,36 +310,59 @@ def testCoreStatus_XMLPackUnpack():
for _ in range(n): for _ in range(n):
theStatus.increment(statusKeys[i]) theStatus.increment(statusKeys[i])
nwXML = etree.Element("novelWriterXML")
# Pack # Pack
xStatus = etree.SubElement(nwXML, "status") assert list(theStatus.pack()) == [
theStatus.packXML(xStatus) ("New", {
assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == ( "key": statusKeys[0],
b'<status>' "count": "3",
b'<entry key="sa3b179" count="3" red="100" green="100" blue="100">New</entry>' "red": "100",
b'<entry key="s1c8031" count="5" red="200" green="50" blue="0">Note</entry>' "green": "100",
b'<entry key="s06671a" count="7" red="200" green="150" blue="0">Draft</entry>' "blue": "100"
b'<entry key="sbdd640" count="9" red="50" green="200" blue="0">Finished</entry>' }),
b'</status>' ("Note", {
) "key": statusKeys[1],
"count": "5",
"red": "200",
"green": "50",
"blue": "0"
}),
("Draft", {
"key": statusKeys[2],
"count": "7",
"red": "200",
"green": "150",
"blue": "0"
}),
("Finished", {
"key": statusKeys[3],
"count": "9",
"red": "50",
"green": "200",
"blue": "0"
}),
]
# Unpack # Unpack
theStatus = NWStatus(NWStatus.STATUS) theStatus = NWStatus(NWStatus.STATUS)
assert theStatus.unpackXML(xStatus) assert theStatus.unpack({
statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]},
statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]},
statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]},
statusKeys[3]: {"label": "New3", "colour": (250, 250, 250), "count": countTo[3]},
})
assert len(theStatus._store) == 4 assert len(theStatus._store) == 4
assert list(theStatus._store.keys()) == statusKeys assert list(theStatus._store.keys()) == statusKeys
assert theStatus._store[statusKeys[0]]["name"] == "New" assert theStatus._store[statusKeys[0]]["name"] == "New0"
assert theStatus._store[statusKeys[1]]["name"] == "Note" assert theStatus._store[statusKeys[1]]["name"] == "New1"
assert theStatus._store[statusKeys[2]]["name"] == "Draft" assert theStatus._store[statusKeys[2]]["name"] == "New2"
assert theStatus._store[statusKeys[3]]["name"] == "Finished" assert theStatus._store[statusKeys[3]]["name"] == "New3"
assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100) assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100)
assert theStatus._store[statusKeys[1]]["cols"] == (200, 50, 0) assert theStatus._store[statusKeys[1]]["cols"] == (150, 150, 150)
assert theStatus._store[statusKeys[2]]["cols"] == (200, 150, 0) assert theStatus._store[statusKeys[2]]["cols"] == (200, 200, 200)
assert theStatus._store[statusKeys[3]]["cols"] == (50, 200, 0) assert theStatus._store[statusKeys[3]]["cols"] == (250, 250, 250)
assert theStatus._store[statusKeys[0]]["count"] == countTo[0] assert theStatus._store[statusKeys[0]]["count"] == countTo[0]
assert theStatus._store[statusKeys[1]]["count"] == countTo[1] assert theStatus._store[statusKeys[1]]["count"] == countTo[1]
assert theStatus._store[statusKeys[2]]["count"] == countTo[2] assert theStatus._store[statusKeys[2]]["count"] == countTo[2]
assert theStatus._store[statusKeys[3]]["count"] == countTo[3] assert theStatus._store[statusKeys[3]]["count"] == countTo[3]
# END Test testCoreStatus_XMLPackUnpack # END Test testCoreStatus_PackUnpack