From 494b94430ca2bdff24fb8ba3520687ec8bd60347 Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Tue, 1 Nov 2022 18:31:03 +0100 Subject: [PATCH] Fix tests broken by xml rewrite --- novelwriter/core/item.py | 16 +- tests/test_core/test_core_item.py | 422 ++++++++++++--------------- tests/test_core/test_core_project.py | 312 +++++++------------- tests/test_core/test_core_status.py | 87 +++--- 4 files changed, 355 insertions(+), 482 deletions(-) diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index 5b7deabe..8a45fa35 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -190,7 +190,7 @@ class NWItem: if "handle" in data: self.setHandle(data["handle"]) else: - logger.error("XML item entry does not have a handle") + logger.error("Item does not have a handle") return False self.setName(data.get("label", "")) @@ -217,12 +217,14 @@ class NWItem: self._parent = None # Root items cannot have a parent if self._type != nwItemType.FILE: - self._heading = "H0" # Only files have headers - self._active = False # Can only be True for files - self._charCount = 0 # Only set for files - self._wordCount = 0 # Only set for files - self._paraCount = 0 # Only set for files - self._cursorPos = 0 # Only set for files + # Reset values that should only be set for files + self._layout = nwItemLayout.NO_LAYOUT + self._heading = "H0" + self._active = False + self._charCount = 0 + self._wordCount = 0 + self._paraCount = 0 + self._cursorPos = 0 return True diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index 02fe3f8f..714d6d57 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -21,8 +21,6 @@ along with this program. If not, see . import pytest -from lxml import etree - from PyQt5.QtGui import QIcon from tools import C, buildTestProject @@ -497,257 +495,199 @@ def testCoreItem_ClassDefaults(mockGUI): @pytest.mark.core -@pytest.mark.skip -def testCoreItem_XMLPackUnpack(mockGUI, caplog, mockRnd): - """Test packing and unpacking XML objects for the NWItem class. +def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd): + """Test packing and unpacking entries for the NWItem class. """ 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"] - importKeys = ["i000004", "i000005", "i000006", "i000007"] + # Invalid + theItem = NWItem(theProject) + assert theItem.unpack({}) is False # File - # ==== - theItem = NWItem(theProject) - theItem.setHandle("0123456789abc") - theItem.setParent("0123456789abc") - theItem.setRoot("0123456789abc") - theItem.setOrder(1) - theItem.setName("A Name") - theItem.setClass("NOVEL") - theItem.setType("FILE") - theItem.setImport(importKeys[3]) - theItem.setLayout("NOTE") - theItem.setActive(False) - theItem.setParaCount(3) - theItem.setWordCount(5) - theItem.setCharCount(7) - theItem.setCursorPos(11) + assert theItem.unpack({ + "label": "A File", + "handle": "0000000000003", + "parent": "0000000000002", + "root": "0000000000001", + "order": 1, + "type": "FILE", + "class": "NOVEL", + "layout": "DOCUMENT", + "expanded": True, + "status": None, + "import": None, + "heading": "H1", + "charCount": 100, + "wordCount": 20, + "paraCount": 2, + "cursorPos": 50, + "active": False, + }) is True - # Pack - xContent = etree.SubElement(nwXML, "content") - theItem.packXML(xContent) - assert etree.tostring(xContent, pretty_print=False, encoding="utf-8") == ( - b'' - b'A Name' - b'' - ) % 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.itemName == "A File" + assert theItem.itemHandle == "0000000000003" + assert theItem.itemParent == "0000000000002" + assert theItem.itemRoot == "0000000000001" 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.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'' - b'A Name' - b'' - ) % 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.isActive is True - assert theItem.paraCount == 0 - assert theItem.wordCount == 0 - assert theItem.charCount == 0 - 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"value" - ) - - # 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"" - ) - -# 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.mainHeading == "H1" + assert theItem.charCount == 100 + assert theItem.wordCount == 20 + assert theItem.paraCount == 2 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.itemType == nwItemType.FILE - assert theItem.itemLayout == nwItemLayout.DOCUMENT + 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 - # Deprecated Type - theItem.setType("TRASH") + assert theItem.pack() == { + "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.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 diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 710d8160..02ce6c27 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -20,13 +20,14 @@ along with this program. If not, see . """ import os +import shutil import pytest from shutil import copyfile from zipfile import ZipFile -from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C from mock import causeOSError +from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE, C from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout from novelwriter.common import formatTimeStamp @@ -36,6 +37,7 @@ from novelwriter.core.index import NWIndex from novelwriter.core.project import NWProject from novelwriter.core.options import OptionState from novelwriter.core.document import NWDoc +from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState @pytest.mark.core @@ -380,140 +382,90 @@ def testCoreProject_NewFileFolder(monkeypatch, fncDir, outDir, refDir, mockGUI, @pytest.mark.core -@pytest.mark.skip -def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI): +def testCoreProject_Open(monkeypatch, caplog, mockGUI, fncDir, mockRnd): """Test opening a project. """ theProject = NWProject(mockGUI) + mockRnd.reset() + buildTestProject(theProject, fncDir) # Rename the project file to check handling - rName = os.path.join(nwMinimal, nwFiles.PROJ_FILE) - wName = os.path.join(nwMinimal, nwFiles.PROJ_FILE+"_sdfghj") + rName = os.path.join(fncDir, nwFiles.PROJ_FILE) + wName = os.path.join(fncDir, nwFiles.PROJ_FILE+"_sdfghj") os.rename(rName, wName) - assert theProject.openProject(nwMinimal) is False + assert theProject.openProject(fncDir) is False os.rename(wName, rName) # Fail on folder structure check with monkeypatch.context() as mp: 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 - theProject.setProjectPath(nwMinimal) + theProject.setProjectPath(fncDir) assert theProject._writeLockFile() - assert theProject.openProject(nwMinimal) is False + assert theProject.openProject(fncDir) is False # Fail to read lockfile (which still opens the project) with monkeypatch.context() as mp: 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() # Force open with lockfile - theProject.setProjectPath(nwMinimal) + theProject.setProjectPath(fncDir) assert theProject._writeLockFile() - assert theProject.openProject(nwMinimal, overrideLock=True) is True + assert theProject.openProject(fncDir, overrideLock=True) is True assert theProject.closeProject() - # Make a junk XML file - oName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"orig") - bName = os.path.join(nwMinimal, nwFiles.PROJ_FILE[:-3]+"bak") - os.rename(rName, oName) - writeFile(rName, "stuff") - assert theProject.openProject(nwMinimal) is False + # Not a novelwriter XML file + with monkeypatch.context() as mp: + mp.setattr(ProjectXMLReader, "read", lambda *a: False) + mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.NOT_NWX_FILE)) + assert theProject.openProject(fncDir) is False + assert "Project file does not appear" in mockGUI.lastAlert - # Also write a jun XML backup file - writeFile(bName, "stuff") - assert theProject.openProject(nwMinimal) is False + # Unknown project file version + with monkeypatch.context() as mp: + 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 - writeFile(rName, "\n") - assert theProject.openProject(nwMinimal) is False + # Other parse error + with monkeypatch.context() as mp: + 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 - writeFile(rName, ( - "\n" - "\n" - "\n" - )) - mockGUI.askResponse = False - assert theProject.openProject(nwMinimal) is False - mockGUI.undo() + # Won't convert legacy file + with monkeypatch.context() as mp: + mp.setattr(ProjectXMLReader, "state", property(lambda *a: XMLReadState.WAS_LEGACY)) + mockGUI.askResponse = False + assert theProject.openProject(fncDir) is False + assert "The file format of your project is about to be" in mockGUI.lastQuestion[1] + mockGUI.askResponse = True - # Future file version - writeFile(rName, ( - "\n" - "\n" - "\n" - )) - assert theProject.openProject(nwMinimal) is False - - # Update file version - writeFile(rName, ( - "\n" - "\n" - "\n" - )) - mockGUI.askResponse = False - assert theProject.openProject(nwMinimal) is False - assert mockGUI.lastQuestion[0] == "File Version" - mockGUI.undo() - - # Larger hex version - writeFile(rName, ( - "\n" - "\n" - "\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, ( - "\n" - "\n" - "\n" - "\n" - "\n" - )) - assert theProject.openProject(nwMinimal) is True - assert theProject.closeProject() - - # Clean up XML files - os.unlink(rName) - os.unlink(bName) - os.rename(oName, rName) + # Won't convert legacy file + with monkeypatch.context() as mp: + mp.setattr(ProjectXMLReader, "hexVersion", property(lambda *a: "0x99999999")) + mockGUI.askResponse = False + assert theProject.openProject(fncDir) is False + assert "This project was saved by a newer version" in mockGUI.lastQuestion[1] + mockGUI.askResponse = True # Add some legacy stuff that cannot be removed with monkeypatch.context() as mp: mp.setattr(theProject, "_legacyDataFolder", causeOSError) - os.mkdir(os.path.join(nwMinimal, "data_0")) - writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff") - writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff") + os.mkdir(os.path.join(fncDir, "data_0")) + writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.nwd"), "stuff") + writeFile(os.path.join(fncDir, "data_0", "123456789abc_main.bak"), "stuff") 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 theProject.closeProject() @@ -522,57 +474,31 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI): @pytest.mark.core -@pytest.mark.skip -def testCoreProject_Save(monkeypatch, nwMinimal, mockGUI, refDir): +def testCoreProject_Save(monkeypatch, mockGUI, mockRnd, fncDir, refDir): """Test saving a project. """ 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 assert theProject.saveProject() is False - # Open test project - assert theProject.openProject(nwMinimal) + mockRnd.reset() + buildTestProject(theProject, fncDir) # Fail on folder structure check 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 - # Fail on open file + # Fail writing with monkeypatch.context() as mp: - mp.setattr("builtins.open", causeOSError) + mp.setattr(ProjectXMLWriter, "write", lambda *a: False) assert theProject.saveProject() is False - # Fail on creating .bak file - with monkeypatch.context() as mp: - 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 + # Save with and without autosave + assert theProject.saveProject(autoSave=False) 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() # END Test testCoreProject_Save @@ -683,11 +609,11 @@ def testCoreProject_Helpers(monkeypatch, fncDir, mockGUI): @pytest.mark.core -def testCoreProject_AccessItems(nwMinimal, mockGUI): +def testCoreProject_AccessItems(mockGUI, fncDir, mockRnd): """Test helper functions for the project folder. """ theProject = NWProject(mockGUI) - theProject.openProject(nwMinimal) + buildTestProject(theProject, fncDir) # Storage Objects assert isinstance(theProject.index, NWIndex) @@ -696,34 +622,34 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI): # Move Novel ROOT to after its files oldOrder = [ - "a508bb932959c", # ROOT: Novel - "a35baf2e93843", # FILE: Title Page - "a6d311a93600a", # FOLDER: New Chapter - "f5ab3e30151e1", # FILE: New Chapter - "8c659a11cd429", # FILE: New Scene - "7695ce551d265", # ROOT: Plot - "afb3043c7b2b3", # ROOT: Characters - "9d5247ab588e0", # ROOT: World + C.hNovelRoot, + C.hPlotRoot, + C.hCharRoot, + C.hWorldRoot, + C.hTitlePage, + C.hChapterDir, + C.hChapterDoc, + C.hSceneDoc, ] newOrder = [ - "a35baf2e93843", # FILE: Title Page - "f5ab3e30151e1", # FILE: New Chapter - "8c659a11cd429", # FILE: New Scene - "a6d311a93600a", # FOLDER: New Chapter - "a508bb932959c", # ROOT: Novel - "7695ce551d265", # ROOT: Plot - "afb3043c7b2b3", # ROOT: Characters - "9d5247ab588e0", # ROOT: World + C.hTitlePage, + C.hChapterDoc, + C.hSceneDoc, + C.hChapterDir, + C.hNovelRoot, + C.hPlotRoot, + C.hCharRoot, + C.hWorldRoot, ] assert theProject.tree.handles() == oldOrder assert theProject.setTreeOrder(newOrder) assert theProject.tree.handles() == newOrder # Add a non-existing item - theProject.tree._treeOrder.append("01234567789abc") + theProject.tree._treeOrder.append(C.hInvalid) # 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") assert theProject.tree[nHandle].itemParent == "cba9876543210" @@ -732,15 +658,15 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI): retOrder.append(tItem.itemHandle) assert retOrder == [ - "a508bb932959c", # ROOT: Novel - "7695ce551d265", # ROOT: Plot - "afb3043c7b2b3", # ROOT: Characters - "9d5247ab588e0", # ROOT: World - nHandle, # FILE: Test File - "a35baf2e93843", # FILE: Title Page - "a6d311a93600a", # FOLDER: New Chapter - "f5ab3e30151e1", # FILE: New Chapter - "8c659a11cd429", # FILE: New Scene + C.hNovelRoot, + C.hPlotRoot, + C.hCharRoot, + C.hWorldRoot, + nHandle, + C.hTitlePage, + C.hChapterDir, + C.hChapterDoc, + C.hSceneDoc, ] assert theProject.tree[nHandle].itemParent is None @@ -748,28 +674,28 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI): @pytest.mark.core -@pytest.mark.skip def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): """Test the status and importance flag handling. """ theProject = NWProject(mockGUI) + mockRnd.reset() buildTestProject(theProject, fncDir) - statusKeys = ["s000008", "s000009", "s00000a", "s00000b"] - importKeys = ["i00000c", "i00000d", "i00000e", "i00000f"] + statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished] + importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] # Change Status # ============= - theProject.tree["0000000000014"].setStatus(statusKeys[3]) - theProject.tree["0000000000015"].setStatus(statusKeys[2]) - theProject.tree["0000000000016"].setStatus(statusKeys[1]) - theProject.tree["0000000000017"].setStatus(statusKeys[3]) + theProject.tree[C.hNovelRoot].setStatus(statusKeys[3]) + theProject.tree[C.hPlotRoot].setStatus(statusKeys[2]) + theProject.tree[C.hCharRoot].setStatus(statusKeys[1]) + theProject.tree[C.hWorldRoot].setStatus(statusKeys[3]) - assert theProject.tree["0000000000014"].itemStatus == statusKeys[3] - assert theProject.tree["0000000000015"].itemStatus == statusKeys[2] - assert theProject.tree["0000000000016"].itemStatus == statusKeys[1] - assert theProject.tree["0000000000017"].itemStatus == statusKeys[3] + assert theProject.tree[C.hNovelRoot].itemStatus == statusKeys[3] + assert theProject.tree[C.hPlotRoot].itemStatus == statusKeys[2] + assert theProject.tree[C.hCharRoot].itemStatus == statusKeys[1] + assert theProject.tree[C.hWorldRoot].itemStatus == statusKeys[3] newList = [ {"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) # Check the new entry - lastKey = theProject.data.itemStatus.check("s000018") - assert lastKey == "s000018" + lastKey = theProject.data.itemStatus.check("s000010") + assert lastKey == "s000010" assert theProject.data.itemStatus.name(lastKey) == "Finished" assert theProject.data.itemStatus.cols(lastKey) == (5, 5, 5) @@ -804,7 +730,7 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd): # Change Importance # ================= - fHandle = theProject.newFile("Jane Doe", "0000000000012") + fHandle = theProject.newFile("Jane Doe", C.hCharRoot) theProject.tree[fHandle].setImport(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) # Check the new entry - lastKey = theProject.data.itemImport.check("i00001a") - assert lastKey == "i00001a" + lastKey = theProject.data.itemImport.check("i000012") + assert lastKey == "i000012" assert theProject.data.itemImport.name(lastKey) == "Max" 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.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 @@ -1268,14 +1182,14 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir): @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 creates a backup of the Minimal test project, and then unzips the backupd file and checks that the project XML file is identical to the original file. """ theProject = NWProject(mockGUI) - assert theProject.openProject(nwMinimal) + buildTestProject(theProject, fncDir) # Test faulty settings @@ -1299,11 +1213,11 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir): assert theProject.zipIt(doNotify=False) is False # Same folder as project (causes infinite loop in zipping) - theProject.mainConf.backupPath = nwMinimal + theProject.mainConf.backupPath = fncDir assert theProject.zipIt(doNotify=False) is False # 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 # Set a valid folder @@ -1335,7 +1249,7 @@ def testCoreProject_Backup(monkeypatch, mockGUI, nwMinimal, tmpDir): # Check that the main project file was restored assert cmpFiles( - os.path.join(nwMinimal, "nwProject.nwx"), + os.path.join(fncDir, "nwProject.nwx"), os.path.join(tmpDir, "extract", "nwProject.nwx") ) diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index 9f01136c..760f31f5 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -20,23 +20,21 @@ along with this program. If not, see . """ import pytest -import random -from lxml import etree +from tools import C from PyQt5.QtGui import QIcon from novelwriter.core.status import NWStatus -statusKeys = ["sa3b179", "s1c8031", "s06671a", "sbdd640"] -importKeys = ["i466852", "i3eb13b", "i392456", "i23b8c1"] +statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished] +importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] @pytest.mark.core -def testCoreStatus_Internal(): +def testCoreStatus_Internal(mockRnd): """Test all the internal functions of the NWStatus class. """ - random.seed(42) theStatus = NWStatus(NWStatus.STATUS) theImport = NWStatus(NWStatus.IMPORT) @@ -87,10 +85,9 @@ def testCoreStatus_Internal(): @pytest.mark.core -def testCoreStatus_Iterator(): +def testCoreStatus_Iterator(mockRnd): """Test the iterator functions of the NWStatus class. """ - random.seed(42) theStatus = NWStatus(NWStatus.STATUS) theStatus.write(None, "New", (100, 100, 100)) @@ -132,10 +129,9 @@ def testCoreStatus_Iterator(): @pytest.mark.core -def testCoreStatus_Entries(): +def testCoreStatus_Entries(mockRnd): """Test all the simple setters for the NWStatus class. """ - random.seed(42) theStatus = NWStatus(NWStatus.STATUS) # Write @@ -300,11 +296,9 @@ def testCoreStatus_Entries(): @pytest.mark.core -@pytest.mark.skip -def testCoreStatus_XMLPackUnpack(): - """Test all the XML pack/unpack of the NWStatus class. +def testCoreStatus_PackUnpack(mockRnd): + """Test all the pack/unpack of the NWStatus class. """ - random.seed(42) theStatus = NWStatus(NWStatus.STATUS) theStatus.write(None, "New", (100, 100, 100)) theStatus.write(None, "Note", (200, 50, 0)) @@ -316,36 +310,59 @@ def testCoreStatus_XMLPackUnpack(): for _ in range(n): theStatus.increment(statusKeys[i]) - nwXML = etree.Element("novelWriterXML") - # Pack - xStatus = etree.SubElement(nwXML, "status") - theStatus.packXML(xStatus) - assert etree.tostring(xStatus, pretty_print=False, encoding="utf-8") == ( - b'' - b'New' - b'Note' - b'Draft' - b'Finished' - b'' - ) + assert list(theStatus.pack()) == [ + ("New", { + "key": statusKeys[0], + "count": "3", + "red": "100", + "green": "100", + "blue": "100" + }), + ("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 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 list(theStatus._store.keys()) == statusKeys - assert theStatus._store[statusKeys[0]]["name"] == "New" - assert theStatus._store[statusKeys[1]]["name"] == "Note" - assert theStatus._store[statusKeys[2]]["name"] == "Draft" - assert theStatus._store[statusKeys[3]]["name"] == "Finished" + assert theStatus._store[statusKeys[0]]["name"] == "New0" + assert theStatus._store[statusKeys[1]]["name"] == "New1" + assert theStatus._store[statusKeys[2]]["name"] == "New2" + assert theStatus._store[statusKeys[3]]["name"] == "New3" assert theStatus._store[statusKeys[0]]["cols"] == (100, 100, 100) - assert theStatus._store[statusKeys[1]]["cols"] == (200, 50, 0) - assert theStatus._store[statusKeys[2]]["cols"] == (200, 150, 0) - assert theStatus._store[statusKeys[3]]["cols"] == (50, 200, 0) + assert theStatus._store[statusKeys[1]]["cols"] == (150, 150, 150) + assert theStatus._store[statusKeys[2]]["cols"] == (200, 200, 200) + assert theStatus._store[statusKeys[3]]["cols"] == (250, 250, 250) assert theStatus._store[statusKeys[0]]["count"] == countTo[0] assert theStatus._store[statusKeys[1]]["count"] == countTo[1] assert theStatus._store[statusKeys[2]]["count"] == countTo[2] assert theStatus._store[statusKeys[3]]["count"] == countTo[3] -# END Test testCoreStatus_XMLPackUnpack +# END Test testCoreStatus_PackUnpack